• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright (c) 2019 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
5"""This file provides core logic for labstation verify/repair process."""
6
7import logging
8
9from autotest_lib.client.common_lib import error
10from autotest_lib.server.hosts import base_label
11from autotest_lib.server.hosts import cros_label
12from autotest_lib.server.cros import autoupdater
13from autotest_lib.server.hosts import labstation_repair
14from autotest_lib.server.cros import provision
15from autotest_lib.server.hosts import base_servohost
16from autotest_lib.client.cros import constants as client_constants
17from autotest_lib.server.cros.dynamic_suite import constants as ds_constants
18from autotest_lib.server.cros.dynamic_suite import tools
19from autotest_lib.client.common_lib import lsbrelease_utils
20from autotest_lib.client.common_lib.cros import dev_server
21from autotest_lib.server import utils as server_utils
22
23
24class LabstationHost(base_servohost.BaseServoHost):
25    """Labstation specific host class"""
26
27    # Threshold we decide to ignore a in_use file lock. In minutes
28    IN_USE_FILE_EXPIRE_MINS = 120
29
30    # Uptime threshold to perform a labstation reboot, this is to prevent a
31    # broken DUT keep trying to reboot a labstation. In hours
32    UP_TIME_THRESH_HOLD_HOURS = 24
33
34    VERSION_PREFIX = provision.CROS_VERSION_PREFIX
35
36    @staticmethod
37    def check_host(host, timeout=10):
38        """
39        Check if the given host is a labstation host.
40
41        @param host: An ssh host representing a device.
42        @param timeout: The timeout for the run command.
43
44        @return: True if the host device is labstation.
45
46        @raises AutoservRunError: If the command failed.
47        @raises AutoservSSHTimeout: Ssh connection has timed out.
48
49        """
50        try:
51            result = host.run(
52                'grep -q labstation /etc/lsb-release',
53                ignore_status=True, timeout=timeout)
54        except (error.AutoservRunError, error.AutoservSSHTimeout):
55            return False
56        return result.exit_status == 0
57
58
59    def _initialize(self, hostname, *args, **dargs):
60        super(LabstationHost, self)._initialize(hostname=hostname,
61                                                *args, **dargs)
62        self._repair_strategy = (
63            labstation_repair.create_labstation_repair_strategy())
64        self.labels = base_label.LabelRetriever(cros_label.LABSTATION_LABELS)
65        logging.info('adding fake host_info_store to LabstationHost')
66        try:
67            host_info = self.host_info_store.get()
68            host_info.stable_versions['servo-cros'] = host_info.stable_versions['cros']
69            self.set_dut_host_info(host_info)
70        except Exception as e:
71            logging.exception(e)
72
73
74    def is_reboot_requested(self):
75        """Check if a reboot is requested for this labstation, the reboot can
76        either be requested from labstation or DUTs. For request from DUTs we
77        only process it if uptime longer than a threshold because we want
78        to prevent a broken servo keep its labstation in reboot cycle.
79
80        @returns True if a reboot is required, otherwise False
81        """
82        if self._check_update_status() == autoupdater.UPDATER_NEED_REBOOT:
83            logging.info('Labstation reboot requested from labstation for'
84                         ' update image')
85            return True
86
87        if not self._validate_uptime():
88            logging.info('Ignoring DUTs reboot request because %s was'
89                         ' rebooted in last 24 hours.', self.hostname)
90            return False
91
92        cmd = 'find %s*%s' % (self.TEMP_FILE_DIR, self.REBOOT_FILE_POSTFIX)
93        output = self.run(cmd, ignore_status=True).stdout
94        if output:
95            in_use_file_list = output.strip().split('\n')
96            logging.info('%s DUT(s) are currently requesting to'
97                         ' reboot labstation.', len(in_use_file_list))
98            return True
99        else:
100            return False
101
102
103    def try_reboot(self):
104        """Try to reboot the labstation if it's safe to do(no servo in use,
105         and not processing updates), and cleanup reboot control file.
106        """
107        if (self._is_servo_in_use() or self._check_update_status()
108            in autoupdater.UPDATER_PROCESSING_UPDATE):
109            logging.info('Aborting reboot action because some DUT(s) are'
110                         ' currently using servo(s) or'
111                         ' labstation update is in processing.')
112            return
113        self._servo_host_reboot()
114        logging.info('Cleaning up reboot control files.')
115        self._cleanup_post_reboot()
116
117
118    def get_labels(self):
119        """Return the detected labels on the host."""
120        return self.labels.get_labels(self)
121
122
123    def get_os_type(self):
124        return 'labstation'
125
126
127    def prepare_for_update(self):
128        """Prepares the DUT for an update.
129        Subclasses may override this to perform any special actions
130        required before updating.
131        """
132        pass
133
134
135    def _get_lsb_release_content(self):
136        """Return the content of lsb-release file of host."""
137        return self.run(
138            'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
139
140
141    def get_release_version(self):
142        """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
143
144        @returns The version string in lsb-release, under attribute
145                 CHROMEOS_RELEASE_VERSION.
146        """
147        return lsbrelease_utils.get_chromeos_release_version(
148            lsb_release_content=self._get_lsb_release_content())
149
150    def verify_job_repo_url(self, tag=''):
151        """
152        Make sure job_repo_url of this host is valid.
153
154        Eg: The job_repo_url "http://lmn.cd.ab.xyx:8080/static/\
155        lumpy-release/R29-4279.0.0/autotest/packages" claims to have the
156        autotest package for lumpy-release/R29-4279.0.0. If this isn't the case,
157        download and extract it. If the devserver embedded in the url is
158        unresponsive, update the job_repo_url of the host after staging it on
159        another devserver.
160
161        @param job_repo_url: A url pointing to the devserver where the autotest
162            package for this build should be staged.
163        @param tag: The tag from the server job, in the format
164                    <job_id>-<user>/<hostname>, or <hostless> for a server job.
165
166        @raises DevServerException: If we could not resolve a devserver.
167        @raises AutoservError: If we're unable to save the new job_repo_url as
168            a result of choosing a new devserver because the old one failed to
169            respond to a health check.
170        @raises urllib2.URLError: If the devserver embedded in job_repo_url
171                                  doesn't respond within the timeout.
172        """
173        info = self.host_info_store.get()
174        job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
175        if not job_repo_url:
176            logging.warning('No job repo url set on host %s', self.hostname)
177            return
178
179        logging.info('Verifying job repo url %s', job_repo_url)
180        devserver_url, image_name = tools.get_devserver_build_from_package_url(
181            job_repo_url)
182
183        ds = dev_server.ImageServer(devserver_url)
184
185        logging.info('Staging autotest artifacts for %s on devserver %s',
186                     image_name, ds.url())
187
188        ds.stage_artifacts(image_name, ['autotest_packages'])
189
190
191    def host_version_prefix(self, image):
192        """Return version label prefix.
193
194        In case the CrOS provisioning version is something other than the
195        standard CrOS version e.g. CrOS TH version, this function will
196        find the prefix from provision.py.
197
198        @param image: The image name to find its version prefix.
199        @returns: A prefix string for the image type.
200        """
201        return provision.get_version_label_prefix(image)
202
203
204    def stage_server_side_package(self, image=None):
205        """Stage autotest server-side package on devserver.
206
207        @param image: Full path of an OS image to install or a build name.
208
209        @return: A url to the autotest server-side package.
210
211        @raise: error.AutoservError if fail to locate the build to test with, or
212                fail to stage server-side package.
213        """
214        # If enable_drone_in_restricted_subnet is False, do not set hostname
215        # in devserver.resolve call, so a devserver in non-restricted subnet
216        # is picked to stage autotest server package for drone to download.
217        hostname = self.hostname
218        if not server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
219            hostname = None
220        if image:
221            image_name = tools.get_build_from_image(image)
222            if not image_name:
223                raise error.AutoservError(
224                    'Failed to parse build name from %s' % image)
225            ds = dev_server.ImageServer.resolve(image_name, hostname)
226        else:
227            info = self.host_info_store.get()
228            job_repo_url = info.attributes.get(ds_constants.JOB_REPO_URL, '')
229            if job_repo_url:
230                devserver_url, image_name = (
231                    tools.get_devserver_build_from_package_url(job_repo_url))
232                # If enable_drone_in_restricted_subnet is True, use the
233                # existing devserver. Otherwise, resolve a new one in
234                # non-restricted subnet.
235                if server_utils.ENABLE_DRONE_IN_RESTRICTED_SUBNET:
236                    ds = dev_server.ImageServer(devserver_url)
237                else:
238                    ds = dev_server.ImageServer.resolve(image_name)
239            elif info.build is not None:
240                ds = dev_server.ImageServer.resolve(info.build, hostname)
241                image_name = info.build
242            else:
243                raise error.AutoservError(
244                    'Failed to stage server-side package. The host has '
245                    'no job_repo_url attribute or cros-version label.')
246
247        ds.stage_artifacts(image_name, ['autotest_server_package'])
248        return '%s/static/%s/%s' % (ds.url(), image_name,
249                                    'autotest_server_package.tar.bz2')
250
251
252    def repair(self):
253        """Attempt to repair a labstation.
254        """
255        message = 'Beginning repair for host %s board %s model %s'
256        info = self.host_info_store.get()
257        message %= (self.hostname, info.board, info.model)
258        self.record('INFO', None, None, message)
259        self._repair_strategy.repair(self)
260
261
262    def _validate_uptime(self):
263        return (float(self.check_uptime()) >
264                self.UP_TIME_THRESH_HOLD_HOURS * 3600)
265
266
267    def _is_servo_in_use(self):
268        """Determine if there are any DUTs currently running task that uses
269         servo, only files that has been touched within pre-set threshold of
270          minutes counts.
271
272        @returns True if any DUTs is using servos, otherwise False.
273        """
274        cmd = 'find %s*%s -mmin -%s' % (self.TEMP_FILE_DIR,
275                                        self.LOCK_FILE_POSTFIX,
276                                        self.IN_USE_FILE_EXPIRE_MINS)
277        result = self.run(cmd, ignore_status=True)
278        return bool(result.stdout)
279
280
281    def _cleanup_post_reboot(self):
282        """Clean up all xxxx_reboot file after reboot."""
283        cmd = 'rm %s*%s' % (self.TEMP_FILE_DIR, self.REBOOT_FILE_POSTFIX)
284        self.run(cmd, ignore_status=True)
285