blob: 89b571d4f62bfb400578ff9dac0cd7c7fcf30524 [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
Chrome Bot9a1137d2011-07-19 14:35:00 -070011from autotest_lib.client.common_lib import error
Chris Sosa8ee1d592011-08-14 16:50:31 -070012from autotest_lib.server import autotest, site_host_attributes, test, utils
13from autotest_lib.server.cros import servo
Craig Harrison91944552011-08-04 14:09:55 -070014
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070015class ServoTest(test.test):
16 """AutoTest test class that creates and destroys a servo object.
17
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080018 Servo-based server side AutoTests can inherit from this object.
19 There are 2 remote clients supported:
20 If use_pyauto flag is True, a remote PyAuto client will be launched;
21 If use_faft flag is Ture, a remote FAFT client will be launched.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070022 """
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080023 version = 2
Craig Harrison91944552011-08-04 14:09:55 -070024 # Abstracts access to all Servo functions.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070025 servo = None
Craig Harrison91944552011-08-04 14:09:55 -070026 # Exposes RPC access to a remote PyAuto client.
27 pyauto = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080028 # Exposes RPC access to a remote FAFT client.
29 faft_client = None
30
Craig Harrison91944552011-08-04 14:09:55 -070031 # Autotest references to the client.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080032 _autotest_client = None
33 # Remote client info list.
34 _remote_infos = {
35 'pyauto': {
36 # Used or not.
37 'used': False,
38 # Reference name of RPC object in this class.
39 'ref_name': 'pyauto',
40 # Port number of the remote RPC.
41 'port': 9988,
42 # Client test for installing dependency.
43 'client_test': 'desktopui_ServoPyAuto',
44 # The remote command to be run.
45 'remote_command': 'python /usr/local/autotest/cros/servo_pyauto.py'
46 ' --no-http-server',
47 # The short form of remote command, used by pkill.
48 'remote_command_short': 'servo_pyauto',
49 # The remote process info.
50 'remote_process': None,
51 # The ssh tunnel process info.
52 'ssh_tunnel': None,
53 # Polling RPC function name for testing the server availability.
54 'polling_rpc': 'IsLinux',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080055 # Additional SSH options.
56 'ssh_config': '-o StrictHostKeyChecking=no ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080057 },
58 'faft': {
59 'used': False,
60 'ref_name': 'faft_client',
61 'port': 9990,
62 'client_test': 'firmware_FAFTClient',
63 'remote_command': 'python /usr/local/autotest/cros/faft_client.py',
64 'remote_command_short': 'faft_client',
65 'remote_process': None,
66 'ssh_tunnel': None,
67 'polling_rpc': 'is_available',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080068 'ssh_config': '-o StrictHostKeyChecking=no '
69 '-o UserKnownHostsFile=/dev/null ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080070 },
71 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070072
73
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080074 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
75 """Create a Servo object and install the dependency.
Craig Harrison91944552011-08-04 14:09:55 -070076
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080077 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
78 installed on the client and a remote PyAuto/FAFTClient server is
79 launched and connected.
Todd Brochf24d2782011-08-19 10:55:41 -070080 """
81 # Assign default arguments for servo invocation.
82 args = {
83 'servo_host': 'localhost', 'servo_port': 9999,
84 'xml_config': 'servo.xml', 'servo_vid': None, 'servo_pid': None,
85 'servo_serial': None, 'use_pyauto': False}
86
87 # Parse arguments from AFE and override servo defaults above.
88 client_attributes = site_host_attributes.HostAttributes(host.hostname)
89 if hasattr(site_host_attributes, 'servo_serial'):
90 args['servo_serial'] = client_attributes.servo_serial
91
92 # Parse arguments from command line and override previous AFE or servo
93 # defaults
94 for arg in cmdline_args:
95 match = re.search("^(\w+)=(.+)", arg)
96 if match:
97 args[match.group(1)] = match.group(2)
98
Chris Sosa33320a82011-10-24 14:28:32 -070099 # Initialize servotest args.
100 self._client = host;
101 self._remote_infos['pyauto']['used'] = use_pyauto
102 self._remote_infos['faft']['used'] = use_faft
103
Chris Sosa8ee1d592011-08-14 16:50:31 -0700104 self.servo = servo.Servo(
Todd Brochf24d2782011-08-19 10:55:41 -0700105 args['servo_host'], args['servo_port'], args['xml_config'],
106 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700107 # Initializes dut, may raise AssertionError if pre-defined gpio
108 # sequence to set GPIO's fail. Autotest does not handle exception
109 # throwing in initialize and will cause a test to hang.
110 try:
111 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700112 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700113 del self.servo
114 raise error.TestFail(e)
115
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800116 # Install PyAuto/FAFTClient dependency.
117 for info in self._remote_infos.itervalues():
118 if info['used']:
119 if not self._autotest_client:
120 self._autotest_client = autotest.Autotest(self._client)
121 self._autotest_client.run_test(info['client_test'])
122 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700123
Chris Sosa8ee1d592011-08-14 16:50:31 -0700124 def install_recovery_image(self, image_path=None, usb_mount_point=None):
125 """Install the recovery image specied by the path onto the DUT.
126
127 This method uses google recovery mode to install a recovery image
128 onto a DUT through the use of a USB stick that is mounted on a servo
129 board specified by the usb_mount_point. If no image path is specified
130 we use the recovery image already on the usb image.
131
132 Args:
133 image_path: Path on the host to the recovery image.
134 usb_mount_point: When servo_sees_usbkey is enabled, which dev
135 e.g. /dev/sdb will the usb key show up as.
136 """
137 # Set up Servo's usb mux.
138 self.servo.set('prtctl4_pwren', 'on')
139 self.servo.enable_usb_hub(host=True)
140 if image_path and usb_mount_point:
141 logging.info('Installing recovery image onto usb stick. '
142 'This takes a while ...')
143 utils.system(' '.join(
144 ['sudo', 'dd', 'if=%s' % image_path,
145 'of=%s' % usb_mount_point, 'bs=4M']))
146
147 # Turn the device off.
148 self.servo.power_normal_press()
149 time.sleep(servo.Servo.SLEEP_DELAY)
150
151 # Boot in recovery mode.
152 try:
153 self.servo.enable_recovery_mode()
154 self.servo.power_normal_press()
155 time.sleep(servo.Servo.BOOT_DELAY)
156
157 # Enable recovery installation.
158 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
159 logging.info('Running the recovery process on the DUT. '
160 'Waiting %d seconds for recovery to complete ...',
161 servo.Servo.RECOVERY_INSTALL_DELAY)
162 time.sleep(servo.Servo.RECOVERY_INSTALL_DELAY)
163
164 # Go back into normal mode and reboot.
165 # Machine automatically reboots after the usb key is removed.
166 self.servo.disable_recovery_mode()
167 logging.info('Removing the usb key from the DUT.')
168 self.servo.disable_usb_hub()
169 time.sleep(servo.Servo.BOOT_DELAY)
170 except:
171 # In case anything went wrong we want to make sure to do a clean
172 # reset.
173 self.servo.disable_recovery_mode()
174 self.servo.warm_reset()
175 raise
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700176
177 def assert_ping(self):
178 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700179 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700180
181
182 def assert_pingfail(self):
183 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700184 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700185
186
187 def ping_test(self, hostname, timeout=5):
188 """Verify whether a host responds to a ping.
189
190 Args:
191 hostname: Hostname to ping.
192 timeout: Time in seconds to wait for a response.
193 """
194 return subprocess.call(['ping', '-c', '1', '-W',
195 str(timeout), hostname]) == 0
196
197
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800198 def launch_client(self, info):
199 """Launch a remote process on client and set up an xmlrpc connection.
200
201 Args:
202 info: A dict of remote info, see the definition of self._remote_infos.
203 """
204 assert info['used'], \
205 'Remote %s dependency not installed.' % info['ref_name']
206 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
207 self._launch_ssh_tunnel(info)
208 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700209 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800210
211 # Launch RPC server remotely.
212 self._kill_remote_process(info)
213 logging.info('Client command: %s' % info['remote_command'])
214 info['remote_process'] = subprocess.Popen([
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800215 'ssh -n %s root@%s \'%s\'' % (info['ssh_config'],
216 self._client.ip, info['remote_command'])], shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800217
218 # Connect to RPC object.
219 logging.info('Connecting to client RPC server...')
220 remote_url = 'http://localhost:%s' % info['port']
221 setattr(self, info['ref_name'],
222 xmlrpclib.ServerProxy(remote_url, allow_none=True))
223 logging.info('Server proxy: %s' % remote_url)
224
Craig Harrison91944552011-08-04 14:09:55 -0700225 # Poll for client RPC server to come online.
226 timeout = 10
227 succeed = False
228 while timeout > 0 and not succeed:
229 time.sleep(2)
230 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800231 remote_object = getattr(self, info['ref_name'])
232 polling_rpc = getattr(remote_object, info['polling_rpc'])
233 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700234 succeed = True
235 except:
236 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800237 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700238
239
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800240 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700241 """Wait for the client to come back online.
242
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800243 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800244
245 Args:
246 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700247 """
248 timeout = 10
249 # Ensure old ssh connections are terminated.
250 self._terminate_all_ssh()
251 # Wait for the client to come up.
252 while timeout > 0 and not self.ping_test(self._client.ip):
253 time.sleep(5)
254 timeout -= 1
255 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800256 logging.info('Server: Client machine is up.')
257 # Relaunch remote clients.
258 for name, info in self._remote_infos.iteritems():
259 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800260 # This 5s delay to ensure sshd launched after network is up.
261 # TODO(waihong) Investigate pinging port via netcat or nmap
262 # to interrogate client for when sshd has launched.
263 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800264 if install_deps:
265 if not self._autotest_client:
266 self._autotest_client = autotest.Autotest(self._client)
267 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800268 self.launch_client(info)
269 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700270
271
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800272 def wait_for_client_offline(self, timeout=30):
273 """Wait for the client to come offline.
274
275 Args:
276 timeout: Time in seconds to wait the client to come offline.
277 """
278 # Wait for the client to come offline.
279 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
280 time.sleep(1)
281 timeout -= 1
282 assert timeout, 'Timed out waiting for client offline.'
283 logging.info('Server: Client machine is offline.')
284
285
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700286 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800287 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700288 if self.servo:
289 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800290 for info in self._remote_infos.itervalues():
291 if info['remote_process'] and info['remote_process'].poll() is None:
292 remote_object = getattr(self, info['ref_name'])
293 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700294 self._terminate_all_ssh()
295
296
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800297 def _launch_ssh_tunnel(self, info):
298 """Establish an ssh tunnel for connecting to the remote RPC server.
299
300 Args:
301 info: A dict of remote info, see the definition of self._remote_infos.
302 """
303 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800304 info['ssh_tunnel'] = subprocess.Popen([
305 'ssh -N -n %s -L %s:localhost:%s root@%s' %
306 (info['ssh_config'], info['port'], info['port'],
307 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700308
309
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800310 def _kill_remote_process(self, info):
311 """Ensure the remote process and local ssh process are terminated.
312
313 Args:
314 info: A dict of remote info, see the definition of self._remote_infos.
315 """
316 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800317 subprocess.call(['ssh -n %s root@%s \'%s\'' %
318 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700319 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800320 if info['remote_process'] and info['remote_process'].poll() is None:
321 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700322
323
324 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800325 """Terminate all ssh connections associated with remote processes."""
326 for info in self._remote_infos.itervalues():
327 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
328 info['ssh_tunnel'].terminate()
329 self._kill_remote_process(info)