blob: a0a60aac4a019bc5d6d25ca3228b6e8ee8a8a5d0 [file] [log] [blame]
Derek Beckettf73baca2020-08-19 15:08:47 -07001# Lint as: python2, python3
Garry Wangebc015b2019-06-06 17:45:06 -07002# Copyright (c) 2019 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#
6# Expects to be run in an environment with sudo and no interactive password
7# prompt, such as within the Chromium OS development chroot.
8
9
10"""This is a base host class for servohost and labstation."""
11
12
Derek Beckettf73baca2020-08-19 15:08:47 -070013import six.moves.http_client
Garry Wangebc015b2019-06-06 17:45:06 -070014import logging
15import socket
Derek Beckettf73baca2020-08-19 15:08:47 -070016import six.moves.xmlrpc_client
Garry Wang78ce64d2020-10-13 18:23:45 -070017import time
Garry Wangebc015b2019-06-06 17:45:06 -070018
19from autotest_lib.client.bin import utils
Derek Beckett15775132020-10-01 12:49:45 -070020from autotest_lib.client.common_lib import autotest_enum
Garry Wangebc015b2019-06-06 17:45:06 -070021from autotest_lib.client.common_lib import error
22from autotest_lib.client.common_lib import hosts
23from autotest_lib.client.common_lib import lsbrelease_utils
24from autotest_lib.client.common_lib.cros import dev_server
Garry Wang358aad42020-08-02 20:56:04 -070025from autotest_lib.client.common_lib.cros import kernel_utils
Garry Wangebc015b2019-06-06 17:45:06 -070026from autotest_lib.client.cros import constants as client_constants
Garry Wang358aad42020-08-02 20:56:04 -070027from autotest_lib.server import autotest
Garry Wangebc015b2019-06-06 17:45:06 -070028from autotest_lib.server import site_utils as server_utils
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -070029from autotest_lib.server.cros import provisioner
Garry Wangebc015b2019-06-06 17:45:06 -070030from autotest_lib.server.hosts import ssh_host
31from autotest_lib.site_utils.rpm_control_system import rpm_client
32
Garry Wangebc015b2019-06-06 17:45:06 -070033
34class BaseServoHost(ssh_host.SSHHost):
35 """Base host class for a host that manage servo(s).
36 E.g. beaglebone, labstation.
37 """
Garry Wang3d84a162020-01-24 13:29:43 +000038 REBOOT_CMD = 'sleep 5; reboot & sleep 10; reboot -f'
Garry Wangebc015b2019-06-06 17:45:06 -070039
Garry Wang79e9af62019-06-12 15:19:19 -070040 TEMP_FILE_DIR = '/var/lib/servod/'
41
42 LOCK_FILE_POSTFIX = '_in_use'
43 REBOOT_FILE_POSTFIX = '_reboot'
Garry Wangebc015b2019-06-06 17:45:06 -070044
Garry Wang5715ee52019-12-23 11:00:47 -080045 # Time to wait a rebooting servohost, in seconds.
Garry Wangfb253432019-09-11 17:08:38 -070046 REBOOT_TIMEOUT = 240
Garry Wangebc015b2019-06-06 17:45:06 -070047
Garry Wang5715ee52019-12-23 11:00:47 -080048 # Timeout value to power cycle a servohost, in seconds.
49 BOOT_TIMEOUT = 240
50
Garry Wang358aad42020-08-02 20:56:04 -070051 # Constants that reflect current host update state.
Derek Beckett15775132020-10-01 12:49:45 -070052 UPDATE_STATE = autotest_enum.AutotestEnum('IDLE', 'RUNNING',
53 'PENDING_REBOOT')
Garry Wangebc015b2019-06-06 17:45:06 -070054
55 def _initialize(self, hostname, is_in_lab=None, *args, **dargs):
56 """Construct a BaseServoHost object.
57
58 @param is_in_lab: True if the servo host is in Cros Lab. Default is set
59 to None, for which utils.host_is_in_lab_zone will be
60 called to check if the servo host is in Cros lab.
61
62 """
63 super(BaseServoHost, self)._initialize(hostname=hostname,
64 *args, **dargs)
65 self._is_localhost = (self.hostname == 'localhost')
66 if self._is_localhost:
67 self._is_in_lab = False
68 elif is_in_lab is None:
69 self._is_in_lab = utils.host_is_in_lab_zone(self.hostname)
70 else:
71 self._is_in_lab = is_in_lab
72
73 # Commands on the servo host must be run by the superuser.
74 # Our account on a remote host is root, but if our target is
75 # localhost then we might be running unprivileged. If so,
76 # `sudo` will have to be added to the commands.
77 if self._is_localhost:
78 self._sudo_required = utils.system_output('id -u') != '0'
79 else:
80 self._sudo_required = False
81
82 self._is_labstation = None
Gregory Nisbet8e2fbb22019-12-05 11:36:37 -080083 self._dut_host_info = None
Otabek Kasimov2b50cdb2020-07-06 19:16:06 -070084 self._dut_hostname = None
Garry Wangebc015b2019-06-06 17:45:06 -070085
86
87 def get_board(self):
88 """Determine the board for this servo host. E.g. fizz-labstation
89
Garry Wang5e118c02019-09-25 14:24:57 -070090 @returns a string representing this labstation's board or None if
91 target host is not using a ChromeOS image(e.g. test in chroot).
Garry Wangebc015b2019-06-06 17:45:06 -070092 """
Garry Wang5e118c02019-09-25 14:24:57 -070093 output = self.run('cat /etc/lsb-release', ignore_status=True).stdout
94 return lsbrelease_utils.get_current_board(lsb_release_content=output)
Garry Wangebc015b2019-06-06 17:45:06 -070095
96
Garry Wangd7367482020-02-27 13:52:40 -080097 def set_dut_host_info(self, dut_host_info):
98 """
99 @param dut_host_info: A HostInfo object.
100 """
101 logging.info('setting dut_host_info field to (%s)', dut_host_info)
102 self._dut_host_info = dut_host_info
103
104
105 def get_dut_host_info(self):
106 """
107 @return A HostInfo object.
108 """
109 return self._dut_host_info
Gregory Nisbet8e2fbb22019-12-05 11:36:37 -0800110
111
Otabek Kasimov2b50cdb2020-07-06 19:16:06 -0700112 def set_dut_hostname(self, dut_hostname):
113 """
114 @param dut_hostname: hostname of the DUT that connected to this servo.
115 """
116 logging.info('setting dut_hostname as (%s)', dut_hostname)
117 self._dut_hostname = dut_hostname
118
119
120 def get_dut_hostname(self):
121 """
122 @returns hostname of the DUT that connected to this servo.
123 """
124 return self._dut_hostname
125
126
Garry Wangebc015b2019-06-06 17:45:06 -0700127 def is_labstation(self):
128 """Determine if the host is a labstation
129
130 @returns True if ths host is a labstation otherwise False.
131 """
132 if self._is_labstation is None:
133 board = self.get_board()
Garry Wang88dc8632019-07-24 16:53:50 -0700134 self._is_labstation = board is not None and 'labstation' in board
Garry Wangebc015b2019-06-06 17:45:06 -0700135
136 return self._is_labstation
137
138
Garry Wang14831832020-03-04 17:21:49 -0800139 def _get_lsb_release_content(self):
140 """Return the content of lsb-release file of host."""
141 return self.run(
142 'cat "%s"' % client_constants.LSB_RELEASE).stdout.strip()
143
144
145 def get_release_version(self):
Garry Wangebc015b2019-06-06 17:45:06 -0700146 """Get the value of attribute CHROMEOS_RELEASE_VERSION from lsb-release.
147
148 @returns The version string in lsb-release, under attribute
Garry Wang14831832020-03-04 17:21:49 -0800149 CHROMEOS_RELEASE_VERSION(e.g. 12900.0.0). None on fail.
Garry Wangebc015b2019-06-06 17:45:06 -0700150 """
Garry Wangebc015b2019-06-06 17:45:06 -0700151 return lsbrelease_utils.get_chromeos_release_version(
Garry Wang14831832020-03-04 17:21:49 -0800152 lsb_release_content=self._get_lsb_release_content()
153 )
154
155
156 def get_full_release_path(self):
157 """Get full release path from servohost as string.
158
159 @returns full release path as a string
160 (e.g. fizz-labstation-release/R82.12900.0.0). None on fail.
161 """
162 return lsbrelease_utils.get_chromeos_release_builder_path(
163 lsb_release_content=self._get_lsb_release_content()
164 )
Garry Wangebc015b2019-06-06 17:45:06 -0700165
166
167 def _check_update_status(self):
Garry Wang358aad42020-08-02 20:56:04 -0700168 """ Check servohost's current update state.
169
170 @returns: one of below state of from self.UPDATE_STATE
171 IDLE -- if the target host is not currently updating and not
172 pending on a reboot.
173 RUNNING -- if there is another updating process that running on
174 target host(note: we don't expect to hit this scenario).
175 PENDING_REBOOT -- if the target host had an update and pending
176 on reboot.
177 """
178 result = self.run('pgrep -f quick-provision | grep -v $$',
179 ignore_status=True)
180 # We don't expect any output unless there are another quick
181 # provision process is running.
182 if result.exit_status == 0:
183 return self.UPDATE_STATE.RUNNING
184
185 # Determine if we have an update that pending on reboot by check if
186 # the current inactive kernel has priority for the next boot.
187 try:
188 inactive_kernel = kernel_utils.get_kernel_state(self)[1]
189 next_kernel = kernel_utils.get_next_kernel(self)
190 if inactive_kernel == next_kernel:
191 return self.UPDATE_STATE.PENDING_REBOOT
192 except Exception as e:
193 logging.error('Unexpected error while checking kernel info; %s', e)
194 return self.UPDATE_STATE.IDLE
Garry Wangebc015b2019-06-06 17:45:06 -0700195
196
197 def is_in_lab(self):
198 """Check whether the servo host is a lab device.
199
200 @returns: True if the servo host is in Cros Lab, otherwise False.
201
202 """
203 return self._is_in_lab
204
205
206 def is_localhost(self):
207 """Checks whether the servo host points to localhost.
208
209 @returns: True if it points to localhost, otherwise False.
210
211 """
212 return self._is_localhost
213
214
215 def is_cros_host(self):
216 """Check if a servo host is running chromeos.
217
218 @return: True if the servo host is running chromeos.
219 False if it isn't, or we don't have enough information.
220 """
221 try:
222 result = self.run('grep -q CHROMEOS /etc/lsb-release',
223 ignore_status=True, timeout=10)
224 except (error.AutoservRunError, error.AutoservSSHTimeout):
225 return False
226 return result.exit_status == 0
227
228
Garry Wang358aad42020-08-02 20:56:04 -0700229 def prepare_for_update(self):
230 """Prepares the DUT for an update.
231 Subclasses may override this to perform any special actions
232 required before updating.
233 """
234 pass
235
236
Garry Wangebc015b2019-06-06 17:45:06 -0700237 def reboot(self, *args, **dargs):
238 """Reboot using special servo host reboot command."""
239 super(BaseServoHost, self).reboot(reboot_cmd=self.REBOOT_CMD,
240 *args, **dargs)
241
242
Garry Wang358aad42020-08-02 20:56:04 -0700243 def update_image(self, stable_version=None):
Garry Wangebc015b2019-06-06 17:45:06 -0700244 """Update the image on the servo host, if needed.
245
246 This method recognizes the following cases:
247 * If the Host is not running Chrome OS, do nothing.
248 * If a previously triggered update is now complete, reboot
249 to the new version.
Garry Wang358aad42020-08-02 20:56:04 -0700250 * If the host is processing an update do nothing.
251 * If the host has an update that pending on reboot, do nothing.
Garry Wangebc015b2019-06-06 17:45:06 -0700252 * If the host is running a version of Chrome OS different
Garry Wang358aad42020-08-02 20:56:04 -0700253 from the default for servo Hosts, start an update.
Garry Wangebc015b2019-06-06 17:45:06 -0700254
Garry Wang14831832020-03-04 17:21:49 -0800255 @stable_version the target build number.(e.g. R82-12900.0.0)
256
Garry Wangebc015b2019-06-06 17:45:06 -0700257 @raises dev_server.DevServerException: If all the devservers are down.
258 @raises site_utils.ParseBuildNameException: If the devserver returns
259 an invalid build name.
Garry Wangebc015b2019-06-06 17:45:06 -0700260 """
261 # servod could be running in a Ubuntu workstation.
262 if not self.is_cros_host():
263 logging.info('Not attempting an update, either %s is not running '
264 'chromeos or we cannot find enough information about '
265 'the host.', self.hostname)
266 return
267
268 if lsbrelease_utils.is_moblab():
269 logging.info('Not attempting an update, %s is running moblab.',
270 self.hostname)
271 return
272
Garry Wang14831832020-03-04 17:21:49 -0800273 if not stable_version:
274 logging.debug("BaseServoHost::update_image attempting to get"
275 " servo cros stable version")
276 try:
277 stable_version = (self.get_dut_host_info().
278 servo_cros_stable_version)
279 except AttributeError:
280 logging.error("BaseServoHost::update_image failed to get"
281 " servo cros stable version.")
Gregory Nisbet8e2fbb22019-12-05 11:36:37 -0800282
Garry Wang14831832020-03-04 17:21:49 -0800283 target_build = "%s-release/%s" % (self.get_board(), stable_version)
Garry Wangebc015b2019-06-06 17:45:06 -0700284 target_build_number = server_utils.ParseBuildName(
285 target_build)[3]
Garry Wang14831832020-03-04 17:21:49 -0800286 current_build_number = self.get_release_version()
Garry Wangebc015b2019-06-06 17:45:06 -0700287
288 if current_build_number == target_build_number:
289 logging.info('servo host %s does not require an update.',
290 self.hostname)
291 return
292
293 status = self._check_update_status()
Garry Wang358aad42020-08-02 20:56:04 -0700294 if status == self.UPDATE_STATE.RUNNING:
295 logging.info('servo host %s already processing an update',
296 self.hostname)
297 return
298 if status == self.UPDATE_STATE.PENDING_REBOOT:
Garry Wangebc015b2019-06-06 17:45:06 -0700299 # Labstation reboot is handled separately here as it require
Garry Wang358aad42020-08-02 20:56:04 -0700300 # synchronized reboot among all managed DUTs. For servo_v3, we'll
301 # reboot when initialize Servohost, if there is a update pending.
302 logging.info('An update has been completed and pending reboot.')
303 return
Garry Wangebc015b2019-06-06 17:45:06 -0700304
Garry Wang358aad42020-08-02 20:56:04 -0700305 ds = dev_server.ImageServer.resolve(self.hostname,
306 hostname=self.hostname)
307 url = ds.get_update_url(target_build)
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700308 cros_provisioner = provisioner.ChromiumOSProvisioner(update_url=url,
Jae Hoon Kim3f004992020-09-10 17:48:33 -0700309 host=self,
310 is_servohost=True)
Garry Wang358aad42020-08-02 20:56:04 -0700311 logging.info('Using devserver url: %s to trigger update on '
312 'servo host %s, from %s to %s', url, self.hostname,
313 current_build_number, target_build_number)
Jae Hoon Kim5f6ca6e2020-09-10 16:11:23 -0700314 cros_provisioner.run_provision()
Garry Wangebc015b2019-06-06 17:45:06 -0700315
316
317 def has_power(self):
318 """Return whether or not the servo host is powered by PoE or RPM."""
319 # TODO(fdeng): See crbug.com/302791
320 # For now, assume all servo hosts in the lab have power.
321 return self.is_in_lab()
322
323
Garry Wang358aad42020-08-02 20:56:04 -0700324 def _post_update_reboot(self):
325 """ Reboot servohost after an quick provision.
326
327 We need to do some specifal cleanup before and after reboot
328 when there is an update pending.
329 """
330 # Regarding the 'crossystem' command below: In some cases,
331 # the update flow puts the TPM into a state such that it
332 # fails verification. We don't know why. However, this
333 # call papers over the problem by clearing the TPM during
334 # the reboot.
335 #
336 # We ignore failures from 'crossystem'. Although failure
337 # here is unexpected, and could signal a bug, the point of
338 # the exercise is to paper over problems; allowing this to
339 # fail would defeat the purpose.
340 self.run('crossystem clear_tpm_owner_request=1', ignore_status=True)
341 self._servo_host_reboot()
342 logging.debug('Cleaning up autotest directories if exist.')
343 try:
344 installed_autodir = autotest.Autotest.get_installed_autodir(self)
345 self.run('rm -rf ' + installed_autodir)
346 except autotest.AutodirNotFoundError:
347 logging.debug('No autotest installed directory found.')
348
349
Garry Wangebc015b2019-06-06 17:45:06 -0700350 def power_cycle(self):
351 """Cycle power to this host via PoE(servo v3) or RPM(labstation)
352 if it is a lab device.
353
354 @raises AutoservRepairError if it fails to power cycle the
355 servo host.
356
357 """
358 if self.has_power():
359 try:
360 rpm_client.set_power(self, 'CYCLE')
Derek Beckettf73baca2020-08-19 15:08:47 -0700361 except (socket.error, six.moves.xmlrpc_client.Error,
362 six.moves.http_client.BadStatusLine,
Garry Wangebc015b2019-06-06 17:45:06 -0700363 rpm_client.RemotePowerException) as e:
364 raise hosts.AutoservRepairError(
365 'Power cycling %s failed: %s' % (self.hostname, e),
366 'power_cycle_via_rpm_failed'
367 )
368 else:
369 logging.info('Skipping power cycling, not a lab device.')
370
371
372 def _servo_host_reboot(self):
373 """Reboot this servo host because a reboot is requested."""
374 logging.info('Rebooting servo host %s from build %s', self.hostname,
Garry Wang14831832020-03-04 17:21:49 -0800375 self.get_release_version())
Garry Wangebc015b2019-06-06 17:45:06 -0700376 # Tell the reboot() call not to wait for completion.
377 # Otherwise, the call will log reboot failure if servo does
378 # not come back. The logged reboot failure will lead to
379 # test job failure. If the test does not require servo, we
380 # don't want servo failure to fail the test with error:
381 # `Host did not return from reboot` in status.log.
382 self.reboot(fastsync=True, wait=False)
383
384 # We told the reboot() call not to wait, but we need to wait
385 # for the reboot before we continue. Alas. The code from
386 # here below is basically a copy of Host.wait_for_restart(),
387 # with the logging bits ripped out, so that they can't cause
388 # the failure logging problem described above.
389 #
390 # The black stain that this has left on my soul can never be
391 # erased.
392 old_boot_id = self.get_boot_id()
393 if not self.wait_down(timeout=self.WAIT_DOWN_REBOOT_TIMEOUT,
394 warning_timer=self.WAIT_DOWN_REBOOT_WARNING,
395 old_boot_id=old_boot_id):
396 raise error.AutoservHostError(
397 'servo host %s failed to shut down.' %
398 self.hostname)
Garry Wang79e9af62019-06-12 15:19:19 -0700399 if self.wait_up(timeout=self.REBOOT_TIMEOUT):
Garry Wangebc015b2019-06-06 17:45:06 -0700400 logging.info('servo host %s back from reboot, with build %s',
Garry Wang14831832020-03-04 17:21:49 -0800401 self.hostname, self.get_release_version())
Garry Wangebc015b2019-06-06 17:45:06 -0700402 else:
403 raise error.AutoservHostError(
404 'servo host %s failed to come back from reboot.' %
405 self.hostname)
406
407
408 def make_ssh_command(self, user='root', port=22, opts='', hosts_file=None,
409 connect_timeout=None, alive_interval=None, alive_count_max=None,
410 connection_attempts=None):
411 """Override default make_ssh_command to use tuned options.
412
413 Tuning changes:
414 - ConnectTimeout=30; maximum of 30 seconds allowed for an SSH
415 connection failure. Consistency with remote_access.py.
416
417 - ServerAliveInterval=180; which causes SSH to ping connection every
418 180 seconds. In conjunction with ServerAliveCountMax ensures
419 that if the connection dies, Autotest will bail out quickly.
420
421 - ServerAliveCountMax=3; consistency with remote_access.py.
422
423 - ConnectAttempts=4; reduce flakiness in connection errors;
424 consistency with remote_access.py.
425
426 - UserKnownHostsFile=/dev/null; we don't care about the keys.
427
428 - SSH protocol forced to 2; needed for ServerAliveInterval.
429
430 @param user User name to use for the ssh connection.
431 @param port Port on the target host to use for ssh connection.
432 @param opts Additional options to the ssh command.
433 @param hosts_file Ignored.
434 @param connect_timeout Ignored.
435 @param alive_interval Ignored.
436 @param alive_count_max Ignored.
437 @param connection_attempts Ignored.
438
439 @returns: An ssh command with the requested settings.
440
441 """
442 options = ' '.join([opts, '-o Protocol=2'])
443 return super(BaseServoHost, self).make_ssh_command(
444 user=user, port=port, opts=options, hosts_file='/dev/null',
445 connect_timeout=30, alive_interval=180, alive_count_max=3,
446 connection_attempts=4)
447
448
449 def _make_scp_cmd(self, sources, dest):
450 """Format scp command.
451
452 Given a list of source paths and a destination path, produces the
453 appropriate scp command for encoding it. Remote paths must be
454 pre-encoded. Overrides _make_scp_cmd in AbstractSSHHost
455 to allow additional ssh options.
456
457 @param sources: A list of source paths to copy from.
458 @param dest: Destination path to copy to.
459
460 @returns: An scp command that copies |sources| on local machine to
461 |dest| on the remote servo host.
462
463 """
464 command = ('scp -rq %s -o BatchMode=yes -o StrictHostKeyChecking=no '
465 '-o UserKnownHostsFile=/dev/null -P %d %s "%s"')
466 return command % (self._master_ssh.ssh_option,
467 self.port, sources, dest)
468
469
470 def run(self, command, timeout=3600, ignore_status=False,
471 stdout_tee=utils.TEE_TO_LOGS, stderr_tee=utils.TEE_TO_LOGS,
472 connect_timeout=30, ssh_failure_retry_ok=False,
473 options='', stdin=None, verbose=True, args=()):
474 """Run a command on the servo host.
475
476 Extends method `run` in SSHHost. If the servo host is a remote device,
477 it will call `run` in SSHost without changing anything.
478 If the servo host is 'localhost', it will call utils.system_output.
479
480 @param command: The command line string.
481 @param timeout: Time limit in seconds before attempting to
482 kill the running process. The run() function
483 will take a few seconds longer than 'timeout'
484 to complete if it has to kill the process.
485 @param ignore_status: Do not raise an exception, no matter
486 what the exit code of the command is.
487 @param stdout_tee/stderr_tee: Where to tee the stdout/stderr.
488 @param connect_timeout: SSH connection timeout (in seconds)
489 Ignored if host is 'localhost'.
490 @param options: String with additional ssh command options
491 Ignored if host is 'localhost'.
492 @param ssh_failure_retry_ok: when True and ssh connection failure is
493 suspected, OK to retry command (but not
494 compulsory, and likely not needed here)
495 @param stdin: Stdin to pass (a string) to the executed command.
496 @param verbose: Log the commands.
497 @param args: Sequence of strings to pass as arguments to command by
498 quoting them in " and escaping their contents if necessary.
499
500 @returns: A utils.CmdResult object.
501
502 @raises AutoservRunError if the command failed.
503 @raises AutoservSSHTimeout SSH connection has timed out. Only applies
504 when servo host is not 'localhost'.
505
506 """
Gregory Nisbet32e74022020-07-14 18:42:30 -0700507 run_args = {
508 'command' : command,
509 'timeout' : timeout,
510 'ignore_status' : ignore_status,
511 'stdout_tee' : stdout_tee,
512 'stderr_tee' : stderr_tee,
513 # connect_timeout n/a for localhost
514 # options n/a for localhost
Andrew McRaeed8b52f2020-07-20 11:29:26 +1000515 # ssh_failure_retry_ok n/a for localhost
Gregory Nisbet32e74022020-07-14 18:42:30 -0700516 'stdin' : stdin,
517 'verbose' : verbose,
518 'args' : args,
519 }
Garry Wangebc015b2019-06-06 17:45:06 -0700520 if self.is_localhost():
521 if self._sudo_required:
522 run_args['command'] = 'sudo -n sh -c "%s"' % utils.sh_escape(
523 command)
524 try:
525 return utils.run(**run_args)
526 except error.CmdError as e:
527 logging.error(e)
528 raise error.AutoservRunError('command execution error',
529 e.result_obj)
530 else:
531 run_args['connect_timeout'] = connect_timeout
532 run_args['options'] = options
Andrew McRaeed8b52f2020-07-20 11:29:26 +1000533 run_args['ssh_failure_retry_ok'] = ssh_failure_retry_ok
Garry Wangebc015b2019-06-06 17:45:06 -0700534 return super(BaseServoHost, self).run(**run_args)
Garry Wang2b5eef92020-08-21 16:23:35 -0700535
536 def _mount_drive(self, src_path, dst_path):
537 """Mount an external drive on servohost.
538
539 @param: src_path the drive path to mount(e.g. /dev/sda3).
540 @param: dst_path the destination directory on servohost to mount
541 the drive.
542
543 @returns: True if mount success otherwise False.
544 """
545 # Make sure the dst dir exists.
546 self.run('mkdir -p %s' % dst_path)
547
548 result = self.run('mount -o ro %s %s' % (src_path, dst_path),
549 ignore_status=True)
550 return result.exit_status == 0
551
552 def _unmount_drive(self, mount_path):
553 """Unmount a drive from servohost.
554
555 @param: mount_path the path on servohost to unmount.
556
557 @returns: True if unmount success otherwise False.
558 """
559 result = self.run('umount %s' % mount_path, ignore_status=True)
560 return result.exit_status == 0
Garry Wang78ce64d2020-10-13 18:23:45 -0700561
562 def wait_ready(self, required_uptime=300):
563 """Wait ready for a servohost if it has been rebooted recently.
564
565 It may take a few minutes until all servos and their componments
566 re-enumerated and become ready after a servohost(especially labstation
567 as it supports multiple servos) reboot, so we need to make sure the
568 servohost has been up for a given a mount of time before trying to
569 start any actions.
570
571 @param required_uptime: Minimum uptime in seconds that we can
572 consdier a servohost be ready.
573 """
574 uptime = float(self.check_uptime())
575 # To prevent unexpected output from check_uptime() that causes long
576 # sleep, make sure the maximum wait time <= required_uptime.
577 diff = min(required_uptime - uptime, required_uptime)
578 if diff > 0:
579 logging.info(
580 'The servohost was just rebooted, wait %s'
581 ' seconds for it to become ready', diff)
582 time.sleep(diff)