blob: e29a5ee6fec3c14232aa35e56441266626755e8f [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,
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +080087 'xml_config': ['servo.xml'], 'servo_vid': None, 'servo_pid': None,
Todd Brochf24d2782011-08-19 10:55:41 -070088 '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:
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +0800100 key = match.group(1)
101 val = match.group(2)
102 # Support multiple xml_config by appending it to a list.
103 if key == 'xml_config':
104 args[key].append(val)
105 else:
106 args[key] = val
Todd Brochf24d2782011-08-19 10:55:41 -0700107
Chris Sosa33320a82011-10-24 14:28:32 -0700108 # Initialize servotest args.
109 self._client = host;
110 self._remote_infos['pyauto']['used'] = use_pyauto
111 self._remote_infos['faft']['used'] = use_faft
112
Chris Sosa8ee1d592011-08-14 16:50:31 -0700113 self.servo = servo.Servo(
Todd Brochf24d2782011-08-19 10:55:41 -0700114 args['servo_host'], args['servo_port'], args['xml_config'],
115 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700116 # Initializes dut, may raise AssertionError if pre-defined gpio
117 # sequence to set GPIO's fail. Autotest does not handle exception
118 # throwing in initialize and will cause a test to hang.
119 try:
120 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700121 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700122 del self.servo
123 raise error.TestFail(e)
124
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800125 # Install PyAuto/FAFTClient dependency.
126 for info in self._remote_infos.itervalues():
127 if info['used']:
128 if not self._autotest_client:
129 self._autotest_client = autotest.Autotest(self._client)
130 self._autotest_client.run_test(info['client_test'])
131 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700132
Tom Wai-Hong Tam76c75072011-10-25 18:00:12 +0800133 # TODO(waihong) It may fail if multiple servo's are connected to the same
134 # host. Should look for a better way, like the USB serial name, to identify
135 # the USB device.
136 def probe_host_usb_dev(self):
137 """Probe the USB disk device plugged-in the servo from the host side.
138
139 It tries to switch the USB mux to make the host unable to see the
140 USB disk and compares the result difference.
141
142 Returns:
143 A string of USB disk path, like '/dev/sdb', or None if not existed.
144 """
145 cmd = 'ls /dev/sd[a-z]'
146 original_value = self.servo.get('usb_mux_sel1')
147
148 # Make the host unable to see the USB disk.
149 if original_value != 'dut_sees_usbkey':
150 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
151 time.sleep(self.USB_DETECTION_DELAY)
152 no_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
153
154 # Make the host able to see the USB disk.
155 self.servo.set('usb_mux_sel1', 'servo_sees_usbkey')
156 time.sleep(self.USB_DETECTION_DELAY)
157 has_usb_set = set(utils.system_output(cmd, ignore_status=True).split())
158
159 # Back to its original value.
160 if original_value != 'servo_sees_usbkey':
161 self.servo.set('usb_mux_sel1', original_value)
162 time.sleep(self.USB_DETECTION_DELAY)
163
164 diff_set = has_usb_set - no_usb_set
165 if len(diff_set) == 1:
166 return diff_set.pop()
167 else:
168 return None
169
Chris Sosa8ee1d592011-08-14 16:50:31 -0700170 def install_recovery_image(self, image_path=None, usb_mount_point=None):
171 """Install the recovery image specied by the path onto the DUT.
172
173 This method uses google recovery mode to install a recovery image
174 onto a DUT through the use of a USB stick that is mounted on a servo
175 board specified by the usb_mount_point. If no image path is specified
176 we use the recovery image already on the usb image.
177
178 Args:
179 image_path: Path on the host to the recovery image.
180 usb_mount_point: When servo_sees_usbkey is enabled, which dev
181 e.g. /dev/sdb will the usb key show up as.
182 """
183 # Set up Servo's usb mux.
184 self.servo.set('prtctl4_pwren', 'on')
185 self.servo.enable_usb_hub(host=True)
186 if image_path and usb_mount_point:
187 logging.info('Installing recovery image onto usb stick. '
188 'This takes a while ...')
189 utils.system(' '.join(
190 ['sudo', 'dd', 'if=%s' % image_path,
191 'of=%s' % usb_mount_point, 'bs=4M']))
192
193 # Turn the device off.
194 self.servo.power_normal_press()
195 time.sleep(servo.Servo.SLEEP_DELAY)
196
197 # Boot in recovery mode.
198 try:
199 self.servo.enable_recovery_mode()
200 self.servo.power_normal_press()
201 time.sleep(servo.Servo.BOOT_DELAY)
202
203 # Enable recovery installation.
204 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
205 logging.info('Running the recovery process on the DUT. '
206 'Waiting %d seconds for recovery to complete ...',
207 servo.Servo.RECOVERY_INSTALL_DELAY)
208 time.sleep(servo.Servo.RECOVERY_INSTALL_DELAY)
209
210 # Go back into normal mode and reboot.
211 # Machine automatically reboots after the usb key is removed.
212 self.servo.disable_recovery_mode()
213 logging.info('Removing the usb key from the DUT.')
214 self.servo.disable_usb_hub()
215 time.sleep(servo.Servo.BOOT_DELAY)
216 except:
217 # In case anything went wrong we want to make sure to do a clean
218 # reset.
219 self.servo.disable_recovery_mode()
220 self.servo.warm_reset()
221 raise
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700222
223 def assert_ping(self):
224 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700225 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700226
227
228 def assert_pingfail(self):
229 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700230 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700231
232
233 def ping_test(self, hostname, timeout=5):
234 """Verify whether a host responds to a ping.
235
236 Args:
237 hostname: Hostname to ping.
238 timeout: Time in seconds to wait for a response.
239 """
240 return subprocess.call(['ping', '-c', '1', '-W',
241 str(timeout), hostname]) == 0
242
243
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800244 def launch_client(self, info):
245 """Launch a remote process on client and set up an xmlrpc connection.
246
247 Args:
248 info: A dict of remote info, see the definition of self._remote_infos.
249 """
250 assert info['used'], \
251 'Remote %s dependency not installed.' % info['ref_name']
252 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
253 self._launch_ssh_tunnel(info)
254 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700255 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800256
257 # Launch RPC server remotely.
258 self._kill_remote_process(info)
259 logging.info('Client command: %s' % info['remote_command'])
260 info['remote_process'] = subprocess.Popen([
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800261 'ssh -n %s root@%s \'%s\'' % (info['ssh_config'],
262 self._client.ip, info['remote_command'])], shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800263
264 # Connect to RPC object.
265 logging.info('Connecting to client RPC server...')
266 remote_url = 'http://localhost:%s' % info['port']
267 setattr(self, info['ref_name'],
268 xmlrpclib.ServerProxy(remote_url, allow_none=True))
269 logging.info('Server proxy: %s' % remote_url)
270
Craig Harrison91944552011-08-04 14:09:55 -0700271 # Poll for client RPC server to come online.
272 timeout = 10
273 succeed = False
274 while timeout > 0 and not succeed:
275 time.sleep(2)
276 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800277 remote_object = getattr(self, info['ref_name'])
278 polling_rpc = getattr(remote_object, info['polling_rpc'])
279 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700280 succeed = True
281 except:
282 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800283 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700284
285
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800286 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700287 """Wait for the client to come back online.
288
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800289 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800290
291 Args:
292 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700293 """
294 timeout = 10
295 # Ensure old ssh connections are terminated.
296 self._terminate_all_ssh()
297 # Wait for the client to come up.
298 while timeout > 0 and not self.ping_test(self._client.ip):
299 time.sleep(5)
300 timeout -= 1
301 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800302 logging.info('Server: Client machine is up.')
303 # Relaunch remote clients.
304 for name, info in self._remote_infos.iteritems():
305 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800306 # This 5s delay to ensure sshd launched after network is up.
307 # TODO(waihong) Investigate pinging port via netcat or nmap
308 # to interrogate client for when sshd has launched.
309 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800310 if install_deps:
311 if not self._autotest_client:
312 self._autotest_client = autotest.Autotest(self._client)
313 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800314 self.launch_client(info)
315 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700316
317
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800318 def wait_for_client_offline(self, timeout=30):
319 """Wait for the client to come offline.
320
321 Args:
322 timeout: Time in seconds to wait the client to come offline.
323 """
324 # Wait for the client to come offline.
325 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
326 time.sleep(1)
327 timeout -= 1
328 assert timeout, 'Timed out waiting for client offline.'
329 logging.info('Server: Client machine is offline.')
330
331
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700332 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800333 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700334 if self.servo:
335 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800336 for info in self._remote_infos.itervalues():
337 if info['remote_process'] and info['remote_process'].poll() is None:
338 remote_object = getattr(self, info['ref_name'])
339 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700340 self._terminate_all_ssh()
341
342
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800343 def _launch_ssh_tunnel(self, info):
344 """Establish an ssh tunnel for connecting to the remote RPC server.
345
346 Args:
347 info: A dict of remote info, see the definition of self._remote_infos.
348 """
349 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800350 info['ssh_tunnel'] = subprocess.Popen([
351 'ssh -N -n %s -L %s:localhost:%s root@%s' %
352 (info['ssh_config'], info['port'], info['port'],
353 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700354
355
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800356 def _kill_remote_process(self, info):
357 """Ensure the remote process and local ssh process are terminated.
358
359 Args:
360 info: A dict of remote info, see the definition of self._remote_infos.
361 """
362 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800363 subprocess.call(['ssh -n %s root@%s \'%s\'' %
364 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700365 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800366 if info['remote_process'] and info['remote_process'].poll() is None:
367 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700368
369
370 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800371 """Terminate all ssh connections associated with remote processes."""
372 for info in self._remote_infos.itervalues():
373 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
374 info['ssh_tunnel'].terminate()
375 self._kill_remote_process(info)