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 collections
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.client.common_lib import global_config
11from autotest_lib.client.common_lib import utils
12from autotest_lib.client.common_lib.cros import dev_server
13from autotest_lib.client.common_lib.cros.network import ping_runner
14from autotest_lib.server import hosts
15from autotest_lib.server import site_linux_router
16from autotest_lib.server import test
17from autotest_lib.server.cros import dnsname_mangler
18from autotest_lib.server.cros import provisioner
19from autotest_lib.server.cros.network import wifi_test_context_manager
20
21
22# Stable versions come from the builders.
23# The builder version is used to build the URL of the corresponding image
24# in Google Storage.
25# The image version is a line from /etc/lsb-release in the corresponding image.
26StableVersion = collections.namedtuple('StableVersion',
27                                       ['builder_version', 'release_version'])
28
29class network_WiFi_UpdateRouter(test.test):
30    """Updates a router to the most recent stable version.
31
32    This is not a test per se, since it does not test client behavior.  However
33    it is advantageous to write this as a test so that we can schedule it to
34    run periodically via the same infrastructure we use to run tests.
35
36    Note that this test is very much patterned on provision_QuickProvision.
37
38    """
39    version = 1
40
41    STABLE_VERSIONS = {
42            ## crbug.com/1098024: these are left here as documentation of what the
43            # last stable version is, but the current updater code does not support
44            # them.
45            'whirlwind':
46            StableVersion('whirlwind-test-ap-tryjob/R85-13310.60.0-b4641849',
47                          '13310.60.2020_08_25_0212'),
48            'gale':
49            StableVersion('gale-test-ap-tryjob/R92-13982.81.0-b4959409',
50                          '13982.81.2021_08_11_1044'),
51    }
52
53    # List of files to remove.
54    FILES_TO_REMOVE = ['/var/spool/crash/*', '/tmp/*',
55                       '/var/lib/metrics/uma-events']
56
57
58    def get_release_version(self, host):
59        result = host.run('cat /etc/lsb-release')
60        for line in result.stdout.splitlines():
61            if line.startswith('CHROMEOS_RELEASE_VERSION='):
62                return line.split('=', 1)[1]
63
64
65    def get_update_url(self, ds_url, image):
66        CONFIG = global_config.global_config
67        IMAGE_URL_PATTERN = CONFIG.get_config_value(
68                'CROS', 'image_url_pattern', type=str)
69        return IMAGE_URL_PATTERN % (ds_url, image)
70
71
72    def warmup(self, raw_cmdline_args):
73        """Possibly parse the router hostname from the commandline.
74
75        @param raw_cmdline_args raw input from autotest.
76
77        """
78        cmdline_args = utils.args_to_dict(raw_cmdline_args)
79        logging.info('Running wifi test with commandline arguments: %r',
80                     cmdline_args)
81        self._router_hostname_from_cmdline = cmdline_args.get(
82                wifi_test_context_manager.WiFiTestContextManager. \
83                        CMDLINE_ROUTER_ADDR)
84
85
86    def freeup_disk_space(self, device_host):
87        """Remove files to free up disk space.
88
89        @param device_host: router / pcap host object
90
91        """
92        for path in self.FILES_TO_REMOVE:
93            device_host.run('rm -rf %s' % path, ignore_status=True)
94
95    def stop_recover_duts(self, device_host):
96        """Stop running recover_duts on the host.
97
98        b/177380545: recover_duts is currently providing negative value on
99        routers. TBD: decided whether we should re-enable this when router
100        images are updated to fix hang issues?
101
102        @param device_host: router / pcap host object
103        """
104        device_host.run('rm -f %s' % provisioner.LAB_MACHINE_FILE,
105                        ignore_status=True)
106        device_host.run('stop recover_duts', ignore_status=True)
107
108    def run_once(self, host, is_pcap=False):
109        """Update router / packet capture associated with host.
110
111        @param host DUT connected to AP/Pcap that needs update
112
113        """
114        if is_pcap:
115            device_hostname = dnsname_mangler.get_pcap_addr(
116                    client_hostname=host.hostname)
117        else:
118            device_hostname = site_linux_router.build_router_hostname(
119                client_hostname=host.hostname,
120                router_hostname=self._router_hostname_from_cmdline)
121
122        ping_helper = ping_runner.PingRunner()
123        if not ping_helper.simple_ping(device_hostname):
124            # Pcap devices aren't always present. Just claim Not Applicable if
125            # we couldn't find it.
126            e = error.TestNAError if is_pcap else error.TestError
127            raise e('%s not found / is down.' % device_hostname)
128
129        # Use CrosHost for all router/pcap hosts and avoid host detection.
130        # Host detection would use JetstreamHost for Whirlwind routers.
131        # JetstreamHost assumes ap-daemons are running.
132        # Testbed routers run the testbed-ap profile with no ap-daemons.
133        # TODO(ecgh): crbug.com/757075 Fix testbed-ap JetstreamHost detection.
134        device_host = hosts.create_host(device_hostname,
135                                        host_class=hosts.CrosHost,
136                                        allow_failure=True)
137
138        # Stop recover_duts now, for cases where we don't go through a full
139        # update below.
140        self.stop_recover_duts(device_host)
141
142        # Remove un-wanted files to freeup diskspace before starting update.
143        self.freeup_disk_space(device_host)
144        self.update_device(device_host)
145
146        # Stop recover_duts again, in case provisioning re-enabled it.
147        self.stop_recover_duts(device_host)
148
149    def update_device(self, device_host):
150        """Update router and pcap associated with host.
151
152        @param device_host: router / pcap host object
153        @param device_board: router / pcap board name
154
155        """
156        device_board = device_host.get_board().split(':', 1)[1]
157        desired = self.STABLE_VERSIONS.get(device_board, None)
158        if desired is None:
159            raise error.TestFail('No stable version found for %s with board=%s.'
160                                 % (device_host.hostname, device_board))
161
162        logging.info('Checking whether %s is at the latest stable version: %s',
163                     device_host.hostname, desired.release_version)
164        current_release_version = self.get_release_version(device_host)
165        if desired.release_version == current_release_version:
166            raise error.TestNAError('%s is already at latest version %s.' %
167                                    (device_host.hostname,
168                                     desired.release_version))
169
170        logging.info('Updating %s to image %s from %s',
171                     device_host.hostname, desired.release_version,
172                     current_release_version)
173        try:
174            ds = dev_server.ImageServer.resolve(desired.builder_version,
175                                                device_host.hostname)
176        except dev_server.DevServerException as e:
177            logging.error(e)
178            raise error.TestFail(str(e))
179
180        url = self.get_update_url(ds.url(), desired.builder_version)
181        provisioner.ChromiumOSProvisioner(url,
182                                          host=device_host).run_provision()
183