1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
|
#!/usr/bin/python
# Copyright (c) 2011 The Chromium 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 copy
import dbus
import logging
import os
import time
from chromeos.power_strip import PowerStrip
import pyauto
import pyauto_errors
class WifiPowerStrip(PowerStrip):
"""Manages the power state of wifi routers connected to a power strip.
This class provides additional functionality over PowerStrip by providing
a timeout feature for wifi routers connected to the strip. This is to prevent
repeated on/off calls to the same router which may put the router in an
undesired state.
"""
def __init__ (self, host, routers):
"""Initializes a WifiPowerStrip object.
Args:
host: IP of the switch that the routers are attached to.
routers: Dictionary of wifi routers in the following format:
{
'< router name >': {
'strip_id' : '.aX' # where X is the port number
< additional fields may be added here for each router >
}
}
"""
self._router_dict = routers
# Each router will have a timestamp associated to it regarding whether
# or not an action can be performed on it yet. This is to prevent
# the spamming of power on/off calls on a particular router.
# The WifiPowerStrip_UsableTime field specifies the earliest time
# after which the router may be used. We will initialize it to now
# since they should all be usable at init.
for router_info in self._router_dict.values():
router_info['WifiPowerStrip_UsableTime'] = time.time()
# _routers_used keeps track of which routers were used during the lifetime
# of the WifiPowerStrip instance. Adding used routers occurs when
# a wifi router has been turned on. Otherwise, it get clears upon
# the TurnOffUsedRouters call.
self._routers_used = set()
PowerStrip.__init__(self, host)
def GetRouterConfig(self, router_name):
"""Returns the configuration for the specified router.
Args:
router_name: A string specifying the router.
Returns:
The config dictionary for the given router if the router is defined.
None otherwise.
"""
return copy.deepcopy(self._router_dict.get(router_name))
def RouterPower(self, router_name, power_state, pause_after=5):
"""Executes PowerStrip commands.
Args:
router_name: The name of the router to perform the action on.
power_state: A boolean value where True represents turning the router on
and False represents turning the router off.
pause_after: Specified in seconds, and specifies the time to sleep
after a command is run. This is to prevent spamming of
power on/off of the same router which has put the router
in an undesirable state.
Raises:
Exception if router_name is not a valid router.
"""
router = self.GetRouterConfig(router_name)
if not router: raise Exception('Invalid router name \'%s\'.' % router_name)
# Hidden routers will always be on. Don't allow controlling of the power
# for these networks.
if router.get('hidden'):
return
sleep_time = router['WifiPowerStrip_UsableTime'] - time.time()
if sleep_time > 0:
time.sleep(sleep_time)
if power_state:
self._routers_used |= set([router_name])
logging.debug('Turning on router %s:%s.' %
(router['strip_id'], router_name))
self.PowerOn(router['strip_id'])
else:
logging.debug('Turning off router %s:%s.' %
(router['strip_id'], router_name))
self.PowerOff(router['strip_id'])
# Set the Usable time of the particular router to pause_after
# seconds after the current time.
router['WifiPowerStrip_UsableTime'] = time.time() + pause_after
def TurnOffAllRouters(self):
"""Turns off all the routers."""
for router in self._router_dict:
self.RouterPower(router, False, pause_after=0)
def TurnOffUsedRouters(self):
"""Turns off the routers that were once turned on."""
for router in self._routers_used:
self.RouterPower(router, False, pause_after=0)
self._routers_used = set()
class PyNetworkUITest(pyauto.PyUITest):
"""A subclass of PyUITest for Chrome OS network tests.
A subclass of PyUITest that automatically sets the flimflam
priorities to put wifi connections first before starting tests.
This is for convenience when writing wifi tests.
"""
_ROUTER_CONFIG_FILE = os.path.join(pyauto.PyUITest.DataDir(),
'pyauto_private', 'chromeos', 'network',
'wifi_testbed_config')
_FLIMFLAM_PATH = 'org.chromium.flimflam'
_proxy = dbus.SystemBus().get_object(_FLIMFLAM_PATH, '/')
_manager = dbus.Interface(_proxy, _FLIMFLAM_PATH + '.Manager')
def setUp(self):
# Move ethernet to the end of the flimflam priority list,
# effectively hiding any ssh connections that the
# test harness might be using and putting wifi ahead.
self._PushServiceOrder('vpn,bluetooth,wifi,wimax,cellular,ethernet')
self._ParseDefaultRoutingTable()
pyauto.PyUITest.setUp(self)
self.ForgetAllRememberedNetworks()
self._wifi_power_strip = None
def tearDown(self):
self.ForgetAllRememberedNetworks()
pyauto.PyUITest.tearDown(self)
self._PopServiceOrder()
if self._wifi_power_strip:
self._wifi_power_strip.TurnOffUsedRouters()
# Remove the route entry for the power strip.
if hasattr(self, 'ps_route_entry'):
os.system('route del -net %(ipaddress)s gateway %(gateway)s netmask '
'%(netmask)s dev %(iface)s' % self.ps_route_entry)
def _ParseDefaultRoutingTable(self):
"""Creates and stores a dictionary of the default routing paths."""
route_table_headers = ['destination', 'gateway', 'genmask', 'flags',
'metric', 'ref', 'use', 'iface']
routes = os.popen('route -n | egrep "^0.0.0.0"').read()
routes = [interface.split() for interface in routes.split('\n')][:-1]
self.default_routes = {}
for iface in routes:
self.default_routes[iface[-1]] = dict(zip(route_table_headers, iface))
def ForgetAllRememberedNetworks(self):
"""Forgets all networks that the device has marked as remembered."""
for service in self.GetNetworkInfo()['remembered_wifi']:
self.ForgetWifiNetwork(service)
def _SetServiceOrder(self, service_order):
self._manager.SetServiceOrder(service_order)
# Flimflam throws a dbus exception if device is already disabled. This
# is not an error.
try:
self._manager.DisableTechnology('wifi')
except dbus.DBusException as e:
if 'org.chromium.flimflam.Error.AlreadyDisabled' not in str(e):
raise e
self._manager.EnableTechnology('wifi')
def _PushServiceOrder(self, service_order):
self._old_service_order = self._manager.GetServiceOrder()
self._SetServiceOrder(service_order)
service_order = service_order.split(',')
# Verify services that are present in both the service_order
# we set and the one retrieved from device are in the correct order.
set_service_order = self._manager.GetServiceOrder().split(',')
common_service = set(service_order) & set(set_service_order)
service_order = [s for s in service_order if s in common_service]
set_service_order = [s for s in set_service_order if s in common_service]
assert service_order == set_service_order, \
'Flimflam service order not set properly. %s != %s' % \
(service_order, set_service_order)
def _PopServiceOrder(self):
self._SetServiceOrder(self._old_service_order)
# Verify services that are present in both the service_order
# we set and the one retrieved from device are in the correct order.
old_service_order = self._old_service_order.split(',')
set_service_order = self._manager.GetServiceOrder().split(',')
common_service = set(old_service_order) & set(set_service_order)
old_service_order = [s for s in old_service_order if s in common_service]
set_service_order = [s for s in set_service_order if s in common_service]
assert old_service_order == set_service_order, \
'Flimflam service order not set properly. %s != %s' % \
(old_service_order, set_service_order)
def _SetupRouteForPowerStrip(self, ipaddress, iface='eth'):
"""Create a route table entry for the power strip."""
# Assume device has only one interface that is prepended with
# $iface and use that one.
try:
iface = [ key for key in self.default_routes.keys() if iface in key ][0]
except:
assert 'Unable to find interface of type %s.' % iface
self.ps_route_entry = {
'iface' : iface,
'gateway' : self.default_routes[iface]['gateway'],
'netmask' : '255.255.255.255',
'ipaddress' : ipaddress
}
os.system('route add -net %(ipaddress)s gateway %(gateway)s netmask '
'%(netmask)s dev %(iface)s' % self.ps_route_entry)
# Verify the route was added.
assert os.system('route -n | egrep "^%(ipaddress)s[[:space:]]+%(gateway)s'
'[[:space:]]+%(netmask)s"' % self.ps_route_entry) == 0, \
'Failed to create default route for powerstrip.'
def InitWifiPowerStrip(self):
"""Initializes the router controller using the specified config file."""
assert os.path.exists(PyNetworkUITest._ROUTER_CONFIG_FILE), \
'Router configuration file does not exist.'
config = pyauto.PyUITest.EvalDataFrom(self._ROUTER_CONFIG_FILE)
strip_ip, routers = config['strip_ip'], config['routers']
self._SetupRouteForPowerStrip(strip_ip)
self._wifi_power_strip = WifiPowerStrip(strip_ip, routers)
self.RouterPower = self._wifi_power_strip.RouterPower
self.TurnOffAllRouters = self._wifi_power_strip.TurnOffAllRouters
self.GetRouterConfig = self._wifi_power_strip.GetRouterConfig
def WaitUntilWifiNetworkAvailable(self, ssid, timeout=60, is_hidden=False):
"""Waits until the given network is available.
Routers that are just turned on may take up to 1 minute upon turning them
on to broadcast their SSID.
Args:
ssid: SSID of the service we want to connect to.
timeout: timeout (in seconds)
Raises:
Exception if timeout duration has been hit before wifi router is seen.
Returns:
True, when the wifi network is seen within the timout period.
False, otherwise.
"""
def _GotWifiNetwork():
# Returns non-empty array if desired SSID is available.
try:
return [wifi for wifi in
self.NetworkScan().get('wifi_networks', {}).values()
if wifi.get('name') == ssid]
except pyauto_errors.JSONInterfaceError:
# Temporary fix until crosbug.com/14174 is fixed.
# NetworkScan is only used in updating the list of networks so errors
# thrown by it are not critical to the results of wifi tests that use
# this method.
return False
# The hidden AP's will always be on, thus we will assume it is ready to
# connect to.
if is_hidden:
return bool(_GotWifiNetwork())
return self.WaitUntil(_GotWifiNetwork, timeout=timeout, retry_sleep=1)
def GetConnectedWifi(self):
"""Returns the SSID of the currently connected wifi network.
Returns:
The SSID of the connected network or None if we're not connected.
"""
service_list = self.GetNetworkInfo()
connected_service_path = service_list.get('connected_wifi')
if 'wifi_networks' in service_list and \
connected_service_path in service_list['wifi_networks']:
return service_list['wifi_networks'][connected_service_path]['name']
def GetServicePath(self, ssid):
"""Returns the service path associated with an SSID.
Args:
ssid: String defining the SSID we are searching for.
Returns:
The service path or None if SSID does not exist.
"""
service_list = self.GetNetworkInfo()
service_list = service_list.get('wifi_networks', [])
for service_path, service_obj in service_list.iteritems():
if service_obj['name'] == ssid:
return service_path
return None
def ConnectToWifiRouter(self, router_name):
"""Connects to a router by name.
Args:
router_name: The name of the router that is specified in the
configuration file.
"""
router = self._wifi_power_strip.GetRouterConfig(router_name)
assert router, 'Router with name %s is not defined ' \
'in the router configuration.' % router_name
security = router.get('security', 'SECURITY_NONE')
passphrase = router.get('passphrase', '')
# Branch off the connect calls depending on if the wifi network is hidden
# or not.
error_string = None
if router.get('hidden'):
error_string = self.ConnectToHiddenWifiNetwork(router['ssid'], security,
passphrase)
else:
service_path = self.GetServicePath(router['ssid'])
assert service_path, 'Service with SSID %s is not present.' % \
router['ssid']
logging.debug('Connecting to router %s.' % router_name)
error_string = self.ConnectToWifiNetwork(service_path, passphrase)
return error_string
|