blob: a47f74921768290e12c712f5315aac5d17335bbf [file] [log] [blame]
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07001# Copyright (c) 2011 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
Craig Harrison91944552011-08-04 14:09:55 -07005import logging
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +08006import os
Todd Brochf24d2782011-08-19 10:55:41 -07007import re
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07008import subprocess
Craig Harrison91944552011-08-04 14:09:55 -07009import time
10import xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070011
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080012from autotest_lib.client.bin import utils
Chrome Bot9a1137d2011-07-19 14:35:00 -070013from autotest_lib.client.common_lib import error
Chris Sosa8ee1d592011-08-14 16:50:31 -070014from autotest_lib.server import autotest, site_host_attributes, test, utils
15from autotest_lib.server.cros import servo
Craig Harrison91944552011-08-04 14:09:55 -070016
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070017class ServoTest(test.test):
18 """AutoTest test class that creates and destroys a servo object.
19
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080020 Servo-based server side AutoTests can inherit from this object.
21 There are 2 remote clients supported:
22 If use_pyauto flag is True, a remote PyAuto client will be launched;
23 If use_faft flag is Ture, a remote FAFT client will be launched.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070024 """
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080025 version = 2
Craig Harrison91944552011-08-04 14:09:55 -070026 # Abstracts access to all Servo functions.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070027 servo = None
Craig Harrison91944552011-08-04 14:09:55 -070028 # Exposes RPC access to a remote PyAuto client.
29 pyauto = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080030 # Exposes RPC access to a remote FAFT client.
31 faft_client = None
32
Craig Harrison91944552011-08-04 14:09:55 -070033 # Autotest references to the client.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080034 _autotest_client = None
35 # Remote client info list.
36 _remote_infos = {
37 'pyauto': {
38 # Used or not.
39 'used': False,
40 # Reference name of RPC object in this class.
41 'ref_name': 'pyauto',
42 # Port number of the remote RPC.
43 'port': 9988,
44 # Client test for installing dependency.
45 'client_test': 'desktopui_ServoPyAuto',
46 # The remote command to be run.
47 'remote_command': 'python /usr/local/autotest/cros/servo_pyauto.py'
48 ' --no-http-server',
49 # The short form of remote command, used by pkill.
50 'remote_command_short': 'servo_pyauto',
51 # The remote process info.
52 'remote_process': None,
53 # The ssh tunnel process info.
54 'ssh_tunnel': None,
55 # Polling RPC function name for testing the server availability.
56 'polling_rpc': 'IsLinux',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080057 # Additional SSH options.
58 'ssh_config': '-o StrictHostKeyChecking=no ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080059 },
60 'faft': {
61 'used': False,
62 'ref_name': 'faft_client',
63 'port': 9990,
64 'client_test': 'firmware_FAFTClient',
65 'remote_command': 'python /usr/local/autotest/cros/faft_client.py',
66 'remote_command_short': 'faft_client',
67 'remote_process': None,
68 'ssh_tunnel': None,
69 'polling_rpc': 'is_available',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080070 'ssh_config': '-o StrictHostKeyChecking=no '
71 '-o UserKnownHostsFile=/dev/null ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080072 },
73 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070074
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080075 # Time between an usb disk plugged-in and detected in the system.
76 USB_DETECTION_DELAY = 10
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070077
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080078 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
79 """Create a Servo object and install the dependency.
Craig Harrison91944552011-08-04 14:09:55 -070080
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080081 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
82 installed on the client and a remote PyAuto/FAFTClient server is
83 launched and connected.
Todd Brochf24d2782011-08-19 10:55:41 -070084 """
85 # Assign default arguments for servo invocation.
86 args = {
87 'servo_host': 'localhost', 'servo_port': 9999,
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +080088 'xml_config': ['servo.xml'], 'servo_vid': None, 'servo_pid': None,
Todd Brochf24d2782011-08-19 10:55:41 -070089 'servo_serial': None, 'use_pyauto': False}
90
91 # Parse arguments from AFE and override servo defaults above.
92 client_attributes = site_host_attributes.HostAttributes(host.hostname)
93 if hasattr(site_host_attributes, 'servo_serial'):
94 args['servo_serial'] = client_attributes.servo_serial
95
96 # Parse arguments from command line and override previous AFE or servo
97 # defaults
98 for arg in cmdline_args:
99 match = re.search("^(\w+)=(.+)", arg)
100 if match:
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +0800101 key = match.group(1)
102 val = match.group(2)
103 # Support multiple xml_config by appending it to a list.
104 if key == 'xml_config':
105 args[key].append(val)
106 else:
107 args[key] = val
Todd Brochf24d2782011-08-19 10:55:41 -0700108
Chris Sosa33320a82011-10-24 14:28:32 -0700109 # Initialize servotest args.
110 self._client = host;
111 self._remote_infos['pyauto']['used'] = use_pyauto
112 self._remote_infos['faft']['used'] = use_faft
113
Chris Sosa8ee1d592011-08-14 16:50:31 -0700114 self.servo = servo.Servo(
Todd Brochf24d2782011-08-19 10:55:41 -0700115 args['servo_host'], args['servo_port'], args['xml_config'],
116 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700117 # Initializes dut, may raise AssertionError if pre-defined gpio
118 # sequence to set GPIO's fail. Autotest does not handle exception
119 # throwing in initialize and will cause a test to hang.
120 try:
121 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700122 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700123 del self.servo
124 raise error.TestFail(e)
125
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800126 # Install PyAuto/FAFTClient dependency.
127 for info in self._remote_infos.itervalues():
128 if info['used']:
129 if not self._autotest_client:
130 self._autotest_client = autotest.Autotest(self._client)
131 self._autotest_client.run_test(info['client_test'])
132 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700133
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800134 # TODO(waihong) It may fail if multiple servo's are connected to the same
135 # host. Should look for a better way, like the USB serial name, to identify
136 # the USB device.
137 def probe_host_usb_dev(self):
138 """Probe the USB disk device plugged-in the servo from the host side.
139
140 It tries to switch the USB mux to make the host unable to see the
141 USB disk and compares the result difference.
142
143 Returns:
144 A string of USB disk path, like '/dev/sdb', or None if not existed.
145 """
146 cmd = 'ls /dev/sd[a-z]'
147 original_value = self.servo.get('usb_mux_sel1')
148
149 # Make the host unable to see the USB disk.
150 if original_value != 'dut_sees_usbkey':
151 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
152 time.sleep(self.USB_DETECTION_DELAY)
153 no_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
154
155 # Make the host able to see the USB disk.
156 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
157 time.sleep(self.USB_DETECTION_DELAY)
158 has_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
159
160 # Back to its original value.
161 if original_value != 'servo_sees_usbkey':
162 self.servo.set('usb_mux_sel1', original_value)
163 time.sleep(self.USB_DETECTION_DELAY)
164
165 diff_set = has_usb_set - no_usb_set
166 if len(diff_set) == 1:
167 return diff_set.pop()
168 else:
169 return None
170
Chris Sosa8ee1d592011-08-14 16:50:31 -0700171 def install_recovery_image(self, image_path=None, usb_mount_point=None):
172 """Install the recovery image specied by the path onto the DUT.
173
174 This method uses google recovery mode to install a recovery image
175 onto a DUT through the use of a USB stick that is mounted on a servo
176 board specified by the usb_mount_point. If no image path is specified
177 we use the recovery image already on the usb image.
178
179 Args:
180 image_path: Path on the host to the recovery image.
181 usb_mount_point: When servo_sees_usbkey is enabled, which dev
182 e.g. /dev/sdb will the usb key show up as.
183 """
184 # Set up Servo's usb mux.
185 self.servo.set('prtctl4_pwren', 'on')
186 self.servo.enable_usb_hub(host=True)
187 if image_path and usb_mount_point:
188 logging.info('Installing recovery image onto usb stick. '
189 'This takes a while ...')
190 utils.system(' '.join(
191 ['sudo', 'dd', 'if=%s' % image_path,
192 'of=%s' % usb_mount_point, 'bs=4M']))
193
194 # Turn the device off.
195 self.servo.power_normal_press()
196 time.sleep(servo.Servo.SLEEP_DELAY)
197
198 # Boot in recovery mode.
199 try:
200 self.servo.enable_recovery_mode()
201 self.servo.power_normal_press()
202 time.sleep(servo.Servo.BOOT_DELAY)
203
204 # Enable recovery installation.
205 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
206 logging.info('Running the recovery process on the DUT. '
207 'Waiting %d seconds for recovery to complete ...',
208 servo.Servo.RECOVERY_INSTALL_DELAY)
209 time.sleep(servo.Servo.RECOVERY_INSTALL_DELAY)
210
211 # Go back into normal mode and reboot.
212 # Machine automatically reboots after the usb key is removed.
213 self.servo.disable_recovery_mode()
214 logging.info('Removing the usb key from the DUT.')
215 self.servo.disable_usb_hub()
216 time.sleep(servo.Servo.BOOT_DELAY)
217 except:
218 # In case anything went wrong we want to make sure to do a clean
219 # reset.
220 self.servo.disable_recovery_mode()
221 self.servo.warm_reset()
222 raise
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700223
224 def assert_ping(self):
225 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700226 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700227
228
229 def assert_pingfail(self):
230 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700231 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700232
233
234 def ping_test(self, hostname, timeout=5):
235 """Verify whether a host responds to a ping.
236
237 Args:
238 hostname: Hostname to ping.
239 timeout: Time in seconds to wait for a response.
240 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800241 with open(os.devnull, 'w') as fnull:
242 return subprocess.call(
243 ['ping', '-c', '1', '-W', str(timeout), hostname],
244 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700245
246
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800247 def launch_client(self, info):
248 """Launch a remote process on client and set up an xmlrpc connection.
249
250 Args:
251 info: A dict of remote info, see the definition of self._remote_infos.
252 """
253 assert info['used'], \
254 'Remote %s dependency not installed.' % info['ref_name']
255 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
256 self._launch_ssh_tunnel(info)
257 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700258 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800259
260 # Launch RPC server remotely.
261 self._kill_remote_process(info)
262 logging.info('Client command: %s' % info['remote_command'])
263 info['remote_process'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800264 'ssh -n -q %s root@%s \'%s\'' % (info['ssh_config'],
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800265 self._client.ip, info['remote_command'])], shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800266
267 # Connect to RPC object.
268 logging.info('Connecting to client RPC server...')
269 remote_url = 'http://localhost:%s' % info['port']
270 setattr(self, info['ref_name'],
271 xmlrpclib.ServerProxy(remote_url, allow_none=True))
272 logging.info('Server proxy: %s' % remote_url)
273
Craig Harrison91944552011-08-04 14:09:55 -0700274 # Poll for client RPC server to come online.
275 timeout = 10
276 succeed = False
277 while timeout > 0 and not succeed:
278 time.sleep(2)
279 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800280 remote_object = getattr(self, info['ref_name'])
281 polling_rpc = getattr(remote_object, info['polling_rpc'])
282 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700283 succeed = True
284 except:
285 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800286 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700287
288
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800289 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700290 """Wait for the client to come back online.
291
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800292 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800293
294 Args:
295 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700296 """
297 timeout = 10
298 # Ensure old ssh connections are terminated.
299 self._terminate_all_ssh()
300 # Wait for the client to come up.
301 while timeout > 0 and not self.ping_test(self._client.ip):
302 time.sleep(5)
303 timeout -= 1
304 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800305 logging.info('Server: Client machine is up.')
306 # Relaunch remote clients.
307 for name, info in self._remote_infos.iteritems():
308 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800309 # This 5s delay to ensure sshd launched after network is up.
310 # TODO(waihong) Investigate pinging port via netcat or nmap
311 # to interrogate client for when sshd has launched.
312 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800313 if install_deps:
314 if not self._autotest_client:
315 self._autotest_client = autotest.Autotest(self._client)
316 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800317 self.launch_client(info)
318 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700319
320
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800321 def wait_for_client_offline(self, timeout=30):
322 """Wait for the client to come offline.
323
324 Args:
325 timeout: Time in seconds to wait the client to come offline.
326 """
327 # Wait for the client to come offline.
328 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
329 time.sleep(1)
330 timeout -= 1
331 assert timeout, 'Timed out waiting for client offline.'
332 logging.info('Server: Client machine is offline.')
333
334
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700335 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800336 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700337 if self.servo:
338 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800339 for info in self._remote_infos.itervalues():
340 if info['remote_process'] and info['remote_process'].poll() is None:
341 remote_object = getattr(self, info['ref_name'])
342 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700343 self._terminate_all_ssh()
344
345
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800346 def _launch_ssh_tunnel(self, info):
347 """Establish an ssh tunnel for connecting to the remote RPC server.
348
349 Args:
350 info: A dict of remote info, see the definition of self._remote_infos.
351 """
352 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800353 info['ssh_tunnel'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800354 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800355 (info['ssh_config'], info['port'], info['port'],
356 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700357
358
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800359 def _kill_remote_process(self, info):
360 """Ensure the remote process and local ssh process are terminated.
361
362 Args:
363 info: A dict of remote info, see the definition of self._remote_infos.
364 """
365 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800366 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800367 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700368 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800369 if info['remote_process'] and info['remote_process'].poll() is None:
370 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700371
372
373 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800374 """Terminate all ssh connections associated with remote processes."""
375 for info in self._remote_infos.itervalues():
376 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
377 info['ssh_tunnel'].terminate()
378 self._kill_remote_process(info)