• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
7import errno
8import logging
9import os
10
11from contextlib import contextmanager
12
13from autotest_lib.client.common_lib import error
14from autotest_lib.client.common_lib import utils
15from autotest_lib.client.cros.networking import shill_proxy
16
17SHILL_START_LOCK_PATH = '/var/lock/shill-start.lock'
18
19class ContextError(Exception):
20    """An error raised by a context managers dealing with shill objects."""
21    pass
22
23
24class 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
113def 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.stop_service('shill')
151    yield
152    utils.start_service('shill')
153    os.remove(SHILL_START_LOCK_PATH)
154