| #!/usr/bin/python |
| |
| """Wait for a series of SSID state transitions. |
| |
| Accepts a list of ServiceName=State pairs and waits for each transition to |
| occur. |
| |
| This provides a means for laying out a series of service state |
| transitions we expect to happen. The script runs and finds each |
| individual service (by default it also waits for the service to |
| actually come into existence) and then waits for the servive state |
| to transition to the desired state. The script then moves on to |
| the next transition. |
| |
| On success, a list that is as long as the input transitions is |
| returned, each with the number of seconds it took for each transiton |
| to occur. On failure, the last element will be a string containing |
| "ERR_..." which is the error that caused this state transition to |
| have failed. |
| """ |
| |
| import optparse |
| import sys |
| import time |
| import dbus |
| import dbus.mainloop.glib |
| import gobject |
| import subprocess |
| |
| FLIMFLAM = 'org.chromium.flimflam' |
| SUPPLICANT = 'fi.w1.wpa_supplicant1' |
| |
| bus_loop = dbus.mainloop.glib.DBusGMainLoop(set_as_default=True) |
| bus = dbus.SystemBus(mainloop=bus_loop) |
| manager = dbus.Interface(bus.get_object(FLIMFLAM, '/'), FLIMFLAM + '.Manager') |
| |
| |
| def GetObject(kind, path): |
| return dbus.Interface(bus.get_object(FLIMFLAM, path), FLIMFLAM + '.' + kind) |
| |
| def GetObjectList(kind, path_list): |
| if not path_list: |
| path_list = manager.GetProperties().get(kind + 's', []) |
| return [GetObject(kind, path) for path in path_list] |
| |
| |
| def PrintProperties(item): |
| print>>sys.stderr, '[ %s ]' % (item.object_path) |
| for key, val in item.GetProperties().items(): |
| print>>sys.stderr, ' %s = %s' % (key, str(val)) |
| |
| |
| # Open each log file and seek to the current end |
| def OpenLogs(*logfiles): |
| logs = [] |
| for logfile in logfiles: |
| try: |
| msgs = open(logfile) |
| msgs.seek(0, 2) |
| logs.append({ 'name': logfile, 'file': msgs }) |
| except Exception, e: |
| # If we cannot open the file, this is not necessarily an error |
| pass |
| |
| return logs |
| |
| |
| def DumpObjectList(kind): |
| print>>sys.stderr, '%s list:' % kind |
| for item in GetObjectList(kind, None): |
| PrintProperties(item) |
| |
| |
| # Returns the list of the wifi interfaces (e.g. "wlan0") known to flimflam |
| def GetWifiInterfaces(): |
| interfaces = [] |
| device_paths = manager.GetProperties().get('Devices', None) |
| for device_path in device_paths: |
| device = dbus.Interface( |
| bus.get_object('org.chromium.flimflam', device_path), |
| 'org.chromium.flimflam.Device') |
| props = device.GetProperties() |
| type = props.get('Type', None) |
| interface = props.get('Interface', None) |
| if type == 'wifi': |
| interfaces.append(interface) |
| return interfaces |
| |
| |
| def DumpLogs(logs): |
| for log in logs: |
| print>>sys.stderr, 'Content of %s during our run:' % log['name'] |
| print>>sys.stderr, ' ))) '.join(log['file'].readlines()) |
| |
| for interface in GetWifiInterfaces(): |
| print>>sys.stderr, 'iw dev %s scan output: %s' % \ |
| ( interface, |
| subprocess.Popen(['iw', 'dev', interface, 'scan', 'dump'], |
| stdout=subprocess.PIPE).communicate()[0]) |
| |
| DumpObjectList('Service') |
| |
| |
| def GetObjectProperty(kind, attr, props): |
| if attr == 'SSID' and kind == 'Service': |
| # Special case: Services don't actually have an "SSID" property |
| if 'WiFi.HexSSID' in props: |
| # Use the Service's hex WiFi.HexSSID property to generate a raw string |
| hex_ssid = props['WiFi.HexSSID'] |
| return ''.join([chr(int(hex_ssid[offset:offset+2], 16)) |
| for offset in range(0, len(hex_ssid), 2)]) |
| # Use the Service's name |
| return str(props.get('Name')) |
| return props.get(attr) |
| |
| |
| def FindObjects(kind, attr, val, path_list=None, cache=None): |
| """Find an object in the manager of type _kind_ with _attr_ set to _val_.""" |
| |
| if cache is None: |
| cache = {} |
| |
| ret = [] |
| if val in cache: |
| return cache[val] |
| |
| values = cache.values() |
| for obj in GetObjectList(kind, path_list): |
| if obj in values: |
| continue |
| try: |
| props = obj.GetProperties() |
| except dbus.exceptions.DBusException, e: |
| print>>sys.stderr, ('Got exception %s while getting props on %s' % |
| (e.get_dbus_name() , obj)) |
| continue |
| objval = GetObjectProperty(kind, attr, props) |
| if objval: |
| if not objval in cache: |
| cache[objval] = [obj] |
| else: |
| cache[objval].append(obj) |
| if objval == val: |
| ret.append(obj) |
| return ret |
| |
| class StateHandler(object): |
| """Listens for state transitions.""" |
| |
| def __init__(self, dbus_bus, in_state_list, run_timeout, step_timeout=None, |
| svc_timeout=None, debug=False): |
| self.bus = dbus_bus |
| self.state_list = list(in_state_list) |
| self.run_timeout = run_timeout |
| if step_timeout is None: |
| self.step_timeout = run_timeout |
| else: |
| self.step_timeout = step_timeout |
| if svc_timeout is None: |
| self.svc_timeout = self.step_timeout |
| else: |
| self.svc_timeout = svc_timeout |
| self.debug = debug |
| self.waiting_paths = {} |
| self.run_start_time = None |
| self.step_start_time = None |
| self.event_timeout_ptr = None |
| self.wait_path = None |
| self.wait_state = None |
| self.waiting_for_services = False |
| self.results = [] |
| self.service_cache = {} |
| self.svc_state = None |
| self.failure = False |
| |
| def Debug(self, debugstr): |
| if self.debug: |
| elapsed_time = time.time() - self.step_start_time |
| print>>sys.stderr, '[%8.3f] %s' % (elapsed_time, debugstr) |
| |
| def StateChangeCallback(self, attr, value, **kwargs): |
| """State change callback handle: did we enter the desired state?""" |
| |
| if str(attr) != 'State': |
| if str(attr) == 'Strength': |
| value = int(value) |
| self.Debug('Received signal (%s=%s)' % (str(attr), str(value))) |
| return |
| |
| state = str(value) |
| |
| if not 'path' in kwargs: |
| self.Debug('Cannot get path out of args passed to StateChangeCB') |
| self.runloop.quit() |
| |
| if str(kwargs['path']) != self.wait_path: |
| self.Debug('Path %s is not expected %s' % |
| (kwargs['path'], self.wait_path)) |
| return |
| |
| self.svc_state = state |
| self.Debug('Service %s changed state: %s' % |
| (repr(self.service_name), state)) |
| |
| if state == self.wait_state: |
| self.results.append('%.3f' % (time.time() - self.step_start_time)) |
| if not self.NextState(): |
| self.runloop.quit() |
| else: |
| self.StateChanged() |
| |
| def StateChanged(self): |
| pass |
| |
| def ServicesChangeCallback(self, attr, value): |
| """Each time service list changes, check to see if we find our service.""" |
| |
| if self.wait_path: |
| # Not interested -- we already have our service handle |
| return |
| |
| if str(attr) != 'Services': |
| # Not interested -- this is not a change to "Services" |
| return |
| |
| svc = self.FindService(value) |
| if svc: |
| self.Debug('Service %s added to service list' % repr(self.service_name)) |
| self.CancelTimeout() |
| elapsed_time = time.time() - self.step_start_time |
| if self.WaitForState(svc, self.step_timeout - elapsed_time): |
| self.results.append('%.3f' % elapsed_time) |
| return self.NextState() |
| elif self.failure: |
| self.results.append('ERR_FAILURE') |
| self.runloop.quit() |
| |
| def FindService(self, path_list=None): |
| ret = FindObjects('Service', 'Name', self.service_name, |
| path_list=path_list, cache=self.service_cache) |
| if len(ret): |
| return ret[0] |
| return None |
| |
| |
| def NextState(self): |
| """Set up a timer for the next desired state transition.""" |
| |
| self.CancelTimeout() |
| |
| if not self.state_list: |
| return False |
| |
| self.service_name, self.wait_state = self.state_list.pop(0) |
| if self.wait_state.startswith('+'): |
| self.wait_state = self.wait_state[1:] |
| self.waitchange = True |
| else: |
| self.waitchange = False |
| |
| now = time.time() |
| if self.run_start_time is None: |
| self.run_start_time = now |
| elapsed_time = time.time() - self.run_start_time |
| self.step_timeout = min(self.step_timeout, |
| self.run_timeout - elapsed_time) |
| self.step_start_time = now |
| |
| # Find a service that matches this ssid |
| svc = self.FindService() |
| if not svc: |
| if self.failure: |
| self.results.append('ERR_FAILURE') |
| return False |
| elif self.svc_timeout <= 0: |
| self.results.append('ERR_NOTFOUND') |
| return False |
| self.WaitForService(min(self.svc_timeout, self.step_timeout)) |
| else: |
| if self.WaitForState(svc, self.step_timeout): |
| self.results.append('0.0') |
| return self.NextState() |
| |
| return True |
| |
| def WaitForService(self, wait_time): |
| """Setup a callback for changes to the service list.""" |
| |
| self.svc_state = None |
| self.wait_path = None |
| |
| if not self.waiting_for_services: |
| self.waiting_for_services = True |
| self.bus.add_signal_receiver(self.ServicesChangeCallback, |
| signal_name='PropertyChanged', |
| dbus_interface=FLIMFLAM+'.Manager', |
| path='/') |
| |
| self.StartTimeout(wait_time) |
| |
| def WaitForState(self, svc, wait_time): |
| """Setup a callback for state changes on our service.""" |
| |
| # Are we already in the desired state? |
| self.svc_state = svc.GetProperties().get('State') |
| self.wait_path = svc.object_path |
| |
| self.StateChanged() |
| |
| if self.svc_state == self.wait_state and not self.waitchange: |
| return True |
| |
| if not self.wait_path in self.waiting_paths: |
| self.waiting_paths[self.wait_path] = True |
| self.bus.add_signal_receiver(self.StateChangeCallback, |
| signal_name='PropertyChanged', |
| dbus_interface=FLIMFLAM+'.Service', |
| path=self.wait_path, |
| path_keyword='path') |
| |
| self.StartTimeout(wait_time) |
| |
| def StartTimeout(self, wait_time): |
| if wait_time <= 0: |
| self.HandleTimeout() |
| else: |
| self.event_timeout_ptr = gobject.timeout_add(int(wait_time*1000), |
| self.HandleTimeout) |
| |
| def CancelTimeout(self): |
| if self.event_timeout_ptr is not None: |
| gobject.source_remove(self.event_timeout_ptr) |
| self.event_timeout_ptr = None |
| |
| def HandleTimeout(self): |
| if self.svc_state: |
| self.results.append('ERR_TIMEDOUT=' + self.svc_state) |
| else: |
| self.results.append('ERR_NOTFOUND') |
| self.runloop.quit() |
| |
| def PrintSummary(self): |
| print ' '.join(map(str, self.results)) |
| |
| def RunLoop(self): |
| self.runloop = gobject.MainLoop() |
| self.runloop.run() |
| |
| def Success(self): |
| if self.state_list or (self.results and self.results[-1].startswith('ERR')): |
| return False |
| return True |
| |
| def Failure(self): |
| return self.failure |
| |
| def main(argv): |
| parser = optparse.OptionParser('Usage: %prog [options...] [SSID=state...]') |
| parser.add_option('--run_timeout', dest='run_timeout', type='int', default=10, |
| help='Maximum time for sequence of state changes to occur') |
| parser.add_option('--step_timeout', dest='step_timeout', type='int', |
| help='Maximum time for a single state change to occur') |
| parser.add_option('--svc_timeout', dest='svc_timeout', type='int', |
| help='Maximum time to wait for service to exist') |
| parser.add_option('--debug', dest='debug', action='store_true', |
| help='Show debug info') |
| (options, args) = parser.parse_args(argv[1:]) |
| |
| state_list = [] |
| for arg in args: |
| name, sep, desired_state = arg.partition('=') |
| if sep != '=' or not desired_state: |
| parser.error('Invalid argument %s; arguments should be of the form ' |
| '"SSID=STATE..."' % arg) |
| |
| state_list.append((name, desired_state)) |
| |
| handler = StateHandler(bus, state_list, options.run_timeout, |
| options.step_timeout, options.svc_timeout, |
| options.debug) |
| |
| logs = OpenLogs('/var/log/messages') |
| if handler.NextState(): |
| handler.RunLoop() |
| |
| handler.PrintSummary() |
| |
| if not handler.Success(): |
| DumpLogs(logs) |
| sys.exit(1) |
| |
| sys.exit(0) |
| |
| if __name__ == '__main__': |
| main(sys.argv) |