Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 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 | # |
| 5 | # Expects to be run in an environment with sudo and no interactive password |
| 6 | # prompt, such as within the Chromium OS development chroot. |
| 7 | |
| 8 | |
| 9 | """This file provides core logic for servo verify/repair process.""" |
| 10 | |
| 11 | |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 12 | import logging |
Raul E Rangel | 52ca2e8 | 2018-07-03 14:10:14 -0600 | [diff] [blame] | 13 | import os |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 14 | import shutil |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 15 | import xmlrpclib |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 16 | |
| 17 | from autotest_lib.client.bin import utils |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 18 | from autotest_lib.client.common_lib import error |
beeps | 5e8c45a | 2013-12-17 22:05:11 -0800 | [diff] [blame] | 19 | from autotest_lib.client.common_lib import global_config |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 20 | from autotest_lib.client.common_lib import hosts |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 21 | from autotest_lib.client.common_lib.cros import retry |
Christopher Wiley | cef1f90 | 2014-06-19 11:11:23 -0700 | [diff] [blame] | 22 | from autotest_lib.client.common_lib.cros.network import ping_runner |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 23 | from autotest_lib.server.cros.servo import servo |
Richard Barnette | d31580e | 2018-05-14 19:58:00 +0000 | [diff] [blame] | 24 | from autotest_lib.server.hosts import servo_repair |
Garry Wang | ebc015b | 2019-06-06 17:45:06 -0700 | [diff] [blame] | 25 | from autotest_lib.server.hosts import base_servohost |
Dan Shi | 5e2efb7 | 2017-02-07 11:40:23 -0800 | [diff] [blame] | 26 | |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 27 | |
Simran Basi | 0739d68 | 2015-02-25 16:22:56 -0800 | [diff] [blame] | 28 | # Names of the host attributes in the database that represent the values for |
| 29 | # the servo_host and servo_port for a servo connected to the DUT. |
| 30 | SERVO_HOST_ATTR = 'servo_host' |
| 31 | SERVO_PORT_ATTR = 'servo_port' |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 32 | SERVO_BOARD_ATTR = 'servo_board' |
Nick Sanders | 2f3c985 | 2018-10-24 12:10:24 -0700 | [diff] [blame] | 33 | # Model is inferred from host labels. |
| 34 | SERVO_MODEL_ATTR = 'servo_model' |
Kevin Cheng | 643ce8a | 2016-09-15 15:42:12 -0700 | [diff] [blame] | 35 | SERVO_SERIAL_ATTR = 'servo_serial' |
Prathmesh Prabhu | cba4429 | 2018-08-28 17:44:45 -0700 | [diff] [blame] | 36 | SERVO_ATTR_KEYS = ( |
| 37 | SERVO_BOARD_ATTR, |
| 38 | SERVO_HOST_ATTR, |
| 39 | SERVO_PORT_ATTR, |
| 40 | SERVO_SERIAL_ATTR, |
| 41 | ) |
Simran Basi | 0739d68 | 2015-02-25 16:22:56 -0800 | [diff] [blame] | 42 | |
Dan Shi | 3b2adf6 | 2015-09-02 17:46:54 -0700 | [diff] [blame] | 43 | _CONFIG = global_config.global_config |
xixuan | 6cf6d2f | 2016-01-29 15:29:00 -0800 | [diff] [blame] | 44 | ENABLE_SSH_TUNNEL_FOR_SERVO = _CONFIG.get_config_value( |
| 45 | 'CROS', 'enable_ssh_tunnel_for_servo', type=bool, default=False) |
Simran Basi | 0739d68 | 2015-02-25 16:22:56 -0800 | [diff] [blame] | 46 | |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 47 | AUTOTEST_BASE = _CONFIG.get_config_value( |
| 48 | 'SCHEDULER', 'drone_installation_directory', |
| 49 | default='/usr/local/autotest') |
| 50 | |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 51 | |
Garry Wang | ebc015b | 2019-06-06 17:45:06 -0700 | [diff] [blame] | 52 | class ServoHost(base_servohost.BaseServoHost): |
| 53 | """Host class for a servo host(e.g. beaglebone, labstation) |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 54 | that with a servo instance for a specific port.""" |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 55 | |
Raul E Rangel | 52ca2e8 | 2018-07-03 14:10:14 -0600 | [diff] [blame] | 56 | DEFAULT_PORT = int(os.getenv('SERVOD_PORT', '9999')) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 57 | |
Dan Shi | e5b3c51 | 2014-08-21 12:12:09 -0700 | [diff] [blame] | 58 | # Timeout for initializing servo signals. |
Wai-Hong Tam | 37b6ed3 | 2017-09-19 15:52:39 -0700 | [diff] [blame] | 59 | INITIALIZE_SERVO_TIMEOUT_SECS = 60 |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 60 | |
xixuan | 6cf6d2f | 2016-01-29 15:29:00 -0800 | [diff] [blame] | 61 | # Ready test function |
| 62 | SERVO_READY_METHOD = 'get_version' |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 63 | |
| 64 | |
Richard Barnette | 17bfc6c | 2016-08-04 18:41:43 -0700 | [diff] [blame] | 65 | def _initialize(self, servo_host='localhost', |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 66 | servo_port=DEFAULT_PORT, servo_board=None, |
Nick Sanders | 2f3c985 | 2018-10-24 12:10:24 -0700 | [diff] [blame] | 67 | servo_model=None, servo_serial=None, is_in_lab=None, |
| 68 | *args, **dargs): |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 69 | """Initialize a ServoHost instance. |
| 70 | |
| 71 | A ServoHost instance represents a host that controls a servo. |
| 72 | |
| 73 | @param servo_host: Name of the host where the servod process |
| 74 | is running. |
Raul E Rangel | 52ca2e8 | 2018-07-03 14:10:14 -0600 | [diff] [blame] | 75 | @param servo_port: Port the servod process is listening on. Defaults |
| 76 | to the SERVOD_PORT environment variable if set, |
| 77 | otherwise 9999. |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 78 | @param servo_board: Board that the servo is connected to. |
Nick Sanders | 2f3c985 | 2018-10-24 12:10:24 -0700 | [diff] [blame] | 79 | @param servo_model: Model that the servo is connected to. |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 80 | @param is_in_lab: True if the servo host is in Cros Lab. Default is set |
| 81 | to None, for which utils.host_is_in_lab_zone will be |
| 82 | called to check if the servo host is in Cros lab. |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 83 | |
| 84 | """ |
| 85 | super(ServoHost, self)._initialize(hostname=servo_host, |
Garry Wang | ebc015b | 2019-06-06 17:45:06 -0700 | [diff] [blame] | 86 | is_in_lab=is_in_lab, *args, **dargs) |
Richard Barnette | 42f4db9 | 2018-08-23 15:05:15 -0700 | [diff] [blame] | 87 | self.servo_port = int(servo_port) |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 88 | self.servo_board = servo_board |
Nick Sanders | 2f3c985 | 2018-10-24 12:10:24 -0700 | [diff] [blame] | 89 | self.servo_model = servo_model |
Kevin Cheng | 643ce8a | 2016-09-15 15:42:12 -0700 | [diff] [blame] | 90 | self.servo_serial = servo_serial |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 91 | self._servo = None |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 92 | # Path of the servo host lock file. |
| 93 | self._lock_file = (self.TEMP_FILE_DIR + str(self.servo_port) |
| 94 | + self.LOCK_FILE_POSTFIX) |
| 95 | # File path to declare a reboot request. |
| 96 | self._reboot_file = (self.TEMP_FILE_DIR + str(self.servo_port) |
| 97 | + self.REBOOT_FILE_POSTFIX) |
| 98 | |
| 99 | # Lock the servo host if it's an in-lab labstation to prevent other |
| 100 | # task to reboot it until current task completes. We also wait and |
| 101 | # make sure the labstation is up here, in the case of the labstation is |
| 102 | # in the middle of reboot. |
Garry Wang | 7c00b0f | 2019-06-25 17:28:17 -0700 | [diff] [blame] | 103 | self._is_locked = False |
Garry Wang | 42b4d86 | 2019-06-25 15:50:49 -0700 | [diff] [blame] | 104 | if (self.wait_up(self.REBOOT_TIMEOUT) and self.is_in_lab() |
| 105 | and self.is_labstation()): |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 106 | self._lock() |
Garry Wang | ebc015b | 2019-06-06 17:45:06 -0700 | [diff] [blame] | 107 | |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 108 | self._repair_strategy = ( |
| 109 | servo_repair.create_servo_repair_strategy()) |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 110 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 111 | self._prev_log_size = 0 |
| 112 | self._prev_log_inode = 0 |
| 113 | |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 114 | def connect_servo(self): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 115 | """Establish a connection to the servod server on this host. |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 116 | |
| 117 | Initializes `self._servo` and then verifies that all network |
| 118 | connections are working. This will create an ssh tunnel if |
| 119 | it's required. |
| 120 | |
| 121 | As a side effect of testing the connection, all signals on the |
| 122 | target servo are reset to default values, and the USB stick is |
| 123 | set to the neutral (off) position. |
| 124 | """ |
Kevin Cheng | 643ce8a | 2016-09-15 15:42:12 -0700 | [diff] [blame] | 125 | servo_obj = servo.Servo(servo_host=self, servo_serial=self.servo_serial) |
Kuang-che Wu | 05763f5 | 2019-08-30 16:48:21 +0800 | [diff] [blame] | 126 | self._servo = servo_obj |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 127 | timeout, _ = retry.timeout( |
| 128 | servo_obj.initialize_dut, |
| 129 | timeout_sec=self.INITIALIZE_SERVO_TIMEOUT_SECS) |
| 130 | if timeout: |
| 131 | raise hosts.AutoservVerifyError( |
| 132 | 'Servo initialize timed out.') |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 133 | |
| 134 | |
| 135 | def disconnect_servo(self): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 136 | """Disconnect our servo if it exists. |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 137 | |
| 138 | If we've previously successfully connected to our servo, |
| 139 | disconnect any established ssh tunnel, and set `self._servo` |
| 140 | back to `None`. |
| 141 | """ |
| 142 | if self._servo: |
| 143 | # N.B. This call is safe even without a tunnel: |
| 144 | # rpc_server_tracker.disconnect() silently ignores |
| 145 | # unknown ports. |
| 146 | self.rpc_server_tracker.disconnect(self.servo_port) |
| 147 | self._servo = None |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 148 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 149 | def fetch_servod_log(self, filename, skip_old=False): |
| 150 | """Save the servod log into the given local file. |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 151 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 152 | The inode number is used for checking whether the log was rotated: |
| 153 | it skips old data only if the log is actually the same file. |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 154 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 155 | If filename is not set, this just refreshes the stored info about the |
| 156 | log file's size and inode, for use in future calls. |
| 157 | |
| 158 | @param filename: save the contents into a file with the given name. |
| 159 | @param skip_old: if True, skip past the old data in the log file. |
| 160 | @type filename: str |
| 161 | @type skip_old: bool |
| 162 | @rtype: None |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 163 | """ |
| 164 | if self.is_localhost(): |
| 165 | return |
| 166 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 167 | log_name = 'servod_%s' % self.servo_port |
| 168 | log_path = '/var/log/%s.log' % log_name |
| 169 | |
| 170 | # %n = name, %i = inode, %s = size. |
| 171 | cmd = "/usr/bin/stat --format '%n|%i|%s' {}".format(log_path) |
| 172 | result = self.run(cmd, ignore_status=True) |
| 173 | if result.exit_status != 0: |
| 174 | if 'No such file or directory' not in result.stderr: |
| 175 | # Warn only if log file is broken/unreadable, not just missing. |
| 176 | logging.warn("Couldn't stat servod log: %s", result.stderr) |
| 177 | |
| 178 | self._prev_log_size = 0 |
| 179 | self._prev_log_inode = 0 |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 180 | return |
| 181 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 182 | (path, inode, size) = result.stdout.split('|') |
| 183 | inode = int(inode) |
| 184 | size = int(size) |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 185 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 186 | prev_inode = self._prev_log_inode |
| 187 | prev_size = self._prev_log_size |
| 188 | if not prev_inode or not prev_size or inode != prev_inode: |
| 189 | # Don't skip if it's actually a different file, or it somehow shrunk |
| 190 | skip_old = False |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 191 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 192 | if filename: |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 193 | try: |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 194 | if skip_old: |
| 195 | # Fetch whole log to .log.tmp, then save only the new bytes. |
| 196 | temp_filename = filename + '.tmp' |
| 197 | self.get_file(log_path, temp_filename) |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 198 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 199 | with open(temp_filename, 'rb') as temp_log_file: |
| 200 | temp_log_file.seek(prev_size) |
| 201 | with open(filename, 'wb') as real_log_file: |
| 202 | # read in pieces, in case the log file is big |
| 203 | shutil.copyfileobj(temp_log_file, real_log_file) |
| 204 | os.unlink(temp_filename) |
| 205 | else: |
| 206 | self.get_file(log_path, filename) |
| 207 | except EnvironmentError: |
| 208 | logging.warn("Couldn't save copy of servod log:", exc_info=True) |
Dana Goyette | 4dc0adc | 2019-05-06 14:51:53 -0700 | [diff] [blame] | 209 | |
Dana Goyette | 6006ead | 2019-10-04 17:12:24 +0000 | [diff] [blame] | 210 | self._prev_log_size = size |
| 211 | self._prev_log_inode = inode |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 212 | |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 213 | def get_servod_server_proxy(self): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 214 | """Return a proxy that can be used to communicate with servod server. |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 215 | |
| 216 | @returns: An xmlrpclib.ServerProxy that is connected to the servod |
| 217 | server on the host. |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 218 | """ |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 219 | if ENABLE_SSH_TUNNEL_FOR_SERVO and not self.is_localhost(): |
| 220 | return self.rpc_server_tracker.xmlrpc_connect( |
| 221 | None, self.servo_port, |
| 222 | ready_test_name=self.SERVO_READY_METHOD, |
Allen Li | 2b1a899 | 2018-11-27 14:17:18 -0800 | [diff] [blame] | 223 | timeout_seconds=60, |
Allen Li | 556f453 | 2018-12-03 18:11:23 -0800 | [diff] [blame] | 224 | request_timeout_seconds=3600) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 225 | else: |
| 226 | remote = 'http://%s:%s' % (self.hostname, self.servo_port) |
| 227 | return xmlrpclib.ServerProxy(remote) |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 228 | |
| 229 | |
Richard Barnette | 1edbb16 | 2016-11-01 11:47:50 -0700 | [diff] [blame] | 230 | def verify(self, silent=False): |
| 231 | """Update the servo host and verify it's in a good state. |
| 232 | |
| 233 | @param silent If true, suppress logging in `status.log`. |
| 234 | """ |
Richard Barnette | abbdc25 | 2018-07-26 16:57:42 -0700 | [diff] [blame] | 235 | message = 'Beginning verify for servo host %s port %s serial %s' |
| 236 | message %= (self.hostname, self.servo_port, self.servo_serial) |
| 237 | self.record('INFO', None, None, message) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 238 | try: |
Richard Barnette | 1edbb16 | 2016-11-01 11:47:50 -0700 | [diff] [blame] | 239 | self._repair_strategy.verify(self, silent) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 240 | except: |
| 241 | self.disconnect_servo() |
| 242 | raise |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 243 | |
| 244 | |
Richard Barnette | 1edbb16 | 2016-11-01 11:47:50 -0700 | [diff] [blame] | 245 | def repair(self, silent=False): |
| 246 | """Attempt to repair servo host. |
| 247 | |
| 248 | @param silent If true, suppress logging in `status.log`. |
| 249 | """ |
Richard Barnette | abbdc25 | 2018-07-26 16:57:42 -0700 | [diff] [blame] | 250 | message = 'Beginning repair for servo host %s port %s serial %s' |
| 251 | message %= (self.hostname, self.servo_port, self.servo_serial) |
| 252 | self.record('INFO', None, None, message) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 253 | try: |
Richard Barnette | 1edbb16 | 2016-11-01 11:47:50 -0700 | [diff] [blame] | 254 | self._repair_strategy.repair(self, silent) |
Garry Wang | 464ff1e | 2019-07-18 17:20:34 -0700 | [diff] [blame] | 255 | # If target is a labstation then try to withdraw any existing |
| 256 | # reboot request created by this servo because it passed repair. |
| 257 | if self.is_labstation(): |
| 258 | self.withdraw_reboot_request() |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 259 | except: |
| 260 | self.disconnect_servo() |
| 261 | raise |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 262 | |
| 263 | |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 264 | def get_servo(self): |
| 265 | """Get the cached servo.Servo object. |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 266 | |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 267 | @return: a servo.Servo object. |
Dana Goyette | 353d1d9 | 2019-06-27 10:43:59 -0700 | [diff] [blame] | 268 | @rtype: autotest_lib.server.cros.servo.servo.Servo |
Fang Deng | 5d518f4 | 2013-08-02 14:04:32 -0700 | [diff] [blame] | 269 | """ |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 270 | return self._servo |
| 271 | |
| 272 | |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 273 | def request_reboot(self): |
| 274 | """Request servohost to be rebooted when it's safe to by touch a file. |
| 275 | """ |
| 276 | logging.debug('Request to reboot servohost %s has been created by ' |
Garry Wang | 464ff1e | 2019-07-18 17:20:34 -0700 | [diff] [blame] | 277 | 'servo with port # %s', self.hostname, self.servo_port) |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 278 | self.run('touch %s' % self._reboot_file, ignore_status=True) |
| 279 | |
| 280 | |
Garry Wang | 464ff1e | 2019-07-18 17:20:34 -0700 | [diff] [blame] | 281 | def withdraw_reboot_request(self): |
| 282 | """Withdraw a servohost reboot request if exists by remove the flag |
| 283 | file. |
| 284 | """ |
| 285 | logging.debug('Withdrawing request to reboot servohost %s that created' |
| 286 | ' by servo with port # %s if exists.', |
| 287 | self.hostname, self.servo_port) |
| 288 | self.run('rm -f %s' % self._reboot_file, ignore_status=True) |
| 289 | |
| 290 | |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 291 | def _lock(self): |
| 292 | """lock servohost by touching a file. |
| 293 | """ |
| 294 | logging.debug('Locking servohost %s by touching %s file', |
| 295 | self.hostname, self._lock_file) |
| 296 | self.run('touch %s' % self._lock_file, ignore_status=True) |
Garry Wang | 7c00b0f | 2019-06-25 17:28:17 -0700 | [diff] [blame] | 297 | self._is_locked = True |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 298 | |
| 299 | |
| 300 | def _unlock(self): |
| 301 | """Unlock servohost by removing the lock file. |
| 302 | """ |
| 303 | logging.debug('Unlocking servohost by removing %s file', |
| 304 | self._lock_file) |
| 305 | self.run('rm %s' % self._lock_file, ignore_status=True) |
Garry Wang | 7c00b0f | 2019-06-25 17:28:17 -0700 | [diff] [blame] | 306 | self._is_locked = False |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 307 | |
| 308 | |
Congbin Guo | a1f9cba | 2018-07-03 11:36:59 -0700 | [diff] [blame] | 309 | def close(self): |
Congbin Guo | fc3b896 | 2019-03-22 17:38:46 -0700 | [diff] [blame] | 310 | """Close the associated servo and the host object.""" |
Congbin Guo | a1f9cba | 2018-07-03 11:36:59 -0700 | [diff] [blame] | 311 | if self._servo: |
Congbin Guo | 2e5e2a2 | 2018-07-27 10:32:48 -0700 | [diff] [blame] | 312 | # In some cases when we run as lab-tools, the job object is None. |
Congbin Guo | fc3b896 | 2019-03-22 17:38:46 -0700 | [diff] [blame] | 313 | if self.job and not self._servo.uart_logs_dir: |
| 314 | self._servo.uart_logs_dir = self.job.resultdir |
Congbin Guo | a1f9cba | 2018-07-03 11:36:59 -0700 | [diff] [blame] | 315 | self._servo.close() |
| 316 | |
Garry Wang | 7c00b0f | 2019-06-25 17:28:17 -0700 | [diff] [blame] | 317 | if self._is_locked: |
| 318 | # Remove the lock if the servohost has been locked. |
Garry Wang | 79e9af6 | 2019-06-12 15:19:19 -0700 | [diff] [blame] | 319 | try: |
| 320 | self._unlock() |
| 321 | except error.AutoservSSHTimeout: |
| 322 | logging.error('Unlock servohost failed due to ssh timeout.' |
| 323 | ' It may caused by servohost went down during' |
| 324 | ' the task.') |
| 325 | |
Congbin Guo | a1f9cba | 2018-07-03 11:36:59 -0700 | [diff] [blame] | 326 | super(ServoHost, self).close() |
| 327 | |
| 328 | |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 329 | def make_servo_hostname(dut_hostname): |
| 330 | """Given a DUT's hostname, return the hostname of its servo. |
| 331 | |
| 332 | @param dut_hostname: hostname of a DUT. |
| 333 | |
| 334 | @return hostname of the DUT's servo. |
| 335 | |
| 336 | """ |
| 337 | host_parts = dut_hostname.split('.') |
| 338 | host_parts[0] = host_parts[0] + '-servo' |
| 339 | return '.'.join(host_parts) |
| 340 | |
| 341 | |
| 342 | def servo_host_is_up(servo_hostname): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 343 | """Given a servo host name, return if it's up or not. |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 344 | |
| 345 | @param servo_hostname: hostname of the servo host. |
| 346 | |
| 347 | @return True if it's up, False otherwise |
| 348 | """ |
| 349 | # Technically, this duplicates the SSH ping done early in the servo |
| 350 | # proxy initialization code. However, this ping ends in a couple |
| 351 | # seconds when if fails, rather than the 60 seconds it takes to decide |
| 352 | # that an SSH ping has timed out. Specifically, that timeout happens |
| 353 | # when our servo DNS name resolves, but there is no host at that IP. |
| 354 | logging.info('Pinging servo host at %s', servo_hostname) |
| 355 | ping_config = ping_runner.PingConfig( |
| 356 | servo_hostname, count=3, |
| 357 | ignore_result=True, ignore_status=True) |
| 358 | return ping_runner.PingRunner().ping(ping_config).received > 0 |
| 359 | |
| 360 | |
Richard Barnette | e519dcd | 2016-08-15 17:37:17 -0700 | [diff] [blame] | 361 | def _map_afe_board_to_servo_board(afe_board): |
| 362 | """Map a board we get from the AFE to a servo appropriate value. |
| 363 | |
| 364 | Many boards are identical to other boards for servo's purposes. |
| 365 | This function makes that mapping. |
| 366 | |
| 367 | @param afe_board string board name received from AFE. |
| 368 | @return board we expect servo to have. |
| 369 | |
| 370 | """ |
| 371 | KNOWN_SUFFIXES = ['-freon', '_freon', '_moblab', '-cheets'] |
| 372 | BOARD_MAP = {'gizmo': 'panther'} |
| 373 | mapped_board = afe_board |
| 374 | if afe_board in BOARD_MAP: |
| 375 | mapped_board = BOARD_MAP[afe_board] |
| 376 | else: |
| 377 | for suffix in KNOWN_SUFFIXES: |
| 378 | if afe_board.endswith(suffix): |
| 379 | mapped_board = afe_board[0:-len(suffix)] |
| 380 | break |
| 381 | if mapped_board != afe_board: |
| 382 | logging.info('Mapping AFE board=%s to %s', afe_board, mapped_board) |
| 383 | return mapped_board |
| 384 | |
| 385 | |
Prathmesh Prabhu | b481023 | 2018-09-07 13:24:08 -0700 | [diff] [blame] | 386 | def get_servo_args_for_host(dut_host): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 387 | """Return servo data associated with a given DUT. |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 388 | |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 389 | @param dut_host Instance of `Host` on which to find the servo |
| 390 | attributes. |
Prathmesh Prabhu | f605dd3 | 2018-08-28 17:09:04 -0700 | [diff] [blame] | 391 | @return `servo_args` dict with host and an optional port. |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 392 | """ |
Prathmesh Prabhu | cba4429 | 2018-08-28 17:44:45 -0700 | [diff] [blame] | 393 | info = dut_host.host_info_store.get() |
| 394 | servo_args = {k: v for k, v in info.attributes.iteritems() |
| 395 | if k in SERVO_ATTR_KEYS} |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 396 | |
Prathmesh Prabhu | cba4429 | 2018-08-28 17:44:45 -0700 | [diff] [blame] | 397 | if SERVO_PORT_ATTR in servo_args: |
| 398 | try: |
| 399 | servo_args[SERVO_PORT_ATTR] = int(servo_args[SERVO_PORT_ATTR]) |
| 400 | except ValueError: |
| 401 | logging.error('servo port is not an int: %s', |
| 402 | servo_args[SERVO_PORT_ATTR]) |
| 403 | # Reset servo_args because we don't want to use an invalid port. |
| 404 | servo_args.pop(SERVO_HOST_ATTR, None) |
| 405 | |
| 406 | if info.board: |
| 407 | servo_args[SERVO_BOARD_ATTR] = _map_afe_board_to_servo_board(info.board) |
Nick Sanders | 2f3c985 | 2018-10-24 12:10:24 -0700 | [diff] [blame] | 408 | if info.model: |
| 409 | servo_args[SERVO_MODEL_ATTR] = info.model |
Prathmesh Prabhu | 6f5f636 | 2018-09-05 17:20:31 -0700 | [diff] [blame] | 410 | return servo_args if SERVO_HOST_ATTR in servo_args else None |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 411 | |
| 412 | |
Prathmesh Prabhu | efb1b48 | 2018-08-28 17:15:05 -0700 | [diff] [blame] | 413 | def _tweak_args_for_ssp_moblab(servo_args): |
| 414 | if servo_args[SERVO_HOST_ATTR] in ['localhost', '127.0.0.1']: |
| 415 | servo_args[SERVO_HOST_ATTR] = _CONFIG.get_config_value( |
| 416 | 'SSP', 'host_container_ip', type=str, default=None) |
| 417 | |
| 418 | |
Dan Shi | 023aae3 | 2016-05-25 11:13:01 -0700 | [diff] [blame] | 419 | def create_servo_host(dut, servo_args, try_lab_servo=False, |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 420 | try_servo_repair=False): |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 421 | """Create a ServoHost object for a given DUT, if appropriate. |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 422 | |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 423 | This function attempts to create and verify or repair a `ServoHost` |
| 424 | object for a servo connected to the given `dut`, subject to various |
| 425 | constraints imposed by the parameters: |
| 426 | * When the `servo_args` parameter is not `None`, a servo |
| 427 | host must be created, and must be checked with `repair()`. |
| 428 | * Otherwise, if a servo exists in the lab and `try_lab_servo` is |
| 429 | true: |
| 430 | * If `try_servo_repair` is true, then create a servo host and |
| 431 | check it with `repair()`. |
| 432 | * Otherwise, if the servo responds to `ping` then create a |
| 433 | servo host and check it with `verify()`. |
Fang Deng | e545abb | 2014-12-30 18:43:47 -0800 | [diff] [blame] | 434 | |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 435 | In cases where `servo_args` was not `None`, repair failure |
| 436 | exceptions are passed back to the caller; otherwise, exceptions |
Richard Barnette | 07c2e1d | 2016-10-26 14:24:28 -0700 | [diff] [blame] | 437 | are logged and then discarded. Note that this only happens in cases |
| 438 | where we're called from a test (not special task) control file that |
| 439 | has an explicit dependency on servo. In that case, we require that |
| 440 | repair not write to `status.log`, so as to avoid polluting test |
| 441 | results. |
| 442 | |
| 443 | TODO(jrbarnette): The special handling for servo in test control |
| 444 | files is a thorn in my flesh; I dearly hope to see it cut out before |
| 445 | my retirement. |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 446 | |
| 447 | Parameters for a servo host consist of a host name, port number, and |
| 448 | DUT board, and are determined from one of these sources, in order of |
| 449 | priority: |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 450 | * Servo attributes from the `dut` parameter take precedence over |
| 451 | all other sources of information. |
| 452 | * If a DNS entry for the servo based on the DUT hostname exists in |
| 453 | the CrOS lab network, that hostname is used with the default |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 454 | port and the DUT's board. |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 455 | * If no other options are found, the parameters will be taken |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 456 | from the `servo_args` dict passed in from the caller. |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 457 | |
| 458 | @param dut An instance of `Host` from which to take |
| 459 | servo parameters (if available). |
| 460 | @param servo_args A dictionary with servo parameters to use if |
| 461 | they can't be found from `dut`. If this |
| 462 | argument is supplied, unrepaired exceptions |
| 463 | from `verify()` will be passed back to the |
| 464 | caller. |
| 465 | @param try_lab_servo If not true, servo host creation will be |
| 466 | skipped unless otherwise required by the |
| 467 | caller. |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 468 | @param try_servo_repair If true, check a servo host with |
| 469 | `repair()` instead of `verify()`. |
Dan Shi | 4d47852 | 2014-02-14 13:46:32 -0800 | [diff] [blame] | 470 | |
| 471 | @returns: A ServoHost object or None. See comments above. |
| 472 | |
| 473 | """ |
Richard Barnette | 07c2e1d | 2016-10-26 14:24:28 -0700 | [diff] [blame] | 474 | servo_dependency = servo_args is not None |
Richard Barnette | 07c2e1d | 2016-10-26 14:24:28 -0700 | [diff] [blame] | 475 | if dut is not None and (try_lab_servo or servo_dependency): |
Prathmesh Prabhu | b481023 | 2018-09-07 13:24:08 -0700 | [diff] [blame] | 476 | servo_args_override = get_servo_args_for_host(dut) |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 477 | if servo_args_override is not None: |
Prathmesh Prabhu | efb1b48 | 2018-08-28 17:15:05 -0700 | [diff] [blame] | 478 | if utils.in_moblab_ssp(): |
| 479 | _tweak_args_for_ssp_moblab(servo_args_override) |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 480 | logging.debug( |
| 481 | 'Overriding provided servo_args (%s) with arguments' |
| 482 | ' determined from the host (%s)', |
| 483 | servo_args, |
| 484 | servo_args_override, |
| 485 | ) |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 486 | servo_args = servo_args_override |
Prathmesh Prabhu | cba4429 | 2018-08-28 17:44:45 -0700 | [diff] [blame] | 487 | |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 488 | if servo_args is None: |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 489 | logging.debug('No servo_args provided, and failed to find overrides.') |
Richard Barnette | ea3e460 | 2016-06-10 12:36:41 -0700 | [diff] [blame] | 490 | return None |
Prathmesh Prabhu | cba4429 | 2018-08-28 17:44:45 -0700 | [diff] [blame] | 491 | if SERVO_HOST_ATTR not in servo_args: |
| 492 | logging.debug('%s attribute missing from servo_args: %s', |
| 493 | SERVO_HOST_ATTR, servo_args) |
| 494 | return None |
Richard Barnette | 07c2e1d | 2016-10-26 14:24:28 -0700 | [diff] [blame] | 495 | if (not servo_dependency and not try_servo_repair and |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 496 | not servo_host_is_up(servo_args[SERVO_HOST_ATTR])): |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 497 | logging.debug('ServoHost is not up.') |
Dan Shi | bbb0cb6 | 2014-03-24 17:50:57 -0700 | [diff] [blame] | 498 | return None |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 499 | |
Garry Wang | ebc015b | 2019-06-06 17:45:06 -0700 | [diff] [blame] | 500 | newhost = ServoHost(**servo_args) |
| 501 | |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 502 | # Note that the logic of repair() includes everything done |
| 503 | # by verify(). It's sufficient to call one or the other; |
| 504 | # we don't need both. |
Richard Barnette | 07c2e1d | 2016-10-26 14:24:28 -0700 | [diff] [blame] | 505 | if servo_dependency: |
| 506 | newhost.repair(silent=True) |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 507 | return newhost |
| 508 | |
| 509 | if try_servo_repair: |
| 510 | try: |
| 511 | newhost.repair() |
| 512 | except Exception: |
| 513 | logging.exception('servo repair failed for %s', newhost.hostname) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 514 | else: |
| 515 | try: |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 516 | newhost.verify() |
Kevin Cheng | 5f2ba6c | 2016-09-28 10:20:05 -0700 | [diff] [blame] | 517 | except Exception: |
Prathmesh Prabhu | 88bf605 | 2018-08-28 16:21:26 -0700 | [diff] [blame] | 518 | logging.exception('servo verify failed for %s', newhost.hostname) |
Richard Barnette | 9a26ad6 | 2016-06-10 12:03:08 -0700 | [diff] [blame] | 519 | return newhost |