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