blob: cdfe47fcf24cbf846a3967b14a148f6bf59198aa [file] [log] [blame]
# Copyright (c) 2013 The Chromium OS Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""A collection of context managers for working with shill objects."""
import dbus
import logging
from contextlib import contextmanager
from autotest_lib.client.common_lib import utils
from autotest_lib.client.cros.networking import shill_proxy
from autotest_lib.client.cros.power import sys_power
class ContextError(Exception):
"""An error raised by a context managers dealing with shill objects."""
pass
class AllowedTechnologiesContext(object):
"""A context manager for allowing only specified technologies in shill.
Usage:
# Suppose both 'wifi' and 'cellular' technology are originally enabled.
allowed = [shill_proxy.ShillProxy.TECHNOLOGY_CELLULAR]
with AllowedTechnologiesContext(allowed):
# Within this context, only the 'cellular' technology is allowed to
# be enabled. The 'wifi' technology is temporarily prohibited and
# disabled until after the context ends.
"""
def __init__(self, allowed):
self._allowed = set(allowed)
def __enter__(self):
shill = shill_proxy.ShillProxy.get_proxy()
# The EnabledTechologies property is an array of strings of technology
# identifiers.
enabled = shill.get_dbus_property(
shill.manager,
shill_proxy.ShillProxy.MANAGER_PROPERTY_ENABLED_TECHNOLOGIES)
self._originally_enabled = set(enabled)
# The ProhibitedTechnologies property is a comma-separated string of
# technology identifiers.
prohibited_csv = shill.get_dbus_property(
shill.manager,
shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES)
prohibited = prohibited_csv.split(',') if prohibited_csv else []
self._originally_prohibited = set(prohibited)
prohibited = ((self._originally_prohibited | self._originally_enabled)
- self._allowed)
prohibited_csv = ','.join(prohibited)
logging.debug('Allowed technologies = [%s]', ','.join(self._allowed))
logging.debug('Originally enabled technologies = [%s]',
','.join(self._originally_enabled))
logging.debug('Originally prohibited technologies = [%s]',
','.join(self._originally_prohibited))
logging.debug('To be prohibited technologies = [%s]',
','.join(prohibited))
# Setting the ProhibitedTechnologies property will disable those
# prohibited technologies.
shill.set_dbus_property(
shill.manager,
shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES,
prohibited_csv)
return self
def __exit__(self, exc_type, exc_value, traceback):
shill = shill_proxy.ShillProxy.get_proxy()
prohibited_csv = ','.join(self._originally_prohibited)
shill.set_dbus_property(
shill.manager,
shill_proxy.ShillProxy.MANAGER_PROPERTY_PROHIBITED_TECHNOLOGIES,
prohibited_csv)
# Re-enable originally enabled technologies as they may have been
# disabled.
enabled = shill.get_dbus_property(
shill.manager,
shill_proxy.ShillProxy.MANAGER_PROPERTY_ENABLED_TECHNOLOGIES)
to_be_reenabled = self._originally_enabled - set(enabled)
for technology in to_be_reenabled:
shill.manager.EnableTechnology(technology)
return False
class ServiceAutoConnectContext(object):
"""A context manager for overriding a service's 'AutoConnect' property.
As the service object of the same service may change during the lifetime
of the context, this context manager does not take a service object at
construction. Instead, it takes a |get_service| function at construction,
which it invokes to obtain a service object when entering and exiting the
context. It is assumed that |get_service| always returns a service object
that refers to the same service.
Usage:
def get_service():
# Some function that returns a service object.
with ServiceAutoConnectContext(get_service, False):
# Within this context, the 'AutoConnect' property of the service
# returned by |get_service| is temporarily set to False if it's
# initially set to True. The property is restored to its initial
# value after the context ends.
"""
def __init__(self, get_service, autoconnect):
self._get_service = get_service
self._autoconnect = autoconnect
self._initial_autoconnect = None
def __enter__(self):
service = self._get_service()
if service is None:
raise ContextError('Could not obtain a service object.')
# Always set the AutoConnect property even if the requested value
# is the same so that shill will retain the AutoConnect property, else
# shill may override it.
service_properties = service.GetProperties()
self._initial_autoconnect = shill_proxy.ShillProxy.dbus2primitive(
service_properties[
shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT])
logging.info('ServiceAutoConnectContext: change autoconnect to %s',
self._autoconnect)
service.SetProperty(
shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT,
self._autoconnect)
# Make sure the cellular service gets persisted by taking it out of
# the ephemeral profile.
if not service_properties[
shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE]:
shill = shill_proxy.ShillProxy.get_proxy()
manager_properties = shill.manager.GetProperties(utf8_strings=True)
active_profile = manager_properties[
shill_proxy.ShillProxy.MANAGER_PROPERTY_ACTIVE_PROFILE]
logging.info('ServiceAutoConnectContext: change cellular service '
'profile to %s', active_profile)
service.SetProperty(
shill_proxy.ShillProxy.SERVICE_PROPERTY_PROFILE,
active_profile)
return self
def __exit__(self, exc_type, exc_value, traceback):
if self._initial_autoconnect != self._autoconnect:
service = self._get_service()
if service is None:
raise ContextError('Could not obtain a service object.')
logging.info('ServiceAutoConnectContext: restore autoconnect to %s',
self._initial_autoconnect)
service.SetProperty(
shill_proxy.ShillProxy.SERVICE_PROPERTY_AUTOCONNECT,
self._initial_autoconnect)
return False
@property
def autoconnect(self):
"""AutoConnect property value within this context."""
return self._autoconnect
@property
def initial_autoconnect(self):
"""Initial AutoConnect property value when entering this context."""
return self._initial_autoconnect
@contextmanager
def stopped_shill():
"""A context for executing code which requires shill to be stopped.
This context stops shill on entry to the context, and starts shill
before exit from the context. This context further guarantees that
shill will be not restarted by recover_duts and that recover_duts
will not otherwise try to run its network connectivity checks, while
this context is active.
Note that the no-restart guarantee applies only if the user of
this context completes with a 'reasonable' amount of time. In
particular: if networking is unavailable for 15 minutes or more,
recover_duts will reboot the DUT.
"""
sys_power.pause_check_network_hook()
utils.stop_service('shill')
yield
utils.start_service('shill')
sys_power.resume_check_network_hook()
class StaticIPContext(object):
"""StaticIPConfig context manager class.
Set a StaticIPConfig to the given service.
"""
def __init__(self, service, config):
self._service = service
self._config = config
def __enter__(self):
"""Configure the StaticIP parameters for the Service and apply those
parameters to the interface by forcing a re-connect."""
self._service.SetProperty(
shill_proxy.ShillProxy.SERVICE_PROPERTY_STATIC_IP_CONFIG,
dbus.Dictionary(self._config, signature='sv'))
self._service.Disconnect()
self._service.Connect()
def __exit__(self, exception, value, traceback):
"""Clear configuration of StaticIP parameters for the Service and force
a re-connect."""
self._service.ClearProperty(
shill_proxy.ShillProxy.SERVICE_PROPERTY_STATIC_IP_CONFIG)
self._service.Disconnect()
self._service.Connect()