blob: dbbafa49706039338c0ee0d75cafc215c5bbf8ac [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
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
Richard Barnettec95c3a92012-04-17 13:35:56 -070025 # Abstracts access to all Servo functions.
26 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.
Scott Zawalski9a985562012-04-03 17:47:30 -040046 'remote_command': 'python /usr/local/autotest/cros/remote_pyauto.py'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080047 ' --no-http-server',
48 # The short form of remote command, used by pkill.
Scott Zawalski9a985562012-04-03 17:47:30 -040049 'remote_command_short': 'remote_pyauto',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080050 # 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
Richard Barnettec95c3a92012-04-17 13:35:56 -070074 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
Richard Barnettec95c3a92012-04-17 13:35:56 -070077 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,
Todd Brochd50ac042012-03-19 16:58:02 -070084 'xml_config': [], 'servo_vid': None, 'servo_pid': None,
Todd Brochf24d2782011-08-19 10:55:41 -070085 '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:
Tom Wai-Hong Tam5fc2c792011-11-03 13:05:39 +080097 key = match.group(1)
98 val = match.group(2)
99 # Support multiple xml_config by appending it to a list.
100 if key == 'xml_config':
101 args[key].append(val)
102 else:
103 args[key] = val
Todd Brochf24d2782011-08-19 10:55:41 -0700104
Chris Sosa33320a82011-10-24 14:28:32 -0700105 # Initialize servotest args.
106 self._client = host;
107 self._remote_infos['pyauto']['used'] = use_pyauto
108 self._remote_infos['faft']['used'] = use_faft
109
Richard Barnettec95c3a92012-04-17 13:35:56 -0700110 self.servo = servo.Servo(
111 args['servo_host'], args['servo_port'], args['xml_config'],
112 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -0700113 # Initializes dut, may raise AssertionError if pre-defined gpio
114 # sequence to set GPIO's fail. Autotest does not handle exception
115 # throwing in initialize and will cause a test to hang.
116 try:
117 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -0700118 except (AssertionError, xmlrpclib.Fault) as e:
Richard Barnettec95c3a92012-04-17 13:35:56 -0700119 del self.servo
Chrome Bot9a1137d2011-07-19 14:35:00 -0700120 raise error.TestFail(e)
121
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800122 # Install PyAuto/FAFTClient dependency.
123 for info in self._remote_infos.itervalues():
124 if info['used']:
125 if not self._autotest_client:
126 self._autotest_client = autotest.Autotest(self._client)
127 self._autotest_client.run_test(info['client_test'])
128 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700129
130
131 def assert_ping(self):
132 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700133 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700134
135
136 def assert_pingfail(self):
137 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700138 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700139
140
141 def ping_test(self, hostname, timeout=5):
142 """Verify whether a host responds to a ping.
143
144 Args:
145 hostname: Hostname to ping.
146 timeout: Time in seconds to wait for a response.
147 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800148 with open(os.devnull, 'w') as fnull:
149 return subprocess.call(
150 ['ping', '-c', '1', '-W', str(timeout), hostname],
151 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700152
153
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800154 def launch_client(self, info):
155 """Launch a remote process on client and set up an xmlrpc connection.
156
157 Args:
158 info: A dict of remote info, see the definition of self._remote_infos.
159 """
160 assert info['used'], \
161 'Remote %s dependency not installed.' % info['ref_name']
162 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
163 self._launch_ssh_tunnel(info)
164 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700165 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800166
167 # Launch RPC server remotely.
168 self._kill_remote_process(info)
169 logging.info('Client command: %s' % info['remote_command'])
170 info['remote_process'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800171 'ssh -n -q %s root@%s \'%s\'' % (info['ssh_config'],
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800172 self._client.ip, info['remote_command'])], shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800173
174 # Connect to RPC object.
175 logging.info('Connecting to client RPC server...')
176 remote_url = 'http://localhost:%s' % info['port']
177 setattr(self, info['ref_name'],
178 xmlrpclib.ServerProxy(remote_url, allow_none=True))
179 logging.info('Server proxy: %s' % remote_url)
180
Craig Harrison91944552011-08-04 14:09:55 -0700181 # Poll for client RPC server to come online.
182 timeout = 10
183 succeed = False
184 while timeout > 0 and not succeed:
185 time.sleep(2)
186 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800187 remote_object = getattr(self, info['ref_name'])
188 polling_rpc = getattr(remote_object, info['polling_rpc'])
189 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700190 succeed = True
191 except:
192 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800193 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700194
195
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800196 def wait_for_client(self, install_deps=False):
Craig Harrison91944552011-08-04 14:09:55 -0700197 """Wait for the client to come back online.
198
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800199 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800200
201 Args:
202 install_deps: If True, install the Autotest dependency when ready.
Craig Harrison91944552011-08-04 14:09:55 -0700203 """
204 timeout = 10
205 # Ensure old ssh connections are terminated.
206 self._terminate_all_ssh()
207 # Wait for the client to come up.
208 while timeout > 0 and not self.ping_test(self._client.ip):
209 time.sleep(5)
210 timeout -= 1
211 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800212 logging.info('Server: Client machine is up.')
213 # Relaunch remote clients.
214 for name, info in self._remote_infos.iteritems():
215 if info['used']:
Tom Wai-Hong Tambf6aad72011-10-26 09:50:41 +0800216 # This 5s delay to ensure sshd launched after network is up.
217 # TODO(waihong) Investigate pinging port via netcat or nmap
218 # to interrogate client for when sshd has launched.
219 time.sleep(5)
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800220 if install_deps:
221 if not self._autotest_client:
222 self._autotest_client = autotest.Autotest(self._client)
223 self._autotest_client.run_test(info['client_test'])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800224 self.launch_client(info)
225 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700226
227
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800228 def wait_for_client_offline(self, timeout=30):
229 """Wait for the client to come offline.
230
231 Args:
232 timeout: Time in seconds to wait the client to come offline.
233 """
234 # Wait for the client to come offline.
235 while timeout > 0 and self.ping_test(self._client.ip, timeout=1):
236 time.sleep(1)
237 timeout -= 1
238 assert timeout, 'Timed out waiting for client offline.'
239 logging.info('Server: Client machine is offline.')
240
241
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700242 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800243 """Delete the Servo object, call remote cleanup, and kill ssh."""
Richard Barnettec95c3a92012-04-17 13:35:56 -0700244 if self.servo:
245 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800246 for info in self._remote_infos.itervalues():
247 if info['remote_process'] and info['remote_process'].poll() is None:
248 remote_object = getattr(self, info['ref_name'])
249 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700250 self._terminate_all_ssh()
251
252
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800253 def _launch_ssh_tunnel(self, info):
254 """Establish an ssh tunnel for connecting to the remote RPC server.
255
256 Args:
257 info: A dict of remote info, see the definition of self._remote_infos.
258 """
259 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800260 info['ssh_tunnel'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800261 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800262 (info['ssh_config'], info['port'], info['port'],
263 self._client.ip)], shell=True)
Craig Harrison91944552011-08-04 14:09:55 -0700264
265
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800266 def _kill_remote_process(self, info):
267 """Ensure the remote process and local ssh process are terminated.
268
269 Args:
270 info: A dict of remote info, see the definition of self._remote_infos.
271 """
272 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800273 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800274 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700275 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800276 if info['remote_process'] and info['remote_process'].poll() is None:
277 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700278
279
280 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800281 """Terminate all ssh connections associated with remote processes."""
282 for info in self._remote_infos.itervalues():
283 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
284 info['ssh_tunnel'].terminate()
285 self._kill_remote_process(info)