blob: acbe465b3ded0b996e90657568e993bd68efa2ab [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
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07008import subprocess
Craig Harrison91944552011-08-04 14:09:55 -07009import time
10import xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070011
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
J. Richard Barnette67ccb872012-04-19 16:34:56 -070025
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.
J. Richard Barnette2f83d712012-04-23 14:31:45 -070043 'client_test': 'desktopui_PyAutoInstall',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080044 # The remote command to be run.
Scott Zawalski9a985562012-04-03 17:47:30 -040045 'remote_command': 'python /usr/local/autotest/cros/remote_pyauto.py'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080046 ' --no-http-server',
47 # The short form of remote command, used by pkill.
Scott Zawalski9a985562012-04-03 17:47:30 -040048 'remote_command_short': 'remote_pyauto',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080049 # 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',
Vic Yangf8fd4542012-08-01 11:37:46 +080065 'remote_log_file': '/tmp/faft_client.log',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080066 '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
J. Richard Barnette67ccb872012-04-19 16:34:56 -070074 def _init_servo(self, host, cmdline_args):
75 """Initialize `self.servo`.
Craig Harrison91944552011-08-04 14:09:55 -070076
J. Richard Barnette67ccb872012-04-19 16:34:56 -070077 If the host has an attached servo object, use that.
78 Otherwise assume that there's a locally attached servo
79 device, and start servod on localhost.
80
Todd Brochf24d2782011-08-19 10:55:41 -070081 """
J. Richard Barnette67ccb872012-04-19 16:34:56 -070082 if host.servo:
83 self.servo = host.servo
84 self._servo_is_local = False
85 return
86
Todd Brochf24d2782011-08-19 10:55:41 -070087 # Assign default arguments for servo invocation.
88 args = {
89 'servo_host': 'localhost', 'servo_port': 9999,
Todd Brochd50ac042012-03-19 16:58:02 -070090 'xml_config': [], 'servo_vid': None, 'servo_pid': None,
Todd Brochf24d2782011-08-19 10:55:41 -070091 'servo_serial': None, 'use_pyauto': False}
92
93 # Parse arguments from AFE and override servo defaults above.
94 client_attributes = site_host_attributes.HostAttributes(host.hostname)
95 if hasattr(site_host_attributes, 'servo_serial'):
96 args['servo_serial'] = client_attributes.servo_serial
97
98 # Parse arguments from command line and override previous AFE or servo
99 # defaults
100 for arg in cmdline_args:
101 match = re.search("^(\w+)=(.+)", arg)
102 if match:
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +0800103 key = match.group(1)
104 val = match.group(2)
105 # Support multiple xml_config by appending it to a list.
106 if key == 'xml_config':
107 args[key].append(val)
108 else:
109 args[key] = val
Todd Brochf24d2782011-08-19 10:55:41 -0700110
Todd Broch0edf7e12012-06-14 09:36:15 -0700111 self.servo = servo.Servo()
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700112 self._servo_is_local = True
113
114
115 def _release_servo(self):
116 """Clean up `self.servo` if it is locally attached."""
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700117 self._servo_is_local = False
118
119
120 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
121 """Create a Servo object and install the dependency.
122
123 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
124 installed on the client and a remote PyAuto/FAFTClient server is
125 launched and connected.
126 """
Chris Sosa33320a82011-10-24 14:28:32 -0700127 # Initialize servotest args.
128 self._client = host;
129 self._remote_infos['pyauto']['used'] = use_pyauto
130 self._remote_infos['faft']['used'] = use_faft
131
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700132 self._init_servo(host, cmdline_args)
133
Chrome Bot9a1137d2011-07-19 14:35:00 -0700134 # Initializes dut, may raise AssertionError if pre-defined gpio
135 # sequence to set GPIO's fail. Autotest does not handle exception
136 # throwing in initialize and will cause a test to hang.
137 try:
138 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700139 except (AssertionError, xmlrpclib.Fault) as e:
J. Richard Barnette67ccb872012-04-19 16:34:56 -0700140 self._release_servo()
Chrome Bot9a1137d2011-07-19 14:35:00 -0700141 raise error.TestFail(e)
142
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800143 # Install PyAuto/FAFTClient dependency.
144 for info in self._remote_infos.itervalues():
145 if info['used']:
146 if not self._autotest_client:
147 self._autotest_client = autotest.Autotest(self._client)
148 self._autotest_client.run_test(info['client_test'])
149 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700150
151
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700152 def _ping_test(self, hostname, timeout=5):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700153 """Verify whether a host responds to a ping.
154
155 Args:
156 hostname: Hostname to ping.
157 timeout: Time in seconds to wait for a response.
158 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800159 with open(os.devnull, 'w') as fnull:
160 return subprocess.call(
161 ['ping', '-c', '1', '-W', str(timeout), hostname],
162 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700163
164
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800165 def launch_client(self, info):
166 """Launch a remote process on client and set up an xmlrpc connection.
167
168 Args:
169 info: A dict of remote info, see the definition of self._remote_infos.
170 """
171 assert info['used'], \
172 'Remote %s dependency not installed.' % info['ref_name']
173 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
174 self._launch_ssh_tunnel(info)
175 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700176 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800177
178 # Launch RPC server remotely.
179 self._kill_remote_process(info)
180 logging.info('Client command: %s' % info['remote_command'])
Vic Yangf8fd4542012-08-01 11:37:46 +0800181 if 'remote_log_file' in info:
182 log_file = info['remote_log_file']
183 else:
184 log_file = '/dev/null'
185 logging.info("Logging to %s", log_file)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800186 info['remote_process'] = subprocess.Popen([
Vic Yangf8fd4542012-08-01 11:37:46 +0800187 'ssh -n -q %s root@%s \'%s &> %s\'' % (info['ssh_config'],
188 self._client.ip, info['remote_command'], log_file)],
189 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800190
191 # Connect to RPC object.
192 logging.info('Connecting to client RPC server...')
193 remote_url = 'http://localhost:%s' % info['port']
194 setattr(self, info['ref_name'],
195 xmlrpclib.ServerProxy(remote_url, allow_none=True))
196 logging.info('Server proxy: %s' % remote_url)
197
Craig Harrison91944552011-08-04 14:09:55 -0700198 # Poll for client RPC server to come online.
199 timeout = 10
200 succeed = False
201 while timeout > 0 and not succeed:
202 time.sleep(2)
203 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800204 remote_object = getattr(self, info['ref_name'])
205 polling_rpc = getattr(remote_object, info['polling_rpc'])
206 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700207 succeed = True
208 except:
209 timeout -= 1
Vic Yangf8fd4542012-08-01 11:37:46 +0800210
211 if not succeed:
212 if 'remote_log_file' in info:
213 p = subprocess.Popen([
214 'ssh -n -q %s root@%s \'cat %s\'' % (info['ssh_config'],
215 self._client.ip, info['remote_log_file'])], shell=True,
216 stdout=subprocess.PIPE)
217 logging.info(p.communicate()[0])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800218 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700219
220
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800221 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700222 """Wait for the client to come back online.
223
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800224 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800225
226 Args:
227 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700228 """
229 timeout = 10
230 # Ensure old ssh connections are terminated.
231 self._terminate_all_ssh()
232 # Wait for the client to come up.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700233 while timeout > 0 and not self._ping_test(self._client.ip):
Craig Harrison91944552011-08-04 14:09:55 -0700234 time.sleep(5)
235 timeout -= 1
236 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800237 logging.info('Server: Client machine is up.')
238 # Relaunch remote clients.
239 for name, info in self._remote_infos.iteritems():
240 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800241 # This 5s delay to ensure sshd launched after network is up.
242 # TODO(waihong) Investigate pinging port via netcat or nmap
243 # to interrogate client for when sshd has launched.
244 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800245 if install_deps:
246 if not self._autotest_client:
247 self._autotest_client = autotest.Autotest(self._client)
248 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800249 self.launch_client(info)
250 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700251
252
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800253 def wait_for_client_offline(self, timeout=30):
254 """Wait for the client to come offline.
255
256 Args:
257 timeout: Time in seconds to wait the client to come offline.
258 """
259 # Wait for the client to come offline.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700260 while timeout > 0 and self._ping_test(self._client.ip, timeout=1):
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800261 time.sleep(1)
262 timeout -= 1
263 assert timeout, 'Timed out waiting for client offline.'
264 logging.info('Server: Client machine is offline.')
265
266
Vic Yang4e0d1f72012-05-24 15:11:11 +0800267 def kill_remote(self):
268 """Call remote cleanup and kill ssh."""
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800269 for info in self._remote_infos.itervalues():
270 if info['remote_process'] and info['remote_process'].poll() is None:
271 remote_object = getattr(self, info['ref_name'])
272 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700273 self._terminate_all_ssh()
274
275
Vic Yang4e0d1f72012-05-24 15:11:11 +0800276 def cleanup(self):
277 """Delete the Servo object, call remote cleanup, and kill ssh."""
278 self._release_servo()
279 self.kill_remote()
280
281
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800282 def _launch_ssh_tunnel(self, info):
283 """Establish an ssh tunnel for connecting to the remote RPC server.
284
285 Args:
286 info: A dict of remote info, see the definition of self._remote_infos.
287 """
288 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800289 info['ssh_tunnel'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800290 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800291 (info['ssh_config'], info['port'], info['port'],
292 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700293
294
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800295 def _kill_remote_process(self, info):
296 """Ensure the remote process and local ssh process are terminated.
297
298 Args:
299 info: A dict of remote info, see the definition of self._remote_infos.
300 """
301 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800302 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800303 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700304 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800305 if info['remote_process'] and info['remote_process'].poll() is None:
306 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700307
308
309 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800310 """Terminate all ssh connections associated with remote processes."""
311 for info in self._remote_infos.itervalues():
312 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
313 info['ssh_tunnel'].terminate()
314 self._kill_remote_process(info)