blob: 95108d5f5345b9194460ccc5c902de99ccb66c54 [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
Todd Brochf24d2782011-08-19 10:55:41 -07006import re
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07007import subprocess
Craig Harrison91944552011-08-04 14:09:55 -07008import time
9import xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070010
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080011from autotest_lib.client.bin import utils
Chrome Bot9a1137d2011-07-19 14:35:00 -070012from autotest_lib.client.common_lib import error
Chris Sosa8ee1d592011-08-14 16:50:31 -070013from autotest_lib.server import autotest, site_host_attributes, test, utils
14from autotest_lib.server.cros import servo
Craig Harrison91944552011-08-04 14:09:55 -070015
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070016class ServoTest(test.test):
17 """AutoTest test class that creates and destroys a servo object.
18
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080019 Servo-based server side AutoTests can inherit from this object.
20 There are 2 remote clients supported:
21 If use_pyauto flag is True, a remote PyAuto client will be launched;
22 If use_faft flag is Ture, a remote FAFT client will be launched.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070023 """
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080024 version = 2
Craig Harrison91944552011-08-04 14:09:55 -070025 # Abstracts access to all Servo functions.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070026 servo = None
Craig Harrison91944552011-08-04 14:09:55 -070027 # Exposes RPC access to a remote PyAuto client.
28 pyauto = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080029 # Exposes RPC access to a remote FAFT client.
30 faft_client = None
31
Craig Harrison91944552011-08-04 14:09:55 -070032 # Autotest references to the client.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080033 _autotest_client = None
34 # Remote client info list.
35 _remote_infos = {
36 'pyauto': {
37 # Used or not.
38 'used': False,
39 # Reference name of RPC object in this class.
40 'ref_name': 'pyauto',
41 # Port number of the remote RPC.
42 'port': 9988,
43 # Client test for installing dependency.
44 'client_test': 'desktopui_ServoPyAuto',
45 # The remote command to be run.
46 'remote_command': 'python /usr/local/autotest/cros/servo_pyauto.py'
47 ' --no-http-server',
48 # The short form of remote command, used by pkill.
49 'remote_command_short': 'servo_pyauto',
50 # The remote process info.
51 'remote_process': None,
52 # The ssh tunnel process info.
53 'ssh_tunnel': None,
54 # Polling RPC function name for testing the server availability.
55 'polling_rpc': 'IsLinux',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080056 # Additional SSH options.
57 'ssh_config': '-o StrictHostKeyChecking=no ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080058 },
59 'faft': {
60 'used': False,
61 'ref_name': 'faft_client',
62 'port': 9990,
63 'client_test': 'firmware_FAFTClient',
64 'remote_command': 'python /usr/local/autotest/cros/faft_client.py',
65 'remote_command_short': 'faft_client',
66 'remote_process': None,
67 'ssh_tunnel': None,
68 'polling_rpc': 'is_available',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080069 'ssh_config': '-o StrictHostKeyChecking=no '
70 '-o UserKnownHostsFile=/dev/null ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080071 },
72 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070073
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +080074 # Time between an usb disk plugged-in and detected in the system.
75 USB_DETECTION_DELAY = 10
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070076
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080077 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
78 """Create a Servo object and install the dependency.
Craig Harrison91944552011-08-04 14:09:55 -070079
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080080 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
81 installed on the client and a remote PyAuto/FAFTClient server is
82 launched and connected.
Todd Brochf24d2782011-08-19 10:55:41 -070083 """
84 # Assign default arguments for servo invocation.
85 args = {
86 'servo_host': 'localhost', 'servo_port': 9999,
87 'xml_config': 'servo.xml', 'servo_vid': None, 'servo_pid': None,
88 'servo_serial': None, 'use_pyauto': False}
89
90 # Parse arguments from AFE and override servo defaults above.
91 client_attributes = site_host_attributes.HostAttributes(host.hostname)
92 if hasattr(site_host_attributes, 'servo_serial'):
93 args['servo_serial'] = client_attributes.servo_serial
94
95 # Parse arguments from command line and override previous AFE or servo
96 # defaults
97 for arg in cmdline_args:
98 match = re.search("^(\w+)=(.+)", arg)
99 if match:
100 args[match.group(1)] = match.group(2)
101
Chris Sosa33320a82011-10-24 14:28:32 -0700102 # Initialize servotest args.
103 self._client = host;
104 self._remote_infos['pyauto']['used'] = use_pyauto
105 self._remote_infos['faft']['used'] = use_faft
106
Chris Sosa8ee1d592011-08-14 16:50:31 -0700107 self.servo = servo.Servo(
Todd Brochf24d2782011-08-19 10:55:41 -0700108 args['servo_host'], args['servo_port'], args['xml_config'],
109 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700110 # Initializes dut, may raise AssertionError if pre-defined gpio
111 # sequence to set GPIO's fail. Autotest does not handle exception
112 # throwing in initialize and will cause a test to hang.
113 try:
114 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700115 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700116 del self.servo
117 raise error.TestFail(e)
118
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800119 # Install PyAuto/FAFTClient dependency.
120 for info in self._remote_infos.itervalues():
121 if info['used']:
122 if not self._autotest_client:
123 self._autotest_client = autotest.Autotest(self._client)
124 self._autotest_client.run_test(info['client_test'])
125 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700126
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800127 # TODO(waihong) It may fail if multiple servo's are connected to the same
128 # host. Should look for a better way, like the USB serial name, to identify
129 # the USB device.
130 def probe_host_usb_dev(self):
131 """Probe the USB disk device plugged-in the servo from the host side.
132
133 It tries to switch the USB mux to make the host unable to see the
134 USB disk and compares the result difference.
135
136 Returns:
137 A string of USB disk path, like '/dev/sdb', or None if not existed.
138 """
139 cmd = 'ls /dev/sd[a-z]'
140 original_value = self.servo.get('usb_mux_sel1')
141
142 # Make the host unable to see the USB disk.
143 if original_value != 'dut_sees_usbkey':
144 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
145 time.sleep(self.USB_DETECTION_DELAY)
146 no_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
147
148 # Make the host able to see the USB disk.
149 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
150 time.sleep(self.USB_DETECTION_DELAY)
151 has_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
152
153 # Back to its original value.
154 if original_value != 'servo_sees_usbkey':
155 self.servo.set('usb_mux_sel1', original_value)
156 time.sleep(self.USB_DETECTION_DELAY)
157
158 diff_set = has_usb_set - no_usb_set
159 if len(diff_set) == 1:
160 return diff_set.pop()
161 else:
162 return None
163
Chris Sosa8ee1d592011-08-14 16:50:31 -0700164 def install_recovery_image(self, image_path=None, usb_mount_point=None):
165 """Install the recovery image specied by the path onto the DUT.
166
167 This method uses google recovery mode to install a recovery image
168 onto a DUT through the use of a USB stick that is mounted on a servo
169 board specified by the usb_mount_point. If no image path is specified
170 we use the recovery image already on the usb image.
171
172 Args:
173 image_path: Path on the host to the recovery image.
174 usb_mount_point: When servo_sees_usbkey is enabled, which dev
175 e.g. /dev/sdb will the usb key show up as.
176 """
177 # Set up Servo's usb mux.
178 self.servo.set('prtctl4_pwren', 'on')
179 self.servo.enable_usb_hub(host=True)
180 if image_path and usb_mount_point:
181 logging.info('Installing recovery image onto usb stick. '
182 'This takes a while ...')
183 utils.system(' '.join(
184 ['sudo', 'dd', 'if=%s' % image_path,
185 'of=%s' % usb_mount_point, 'bs=4M']))
186
187 # Turn the device off.
188 self.servo.power_normal_press()
189 time.sleep(servo.Servo.SLEEP_DELAY)
190
191 # Boot in recovery mode.
192 try:
193 self.servo.enable_recovery_mode()
194 self.servo.power_normal_press()
195 time.sleep(servo.Servo.BOOT_DELAY)
196
197 # Enable recovery installation.
198 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
199 logging.info('Running the recovery process on the DUT. '
200 'Waiting %d seconds for recovery to complete ...',
201 servo.Servo.RECOVERY_INSTALL_DELAY)
202 time.sleep(servo.Servo.RECOVERY_INSTALL_DELAY)
203
204 # Go back into normal mode and reboot.
205 # Machine automatically reboots after the usb key is removed.
206 self.servo.disable_recovery_mode()
207 logging.info('Removing the usb key from the DUT.')
208 self.servo.disable_usb_hub()
209 time.sleep(servo.Servo.BOOT_DELAY)
210 except:
211 # In case anything went wrong we want to make sure to do a clean
212 # reset.
213 self.servo.disable_recovery_mode()
214 self.servo.warm_reset()
215 raise
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700216
217 def assert_ping(self):
218 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700219 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700220
221
222 def assert_pingfail(self):
223 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700224 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700225
226
227 def ping_test(self, hostname, timeout=5):
228 """Verify whether a host responds to a ping.
229
230 Args:
231 hostname: Hostname to ping.
232 timeout: Time in seconds to wait for a response.
233 """
234 return subprocess.call(['ping', '-c', '1', '-W',
235 str(timeout), hostname]) == 0
236
237
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800238 def launch_client(self, info):
239 """Launch a remote process on client and set up an xmlrpc connection.
240
241 Args:
242 info: A dict of remote info, see the definition of self._remote_infos.
243 """
244 assert info['used'], \
245 'Remote %s dependency not installed.' % info['ref_name']
246 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
247 self._launch_ssh_tunnel(info)
248 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700249 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800250
251 # Launch RPC server remotely.
252 self._kill_remote_process(info)
253 logging.info('Client command: %s' % info['remote_command'])
254 info['remote_process'] = subprocess.Popen([
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800255 'ssh -n %s root@%s \'%s\'' % (info['ssh_config'],
256 self._client.ip, info['remote_command'])], shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800257
258 # Connect to RPC object.
259 logging.info('Connecting to client RPC server...')
260 remote_url = 'http://localhost:%s' % info['port']
261 setattr(self, info['ref_name'],
262 xmlrpclib.ServerProxy(remote_url, allow_none=True))
263 logging.info('Server proxy: %s' % remote_url)
264
Craig Harrison91944552011-08-04 14:09:55 -0700265 # Poll for client RPC server to come online.
266 timeout = 10
267 succeed = False
268 while timeout > 0 and not succeed:
269 time.sleep(2)
270 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800271 remote_object = getattr(self, info['ref_name'])
272 polling_rpc = getattr(remote_object, info['polling_rpc'])
273 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700274 succeed = True
275 except:
276 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800277 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700278
279
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800280 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700281 """Wait for the client to come back online.
282
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800283 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800284
285 Args:
286 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700287 """
288 timeout = 10
289 # Ensure old ssh connections are terminated.
290 self._terminate_all_ssh()
291 # Wait for the client to come up.
292 while timeout > 0 and not self.ping_test(self._client.ip):
293 time.sleep(5)
294 timeout -= 1
295 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800296 logging.info('Server: Client machine is up.')
297 # Relaunch remote clients.
298 for name, info in self._remote_infos.iteritems():
299 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800300 # This 5s delay to ensure sshd launched after network is up.
301 # TODO(waihong) Investigate pinging port via netcat or nmap
302 # to interrogate client for when sshd has launched.
303 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800304 if install_deps:
305 if not self._autotest_client:
306 self._autotest_client = autotest.Autotest(self._client)
307 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800308 self.launch_client(info)
309 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700310
311
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800312 def wait_for_client_offline(self, timeout=30):
313 """Wait for the client to come offline.
314
315 Args:
316 timeout: Time in seconds to wait the client to come offline.
317 """
318 # Wait for the client to come offline.
319 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
320 time.sleep(1)
321 timeout -= 1
322 assert timeout, 'Timed out waiting for client offline.'
323 logging.info('Server: Client machine is offline.')
324
325
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700326 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800327 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700328 if self.servo:
329 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800330 for info in self._remote_infos.itervalues():
331 if info['remote_process'] and info['remote_process'].poll() is None:
332 remote_object = getattr(self, info['ref_name'])
333 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700334 self._terminate_all_ssh()
335
336
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800337 def _launch_ssh_tunnel(self, info):
338 """Establish an ssh tunnel for connecting to the remote RPC server.
339
340 Args:
341 info: A dict of remote info, see the definition of self._remote_infos.
342 """
343 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800344 info['ssh_tunnel'] = subprocess.Popen([
345 'ssh -N -n %s -L %s:localhost:%s root@%s' %
346 (info['ssh_config'], info['port'], info['port'],
347 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700348
349
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800350 def _kill_remote_process(self, info):
351 """Ensure the remote process and local ssh process are terminated.
352
353 Args:
354 info: A dict of remote info, see the definition of self._remote_infos.
355 """
356 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800357 subprocess.call(['ssh -n %s root@%s \'%s\'' %
358 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700359 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800360 if info['remote_process'] and info['remote_process'].poll() is None:
361 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700362
363
364 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800365 """Terminate all ssh connections associated with remote processes."""
366 for info in self._remote_infos.itervalues():
367 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
368 info['ssh_tunnel'].terminate()
369 self._kill_remote_process(info)