| # Copyright (c) 2012 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. |
| |
| import logging, time |
| |
| import dbus |
| |
| def make_dbus_boolean(value): |
| value = value.upper() |
| if value in ["ON", "TRUE"]: |
| return dbus.Boolean(1) |
| elif value in ["OFF", "FALSE"]: |
| return dbus.Boolean(0) |
| else: |
| return dbus.Boolean(int(value)) |
| |
| # |
| # Convert a DBus value to a printable value; used |
| # to print properties returned via DBus |
| # |
| def convert_dbus_value(value, indent=0): |
| spacer = ' ' * indent |
| if value.__class__ == dbus.Byte: |
| return int(value) |
| elif value.__class__ == dbus.Boolean: |
| return bool(value) |
| elif value.__class__ == dbus.Dictionary: |
| valstr = "{" |
| for key in value: |
| valstr += "\n" + spacer + " " + \ |
| key + ": " + str(convert_dbus_value(value[key], indent + 4)) |
| valstr += "\n" + spacer + "}" |
| return valstr |
| elif value.__class__ == dbus.Array: |
| valstr = "[" |
| for val in value: |
| valstr += "\n" + spacer + " " + \ |
| str(convert_dbus_value(val, indent + 4)) |
| valstr += "\n" + spacer + "]" |
| return valstr |
| else: |
| return str(value) |
| |
| class FlimFlam(object): |
| |
| SHILL_DBUS_INTERFACE = "org.chromium.flimflam" |
| UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' |
| UNKNOWN_OBJECT = 'org.freedesktop.DBus.Error.UnknownObject' |
| |
| DEVICE_WIMAX = 'wimax' |
| DEVICE_CELLULAR = 'cellular' |
| |
| @staticmethod |
| def _GetContainerName(kind): |
| """Map shill element names to the names of their collections.""" |
| # For example, Device - > Devices. |
| # Just pulling this out so we can use a map if we start |
| # caring about "AvailableTechnologies" |
| return kind + "s" |
| |
| @staticmethod |
| def WaitForServiceState(service, expected_states, timeout, |
| ignore_failure=False, property_name="State"): |
| """Wait until service enters a state in expected_states or times out. |
| Args: |
| service: service to watch |
| expected_states: list of exit states |
| timeout: in seconds |
| ignore_failure: should the failure state be ignored? |
| property_name: name of service property |
| |
| Returns: (state, seconds waited) |
| |
| If the state is "failure" and ignore_failure is False we return |
| immediately without waiting for the timeout. |
| """ |
| |
| state = None |
| start_time = time.time() |
| timeout = start_time + timeout |
| while time.time() < timeout: |
| properties = service.GetProperties(utf8_strings = True) |
| state = properties.get(property_name, None) |
| if ((state == "failure" and not ignore_failure) or |
| state in expected_states): |
| break |
| time.sleep(.5) |
| |
| config_time = time.time() - start_time |
| # str() to remove DBus boxing |
| return (str(state), config_time) |
| |
| @staticmethod |
| def DisconnectService(service, wait_timeout=15): |
| try: |
| service.Disconnect() |
| except dbus.exceptions.DBusException, error: |
| if error.get_dbus_name() not in [ |
| FlimFlam.SHILL_DBUS_INTERFACE + ".Error.InProgress", |
| FlimFlam.SHILL_DBUS_INTERFACE + ".Error.NotConnected", ]: |
| raise error |
| return FlimFlam.WaitForServiceState(service, ['idle'], wait_timeout) |
| |
| def __init__(self, bus=None): |
| if not bus: |
| bus = dbus.SystemBus() |
| self.bus = bus |
| shill = bus.get_object(FlimFlam.SHILL_DBUS_INTERFACE, "/") |
| self.manager = dbus.Interface( |
| shill, |
| FlimFlam.SHILL_DBUS_INTERFACE + ".Manager") |
| |
| def _FindDevice(self, device_type, timeout): |
| """ Return the first device object that matches a given device type. |
| |
| Wait until the device type is avilable or until timeout |
| |
| Args: |
| device_type: string format of the type of device. |
| timeout: in seconds |
| |
| Returns: Device or None |
| """ |
| timeout = time.time() + timeout |
| device_obj = None |
| while time.time() < timeout: |
| device_obj = self.FindElementByPropertySubstring('Device', |
| 'Type', |
| device_type) |
| if device_obj: |
| break |
| time.sleep(1) |
| return device_obj |
| |
| def FindCellularDevice(self, timeout=30): |
| return self._FindDevice(self.DEVICE_CELLULAR, timeout) |
| |
| def FindWimaxDevice(self, timeout=30): |
| return self._FindDevice(self.DEVICE_WIMAX, timeout) |
| |
| def _FindService(self, device_type, timeout): |
| """Return the first service object that matches the device type. |
| |
| Wait until a service is available or until the timeout. |
| |
| Args: |
| device_type: string format of the type of device. |
| timeout: in seconds |
| |
| Returns: service or None |
| """ |
| start_time = time.time() |
| timeout = start_time + timeout |
| service = None |
| while time.time() < timeout: |
| service = self.FindElementByPropertySubstring('Service', |
| 'Type', device_type) |
| if service: |
| break |
| time.sleep(.5) |
| return service |
| |
| def FindCellularService(self, timeout=30): |
| return self._FindService(self.DEVICE_CELLULAR, timeout) |
| |
| def FindWimaxService(self, timeout=30): |
| return self._FindService(self.DEVICE_WIMAX, timeout) |
| |
| def GetService(self, params): |
| path = self.manager.GetService(params) |
| return self.GetObjectInterface("Service", path) |
| |
| def ConnectService(self, assoc_timeout=15, config_timeout=15, |
| async=False, service=None, service_type='', |
| retry=False, retries=1, retry_sleep=15, |
| save_creds=False, |
| **kwargs): |
| """Connect to a service and wait until connection is up |
| Args: |
| assoc_timeout, config_timeout: Timeouts in seconds. |
| async: return immediately. do not wait for connection. |
| service: DBus service |
| service_type: If supplied, invoke type-specific code to find service. |
| retry: Retry connection after Connect failure. |
| retries: Number of retries to allow. |
| retry_sleep: Number of seconds to wait before retrying. |
| kwargs: Additional args for type-specific code |
| |
| Returns: |
| (success, dictionary), where dictionary contains stats and diagnostics. |
| """ |
| output = {} |
| connected_states = ["ready", "portal", "online"] |
| |
| # Retry connections on failure. Need to call GetService again as some |
| # Connect failure states are unrecoverable. |
| connect_success = False |
| while not connect_success: |
| if service_type == "wifi": |
| try: |
| # Sanity check to make sure the caller hasn't provided |
| # both a service and a service type. At which point its |
| # unclear what they actually want to do, so err on the |
| # side of caution and except out. |
| if service: |
| raise Exception('supplied service and service type') |
| params = { |
| "Type": service_type, |
| "Mode": kwargs["mode"], |
| "SSID": kwargs["ssid"], |
| "Security": kwargs.get("security", "none"), |
| "SaveCredentials": save_creds } |
| # Supply a passphrase only if it is non-empty. |
| passphrase = kwargs.get("passphrase", "") |
| if passphrase: |
| params["Passphrase"] = passphrase |
| path = self.manager.GetService(params) |
| service = self.GetObjectInterface("Service", path) |
| except Exception, e: |
| output["reason"] = "FAIL(GetService): exception %s" % e |
| return (False, output) |
| |
| output["service"] = service |
| |
| try: |
| service.Connect() |
| connect_success = True |
| except Exception, e: |
| if not retry or retries == 0: |
| output["reason"] = "FAIL(Connect): exception %s" % e |
| return (False, output) |
| else: |
| logging.info("INFO(Connect): connect failed. Retrying...") |
| retries -= 1 |
| |
| if not connect_success: |
| # FlimFlam can be a little funny sometimes. At least for In |
| # Progress errors, even though the service state may be failed, |
| # it is actually still trying to connect. As such, while we're |
| # waiting for retry, keep checking the service state to see if |
| # it actually succeeded in connecting. |
| state = FlimFlam.WaitForServiceState( |
| service=service, |
| expected_states=connected_states, |
| timeout=retry_sleep, |
| ignore_failure=True)[0] |
| |
| if state in connected_states: |
| return (True, output) |
| |
| # While service can be caller provided, it is also set by the |
| # GetService call above. If service was not caller provided we |
| # need to reset it to None so we don't fail the sanity check |
| # above. |
| if service_type != '': |
| service = None |
| |
| if async: |
| return (True, output) |
| |
| logging.info("Associating...") |
| (state, assoc_time) = ( |
| FlimFlam.WaitForServiceState(service, |
| ["configuration"] + connected_states, |
| assoc_timeout)) |
| output["state"] = state |
| if state == "failure": |
| output["reason"] = "FAIL(assoc)" |
| if assoc_time > assoc_timeout: |
| output["reason"] = "TIMEOUT(assoc)" |
| output["assoc_time"] = assoc_time |
| if "reason" in output: |
| return (False, output) |
| |
| |
| (state, config_time) = ( |
| FlimFlam.WaitForServiceState(service, |
| connected_states, config_timeout)) |
| output["state"] = state |
| if state == "failure": |
| output["reason"] = "FAIL(config)" |
| if config_time > config_timeout: |
| output["reason"] = "TIMEOUT(config)" |
| output["config_time"] = config_time |
| |
| if "reason" in output: |
| return (False, output) |
| |
| return (True, output) |
| |
| def GetObjectInterface(self, kind, path): |
| return dbus.Interface( |
| self.bus.get_object(FlimFlam.SHILL_DBUS_INTERFACE, path), |
| FlimFlam.SHILL_DBUS_INTERFACE + "." + kind) |
| |
| def FindElementByNameSubstring(self, kind, substring): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| for path in properties[FlimFlam._GetContainerName(kind)]: |
| if path.find(substring) >= 0: |
| return self.GetObjectInterface(kind, path) |
| return None |
| |
| def FindElementByPropertySubstring(self, kind, prop, substring): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| for path in properties[FlimFlam._GetContainerName(kind)]: |
| obj = self.GetObjectInterface(kind, path) |
| try: |
| obj_properties = obj.GetProperties(utf8_strings = True) |
| except dbus.exceptions.DBusException, error: |
| if (error.get_dbus_name() == self.UNKNOWN_METHOD or |
| error.get_dbus_name() == self.UNKNOWN_OBJECT): |
| # object disappeared; ignore and keep looking |
| continue |
| else: |
| raise error |
| if (prop in obj_properties and |
| obj_properties[prop].find(substring) >= 0): |
| return obj |
| return None |
| |
| def GetObjectList(self, kind, properties=None): |
| if properties is None: |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return [self.GetObjectInterface(kind, path) |
| for path in properties[FlimFlam._GetContainerName(kind)]] |
| |
| def GetActiveProfile(self): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return self.GetObjectInterface("Profile", properties["ActiveProfile"]) |
| |
| def CreateProfile(self, ident): |
| path = self.manager.CreateProfile(ident) |
| return self.GetObjectInterface("Profile", path) |
| |
| def RemoveProfile(self, ident): |
| self.manager.RemoveProfile(ident) |
| |
| def PushProfile(self, ident): |
| path = self.manager.PushProfile(ident) |
| return self.GetObjectInterface("Profile", path) |
| |
| def PopProfile(self, ident): |
| self.manager.PopProfile(ident) |
| |
| def PopAnyProfile(self): |
| self.manager.PopAnyProfile() |
| |
| def GetSystemState(self): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return properties["State"] |
| |
| def GetDebugTags(self): |
| return self.manager.GetDebugTags() |
| |
| def ListDebugTags(self): |
| return self.manager.ListDebugTags() |
| |
| def SetDebugTags(self, taglist): |
| try: |
| self.manager.SetDebugTags(taglist) |
| self.SetDebugLevel(-4) |
| except dbus.exceptions.DBusException, error: |
| if error.get_dbus_name() not in [ |
| "org.freedesktop.DBus.Error.UnknownMethod" ]: |
| raise error |
| |
| def SetDebugLevel(self, level): |
| self.manager.SetDebugLevel(level) |
| |
| def GetServiceOrder(self): |
| return self.manager.GetServiceOrder() |
| |
| def SetServiceOrder(self, new_order): |
| old_order = self.GetServiceOrder() |
| self.manager.SetServiceOrder(new_order) |
| return (old_order, new_order) |
| |
| def EnableTechnology(self, tech): |
| try: |
| self.manager.EnableTechnology(tech) |
| except dbus.exceptions.DBusException, error: |
| if error.get_dbus_name() not in [ |
| FlimFlam.SHILL_DBUS_INTERFACE + ".Error.AlreadyEnabled" ]: |
| raise error |
| |
| def DisableTechnology(self, tech): |
| self.manager.DisableTechnology(tech, timeout=60) |
| |
| def RequestScan(self, technology): |
| self.manager.RequestScan(technology) |
| |
| def GetCountry(self): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return properties["Country"] |
| |
| def SetCountry(self, country): |
| self.manager.SetProperty("Country", country) |
| |
| def GetCheckPortalList(self): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return properties["CheckPortalList"] |
| |
| def SetCheckPortalList(self, tech_list): |
| self.manager.SetProperty("CheckPortalList", tech_list) |
| |
| def GetPortalURL(self): |
| properties = self.manager.GetProperties(utf8_strings = True) |
| return properties["PortalURL"] |
| |
| def SetPortalURL(self, url): |
| self.manager.SetProperty("PortalURL", url) |
| |
| def GetArpGateway(self): |
| properties = self.manager.GetProperties() |
| return properties["ArpGateway"] |
| |
| def SetArpGateway(self, do_arp_gateway): |
| self.manager.SetProperty("ArpGateway", do_arp_gateway) |
| |
| |
| class DeviceManager(object): |
| """Use flimflam to isolate a given interface for testing. |
| |
| DeviceManager can be used to turn off network devices that are not |
| under test so that they will not interfere with testing. |
| |
| NB: Ethernet devices are special inside Flimflam. You will need to |
| take care of them via other means (like, for example, the |
| backchannel ethernet code in client autotests) |
| |
| Sample usage: |
| |
| device_manager = flimflam.DeviceManager() |
| try: |
| device_manager.ShutdownAllExcept('cellular') |
| use routing.getRouteFor() |
| to verify that only the expected device is used |
| do stuff to test cellular connections |
| finally: |
| device_manager.RestoreDevices() |
| """ |
| |
| @staticmethod |
| def _EnableDevice(device, enable): |
| """Enables/Disables a device in shill.""" |
| if enable: |
| device.Enable() |
| else: |
| device.Disable() |
| |
| def __init__(self, flim=None): |
| self.flim_ = flim or FlimFlam() |
| self.devices_to_restore_ = [] |
| |
| def ShutdownAllExcept(self, device_type): |
| """Shutdown all devices except device_type ones.""" |
| for device in self.flim_.GetObjectList('Device'): |
| device_properties = device.GetProperties(utf8_strings = True) |
| if (device_properties["Type"] != device_type): |
| logging.info("Powering off %s device %s", |
| device_properties["Type"], |
| device.object_path) |
| self.devices_to_restore_.append(device.object_path) |
| DeviceManager._EnableDevice(device, False) |
| |
| def RestoreDevices(self): |
| """Restore devices powered down in ShutdownAllExcept.""" |
| should_raise = False |
| to_raise = Exception("Nothing to raise") |
| for device_path in self.devices_to_restore_: |
| try: |
| logging.info("Attempting to power on device %s", device_path) |
| device = self.flim_.GetObjectInterface("Device", device_path) |
| DeviceManager._EnableDevice(device, True) |
| except Exception, e: |
| # We want to keep on trying to power things on, so save an |
| # exception and continue |
| should_raise = True |
| to_raise = e |
| if should_raise: |
| raise to_raise |