Home | History | Annotate | Download | only in networking
      1 # Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
      2 # Use of this source code is governed by a BSD-style license that can be
      3 # found in the LICENSE file.
      4 
      5 """A collection of context managers for working with shill objects."""
      6 
      7 import errno
      8 import logging
      9 import os
     10 
     11 from contextlib import contextmanager
     12 
     13 from autotest_lib.client.common_lib import error
     14 from autotest_lib.client.common_lib import utils
     15 from autotest_lib.client.cros.networking import shill_proxy
     16 
     17 SHILL_START_LOCK_PATH = '/var/lock/shill-start.lock'
     18 
     19 class ContextError(Exception):
     20     """An error raised by a context managers dealing with shill objects."""
     21     pass
     22 
     23 
     24 class ServiceAutoConnectContext(object):
     25     """A context manager for overriding a service's 'AutoConnect' property.
     26 
     27     As the service object of the same service may change during the lifetime
     28     of the context, this context manager does not take a service object at
     29     construction. Instead, it takes a |get_service| function at construction,
     30     which it invokes to obtain a service object when entering and exiting the
     31     context. It is assumed that |get_service| always returns a service object
     32     that refers to the same service.
     33 
     34     Usage:
     35         def get_service():
     36             # Some function that returns a service object.
     37 
     38         with ServiceAutoConnectContext(get_service, False):
     39             # Within this context, the 'AutoConnect' property of the service
     40             # returned by |get_service| is temporarily set to False if it's
     41             # initially set to True. The property is restored to its initial
     42             # value after the context ends.
     43 
     44     """
     45     def __init__(self, get_service, autoconnect):
     46         self._get_service = get_service
     47         self._autoconnect = autoconnect
     48         self._initial_autoconnect = None
     49 
     50 
     51     def __enter__(self):
     52         service = self._get_service()
     53         if service is None:
     54             raise ContextError('Could not obtain a service object.')
     55 
     56         # Always set the AutoConnect property even if the requested value
     57         # is the same so that shill will retain the AutoConnect property, else
     58         # shill may override it.
     59         service_properties = service.GetProperties()
     60         self._initial_autoconnect = shill_proxy.ShillProxy.dbus2primitive(
     61             service_properties[
     62                 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT])
     63         logging.info('ServiceAutoConnectContext: change autoconnect to %s',
     64                      self._autoconnect)
     65         service.SetProperty(
     66             shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT,
     67             self._autoconnect)
     68 
     69         # Make sure the cellular service gets persisted by taking it out of
     70         # the ephemeral profile.
     71         if not service_properties[
     72                 shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE]:
     73             shill = shill_proxy.ShillProxy.get_proxy()
     74             manager_properties = shill.manager.GetProperties(utf8_strings=True)
     75             active_profile = manager_properties[
     76                     shill_proxy.ShillProxy.MANAGER_PROPERTY_ACTIVE_PROFILE]
     77             logging.info('ServiceAutoConnectContext: change cellular service '
     78                          'profile to %s', active_profile)
     79             service.SetProperty(
     80                     shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE,
     81                     active_profile)
     82 
     83         return self
     84 
     85 
     86     def __exit__(self, exc_type, exc_value, traceback):
     87         if self._initial_autoconnect != self._autoconnect:
     88             service = self._get_service()
     89             if service is None:
     90                 raise ContextError('Could not obtain a service object.')
     91 
     92             logging.info('ServiceAutoConnectContext: restore autoconnect to %s',
     93                          self._initial_autoconnect)
     94             service.SetProperty(
     95                 shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT,
     96                 self._initial_autoconnect)
     97         return False
     98 
     99 
    100     @property
    101     def autoconnect(self):
    102         """AutoConnect property value within this context."""
    103         return self._autoconnect
    104 
    105 
    106     @property
    107     def initial_autoconnect(self):
    108         """Initial AutoConnect property value when entering this context."""
    109         return self._initial_autoconnect
    110 
    111 
    112 @contextmanager
    113 def stopped_shill():
    114     """A context for executing code which requires shill to be stopped.
    115 
    116     This context stops shill on entry to the context, and starts shill
    117     before exit from the context. This context further guarantees that
    118     shill will be not restarted by recover_duts, while this context is
    119     active.
    120 
    121     Note that the no-restart guarantee applies only if the user of
    122     this context completes with a 'reasonable' amount of time. In
    123     particular: if networking is unavailable for 15 minutes or more,
    124     recover_duts will reboot the DUT.
    125 
    126     """
    127     def get_lock_holder(lock_path):
    128         lock_holder = os.readlink(lock_path)
    129         try:
    130             os.stat(lock_holder)
    131             return lock_holder  # stat() success -> valid link -> locker alive
    132         except OSError as e:
    133             if e.errno == errno.ENOENT:  # dangling link -> locker is gone
    134                 return None
    135             else:
    136                 raise
    137 
    138     our_proc_dir = '/proc/%d/' % os.getpid()
    139     try:
    140         os.symlink(our_proc_dir, SHILL_START_LOCK_PATH)
    141     except OSError as e:
    142         if e.errno != errno.EEXIST:
    143             raise
    144         lock_holder = get_lock_holder(SHILL_START_LOCK_PATH)
    145         if lock_holder is not None:
    146             raise error.TestError('Shill start lock held by %s' % lock_holder)
    147         os.remove(SHILL_START_LOCK_PATH)
    148         os.symlink(our_proc_dir, SHILL_START_LOCK_PATH)
    149 
    150     utils.run('stop shill')
    151     yield
    152     utils.run('start shill')
    153     os.remove(SHILL_START_LOCK_PATH)
    154