• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Lint as: python2, python3
2# Copyright (c) 2014 The Chromium OS Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import logging
7import time
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib.cros.network import iw_runner
11from autotest_lib.client.common_lib.cros.network import ping_runner
12from autotest_lib.client.common_lib.cros.network import xmlrpc_datatypes
13from autotest_lib.server import hosts
14from autotest_lib.server.cros.network import wifi_client
15from autotest_lib.server.cros.network import netperf_runner
16
17WORK_CLIENT_CONNECTION_RETRIES = 3
18WAIT_FOR_CONNECTION = 10
19
20class ConnectionWorker(object):
21    """ ConnectionWorker is a thin layer of interfaces for worker classes """
22
23    @property
24    def name(self):
25        """@return a string: representing name of the worker class"""
26        raise NotImplementedError('Missing subclass implementation')
27
28
29    @classmethod
30    def create_from_parent(cls, parent_obj, **init_args):
31        """Creates a derived ConnectionWorker object from the provided parent
32        object.
33
34        @param cls: derived class object which we're trying to create.
35        @param obj: existing parent class object.
36        @param init_args: Args to be passed to the derived class constructor.
37
38        @returns Instance of cls with the required fields copied from parent.
39        """
40        obj = cls(**init_args)
41        obj.work_client = parent_obj.work_client
42        obj.host = parent_obj.host
43        return obj
44
45
46    def prepare_work_client(self, work_client_machine):
47        """Prepare the SSHHost object into WiFiClient object
48
49        @param work_client_machine: a SSHHost object to be wrapped
50
51        """
52        work_client_host = hosts.create_host(work_client_machine.hostname)
53        # All packet captures in chaos lab have dual NICs. Let us use phy1 to
54        # be a radio dedicated for work client
55        iw = iw_runner.IwRunner(remote_host=work_client_host)
56        phys = iw.list_phys()
57        devs = iw.list_interfaces(desired_if_type='managed')
58        if len(devs) > 0:
59            logging.debug('Removing interfaces in work host machine %s', devs)
60            for i in range(len(devs)):
61                iw.remove_interface(devs[i].if_name)
62        if len(phys) > 1:
63            logging.debug('Adding interfaces in work host machine')
64            iw.add_interface('phy1', 'work0', 'managed')
65            logging.debug('Interfaces in work client %s', iw.list_interfaces())
66        elif len(phys) == 1:
67            raise error.TestError('Not enough phys available to create a'
68                                  'work client interface %s.' %
69                                   work_client_host.hostname)
70        self.work_client = wifi_client.WiFiClient(
71                work_client_host, './debug', False)
72        # Make the host object easily accessible
73        self.host = self.work_client.host
74
75
76    def connect_work_client(self, assoc_params):
77        """
78        Connect client to the AP.
79
80        Tries to connect the work client to AP in WORK_CLIENT_CONNECTION_RETRIES
81        tries. If we fail to connect in all tries then we would return False
82        otherwise returns True on successful connection to the AP.
83
84        @param assoc_params: an AssociationParameters object.
85        @return a boolean: True if work client is successfully connected to AP
86                or False on failing to connect to the AP
87
88        """
89        if not self.work_client.shill.init_test_network_state():
90            logging.error('Failed to set up isolated test context profile for '
91                          'work client.')
92            return False
93
94        success = False
95        for i in range(WORK_CLIENT_CONNECTION_RETRIES):
96            logging.info('Connecting work client to AP')
97            assoc_result = xmlrpc_datatypes.deserialize(
98                           self.work_client.shill.connect_wifi(assoc_params))
99            success = assoc_result.success
100            if not success:
101                logging.error('Connection attempt of work client failed, try %d'
102                              ' reason: %s', (i+1), assoc_result.failure_reason)
103            else:
104                logging.info('Work client connected to the AP')
105                self.ssid = assoc_params.ssid
106                break
107        return success
108
109
110    def cleanup(self):
111        """Teardown work_client"""
112        self.work_client.shill.disconnect(self.ssid)
113        self.work_client.shill.clean_profiles()
114
115
116    def run(self, client):
117        """Executes the connection worker
118
119        @param client: WiFiClient object representing the DUT
120
121        """
122        raise NotImplementedError('Missing subclass implementation')
123
124
125class ConnectionDuration(ConnectionWorker):
126    """This test is to check the liveliness of the connection to the AP. """
127
128    def __init__(self, duration_sec=30):
129        """
130        Holds WiFi connection open with periodic pings
131
132        @param duration_sec: amount of time to hold connection in seconds
133
134        """
135
136        self.duration_sec = duration_sec
137
138
139    @property
140    def name(self):
141        """@return a string: representing name of this class"""
142        return 'duration'
143
144
145    def run(self, client):
146        """Periodically pings work client to check liveliness of the connection
147
148        @param client: WiFiClient object representing the DUT
149
150        """
151        ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
152        logging.info('Pinging work client ip: %s', self.work_client.wifi_ip)
153        start_time = time.time()
154        while time.time() - start_time < self.duration_sec:
155            time.sleep(10)
156            ping_result = client.ping(ping_config)
157            logging.info('Connection liveness %r', ping_result)
158
159
160class ConnectionSuspend(ConnectionWorker):
161    """
162    This test is to check the liveliness of the connection to the AP with
163    suspend resume cycle involved.
164
165    """
166
167    def __init__(self, suspend_sec=30):
168        """
169        Construct a ConnectionSuspend.
170
171        @param suspend_sec: amount of time to suspend in seconds
172
173        """
174
175        self._suspend_sec = suspend_sec
176
177
178    @property
179    def name(self):
180        """@return a string: representing name of this class"""
181        return 'suspend'
182
183
184    def run(self, client):
185        """
186        Check the liveliness of the connection to the AP by pinging the work
187        client before and after a suspend resume.
188
189        @param client: WiFiClient object representing the DUT
190
191        """
192        ping_config = ping_runner.PingConfig(self.work_client.wifi_ip, count=10)
193        # pinging work client to ensure we have a connection
194        logging.info('work client ip: %s', self.work_client.wifi_ip)
195        ping_result = client.ping(ping_config)
196        logging.info('before suspend:%r', ping_result)
197        client.do_suspend(self._suspend_sec)
198        # When going to suspend, DUTs using ath9k devices do not disassociate
199        # from the AP. On resume, DUTs would re-use the association from prior
200        # to suspend. However, this leads to some confused state for some APs
201        # (see crbug.com/346417) where the AP responds to actions frames like
202        # NullFunc but not to any data frames like DHCP/ARP packets from the
203        # DUT.  Let us sleep for:
204        #       + 2 seconds for linkmonitor to detect failure if any
205        #       + 10 seconds for ReconnectTimer timeout
206        #       + 5 seconds to reconnect to the AP
207        #       + 3 seconds let us not have a very strict timeline.
208        # 20 seconds before we start to query shill about the connection state.
209        # TODO (krisr): add board detection code in wifi_client and adjust the
210        # sleep time here based on the wireless chipset
211        time.sleep(20)
212
213        # Wait for WAIT_FOR_CONNECTION time before trying to ping.
214        success, state, elapsed_time = client.wait_for_service_states(
215                self.ssid, client.CONNECTED_STATES, WAIT_FOR_CONNECTION)
216        if not success:
217            raise error.TestFail('DUT failed to connect to AP (%s state) after'
218                                 'resume in %d seconds' %
219                                 (state, WAIT_FOR_CONNECTION))
220        else:
221            logging.info('DUT entered %s state after %s seconds',
222                         state, elapsed_time)
223            # ping work client to ensure we have connection after resume.
224            ping_result = client.ping(ping_config)
225            logging.info('after resume:%r', ping_result)
226
227
228class ConnectionNetperf(ConnectionWorker):
229    """
230    This ConnectionWorker is used to run a sustained data transfer between the
231    DUT and the work_client through an AP.
232
233    """
234
235    # Minimum expected throughput for netperf streaming tests
236    NETPERF_MIN_THROUGHPUT = 2.0 # Mbps
237
238    def __init__(self, netperf_config):
239        """
240        Construct a ConnectionNetperf object.
241
242        @param netperf_config: NetperfConfig object to define transfer test.
243
244        """
245        self._config = netperf_config
246
247
248    @property
249    def name(self):
250        """@return a string: representing name of this class"""
251        return 'netperf_%s' % self._config.human_readable_tag
252
253
254    def run(self, client):
255        """
256        Create a NetperfRunner, run netperf between DUT and work_client.
257
258        @param client: WiFiClient object representing the DUT
259
260        """
261        with netperf_runner.NetperfRunner(
262                client, self.work_client, self._config) as netperf:
263            ping_config = ping_runner.PingConfig(
264                    self.work_client.wifi_ip, count=10)
265            # pinging work client to ensure we have a connection
266            logging.info('work client ip: %s', self.work_client.wifi_ip)
267            ping_result = client.ping(ping_config)
268
269            result = netperf.run(self._config)
270            logging.info('Netperf Result: %s', result)
271
272        if result is None:
273            raise error.TestError('Failed to create NetperfResult')
274
275        if result.duration_seconds < self._config.test_time:
276            raise error.TestFail(
277                    'Netperf duration too short: %0.2f < %0.2f' %
278                    (result.duration_seconds, self._config.test_time))
279
280        # TODO: Convert this limit to a perf metric crbug.com/348780
281        if result.throughput <self.NETPERF_MIN_THROUGHPUT:
282            raise error.TestFail(
283                    'Netperf throughput too low: %0.2f < %0.2f' %
284                    (result.throughput, self.NETPERF_MIN_THROUGHPUT))
285