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 logging 6import socket 7 8from autotest_lib.client.common_lib import error 9from autotest_lib.client.common_lib import global_config 10from autotest_lib.client.common_lib import utils 11from autotest_lib.client.common_lib.cros import dev_server 12from autotest_lib.client.common_lib.cros.graphite import autotest_stats 13from autotest_lib.server import afe_utils 14from autotest_lib.server import test 15 16 17_CONFIG = global_config.global_config 18# pylint: disable-msg=E1120 19_IMAGE_URL_PATTERN = _CONFIG.get_config_value( 20 'CROS', 'image_url_pattern', type=str) 21 22 23class provision_AutoUpdate(test.test): 24 """A test that can provision a machine to the correct ChromeOS version.""" 25 version = 1 26 27 def initialize(self, host, value, force=False, is_test_na=False): 28 """Initialize. 29 30 @param host: The host object to update to |value|. 31 @param value: The build type and version to install on the host. 32 @param force: not used by initialize. 33 @param is_test_na: boolean, if True, will simply skip the test 34 and emit TestNAError. The control file 35 determines whether the test should be skipped 36 and passes the decision via this argument. Note 37 we can't raise TestNAError in control file as it won't 38 be caught and handled properly. 39 """ 40 if is_test_na: 41 raise error.TestNAError( 42 'Test not available for test_that. chroot detected, ' 43 'you are probably using test_that.') 44 # We check value in initialize so that it fails faster. 45 if not value: 46 raise error.TestFail('No build version specified.') 47 48 49 @staticmethod 50 def log_devserver_match_stats(dut_hostname, devserver_url): 51 """Log stats whether host and devserver are in the same subnet. 52 53 @param dut_hostname: Hostname of the dut. 54 @param devserver_url: Url to the devserver. 55 """ 56 try: 57 devserver_name = dev_server.ImageServer.get_server_name( 58 devserver_url) 59 devserver_ip = socket.gethostbyname(devserver_name) 60 dut_ip = socket.gethostbyname(dut_hostname) 61 except socket.gaierror as e: 62 logging.error('Failed to get IP address, error: %s', e) 63 return 64 65 # Take the first 2 octets as the indicator of subnet. 66 devserver_subnet = '_'.join(devserver_ip.split('.')[0:2]) 67 dut_subnet = '_'.join(dut_ip.split('.')[0:2]) 68 if not utils.is_in_same_subnet(devserver_ip, dut_ip, 19): 69 counter = ('devserver_mismatch.%s_to_%s' % 70 (devserver_subnet, dut_subnet)) 71 autotest_stats.Counter(counter).increment() 72 counter = 'devserver_mismatch.%s' % devserver_subnet 73 else: 74 counter = 'devserver_match.%s' % devserver_subnet 75 76 autotest_stats.Counter(counter).increment() 77 78 79 def run_once(self, host, value, force=False): 80 """The method called by the control file to start the test. 81 82 @param host: The host object to update to |value|. 83 @param value: The host object to provision with a build corresponding 84 to |value|. 85 @param force: True iff we should re-provision the machine regardless of 86 the current image version. If False and the image 87 version matches our expected image version, no 88 provisioning will be done. 89 90 """ 91 logging.debug('Start provisioning %s to %s', host, value) 92 image = value 93 94 # If the host is already on the correct build, we have nothing to do. 95 # Note that this means we're not doing any sort of stateful-only 96 # update, and that we're relying more on cleanup to do cleanup. 97 # We could just not pass |force_update=True| to |machine_install|, 98 # but I'd like the semantics that a provision test 'returns' TestNA 99 # if the machine is already properly provisioned. 100 if not force and afe_utils.get_build(host) == value: 101 # We can't raise a TestNA, as would make sense, as that makes 102 # job.run_test return False as if the job failed. However, it'd 103 # still be nice to get this into the status.log, so we manually 104 # emit an INFO line instead. 105 self.job.record('INFO', None, None, 106 'Host already running %s' % value) 107 return 108 109 # We're about to reimage a machine, so we need full_payload and 110 # stateful. If something happened where the devserver doesn't have one 111 # of these, then it's also likely that it'll be missing autotest. 112 # Therefore, we require the devserver to also have autotest staged, so 113 # that the test that runs after this provision finishes doesn't error 114 # out because the devserver that its job_repo_url is set to is missing 115 # autotest test code. 116 # TODO(milleral): http://crbug.com/249426 117 # Add an asynchronous staging call so that we can ask the devserver to 118 # fetch autotest in the background here, and then wait on it after 119 # reimaging finishes or at some other point in the provisioning. 120 try: 121 ds = dev_server.ImageServer.resolve(image, host.hostname) 122 ds.stage_artifacts(image, ['full_payload', 'stateful', 123 'autotest_packages']) 124 except dev_server.DevServerException as e: 125 raise error.TestFail(str(e)) 126 127 self.log_devserver_match_stats(host.hostname, ds.url()) 128 129 url = _IMAGE_URL_PATTERN % (ds.url(), image) 130 131 logging.debug('Installing image') 132 try: 133 afe_utils.machine_install_and_update_labels(host, 134 force_update=True, 135 update_url=url, 136 force_full_update=force) 137 except error.InstallError as e: 138 logging.error(e) 139 raise error.TestFail(str(e)) 140 logging.debug('Finished provisioning %s to %s', host, value) 141