1# 2# Copyright (C) 2012 The Android Open Source Project 3# 4# Licensed under the Apache License, Version 2.0 (the "License"); 5# you may not use this file except in compliance with the License. 6# You may obtain a copy of the License at 7# 8# http://www.apache.org/licenses/LICENSE-2.0 9# 10# Unless required by applicable law or agreed to in writing, software 11# distributed under the License is distributed on an "AS IS" BASIS, 12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13# See the License for the specific language governing permissions and 14# limitations under the License. 15# 16 17# DEPRECATED 18# Do not use flimflam.py in future development. 19# Extend / migrate to shill_proxy suite of scripts instead. 20 21import logging, time 22 23import dbus 24 25DEFAULT_CELLULAR_TIMEOUT = 60 26 27def make_dbus_boolean(value): 28 value = value.upper() 29 if value in ["ON", "TRUE"]: 30 return dbus.Boolean(1) 31 elif value in ["OFF", "FALSE"]: 32 return dbus.Boolean(0) 33 else: 34 return dbus.Boolean(int(value)) 35 36# 37# Convert a DBus value to a printable value; used 38# to print properties returned via DBus 39# 40def convert_dbus_value(value, indent=0): 41 # DEPRECATED 42 spacer = ' ' * indent 43 if value.__class__ == dbus.Byte: 44 return int(value) 45 elif value.__class__ == dbus.Boolean: 46 return bool(value) 47 elif value.__class__ == dbus.Dictionary: 48 valstr = "{" 49 for key in value: 50 valstr += "\n" + spacer + " " + \ 51 key + ": " + str(convert_dbus_value(value[key], indent + 4)) 52 valstr += "\n" + spacer + "}" 53 return valstr 54 elif value.__class__ == dbus.Array: 55 valstr = "[" 56 for val in value: 57 valstr += "\n" + spacer + " " + \ 58 str(convert_dbus_value(val, indent + 4)) 59 valstr += "\n" + spacer + "]" 60 return valstr 61 else: 62 return str(value) 63 64class FlimFlam(object): 65 # DEPRECATED 66 67 SHILL_DBUS_INTERFACE = "org.chromium.flimflam" 68 UNKNOWN_METHOD = 'org.freedesktop.DBus.Error.UnknownMethod' 69 UNKNOWN_OBJECT = 'org.freedesktop.DBus.Error.UnknownObject' 70 71 DEVICE_WIMAX = 'wimax' 72 DEVICE_CELLULAR = 'cellular' 73 74 @staticmethod 75 def _GetContainerName(kind): 76 """Map shill element names to the names of their collections.""" 77 # For example, Device - > Devices. 78 # Just pulling this out so we can use a map if we start 79 # caring about "AvailableTechnologies" 80 return kind + "s" 81 82 @staticmethod 83 def WaitForServiceState(service, expected_states, timeout, 84 ignore_failure=False, property_name="State"): 85 """Wait until service enters a state in expected_states or times out. 86 Args: 87 service: service to watch 88 expected_states: list of exit states 89 timeout: in seconds 90 ignore_failure: should the failure state be ignored? 91 property_name: name of service property 92 93 Returns: (state, seconds waited) 94 95 If the state is "failure" and ignore_failure is False we return 96 immediately without waiting for the timeout. 97 """ 98 99 state = None 100 start_time = time.time() 101 timeout = start_time + timeout 102 while time.time() < timeout: 103 properties = service.GetProperties(utf8_strings = True) 104 state = properties.get(property_name, None) 105 if ((state == "failure" and not ignore_failure) or 106 state in expected_states): 107 break 108 time.sleep(.5) 109 110 config_time = time.time() - start_time 111 # str() to remove DBus boxing 112 return (str(state), config_time) 113 114 @staticmethod 115 def DisconnectService(service, wait_timeout=15): 116 try: 117 service.Disconnect() 118 except dbus.exceptions.DBusException, error: 119 if error.get_dbus_name() not in [ 120 FlimFlam.SHILL_DBUS_INTERFACE + ".Error.InProgress", 121 FlimFlam.SHILL_DBUS_INTERFACE + ".Error.NotConnected", ]: 122 raise error 123 return FlimFlam.WaitForServiceState(service, ['idle'], wait_timeout) 124 125 def __init__(self, bus=None): 126 if not bus: 127 bus = dbus.SystemBus() 128 self.bus = bus 129 shill = bus.get_object(FlimFlam.SHILL_DBUS_INTERFACE, "/") 130 self.manager = dbus.Interface( 131 shill, 132 FlimFlam.SHILL_DBUS_INTERFACE + ".Manager") 133 134 def _FindDevice(self, device_type, timeout): 135 """ Return the first device object that matches a given device type. 136 137 Wait until the device type is avilable or until timeout 138 139 Args: 140 device_type: string format of the type of device. 141 timeout: in seconds 142 143 Returns: Device or None 144 """ 145 timeout = time.time() + timeout 146 device_obj = None 147 while time.time() < timeout: 148 device_obj = self.FindElementByPropertySubstring('Device', 149 'Type', 150 device_type) 151 if device_obj: 152 break 153 time.sleep(1) 154 return device_obj 155 156 def FindCellularDevice(self, timeout=DEFAULT_CELLULAR_TIMEOUT): 157 return self._FindDevice(self.DEVICE_CELLULAR, timeout) 158 159 def FindWimaxDevice(self, timeout=30): 160 return self._FindDevice(self.DEVICE_WIMAX, timeout) 161 162 def _FindService(self, device_type, timeout): 163 """Return the first service object that matches the device type. 164 165 Wait until a service is available or until the timeout. 166 167 Args: 168 device_type: string format of the type of device. 169 timeout: in seconds 170 171 Returns: service or None 172 """ 173 start_time = time.time() 174 timeout = start_time + timeout 175 service = None 176 while time.time() < timeout: 177 service = self.FindElementByPropertySubstring('Service', 178 'Type', device_type) 179 if service: 180 break 181 time.sleep(.5) 182 return service 183 184 def FindCellularService(self, timeout=DEFAULT_CELLULAR_TIMEOUT): 185 return self._FindService(self.DEVICE_CELLULAR, timeout) 186 187 def FindWimaxService(self, timeout=30): 188 return self._FindService(self.DEVICE_WIMAX, timeout) 189 190 def GetService(self, params): 191 path = self.manager.GetService(params) 192 return self.GetObjectInterface("Service", path) 193 194 def ConnectService(self, assoc_timeout=15, config_timeout=15, 195 async=False, service=None, service_type='', 196 retry=False, retries=1, retry_sleep=15, 197 save_creds=False, 198 **kwargs): 199 """Connect to a service and wait until connection is up 200 Args: 201 assoc_timeout, config_timeout: Timeouts in seconds. 202 async: return immediately. do not wait for connection. 203 service: DBus service 204 service_type: If supplied, invoke type-specific code to find service. 205 retry: Retry connection after Connect failure. 206 retries: Number of retries to allow. 207 retry_sleep: Number of seconds to wait before retrying. 208 kwargs: Additional args for type-specific code 209 210 Returns: 211 (success, dictionary), where dictionary contains stats and diagnostics. 212 """ 213 output = {} 214 connected_states = ["ready", "portal", "online"] 215 216 # Retry connections on failure. Need to call GetService again as some 217 # Connect failure states are unrecoverable. 218 connect_success = False 219 while not connect_success: 220 if service_type == "wifi": 221 try: 222 # Sanity check to make sure the caller hasn't provided 223 # both a service and a service type. At which point its 224 # unclear what they actually want to do, so err on the 225 # side of caution and except out. 226 if service: 227 raise Exception('supplied service and service type') 228 params = { 229 "Type": service_type, 230 "Mode": kwargs["mode"], 231 "SSID": kwargs["ssid"], 232 "Security": kwargs.get("security", "none"), 233 "SaveCredentials": save_creds } 234 # Supply a passphrase only if it is non-empty. 235 passphrase = kwargs.get("passphrase", "") 236 if passphrase: 237 params["Passphrase"] = passphrase 238 path = self.manager.GetService(params) 239 service = self.GetObjectInterface("Service", path) 240 except Exception, e: 241 output["reason"] = "FAIL(GetService): exception %s" % e 242 return (False, output) 243 244 output["service"] = service 245 246 try: 247 service.Connect() 248 connect_success = True 249 except Exception, e: 250 if not retry or retries == 0: 251 output["reason"] = "FAIL(Connect): exception %s" % e 252 return (False, output) 253 else: 254 logging.info("INFO(Connect): connect failed. Retrying...") 255 retries -= 1 256 257 if not connect_success: 258 # FlimFlam can be a little funny sometimes. At least for In 259 # Progress errors, even though the service state may be failed, 260 # it is actually still trying to connect. As such, while we're 261 # waiting for retry, keep checking the service state to see if 262 # it actually succeeded in connecting. 263 state = FlimFlam.WaitForServiceState( 264 service=service, 265 expected_states=connected_states, 266 timeout=retry_sleep, 267 ignore_failure=True)[0] 268 269 if state in connected_states: 270 return (True, output) 271 272 # While service can be caller provided, it is also set by the 273 # GetService call above. If service was not caller provided we 274 # need to reset it to None so we don't fail the sanity check 275 # above. 276 if service_type != '': 277 service = None 278 279 if async: 280 return (True, output) 281 282 logging.info("Associating...") 283 (state, assoc_time) = ( 284 FlimFlam.WaitForServiceState(service, 285 ["configuration"] + connected_states, 286 assoc_timeout)) 287 output["state"] = state 288 if state == "failure": 289 output["reason"] = "FAIL(assoc)" 290 if assoc_time > assoc_timeout: 291 output["reason"] = "TIMEOUT(assoc)" 292 output["assoc_time"] = assoc_time 293 if "reason" in output: 294 return (False, output) 295 296 297 (state, config_time) = ( 298 FlimFlam.WaitForServiceState(service, 299 connected_states, config_timeout)) 300 output["state"] = state 301 if state == "failure": 302 output["reason"] = "FAIL(config)" 303 if config_time > config_timeout: 304 output["reason"] = "TIMEOUT(config)" 305 output["config_time"] = config_time 306 307 if "reason" in output: 308 return (False, output) 309 310 return (True, output) 311 312 def GetObjectInterface(self, kind, path): 313 return dbus.Interface( 314 self.bus.get_object(FlimFlam.SHILL_DBUS_INTERFACE, path), 315 FlimFlam.SHILL_DBUS_INTERFACE + "." + kind) 316 317 def FindElementByNameSubstring(self, kind, substring): 318 properties = self.manager.GetProperties(utf8_strings = True) 319 for path in properties[FlimFlam._GetContainerName(kind)]: 320 if path.find(substring) >= 0: 321 return self.GetObjectInterface(kind, path) 322 return None 323 324 def FindElementByPropertySubstring(self, kind, prop, substring): 325 properties = self.manager.GetProperties(utf8_strings = True) 326 for path in properties[FlimFlam._GetContainerName(kind)]: 327 obj = self.GetObjectInterface(kind, path) 328 try: 329 obj_properties = obj.GetProperties(utf8_strings = True) 330 except dbus.exceptions.DBusException, error: 331 if (error.get_dbus_name() == self.UNKNOWN_METHOD or 332 error.get_dbus_name() == self.UNKNOWN_OBJECT): 333 # object disappeared; ignore and keep looking 334 continue 335 else: 336 raise error 337 if (prop in obj_properties and 338 obj_properties[prop].find(substring) >= 0): 339 return obj 340 return None 341 342 def GetObjectList(self, kind, properties=None): 343 if properties is None: 344 properties = self.manager.GetProperties(utf8_strings = True) 345 return [self.GetObjectInterface(kind, path) 346 for path in properties[FlimFlam._GetContainerName(kind)]] 347 348 def GetActiveProfile(self): 349 properties = self.manager.GetProperties(utf8_strings = True) 350 return self.GetObjectInterface("Profile", properties["ActiveProfile"]) 351 352 def CreateProfile(self, ident): 353 path = self.manager.CreateProfile(ident) 354 return self.GetObjectInterface("Profile", path) 355 356 def RemoveProfile(self, ident): 357 self.manager.RemoveProfile(ident) 358 359 def PushProfile(self, ident): 360 path = self.manager.PushProfile(ident) 361 return self.GetObjectInterface("Profile", path) 362 363 def PopProfile(self, ident): 364 self.manager.PopProfile(ident) 365 366 def PopAnyProfile(self): 367 self.manager.PopAnyProfile() 368 369 def GetSystemState(self): 370 properties = self.manager.GetProperties(utf8_strings = True) 371 return properties["State"] 372 373 def GetDebugTags(self): 374 return self.manager.GetDebugTags() 375 376 def ListDebugTags(self): 377 return self.manager.ListDebugTags() 378 379 def SetDebugTags(self, taglist): 380 try: 381 self.manager.SetDebugTags(taglist) 382 self.SetDebugLevel(-4) 383 except dbus.exceptions.DBusException, error: 384 if error.get_dbus_name() not in [ 385 "org.freedesktop.DBus.Error.UnknownMethod" ]: 386 raise error 387 388 def SetDebugLevel(self, level): 389 self.manager.SetDebugLevel(level) 390 391 def GetServiceOrder(self): 392 return self.manager.GetServiceOrder() 393 394 def SetServiceOrder(self, new_order): 395 old_order = self.GetServiceOrder() 396 self.manager.SetServiceOrder(new_order) 397 return (old_order, new_order) 398 399 def EnableTechnology(self, tech): 400 try: 401 self.manager.EnableTechnology(tech) 402 except dbus.exceptions.DBusException, error: 403 if error.get_dbus_name() not in [ 404 FlimFlam.SHILL_DBUS_INTERFACE + ".Error.AlreadyEnabled", 405 FlimFlam.SHILL_DBUS_INTERFACE + ".Error.InProgress" ]: 406 raise error 407 408 def DisableTechnology(self, tech): 409 self.manager.DisableTechnology(tech, timeout=60) 410 411 def RequestScan(self, technology): 412 self.manager.RequestScan(technology) 413 414 def GetCountry(self): 415 properties = self.manager.GetProperties(utf8_strings = True) 416 return properties["Country"] 417 418 def SetCountry(self, country): 419 self.manager.SetProperty("Country", country) 420 421 def GetCheckPortalList(self): 422 properties = self.manager.GetProperties(utf8_strings = True) 423 return properties["CheckPortalList"] 424 425 def SetCheckPortalList(self, tech_list): 426 self.manager.SetProperty("CheckPortalList", tech_list) 427 428 def GetPortalURL(self): 429 properties = self.manager.GetProperties(utf8_strings = True) 430 return properties["PortalURL"] 431 432 def SetPortalURL(self, url): 433 self.manager.SetProperty("PortalURL", url) 434 435 def GetArpGateway(self): 436 properties = self.manager.GetProperties() 437 return properties["ArpGateway"] 438 439 def SetArpGateway(self, do_arp_gateway): 440 self.manager.SetProperty("ArpGateway", do_arp_gateway) 441 442 443class DeviceManager(object): 444 # DEPRECATED 445 """Use flimflam to isolate a given interface for testing. 446 447 DeviceManager can be used to turn off network devices that are not 448 under test so that they will not interfere with testing. 449 450 NB: Ethernet devices are special inside Flimflam. You will need to 451 take care of them via other means (like, for example, the 452 backchannel ethernet code in client autotests) 453 454 Sample usage: 455 456 device_manager = flimflam.DeviceManager() 457 try: 458 device_manager.ShutdownAllExcept('cellular') 459 use routing.getRouteFor() 460 to verify that only the expected device is used 461 do stuff to test cellular connections 462 finally: 463 device_manager.RestoreDevices() 464 """ 465 466 @staticmethod 467 def _EnableDevice(device, enable): 468 """Enables/Disables a device in shill.""" 469 if enable: 470 device.Enable() 471 else: 472 device.Disable() 473 474 def __init__(self, flim=None): 475 self.flim_ = flim or FlimFlam() 476 self.devices_to_restore_ = [] 477 478 def ShutdownAllExcept(self, device_type): 479 """Shutdown all devices except device_type ones.""" 480 for device in self.flim_.GetObjectList('Device'): 481 device_properties = device.GetProperties(utf8_strings = True) 482 if (device_properties["Type"] != device_type): 483 logging.info("Powering off %s device %s", 484 device_properties["Type"], 485 device.object_path) 486 self.devices_to_restore_.append(device.object_path) 487 DeviceManager._EnableDevice(device, False) 488 489 def RestoreDevices(self): 490 """Restore devices powered down in ShutdownAllExcept.""" 491 should_raise = False 492 to_raise = Exception("Nothing to raise") 493 for device_path in self.devices_to_restore_: 494 try: 495 logging.info("Attempting to power on device %s", device_path) 496 device = self.flim_.GetObjectInterface("Device", device_path) 497 DeviceManager._EnableDevice(device, True) 498 except Exception, e: 499 # We want to keep on trying to power things on, so save an 500 # exception and continue 501 should_raise = True 502 to_raise = e 503 if should_raise: 504 raise to_raise 505