blob: 1c1fa094fc017bc1627917fc8d92ca1974228a60 [file] [log] [blame]
J. Richard Barnette67ccb872012-04-19 16:34:56 -07001# Copyright (c) 2012 The Chromium OS Authors. All rights reserved.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07002# 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
ctchang74816ac2012-08-31 11:12:43 +08008import socket
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07009import subprocess
Craig Harrison91944552011-08-04 14:09:55 -070010import time
11import xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070012
Chrome Bot9a1137d2011-07-19 14:35:00 -070013from autotest_lib.client.common_lib import error
Vic Yang3a7cf602012-11-07 17:28:39 +080014from autotest_lib.server import autotest, site_host_attributes, test
Chris Sosa8ee1d592011-08-14 16:50:31 -070015from 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
J. Richard Barnette67ccb872012-04-19 16:34:56 -070026
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,
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080043 # The remote command to be run.
Scott Zawalski9a985562012-04-03 17:47:30 -040044 'remote_command': 'python /usr/local/autotest/cros/remote_pyauto.py'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080045 ' --no-http-server',
46 # The short form of remote command, used by pkill.
Scott Zawalski9a985562012-04-03 17:47:30 -040047 'remote_command_short': 'remote_pyauto',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080048 # The remote process info.
49 'remote_process': None,
50 # The ssh tunnel process info.
51 'ssh_tunnel': None,
52 # Polling RPC function name for testing the server availability.
53 'polling_rpc': 'IsLinux',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080054 # Additional SSH options.
55 'ssh_config': '-o StrictHostKeyChecking=no ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080056 },
57 'faft': {
58 'used': False,
59 'ref_name': 'faft_client',
60 'port': 9990,
Tom Wai-Hong Tamc1e0b9a2012-11-01 13:52:39 +080061 'remote_command': '/usr/local/autotest/cros/faft_client.py',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080062 'remote_command_short': 'faft_client',
Vic Yangf8fd4542012-08-01 11:37:46 +080063 'remote_log_file': '/tmp/faft_client.log',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080064 'remote_process': None,
65 'ssh_tunnel': None,
66 'polling_rpc': 'is_available',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080067 'ssh_config': '-o StrictHostKeyChecking=no '
68 '-o UserKnownHostsFile=/dev/null ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080069 },
70 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070071
J. Richard Barnette67ccb872012-04-19 16:34:56 -070072 def _init_servo(self, host, cmdline_args):
73 """Initialize `self.servo`.
Craig Harrison91944552011-08-04 14:09:55 -070074
J. Richard Barnette67ccb872012-04-19 16:34:56 -070075 If the host has an attached servo object, use that.
76 Otherwise assume that there's a locally attached servo
77 device, and start servod on localhost.
78
Todd Brochf24d2782011-08-19 10:55:41 -070079 """
J. Richard Barnette67ccb872012-04-19 16:34:56 -070080 if host.servo:
81 self.servo = host.servo
82 self._servo_is_local = False
83 return
84
Todd Brochf24d2782011-08-19 10:55:41 -070085 # Assign default arguments for servo invocation.
86 args = {
87 'servo_host': 'localhost', 'servo_port': 9999,
Todd Brochd50ac042012-03-19 16:58:02 -070088 'xml_config': [], '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
Todd Broch0edf7e12012-06-14 09:36:15 -0700109 self.servo = servo.Servo()
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700110 self._servo_is_local = True
111
112
113 def _release_servo(self):
114 """Clean up `self.servo` if it is locally attached."""
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700115 self._servo_is_local = False
116
117
118 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
119 """Create a Servo object and install the dependency.
120
121 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
122 installed on the client and a remote PyAuto/FAFTClient server is
123 launched and connected.
124 """
Chris Sosa33320a82011-10-24 14:28:32 -0700125 # Initialize servotest args.
Vic Yang3a7cf602012-11-07 17:28:39 +0800126 self._client = host
Chris Sosa33320a82011-10-24 14:28:32 -0700127 self._remote_infos['pyauto']['used'] = use_pyauto
128 self._remote_infos['faft']['used'] = use_faft
129
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700130 self._init_servo(host, cmdline_args)
131
Chrome Bot9a1137d2011-07-19 14:35:00 -0700132 # Initializes dut, may raise AssertionError if pre-defined gpio
133 # sequence to set GPIO's fail. Autotest does not handle exception
134 # throwing in initialize and will cause a test to hang.
135 try:
136 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700137 except (AssertionError, xmlrpclib.Fault) as e:
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700138 self._release_servo()
Chrome Bot9a1137d2011-07-19 14:35:00 -0700139 raise error.TestFail(e)
140
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800141 # Install PyAuto/FAFTClient dependency.
142 for info in self._remote_infos.itervalues():
143 if info['used']:
144 if not self._autotest_client:
145 self._autotest_client = autotest.Autotest(self._client)
Tom Wai-Hong Tam00d10512012-11-09 15:37:51 +0800146 self._autotest_client.install()
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800147 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700148
149
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700150 def _ping_test(self, hostname, timeout=5):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700151 """Verify whether a host responds to a ping.
152
153 Args:
154 hostname: Hostname to ping.
155 timeout: Time in seconds to wait for a response.
156 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800157 with open(os.devnull, 'w') as fnull:
158 return subprocess.call(
159 ['ping', '-c', '1', '-W', str(timeout), hostname],
160 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700161
162
ctchang74816ac2012-08-31 11:12:43 +0800163 def _sshd_test(self, hostname, timeout=5):
164 """Verify whether sshd is running in host.
165
166 Args:
167 hostname: Hostname to verify.
168 timeout: Time in seconds to wait for a response.
169 """
170 try:
171 sock = socket.create_connection((hostname, 22), timeout=timeout)
172 sock.close()
173 return True
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +0800174 except socket.timeout:
175 return False
ctchang74816ac2012-08-31 11:12:43 +0800176 except socket.error:
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +0800177 time.sleep(timeout)
ctchang74816ac2012-08-31 11:12:43 +0800178 return False
179
180
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800181 def launch_client(self, info):
182 """Launch a remote process on client and set up an xmlrpc connection.
183
184 Args:
185 info: A dict of remote info, see the definition of self._remote_infos.
186 """
187 assert info['used'], \
188 'Remote %s dependency not installed.' % info['ref_name']
189 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
190 self._launch_ssh_tunnel(info)
191 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700192 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800193
194 # Launch RPC server remotely.
195 self._kill_remote_process(info)
Vic Yang3a7cf602012-11-07 17:28:39 +0800196 logging.info('Client command: %s', info['remote_command'])
Vic Yangf8fd4542012-08-01 11:37:46 +0800197 if 'remote_log_file' in info:
Vic Yang3a7cf602012-11-07 17:28:39 +0800198 log_file = info['remote_log_file']
Vic Yangf8fd4542012-08-01 11:37:46 +0800199 else:
Vic Yang3a7cf602012-11-07 17:28:39 +0800200 log_file = '/dev/null'
Vic Yangf8fd4542012-08-01 11:37:46 +0800201 logging.info("Logging to %s", log_file)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800202 info['remote_process'] = subprocess.Popen([
Vic Yangf8fd4542012-08-01 11:37:46 +0800203 'ssh -n -q %s root@%s \'%s &> %s\'' % (info['ssh_config'],
204 self._client.ip, info['remote_command'], log_file)],
205 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800206
207 # Connect to RPC object.
208 logging.info('Connecting to client RPC server...')
209 remote_url = 'http://localhost:%s' % info['port']
210 setattr(self, info['ref_name'],
211 xmlrpclib.ServerProxy(remote_url, allow_none=True))
Vic Yang3a7cf602012-11-07 17:28:39 +0800212 logging.info('Server proxy: %s', remote_url)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800213
Tom Wai-Hong Tam8fd2a772012-11-06 16:14:05 +0800214 # We found that the following RPC call retrial doesn't work all the
215 # time and causes timeout error happened. So add this delay to wait
216 # the client RPC server start-up as a work-around.
217 # TODO(waihong@chromium.org): Find the root cause why retrial not work.
218 time.sleep(5)
219
Craig Harrison91944552011-08-04 14:09:55 -0700220 # Poll for client RPC server to come online.
Vic Yang6c1dc152012-09-13 14:56:11 +0800221 timeout = 20
Craig Harrison91944552011-08-04 14:09:55 -0700222 succeed = False
Vic Yang3a7cf602012-11-07 17:28:39 +0800223 rpc_error = None
Craig Harrison91944552011-08-04 14:09:55 -0700224 while timeout > 0 and not succeed:
Vic Yang6c1dc152012-09-13 14:56:11 +0800225 time.sleep(1)
Craig Harrison91944552011-08-04 14:09:55 -0700226 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
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800231 except (socket.error, xmlrpclib.ProtocolError) as e:
232 # The client RPC server may not come online fast enough. Retry.
Craig Harrison91944552011-08-04 14:09:55 -0700233 timeout -= 1
Vic Yang3a7cf602012-11-07 17:28:39 +0800234 rpc_error = e
Vic Yangf8fd4542012-08-01 11:37:46 +0800235
236 if not succeed:
Vic Yang3a7cf602012-11-07 17:28:39 +0800237 if isinstance(rpc_error, xmlrpclib.ProtocolError):
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800238 logging.info("A protocol error occurred")
Vic Yang3a7cf602012-11-07 17:28:39 +0800239 logging.info("URL: %s", rpc_error.url)
240 logging.info("HTTP/HTTPS headers: %s", rpc_error.headers)
241 logging.info("Error code: %d", rpc_error.errcode)
242 logging.info("Error message: %s", rpc_error.errmsg)
Vic Yangf8fd4542012-08-01 11:37:46 +0800243 if 'remote_log_file' in info:
244 p = subprocess.Popen([
245 'ssh -n -q %s root@%s \'cat %s\'' % (info['ssh_config'],
246 self._client.ip, info['remote_log_file'])], shell=True,
247 stdout=subprocess.PIPE)
Tom Wai-Hong Tamc1e0b9a2012-11-01 13:52:39 +0800248 logging.info('Log of running remote %s:', info['ref_name'])
Vic Yangf8fd4542012-08-01 11:37:46 +0800249 logging.info(p.communicate()[0])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800250 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700251
252
Vic Yang8bbc1c32012-09-13 11:47:44 +0800253 def wait_for_client(self, install_deps=False, timeout=100):
Craig Harrison91944552011-08-04 14:09:55 -0700254 """Wait for the client to come back online.
255
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800256 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800257
258 Args:
259 install_deps: If True, install the Autotest dependency when ready.
Vic Yang8bbc1c32012-09-13 11:47:44 +0800260 timeout: Time in seconds to wait for the client SSH daemon to
261 come up.
Craig Harrison91944552011-08-04 14:09:55 -0700262 """
Craig Harrison91944552011-08-04 14:09:55 -0700263 # Ensure old ssh connections are terminated.
264 self._terminate_all_ssh()
265 # Wait for the client to come up.
Vic Yang6c1dc152012-09-13 14:56:11 +0800266 while timeout > 0 and not self._sshd_test(self._client.ip, timeout=2):
267 timeout -= 2
Vadim Bendebury69f047c2012-10-11 15:20:31 -0700268 assert (timeout > 0), 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800269 logging.info('Server: Client machine is up.')
270 # Relaunch remote clients.
271 for name, info in self._remote_infos.iteritems():
272 if info['used']:
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800273 if install_deps:
274 if not self._autotest_client:
275 self._autotest_client = autotest.Autotest(self._client)
Tom Wai-Hong Tam00d10512012-11-09 15:37:51 +0800276 self._autotest_client.install()
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800277 self.launch_client(info)
Vic Yang3a7cf602012-11-07 17:28:39 +0800278 logging.info('Server: Relaunched remote %s.', name)
Craig Harrison91944552011-08-04 14:09:55 -0700279
280
Vic Yang8bbc1c32012-09-13 11:47:44 +0800281 def wait_for_client_offline(self, timeout=60):
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800282 """Wait for the client to come offline.
283
284 Args:
285 timeout: Time in seconds to wait the client to come offline.
286 """
287 # Wait for the client to come offline.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700288 while timeout > 0 and self._ping_test(self._client.ip, timeout=1):
Vic Yangcf5d6fc2012-09-14 15:22:44 +0800289 time.sleep(1)
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800290 timeout -= 1
291 assert timeout, 'Timed out waiting for client offline.'
292 logging.info('Server: Client machine is offline.')
293
294
Vic Yang4e0d1f72012-05-24 15:11:11 +0800295 def kill_remote(self):
296 """Call remote cleanup and kill ssh."""
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800297 for info in self._remote_infos.itervalues():
298 if info['remote_process'] and info['remote_process'].poll() is None:
299 remote_object = getattr(self, info['ref_name'])
Vadim Bendebury5b82cc52012-10-09 19:05:01 -0700300 try:
301 remote_object.cleanup()
302 logging.info('Cleanup succeeded.')
303 except xmlrpclib.ProtocolError, e:
304 logging.info('Cleanup returned protocol error: ' + str(e))
Craig Harrison91944552011-08-04 14:09:55 -0700305 self._terminate_all_ssh()
306
307
Vic Yang4e0d1f72012-05-24 15:11:11 +0800308 def cleanup(self):
309 """Delete the Servo object, call remote cleanup, and kill ssh."""
310 self._release_servo()
311 self.kill_remote()
312
313
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800314 def _launch_ssh_tunnel(self, info):
315 """Establish an ssh tunnel for connecting to the remote RPC server.
316
317 Args:
318 info: A dict of remote info, see the definition of self._remote_infos.
319 """
320 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800321 info['ssh_tunnel'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800322 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800323 (info['ssh_config'], info['port'], info['port'],
324 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700325
326
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800327 def _kill_remote_process(self, info):
328 """Ensure the remote process and local ssh process are terminated.
329
330 Args:
331 info: A dict of remote info, see the definition of self._remote_infos.
332 """
333 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800334 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800335 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700336 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800337 if info['remote_process'] and info['remote_process'].poll() is None:
338 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700339
340
341 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800342 """Terminate all ssh connections associated with remote processes."""
343 for info in self._remote_infos.itervalues():
344 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
345 info['ssh_tunnel'].terminate()
346 self._kill_remote_process(info)