blob: f8c9e828ade5da77d4a15891fe6798e0deb870cc [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
Vic Yang69249552013-05-09 01:58:11 +08007from autotest_lib.client.common_lib import error, utils
Vadim Bendeburybb731952012-12-05 17:30:36 -08008from autotest_lib.server import autotest, test
J. Richard Barnette33aec9f2013-02-01 16:38:41 -08009
Craig Harrison91944552011-08-04 14:09:55 -070010
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070011class ServoTest(test.test):
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080012 """AutoTest test class to serve as a parent class for FAFT tests.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070013
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080014 TODO(jrbarnette): This class is a legacy, reflecting
15 refactoring that has begun but not completed. The long term
16 plan is to move all function here into FAFT specific classes.
17 http://crosbug.com/33305.
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
Vic Yang8535e772013-05-02 09:07:25 +080021 _REMOTE_PORT = 9990
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080022 _REMOTE_COMMAND = '/usr/local/autotest/cros/faft_client.py'
23 _REMOTE_COMMAND_SHORT = 'faft_client'
24 _REMOTE_LOG_FILE = '/tmp/faft_client.log'
25 _SSH_CONFIG = ('-o StrictHostKeyChecking=no '
26 '-o UserKnownHostsFile=/dev/null ')
J. Richard Barnette67ccb872012-04-19 16:34:56 -070027
Vadim Bendeburybb731952012-12-05 17:30:36 -080028 def initialize(self, host, _, use_pyauto=False, use_faft=False):
J. Richard Barnette67ccb872012-04-19 16:34:56 -070029 """Create a Servo object and install the dependency.
J. Richard Barnette67ccb872012-04-19 16:34:56 -070030 """
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080031 # TODO(jrbarnette): Part of the incomplete refactoring:
32 # assert here that there are no legacy callers passing
33 # parameters for functionality that's been deprecated and
34 # removed.
35 assert use_faft and not use_pyauto
Chris Sosa33320a82011-10-24 14:28:32 -070036
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080037 self.servo = host.servo
38 self.faft_client = None
39 self._client = host
40 self._ssh_tunnel = None
41 self._remote_process = None
Vic Yang8535e772013-05-02 09:07:25 +080042 self._local_port = None
J. Richard Barnette67ccb872012-04-19 16:34:56 -070043
Chrome Bot9a1137d2011-07-19 14:35:00 -070044 # Initializes dut, may raise AssertionError if pre-defined gpio
45 # sequence to set GPIO's fail. Autotest does not handle exception
46 # throwing in initialize and will cause a test to hang.
47 try:
48 self.servo.initialize_dut()
Chris Sosa33320a82011-10-24 14:28:32 -070049 except (AssertionError, xmlrpclib.Fault) as e:
Chrome Bot9a1137d2011-07-19 14:35:00 -070050 raise error.TestFail(e)
51
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080052 # Install faft_client dependency.
53 self._autotest_client = autotest.Autotest(self._client)
54 self._autotest_client.install()
55 self._launch_client()
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070056
J. Richard Barnette134ec2c2012-04-25 12:59:37 -070057 def _ping_test(self, hostname, timeout=5):
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070058 """Verify whether a host responds to a ping.
59
60 Args:
61 hostname: Hostname to ping.
62 timeout: Time in seconds to wait for a response.
63 """
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +080064 with open(os.devnull, 'w') as fnull:
65 return subprocess.call(
66 ['ping', '-c', '1', '-W', str(timeout), hostname],
67 stdout=fnull, stderr=fnull) == 0
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070068
ctchang74816ac2012-08-31 11:12:43 +080069 def _sshd_test(self, hostname, timeout=5):
70 """Verify whether sshd is running in host.
71
72 Args:
73 hostname: Hostname to verify.
74 timeout: Time in seconds to wait for a response.
75 """
76 try:
77 sock = socket.create_connection((hostname, 22), timeout=timeout)
78 sock.close()
79 return True
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +080080 except socket.timeout:
81 return False
ctchang74816ac2012-08-31 11:12:43 +080082 except socket.error:
Tom Wai-Hong Tam711a7aa2012-09-18 10:30:43 +080083 time.sleep(timeout)
ctchang74816ac2012-08-31 11:12:43 +080084 return False
85
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080086 def _launch_client(self):
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +080087 """Launch a remote XML RPC connection on client with retrials.
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +080088 """
89 retry = 3
Tom Wai-Hong Tamb3db3b22012-12-04 11:59:54 +080090 while retry:
91 try:
J. Richard Barnette33aec9f2013-02-01 16:38:41 -080092 self._launch_client_once()
Tom Wai-Hong Tamb3db3b22012-12-04 11:59:54 +080093 break
94 except AssertionError:
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +080095 retry -= 1
Tom Wai-Hong Tamb3db3b22012-12-04 11:59:54 +080096 if retry:
97 logging.info('Retry again...')
98 time.sleep(5)
99 else:
100 raise
Tom Wai-Hong Tame97cb4a2012-11-22 09:52:46 +0800101
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800102 def _launch_client_once(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800103 """Launch a remote process on client and set up an xmlrpc connection.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800104 """
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800105 if self._ssh_tunnel:
106 self._ssh_tunnel.terminate()
107 self._ssh_tunnel = None
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800108
109 # Launch RPC server remotely.
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800110 self._kill_remote_process()
111 self._launch_ssh_tunnel()
Vadim Bendebury692341e2012-12-18 15:24:38 -0800112
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800113 logging.info('Client command: %s', self._REMOTE_COMMAND)
114 logging.info("Logging to %s", self._REMOTE_LOG_FILE)
Vic Yanga3492372013-05-16 16:11:19 +0800115 full_cmd = ['ssh -n %s root@%s \'%s &> %s\'' % (
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800116 self._SSH_CONFIG, self._client.ip,
117 self._REMOTE_COMMAND, self._REMOTE_LOG_FILE)]
Vadim Bendebury89ec24e2012-12-17 12:54:18 -0800118 logging.info('Starting process %s', ' '.join(full_cmd))
Vic Yanga3492372013-05-16 16:11:19 +0800119 self._remote_process = subprocess.Popen(full_cmd, shell=True,
120 stdout=subprocess.PIPE,
121 stderr=subprocess.PIPE)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800122
123 # Connect to RPC object.
124 logging.info('Connecting to client RPC server...')
Vic Yang8535e772013-05-02 09:07:25 +0800125 remote_url = 'http://localhost:%s' % self._local_port
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800126 self.faft_client = xmlrpclib.ServerProxy(remote_url, allow_none=True)
Vic Yang3a7cf602012-11-07 17:28:39 +0800127 logging.info('Server proxy: %s', remote_url)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800128
Craig Harrison91944552011-08-04 14:09:55 -0700129 # Poll for client RPC server to come online.
Vic Yang6c1dc152012-09-13 14:56:11 +0800130 timeout = 20
Craig Harrison91944552011-08-04 14:09:55 -0700131 succeed = False
Vic Yang3a7cf602012-11-07 17:28:39 +0800132 rpc_error = None
Craig Harrison91944552011-08-04 14:09:55 -0700133 while timeout > 0 and not succeed:
Vic Yang6c1dc152012-09-13 14:56:11 +0800134 time.sleep(1)
Vic Yanga3492372013-05-16 16:11:19 +0800135 if self._remote_process.poll() is not None:
136 # The SSH process is gone. Log stderr.
137 logging.error('Remote process died!')
138 sout, serr = self._remote_process.communicate()
139 logging.error('Stdout: %s', sout)
140 logging.error('Stderr: %s', serr)
141 break
142
Craig Harrison91944552011-08-04 14:09:55 -0700143 try:
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800144 self.faft_client.system.is_available()
Craig Harrison91944552011-08-04 14:09:55 -0700145 succeed = True
Vadim Bendebury692341e2012-12-18 15:24:38 -0800146 except (socket.error,
147 xmlrpclib.ProtocolError,
148 httplib.BadStatusLine) as e:
Yusuf Mohsinallyf3cb7642013-04-15 14:20:56 -0700149 logging.info('caught: %s %s, tries left: %s',
150 repr(e), str(e), timeout)
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800151 # The client RPC server may not come online fast enough. Retry.
Craig Harrison91944552011-08-04 14:09:55 -0700152 timeout -= 1
Vic Yang3a7cf602012-11-07 17:28:39 +0800153 rpc_error = e
Vadim Bendebury692341e2012-12-18 15:24:38 -0800154 except:
155 logging.error('Unexpected error: %s', sys.exc_info()[0])
156 raise
Vic Yangf8fd4542012-08-01 11:37:46 +0800157
158 if not succeed:
Vic Yang3a7cf602012-11-07 17:28:39 +0800159 if isinstance(rpc_error, xmlrpclib.ProtocolError):
Tom Wai-Hong Tamac35f082012-11-06 15:00:35 +0800160 logging.info("A protocol error occurred")
Vic Yang3a7cf602012-11-07 17:28:39 +0800161 logging.info("URL: %s", rpc_error.url)
162 logging.info("HTTP/HTTPS headers: %s", rpc_error.headers)
163 logging.info("Error code: %d", rpc_error.errcode)
164 logging.info("Error message: %s", rpc_error.errmsg)
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800165 p = subprocess.Popen([
166 'ssh -n -q %s root@%s \'cat %s\'' % (self._SSH_CONFIG,
167 self._client.ip, self._REMOTE_LOG_FILE)], shell=True,
168 stdout=subprocess.PIPE)
169 logging.info('Log of running remote %s:',
170 self._REMOTE_COMMAND_SHORT)
171 logging.info(p.communicate()[0])
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800172 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700173
Vic Yang8bbc1c32012-09-13 11:47:44 +0800174 def wait_for_client(self, install_deps=False, timeout=100):
Craig Harrison91944552011-08-04 14:09:55 -0700175 """Wait for the client to come back online.
176
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800177 New remote processes will be launched if their used flags are enabled.
Tom Wai-Hong Tam7c17ff22011-10-26 09:44:09 +0800178
Yusuf Mohsinallyf3cb7642013-04-15 14:20:56 -0700179 @param install_deps: If True, install Autotest dependency when ready.
180 @param timeout: Time in seconds to wait for the client SSH daemon to
181 come up.
Craig Harrison91944552011-08-04 14:09:55 -0700182 """
Craig Harrison91944552011-08-04 14:09:55 -0700183 # Ensure old ssh connections are terminated.
184 self._terminate_all_ssh()
185 # Wait for the client to come up.
Vic Yang6c1dc152012-09-13 14:56:11 +0800186 while timeout > 0 and not self._sshd_test(self._client.ip, timeout=2):
187 timeout -= 2
Vadim Bendebury69f047c2012-10-11 15:20:31 -0700188 assert (timeout > 0), 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800189 logging.info('Server: Client machine is up.')
190 # Relaunch remote clients.
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800191 if install_deps:
192 self._autotest_client.install()
193 self._launch_client()
194 logging.info('Server: Relaunched remote %s.', 'faft')
Craig Harrison91944552011-08-04 14:09:55 -0700195
Tom Wai-Hong Tama9b225b2013-05-03 10:35:10 +0800196 def wait_for_client_offline(self, timeout=60, orig_boot_id=None):
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800197 """Wait for the client to come offline.
198
Yusuf Mohsinallyf3cb7642013-04-15 14:20:56 -0700199 @param timeout: Time in seconds to wait the client to come offline.
Tom Wai-Hong Tam12636062013-04-09 17:14:15 +0800200 @param orig_boot_id: A string containing the original boot id.
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800201 """
202 # Wait for the client to come offline.
J. Richard Barnette134ec2c2012-04-25 12:59:37 -0700203 while timeout > 0 and self._ping_test(self._client.ip, timeout=1):
Vic Yangcf5d6fc2012-09-14 15:22:44 +0800204 time.sleep(1)
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800205 timeout -= 1
Tom Wai-Hong Tam12636062013-04-09 17:14:15 +0800206
207 # As get_boot_id() requires DUT online. So we move the comparison here.
208 if timeout == 0 and orig_boot_id:
209 if self._client.get_boot_id() != orig_boot_id:
210 logging.warn('Reboot done very quickly.')
211 return
212
Tom Wai-Hong Tama70f0fe2011-09-02 18:28:47 +0800213 assert timeout, 'Timed out waiting for client offline.'
214 logging.info('Server: Client machine is offline.')
215
Vic Yang4e0d1f72012-05-24 15:11:11 +0800216 def kill_remote(self):
217 """Call remote cleanup and kill ssh."""
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800218 if self._remote_process and self._remote_process.poll() is None:
219 try:
220 self.faft_client.cleanup()
221 logging.info('Cleanup succeeded.')
222 except xmlrpclib.ProtocolError, e:
223 logging.info('Cleanup returned protocol error: ' + str(e))
Craig Harrison91944552011-08-04 14:09:55 -0700224 self._terminate_all_ssh()
225
Vic Yang4e0d1f72012-05-24 15:11:11 +0800226 def cleanup(self):
227 """Delete the Servo object, call remote cleanup, and kill ssh."""
Vic Yang4e0d1f72012-05-24 15:11:11 +0800228 self.kill_remote()
229
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800230 def _launch_ssh_tunnel(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800231 """Establish an ssh tunnel for connecting to the remote RPC server.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800232 """
Vic Yang8535e772013-05-02 09:07:25 +0800233 if self._local_port is None:
Vic Yang69249552013-05-09 01:58:11 +0800234 self._local_port = utils.get_unused_port()
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800235 if not self._ssh_tunnel or self._ssh_tunnel.poll() is not None:
236 self._ssh_tunnel = subprocess.Popen([
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800237 'ssh -N -n -q %s -L %s:localhost:%s root@%s' %
Vic Yang8535e772013-05-02 09:07:25 +0800238 (self._SSH_CONFIG, self._local_port, self._REMOTE_PORT,
Tom Wai-Hong Tame2c66122011-10-25 17:21:35 +0800239 self._client.ip)], shell=True)
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800240 assert self._ssh_tunnel.poll() is None, \
Vic Yang8535e772013-05-02 09:07:25 +0800241 'The SSH tunnel on port %d is not up.' % self._local_port
Craig Harrison91944552011-08-04 14:09:55 -0700242
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800243 def _kill_remote_process(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800244 """Ensure the remote process and local ssh process are terminated.
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800245 """
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800246 kill_cmd = 'pkill -f %s' % self._REMOTE_COMMAND_SHORT
Tom Wai-Hong Tam4a257e52011-11-12 08:36:22 +0800247 subprocess.call(['ssh -n -q %s root@%s \'%s\'' %
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800248 (self._SSH_CONFIG, self._client.ip, kill_cmd)],
Craig Harrison91944552011-08-04 14:09:55 -0700249 shell=True)
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800250 if self._remote_process and self._remote_process.poll() is None:
251 self._remote_process.terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700252
253 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800254 """Terminate all ssh connections associated with remote processes."""
J. Richard Barnette33aec9f2013-02-01 16:38:41 -0800255 if self._ssh_tunnel and self._ssh_tunnel.poll() is None:
256 self._ssh_tunnel.terminate()
257 self._kill_remote_process()
258 self._ssh_tunnel = None