blob: 9fcbff58659b4e75bfd58afa4dd07ac598eb0989 [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
Vadim Bendebury692341e2012-12-18 15:24:38 -08005import httplib, logging, os, socket, subprocess, sys, time, xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07006
Chrome Bot9a1137d2011-07-19 14:35:00 -07007from autotest_lib.client.common_lib import error
Vadim Bendeburybb731952012-12-05 17:30:36 -08008from autotest_lib.server import autotest, test
Chris Sosa8ee1d592011-08-14 16:50:31 -07009from autotest_lib.server.cros import servo
Craig Harrison91944552011-08-04 14:09:55 -070010
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070011class ServoTest(test.test):
12 """AutoTest test class that creates and destroys a servo object.
13
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080014 Servo-based server side AutoTests can inherit from this object.
15 There are 2 remote clients supported:
16 If use_pyauto flag is True, a remote PyAuto client will be launched;
17 If use_faft flag is Ture, a remote FAFT client will be launched.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070018 """
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080019 version = 2
J. Richard Barnette67ccb872012-04-19 16:34:56 -070020
Craig Harrison91944552011-08-04 14:09:55 -070021 # Exposes RPC access to a remote PyAuto client.
22 pyauto = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080023 # Exposes RPC access to a remote FAFT client.
24 faft_client = None
25
Craig Harrison91944552011-08-04 14:09:55 -070026 # Autotest references to the client.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080027 _autotest_client = None
28 # Remote client info list.
29 _remote_infos = {
30 'pyauto': {
31 # Used or not.
32 'used': False,
33 # Reference name of RPC object in this class.
34 'ref_name': 'pyauto',
35 # Port number of the remote RPC.
36 'port': 9988,
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080037 # The remote command to be run.
Scott Zawalski9a985562012-04-03 17:47:30 -040038 'remote_command': 'python /usr/local/autotest/cros/remote_pyauto.py'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080039 ' --no-http-server',
40 # The short form of remote command, used by pkill.
Scott Zawalski9a985562012-04-03 17:47:30 -040041 'remote_command_short': 'remote_pyauto',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080042 # The remote process info.
43 'remote_process': None,
44 # The ssh tunnel process info.
45 'ssh_tunnel': None,
46 # Polling RPC function name for testing the server availability.
47 'polling_rpc': 'IsLinux',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080048 # Additional SSH options.
49 'ssh_config': '-o StrictHostKeyChecking=no ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080050 },
51 'faft': {
52 'used': False,
53 'ref_name': 'faft_client',
54 'port': 9990,
Tom Wai-Hong Tamc1e0b9a2012-11-01 13:52:39 +080055 'remote_command': '/usr/local/autotest/cros/faft_client.py',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080056 'remote_command_short': 'faft_client',
Vic Yangf8fd4542012-08-01 11:37:46 +080057 'remote_log_file': '/tmp/faft_client.log',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080058 'remote_process': None,
59 'ssh_tunnel': None,
Chun-ting Changd43aa9b2012-11-16 10:12:05 +080060 'polling_rpc': 'system.is_available',
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +080061 'ssh_config': '-o StrictHostKeyChecking=no '
62 '-o UserKnownHostsFile=/dev/null ',
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080063 },
64 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070065
Vadim Bendeburybb731952012-12-05 17:30:36 -080066 def _init_servo(self, host):
J. Richard Barnette67ccb872012-04-19 16:34:56 -070067 """Initialize `self.servo`.
Craig Harrison91944552011-08-04 14:09:55 -070068
J. Richard Barnette67ccb872012-04-19 16:34:56 -070069 If the host has an attached servo object, use that.
70 Otherwise assume that there's a locally attached servo
Vadim Bendeburybb731952012-12-05 17:30:36 -080071 device, and use it.
J. Richard Barnette67ccb872012-04-19 16:34:56 -070072
Todd Brochf24d2782011-08-19 10:55:41 -070073 """
J. Richard Barnette67ccb872012-04-19 16:34:56 -070074 if host.servo:
75 self.servo = host.servo
Vadim Bendeburybb731952012-12-05 17:30:36 -080076 else:
77 self.servo = servo.Servo()
J. Richard Barnette67ccb872012-04-19 16:34:56 -070078
79
Vadim Bendeburybb731952012-12-05 17:30:36 -080080 def initialize(self, host, _, use_pyauto=False, use_faft=False):
J. Richard Barnette67ccb872012-04-19 16:34:56 -070081 """Create a Servo object and install the dependency.
82
83 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
84 installed on the client and a remote PyAuto/FAFTClient server is
85 launched and connected.
86 """
Chris Sosa33320a82011-10-24 14:28:32 -070087 # Initialize servotest args.
Vic Yang3a7cf602012-11-07 17:28:39 +080088 self._client = host
Chris Sosa33320a82011-10-24 14:28:32 -070089 self._remote_infos['pyauto']['used'] = use_pyauto
90 self._remote_infos['faft']['used'] = use_faft
91
Vadim Bendeburybb731952012-12-05 17:30:36 -080092 self._init_servo(host)
J. Richard Barnette67ccb872012-04-19 16:34:56 -070093
Chrome Bot9a1137d2011-07-19 14:35:00 -070094 # Initializes dut, may raise AssertionError if pre-defined gpio
95 # sequence to set GPIO's fail. Autotest does not handle exception
96 # throwing in initialize and will cause a test to hang.
97 try:
98 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -070099 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -0700100 raise error.TestFail(e)
101
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800102 # Install PyAuto/FAFTClient dependency.
103 for info in self._remote_infos.itervalues():
104 if info['used']:
105 if not self._autotest_client:
106 self._autotest_client = autotest.Autotest(self._client)
Tom Wai-Hong Tam00d10512012-11-09 15:37:51 +0800107 self._autotest_client.install()
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800108 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700109
110
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700111 def _ping_test(self, hostname, timeout=5):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700112 """Verify whether a host responds to a ping.
113
114 Args:
115 hostname: Hostname to ping.
116 timeout: Time in seconds to wait for a response.
117 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800118 with open(os.devnull, 'w') as fnull:
119 return subprocess.call(
120 ['ping', '-c', '1', '-W', str(timeout), hostname],
121 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700122
123
ctchang74816ac2012-08-31 11:12:43 +0800124 def _sshd_test(self, hostname, timeout=5):
125 """Verify whether sshd is running in host.
126
127 Args:
128 hostname: Hostname to verify.
129 timeout: Time in seconds to wait for a response.
130 """
131 try:
132 sock = socket.create_connection((hostname, 22), timeout=timeout)
133 sock.close()
134 return True
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +0800135 except socket.timeout:
136 return False
ctchang74816ac2012-08-31 11:12:43 +0800137 except socket.error:
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +0800138 time.sleep(timeout)
ctchang74816ac2012-08-31 11:12:43 +0800139 return False
140
141
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800142 def launch_client(self, info):
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +0800143 """Launch a remote XML RPC connection on client with retrials.
144
145 Args:
146 info: A dict of remote info, see the definition of self._remote_infos.
147 """
148 retry = 3
Tom Wai-Hong Tamb3db3b22012-12-04 11:59:54 +0800149 while retry:
150 try:
151 self._launch_client_once(info)
152 break
153 except AssertionError:
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +0800154 retry -= 1
Tom Wai-Hong Tamb3db3b22012-12-04 11:59:54 +0800155 if retry:
156 logging.info('Retry again...')
157 time.sleep(5)
158 else:
159 raise
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +0800160
161
162 def _launch_client_once(self, info):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800163 """Launch a remote process on client and set up an xmlrpc connection.
164
165 Args:
166 info: A dict of remote info, see the definition of self._remote_infos.
167 """
168 assert info['used'], \
169 'Remote %s dependency not installed.' % info['ref_name']
Vadim Bendebury692341e2012-12-18 15:24:38 -0800170
171 if info['ssh_tunnel']:
172 info['ssh_tunnel'].terminate()
173 info['ssh_tunnel'] = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800174
175 # Launch RPC server remotely.
176 self._kill_remote_process(info)
Vadim Bendebury692341e2012-12-18 15:24:38 -0800177 self._launch_ssh_tunnel(info)
178
Vic Yang3a7cf602012-11-07 17:28:39 +0800179 logging.info('Client command: %s', info['remote_command'])
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800180 log_file = info.get('remote_log_file', '/dev/null')
Vic Yangf8fd4542012-08-01 11:37:46 +0800181 logging.info("Logging to %s", log_file)
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800182 full_cmd = ['ssh -n -q %s root@%s \'%s &> %s\'' % (
183 info['ssh_config'],
184 self._client.ip, info['remote_command'], log_file)]
185 logging.info('Starting process %s', ' '.join(full_cmd))
186 info['remote_process'] = subprocess.Popen(full_cmd, shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800187
188 # Connect to RPC object.
189 logging.info('Connecting to client RPC server...')
190 remote_url = 'http://localhost:%s' % info['port']
191 setattr(self, info['ref_name'],
192 xmlrpclib.ServerProxy(remote_url, allow_none=True))
Vic Yang3a7cf602012-11-07 17:28:39 +0800193 logging.info('Server proxy: %s', remote_url)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800194
Craig Harrison91944552011-08-04 14:09:55 -0700195 # Poll for client RPC server to come online.
Vic Yang6c1dc152012-09-13 14:56:11 +0800196 timeout = 20
Craig Harrison91944552011-08-04 14:09:55 -0700197 succeed = False
Vic Yang3a7cf602012-11-07 17:28:39 +0800198 rpc_error = None
Craig Harrison91944552011-08-04 14:09:55 -0700199 while timeout > 0 and not succeed:
Vic Yang6c1dc152012-09-13 14:56:11 +0800200 time.sleep(1)
Craig Harrison91944552011-08-04 14:09:55 -0700201 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800202 remote_object = getattr(self, info['ref_name'])
203 polling_rpc = getattr(remote_object, info['polling_rpc'])
204 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700205 succeed = True
Vadim Bendebury692341e2012-12-18 15:24:38 -0800206 except (socket.error,
207 xmlrpclib.ProtocolError,
208 httplib.BadStatusLine) as e:
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800209 logging.info('caught exception %s', e)
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800210 # The client RPC server may not come online fast enough. Retry.
Craig Harrison91944552011-08-04 14:09:55 -0700211 timeout -= 1
Vic Yang3a7cf602012-11-07 17:28:39 +0800212 rpc_error = e
Vadim Bendebury692341e2012-12-18 15:24:38 -0800213 except:
214 logging.error('Unexpected error: %s', sys.exc_info()[0])
215 raise
Vic Yangf8fd4542012-08-01 11:37:46 +0800216
217 if not succeed:
Vic Yang3a7cf602012-11-07 17:28:39 +0800218 if isinstance(rpc_error, xmlrpclib.ProtocolError):
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800219 logging.info("A protocol error occurred")
Vic Yang3a7cf602012-11-07 17:28:39 +0800220 logging.info("URL: %s", rpc_error.url)
221 logging.info("HTTP/HTTPS headers: %s", rpc_error.headers)
222 logging.info("Error code: %d", rpc_error.errcode)
223 logging.info("Error message: %s", rpc_error.errmsg)
Vic Yangf8fd4542012-08-01 11:37:46 +0800224 if 'remote_log_file' in info:
225 p = subprocess.Popen([
226 'ssh -n -q %s root@%s \'cat %s\'' % (info['ssh_config'],
227 self._client.ip, info['remote_log_file'])], shell=True,
228 stdout=subprocess.PIPE)
Tom Wai-Hong Tamc1e0b9a2012-11-01 13:52:39 +0800229 logging.info('Log of running remote %s:', info['ref_name'])
Vic Yangf8fd4542012-08-01 11:37:46 +0800230 logging.info(p.communicate()[0])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800231 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700232
233
Vic Yang8bbc1c32012-09-13 11:47:44 +0800234 def wait_for_client(self, install_deps=False, timeout=100):
Craig Harrison91944552011-08-04 14:09:55 -0700235 """Wait for the client to come back online.
236
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800237 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800238
239 Args:
240 install_deps: If True, install the Autotest dependency when ready.
Vic Yang8bbc1c32012-09-13 11:47:44 +0800241 timeout: Time in seconds to wait for the client SSH daemon to
242 come up.
Craig Harrison91944552011-08-04 14:09:55 -0700243 """
Craig Harrison91944552011-08-04 14:09:55 -0700244 # Ensure old ssh connections are terminated.
245 self._terminate_all_ssh()
246 # Wait for the client to come up.
Vic Yang6c1dc152012-09-13 14:56:11 +0800247 while timeout > 0 and not self._sshd_test(self._client.ip, timeout=2):
248 timeout -= 2
Vadim Bendebury69f047c2012-10-11 15:20:31 -0700249 assert (timeout > 0), 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800250 logging.info('Server: Client machine is up.')
251 # Relaunch remote clients.
252 for name, info in self._remote_infos.iteritems():
253 if info['used']:
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800254 if install_deps:
255 if not self._autotest_client:
256 self._autotest_client = autotest.Autotest(self._client)
Tom Wai-Hong Tam00d10512012-11-09 15:37:51 +0800257 self._autotest_client.install()
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800258 self.launch_client(info)
Vic Yang3a7cf602012-11-07 17:28:39 +0800259 logging.info('Server: Relaunched remote %s.', name)
Craig Harrison91944552011-08-04 14:09:55 -0700260
261
Vic Yang8bbc1c32012-09-13 11:47:44 +0800262 def wait_for_client_offline(self, timeout=60):
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800263 """Wait for the client to come offline.
264
265 Args:
266 timeout: Time in seconds to wait the client to come offline.
267 """
268 # Wait for the client to come offline.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700269 while timeout > 0 and self._ping_test(self._client.ip, timeout=1):
Vic Yangcf5d6fc2012-09-14 15:22:44 +0800270 time.sleep(1)
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800271 timeout -= 1
272 assert timeout, 'Timed out waiting for client offline.'
273 logging.info('Server: Client machine is offline.')
274
275
Vic Yang4e0d1f72012-05-24 15:11:11 +0800276 def kill_remote(self):
277 """Call remote cleanup and kill ssh."""
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800278 for info in self._remote_infos.itervalues():
279 if info['remote_process'] and info['remote_process'].poll() is None:
280 remote_object = getattr(self, info['ref_name'])
Vadim Bendebury5b82cc52012-10-09 19:05:01 -0700281 try:
282 remote_object.cleanup()
283 logging.info('Cleanup succeeded.')
284 except xmlrpclib.ProtocolError, e:
285 logging.info('Cleanup returned protocol error: ' + str(e))
Craig Harrison91944552011-08-04 14:09:55 -0700286 self._terminate_all_ssh()
287
288
Vic Yang4e0d1f72012-05-24 15:11:11 +0800289 def cleanup(self):
290 """Delete the Servo object, call remote cleanup, and kill ssh."""
Vic Yang4e0d1f72012-05-24 15:11:11 +0800291 self.kill_remote()
292
293
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800294 def _launch_ssh_tunnel(self, info):
295 """Establish an ssh tunnel for connecting to the remote RPC server.
296
297 Args:
298 info: A dict of remote info, see the definition of self._remote_infos.
299 """
300 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800301 info['ssh_tunnel'] = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800302 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800303 (info['ssh_config'], info['port'], info['port'],
304 self._client.ip)], shell=True)
Vadim Bendebury692341e2012-12-18 15:24:38 -0800305 assert info['ssh_tunnel'].poll() is None, \
306 'The SSH tunnel on port %d is not up.' % info['port']
Craig Harrison91944552011-08-04 14:09:55 -0700307
308
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800309 def _kill_remote_process(self, info):
310 """Ensure the remote process and local ssh process are terminated.
311
312 Args:
313 info: A dict of remote info, see the definition of self._remote_infos.
314 """
315 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800316 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800317 (info['ssh_config'], self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700318 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800319 if info['remote_process'] and info['remote_process'].poll() is None:
320 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700321
322
323 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800324 """Terminate all ssh connections associated with remote processes."""
325 for info in self._remote_infos.itervalues():
326 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
327 info['ssh_tunnel'].terminate()
328 self._kill_remote_process(info)
Vadim Bendebury692341e2012-12-18 15:24:38 -0800329 info['ssh_tunnel'] = None