blob: 4a4c0055b9d326fd1ab59b69a239f871789729f8 [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',
55 },
56 'faft': {
57 'used': False,
58 'ref_name': 'faft_client',
59 'port': 9990,
60 'client_test': 'firmware_FAFTClient',
61 'remote_command': 'python /usr/local/autotest/cros/faft_client.py',
62 'remote_command_short': 'faft_client',
63 'remote_process': None,
64 'ssh_tunnel': None,
65 'polling_rpc': 'is_available',
66 },
67 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070068
69
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080070 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
71 """Create a Servo object and install the dependency.
Craig Harrison91944552011-08-04 14:09:55 -070072
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080073 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
74 installed on the client and a remote PyAuto/FAFTClient server is
75 launched and connected.
Todd Brochf24d2782011-08-19 10:55:41 -070076 """
77 # Assign default arguments for servo invocation.
78 args = {
79 'servo_host': 'localhost', 'servo_port': 9999,
80 'xml_config': 'servo.xml', 'servo_vid': None, 'servo_pid': None,
81 'servo_serial': None, 'use_pyauto': False}
82
83 # Parse arguments from AFE and override servo defaults above.
84 client_attributes = site_host_attributes.HostAttributes(host.hostname)
85 if hasattr(site_host_attributes, 'servo_serial'):
86 args['servo_serial'] = client_attributes.servo_serial
87
88 # Parse arguments from command line and override previous AFE or servo
89 # defaults
90 for arg in cmdline_args:
91 match = re.search("^(\w+)=(.+)", arg)
92 if match:
93 args[match.group(1)] = match.group(2)
94
Chris Sosa33320a82011-10-24 14:28:32 -070095 # Initialize servotest args.
96 self._client = host;
97 self._remote_infos['pyauto']['used'] = use_pyauto
98 self._remote_infos['faft']['used'] = use_faft
99
Chris Sosa8ee1d592011-08-14 16:50:31 -0700100 self.servo = servo.Servo(
Todd Brochf24d2782011-08-19 10:55:41 -0700101 args['servo_host'], args['servo_port'], args['xml_config'],
102 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700103 # Initializes dut, may raise AssertionError if pre-defined gpio
104 # sequence to set GPIO's fail. Autotest does not handle exception
105 # throwing in initialize and will cause a test to hang.
106 try:
107 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700108 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700109 del self.servo
110 raise error.TestFail(e)
111
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800112 # Install PyAuto/FAFTClient dependency.
113 for info in self._remote_infos.itervalues():
114 if info['used']:
115 if not self._autotest_client:
116 self._autotest_client = autotest.Autotest(self._client)
117 self._autotest_client.run_test(info['client_test'])
118 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700119
Chris Sosa8ee1d592011-08-14 16:50:31 -0700120 def install_recovery_image(self, image_path=None, usb_mount_point=None):
121 """Install the recovery image specied by the path onto the DUT.
122
123 This method uses google recovery mode to install a recovery image
124 onto a DUT through the use of a USB stick that is mounted on a servo
125 board specified by the usb_mount_point. If no image path is specified
126 we use the recovery image already on the usb image.
127
128 Args:
129 image_path: Path on the host to the recovery image.
130 usb_mount_point: When servo_sees_usbkey is enabled, which dev
131 e.g. /dev/sdb will the usb key show up as.
132 """
133 # Set up Servo's usb mux.
134 self.servo.set('prtctl4_pwren', 'on')
135 self.servo.enable_usb_hub(host=True)
136 if image_path and usb_mount_point:
137 logging.info('Installing recovery image onto usb stick. '
138 'This takes a while ...')
139 utils.system(' '.join(
140 ['sudo', 'dd', 'if=%s' % image_path,
141 'of=%s' % usb_mount_point, 'bs=4M']))
142
143 # Turn the device off.
144 self.servo.power_normal_press()
145 time.sleep(servo.Servo.SLEEP_DELAY)
146
147 # Boot in recovery mode.
148 try:
149 self.servo.enable_recovery_mode()
150 self.servo.power_normal_press()
151 time.sleep(servo.Servo.BOOT_DELAY)
152
153 # Enable recovery installation.
154 self.servo.set('usb_mux_sel1', 'dut_sees_usbkey')
155 logging.info('Running the recovery process on the DUT. '
156 'Waiting %d seconds for recovery to complete ...',
157 servo.Servo.RECOVERY_INSTALL_DELAY)
158 time.sleep(servo.Servo.RECOVERY_INSTALL_DELAY)
159
160 # Go back into normal mode and reboot.
161 # Machine automatically reboots after the usb key is removed.
162 self.servo.disable_recovery_mode()
163 logging.info('Removing the usb key from the DUT.')
164 self.servo.disable_usb_hub()
165 time.sleep(servo.Servo.BOOT_DELAY)
166 except:
167 # In case anything went wrong we want to make sure to do a clean
168 # reset.
169 self.servo.disable_recovery_mode()
170 self.servo.warm_reset()
171 raise
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700172
173 def assert_ping(self):
174 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700175 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700176
177
178 def assert_pingfail(self):
179 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700180 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700181
182
183 def ping_test(self, hostname, timeout=5):
184 """Verify whether a host responds to a ping.
185
186 Args:
187 hostname: Hostname to ping.
188 timeout: Time in seconds to wait for a response.
189 """
190 return subprocess.call(['ping', '-c', '1', '-W',
191 str(timeout), hostname]) == 0
192
193
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800194 def launch_client(self, info):
195 """Launch a remote process on client and set up an xmlrpc connection.
196
197 Args:
198 info: A dict of remote info, see the definition of self._remote_infos.
199 """
200 assert info['used'], \
201 'Remote %s dependency not installed.' % info['ref_name']
202 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
203 self._launch_ssh_tunnel(info)
204 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700205 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800206
207 # Launch RPC server remotely.
208 self._kill_remote_process(info)
209 logging.info('Client command: %s' % info['remote_command'])
210 info['remote_process'] = subprocess.Popen([
Craig Harrison63e9c6a2011-08-10 17:13:57 -0700211 'ssh -o "StrictHostKeyChecking no" -n root@%s \'%s\'' %
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800212 (self._client.ip, info['remote_command'])], shell=True)
213
214 # Connect to RPC object.
215 logging.info('Connecting to client RPC server...')
216 remote_url = 'http://localhost:%s' % info['port']
217 setattr(self, info['ref_name'],
218 xmlrpclib.ServerProxy(remote_url, allow_none=True))
219 logging.info('Server proxy: %s' % remote_url)
220
Craig Harrison91944552011-08-04 14:09:55 -0700221 # Poll for client RPC server to come online.
222 timeout = 10
223 succeed = False
224 while timeout > 0 and not succeed:
225 time.sleep(2)
226 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800227 remote_object = getattr(self, info['ref_name'])
228 polling_rpc = getattr(remote_object, info['polling_rpc'])
229 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700230 succeed = True
231 except:
232 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800233 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700234
235
236 def wait_for_client(self):
237 """Wait for the client to come back online.
238
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800239 New remote processes will be launched if their used flags are enabled.
Craig Harrison91944552011-08-04 14:09:55 -0700240 """
241 timeout = 10
242 # Ensure old ssh connections are terminated.
243 self._terminate_all_ssh()
244 # Wait for the client to come up.
245 while timeout > 0 and not self.ping_test(self._client.ip):
246 time.sleep(5)
247 timeout -= 1
248 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800249 logging.info('Server: Client machine is up.')
250 # Relaunch remote clients.
251 for name, info in self._remote_infos.iteritems():
252 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800253 # This 5s delay to ensure sshd launched after network is up.
254 # TODO(waihong) Investigate pinging port via netcat or nmap
255 # to interrogate client for when sshd has launched.
256 time.sleep(5)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800257 self.launch_client(info)
258 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700259
260
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800261 def wait_for_client_offline(self, timeout=30):
262 """Wait for the client to come offline.
263
264 Args:
265 timeout: Time in seconds to wait the client to come offline.
266 """
267 # Wait for the client to come offline.
268 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
269 time.sleep(1)
270 timeout -= 1
271 assert timeout, 'Timed out waiting for client offline.'
272 logging.info('Server: Client machine is offline.')
273
274
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700275 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800276 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700277 if self.servo:
278 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800279 for info in self._remote_infos.itervalues():
280 if info['remote_process'] and info['remote_process'].poll() is None:
281 remote_object = getattr(self, info['ref_name'])
282 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700283 self._terminate_all_ssh()
284
285
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800286 def _launch_ssh_tunnel(self, info):
287 """Establish an ssh tunnel for connecting to the remote RPC server.
288
289 Args:
290 info: A dict of remote info, see the definition of self._remote_infos.
291 """
292 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
293 info['ssh_tunnel'] = subprocess.Popen(['ssh', '-N', '-n', '-L',
294 '%s:localhost:%s' % (info['port'], info['port']),
Craig Harrison91944552011-08-04 14:09:55 -0700295 'root@%s' % self._client.ip])
296
297
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800298 def _kill_remote_process(self, info):
299 """Ensure the remote process and local ssh process are terminated.
300
301 Args:
302 info: A dict of remote info, see the definition of self._remote_infos.
303 """
304 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Craig Harrison63e9c6a2011-08-10 17:13:57 -0700305 subprocess.call(['ssh -n -o "StrictHostKeyChecking no" root@%s \'%s\'' %
Craig Harrison91944552011-08-04 14:09:55 -0700306 (self._client.ip, kill_cmd)],
307 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800308 if info['remote_process'] and info['remote_process'].poll() is None:
309 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700310
311
312 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800313 """Terminate all ssh connections associated with remote processes."""
314 for info in self._remote_infos.itervalues():
315 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
316 info['ssh_tunnel'].terminate()
317 self._kill_remote_process(info)