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 5import dbus 6import logging 7import time 8 9from autotest_lib.client.bin import utils 10from autotest_lib.client.cros.networking import shill_proxy 11 12 13class CellularProxy(shill_proxy.ShillProxy): 14 """Wrapper around shill dbus interface used by cellular tests.""" 15 16 # Properties exposed by shill. 17 DEVICE_PROPERTY_DBUS_OBJECT = 'DBus.Object' 18 DEVICE_PROPERTY_MODEL_ID = 'Cellular.ModelID' 19 DEVICE_PROPERTY_OUT_OF_CREDITS = 'Cellular.OutOfCredits' 20 DEVICE_PROPERTY_SIM_LOCK_STATUS = 'Cellular.SIMLockStatus' 21 DEVICE_PROPERTY_SIM_PRESENT = 'Cellular.SIMPresent' 22 DEVICE_PROPERTY_TECHNOLOGY_FAMILY = 'Cellular.Family' 23 DEVICE_PROPERTY_TECHNOLOGY_FAMILY_CDMA = 'CDMA' 24 DEVICE_PROPERTY_TECHNOLOGY_FAMILY_GSM = 'GSM' 25 SERVICE_PROPERTY_LAST_GOOD_APN = 'Cellular.LastGoodAPN' 26 27 # APN info property names. 28 APN_INFO_PROPERTY_APN = 'apn' 29 30 # Keys into the dictionaries exposed as properties. 31 PROPERTY_KEY_SIM_LOCK_TYPE = 'LockType' 32 PROPERTY_KEY_SIM_LOCK_ENABLED = 'LockEnabled' 33 PROPERTY_KEY_SIM_LOCK_RETRIES_LEFT = 'RetriesLeft' 34 35 # Valid values taken by properties exposed by shill. 36 VALUE_SIM_LOCK_TYPE_PIN = 'sim-pin' 37 VALUE_SIM_LOCK_TYPE_PUK = 'sim-puk' 38 39 # Various timeouts in seconds. 40 SERVICE_CONNECT_TIMEOUT = 60 41 SERVICE_DISCONNECT_TIMEOUT = 60 42 SERVICE_REGISTRATION_TIMEOUT = 60 43 SLEEP_INTERVAL = 0.1 44 45 def set_logging_for_cellular_test(self): 46 """Set the logging in shill for a test of cellular technology. 47 48 Set the log level to |ShillProxy.LOG_LEVEL_FOR_TEST| and the log scopes 49 to the ones defined in |ShillProxy.LOG_SCOPES_FOR_TEST| for 50 |ShillProxy.TECHNOLOGY_CELLULAR|. 51 52 """ 53 self.set_logging_for_test(self.TECHNOLOGY_CELLULAR) 54 55 56 def find_cellular_service_object(self): 57 """Returns the first dbus object found that is a cellular service. 58 59 @return DBus object for the first cellular service found. None if no 60 service found. 61 62 """ 63 return self.find_object('Service', {'Type': self.TECHNOLOGY_CELLULAR}) 64 65 66 def wait_for_cellular_service_object( 67 self, timeout_seconds=SERVICE_REGISTRATION_TIMEOUT): 68 """Waits for the cellular service object to show up. 69 70 @param timeout_seconds: Amount of time to wait for cellular service. 71 @return DBus object for the first cellular service found. 72 @raises ShillProxyError if no cellular service is found within the 73 registration timeout period. 74 75 """ 76 return utils.poll_for_condition( 77 lambda: self.find_cellular_service_object(), 78 exception=shill_proxy.ShillProxyTimeoutError( 79 'Failed to find cellular service object'), 80 timeout=timeout_seconds) 81 82 83 def find_cellular_device_object(self): 84 """Returns the first dbus object found that is a cellular device. 85 86 @return DBus object for the first cellular device found. None if no 87 device found. 88 89 """ 90 return self.find_object('Device', {'Type': self.TECHNOLOGY_CELLULAR}) 91 92 93 def reset_modem(self, modem, expect_device=True, expect_powered=True, 94 expect_service=True): 95 """Reset |modem|. 96 97 Do, in sequence, 98 (1) Ensure that the current device object disappears. 99 (2) If |expect_device|, ensure that the device reappears. 100 (3) If |expect_powered|, ensure that the device is powered. 101 (4) If |expect_service|, ensure that the service reappears. 102 103 This function does not check the service state for the device after 104 reset. 105 106 @param modem: DBus object for the modem to reset. 107 @param expect_device: If True, ensure that a DBus object reappears for 108 the same modem after the reset. 109 @param expect_powered: If True, ensure that the modem is powered on 110 after the reset. 111 @param expect_service: If True, ensure that a service managing the 112 reappeared modem also reappears. 113 114 @return (device, service) 115 device: DBus object for the reappeared Device after the reset. 116 service: DBus object for the reappeared Service after the reset. 117 Either of these may be None, if the object is not expected to 118 reappear. 119 120 @raises ShillProxyError if any of the conditions (1)-(4) fail. 121 122 """ 123 logging.info('Resetting modem') 124 # Obtain identifying information about the modem. 125 properties = modem.GetProperties(utf8_strings=True) 126 # NOTE: Using the Model ID means that this will break if we have two 127 # identical cellular modems in a DUT. Fortunately, we only support one 128 # modem at a time. 129 model_id = properties.get(self.DEVICE_PROPERTY_MODEL_ID) 130 if not model_id: 131 raise shill_proxy.ShillProxyError( 132 'Failed to get identifying information for the modem.') 133 old_modem_path = modem.object_path 134 old_modem_mm_object = properties.get(self.DEVICE_PROPERTY_DBUS_OBJECT) 135 if not old_modem_mm_object: 136 raise shill_proxy.ShillProxyError( 137 'Failed to get the mm object path for the modem.') 138 139 modem.Reset() 140 141 # (1) Wait for the old modem to disappear 142 utils.poll_for_condition( 143 lambda: self._is_old_modem_gone(old_modem_path, 144 old_modem_mm_object), 145 exception=shill_proxy.ShillProxyTimeoutError( 146 'Old modem disappeared'), 147 timeout=60) 148 149 150 # (2) Wait for the device to reappear 151 if not expect_device: 152 return None, None 153 # The timeout here should be sufficient for our slowest modem to 154 # reappear. 155 new_modem = utils.poll_for_condition( 156 lambda: self._get_reappeared_modem(model_id, 157 old_modem_mm_object), 158 exception=shill_proxy.ShillProxyTimeoutError( 159 'The modem reappeared after reset.'), 160 timeout=60) 161 162 # (3) Check powered state of the device 163 if not expect_powered: 164 return new_modem, None 165 success, _, _ = self.wait_for_property_in(new_modem, 166 self.DEVICE_PROPERTY_POWERED, 167 [self.VALUE_POWERED_ON], 168 timeout_seconds=10) 169 if not success: 170 raise shill_proxy.ShillProxyError( 171 'After modem reset, new modem failed to enter powered ' 172 'state.') 173 174 # (4) Check that service reappears 175 if not expect_service: 176 return new_modem, None 177 new_service = self.get_service_for_device(new_modem) 178 if not new_service: 179 raise shill_proxy.ShillProxyError( 180 'Failed to find a shill service managing the reappeared ' 181 'device.') 182 return new_modem, new_service 183 184 185 def disable_modem_for_test_setup(self, timeout_seconds=10): 186 """ 187 Disables all cellular modems. 188 189 Use this method only for setting up tests. Do not use this method to 190 test disable functionality because this method repeatedly attempts to 191 disable the cellular technology until it succeeds (ignoring all DBus 192 errors) since the DisableTechnology() call may fail for various reasons 193 (eg. an enable is in progress). 194 195 @param timeout_seconds: Amount of time to wait until the modem is 196 disabled. 197 @raises ShillProxyError if the modems fail to disable within 198 |timeout_seconds|. 199 200 """ 201 def _disable_cellular_technology(self): 202 try: 203 self._manager.DisableTechnology(self.TECHNOLOGY_CELLULAR) 204 return True 205 except dbus.DBusException as e: 206 return False 207 208 utils.poll_for_condition( 209 lambda: _disable_cellular_technology(self), 210 exception=shill_proxy.ShillProxyTimeoutError( 211 'Failed to disable cellular technology.'), 212 timeout=timeout_seconds) 213 modem = self.find_cellular_device_object() 214 self.wait_for_property_in(modem, self.DEVICE_PROPERTY_POWERED, 215 [self.VALUE_POWERED_OFF], 216 timeout_seconds=timeout_seconds) 217 218 219 def _is_old_modem_gone(self, modem_path, modem_mm_object): 220 """Tests if the DBus object for modem disappears after Reset. 221 222 @param modem_path: The DBus path for the modem object that must vanish. 223 @param modem_mm_object: The modemmanager object path reported by the 224 old modem. This is unique everytime a new modem is (re)exposed. 225 226 @return True if the object disappeared, false otherwise. 227 228 """ 229 device = self.get_dbus_object(self.DBUS_TYPE_DEVICE, modem_path) 230 try: 231 properties = device.GetProperties() 232 # DBus object exists, perhaps a reappeared device? 233 return (properties.get(self.DEVICE_PROPERTY_DBUS_OBJECT) != 234 modem_mm_object) 235 except dbus.DBusException as e: 236 if e.get_dbus_name() == self.DBUS_ERROR_UNKNOWN_OBJECT: 237 return True 238 return False 239 240 241 def _get_reappeared_modem(self, model_id, old_modem_mm_object): 242 """Check that a vanished modem reappers. 243 244 @param model_id: The model ID reported by the vanished modem. 245 @param old_modem_mm_object: The previously reported modemmanager object 246 path for this modem. 247 248 @return The reappeared DBus object, if any. None otherwise. 249 250 """ 251 # TODO(pprabhu) This will break if we have multiple cellular devices 252 # in the system at the same time. 253 device = self.find_cellular_device_object() 254 if not device: 255 return None 256 properties = device.GetProperties(utf8_strings=True) 257 if (model_id == properties.get(self.DEVICE_PROPERTY_MODEL_ID) and 258 (old_modem_mm_object != 259 properties.get(self.DEVICE_PROPERTY_DBUS_OBJECT))): 260 return device 261 return None 262