blob: a52d724be5cde26e561b032656052ed724c7a303 [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
Todd Brochf24d2782011-08-19 10:55:41 -07006import re
Craig Harrison2b6c6fc2011-06-23 10:34:02 -07007import subprocess
Craig Harrison91944552011-08-04 14:09:55 -07008import time
9import xmlrpclib
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070010
Chrome Bot9a1137d2011-07-19 14:35:00 -070011from autotest_lib.client.common_lib import error
Todd Brochf24d2782011-08-19 10:55:41 -070012from autotest_lib.server import autotest, site_host_attributes, test
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070013import autotest_lib.server.cros.servo
14
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
Craig Harrison91944552011-08-04 14:09:55 -070025 # Abstracts access to all Servo functions.
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070026 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.
46 'remote_command': 'python /usr/local/autotest/cros/servo_pyauto.py'
47 ' --no-http-server',
48 # The short form of remote command, used by pkill.
49 'remote_command_short': 'servo_pyauto',
50 # 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',
56 },
57 'faft': {
58 'used': False,
59 'ref_name': 'faft_client',
60 'port': 9990,
61 'client_test': 'firmware_FAFTClient',
62 'remote_command': 'python /usr/local/autotest/cros/faft_client.py',
63 'remote_command_short': 'faft_client',
64 'remote_process': None,
65 'ssh_tunnel': None,
66 'polling_rpc': 'is_available',
67 },
68 }
Craig Harrison2b6c6fc2011-06-23 10:34:02 -070069
70
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080071 def initialize(self, host, cmdline_args, use_pyauto=False, use_faft=False):
72 """Create a Servo object and install the dependency.
Craig Harrison91944552011-08-04 14:09:55 -070073
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +080074 If use_pyauto/use_faft is True the PyAuto/FAFTClient dependency is
75 installed on the client and a remote PyAuto/FAFTClient server is
76 launched and connected.
Todd Brochf24d2782011-08-19 10:55:41 -070077 """
78 # Assign default arguments for servo invocation.
79 args = {
80 'servo_host': 'localhost', 'servo_port': 9999,
81 'xml_config': 'servo.xml', 'servo_vid': None, 'servo_pid': None,
82 'servo_serial': None, 'use_pyauto': False}
83
84 # Parse arguments from AFE and override servo defaults above.
85 client_attributes = site_host_attributes.HostAttributes(host.hostname)
86 if hasattr(site_host_attributes, 'servo_serial'):
87 args['servo_serial'] = client_attributes.servo_serial
88
89 # Parse arguments from command line and override previous AFE or servo
90 # defaults
91 for arg in cmdline_args:
92 match = re.search("^(\w+)=(.+)", arg)
93 if match:
94 args[match.group(1)] = match.group(2)
95
96 self.servo = autotest_lib.server.cros.servo.Servo(
97 args['servo_host'], args['servo_port'], args['xml_config'],
98 args['servo_vid'], args['servo_pid'], args['servo_serial'])
Chrome Bot9a1137d2011-07-19 14:35:00 -070099 # Initializes dut, may raise AssertionError if pre-defined gpio
100 # sequence to set GPIO's fail. Autotest does not handle exception
101 # throwing in initialize and will cause a test to hang.
102 try:
103 self.servo.initialize_dut()
104 except AssertionError as e:
105 del self.servo
106 raise error.TestFail(e)
107
Craig Harrison91944552011-08-04 14:09:55 -0700108 self._client = host;
109
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800110 self._remote_infos['pyauto']['used'] = use_pyauto
111 self._remote_infos['faft']['used'] = use_faft
112
113 # Install PyAuto/FAFTClient dependency.
114 for info in self._remote_infos.itervalues():
115 if info['used']:
116 if not self._autotest_client:
117 self._autotest_client = autotest.Autotest(self._client)
118 self._autotest_client.run_test(info['client_test'])
119 self.launch_client(info)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700120
121
122 def assert_ping(self):
123 """Ping to assert that the device is up."""
Craig Harrison91944552011-08-04 14:09:55 -0700124 assert self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700125
126
127 def assert_pingfail(self):
128 """Ping to assert that the device is down."""
Craig Harrison91944552011-08-04 14:09:55 -0700129 assert not self.ping_test(self._client.ip)
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700130
131
132 def ping_test(self, hostname, timeout=5):
133 """Verify whether a host responds to a ping.
134
135 Args:
136 hostname: Hostname to ping.
137 timeout: Time in seconds to wait for a response.
138 """
139 return subprocess.call(['ping', '-c', '1', '-W',
140 str(timeout), hostname]) == 0
141
142
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800143 def launch_client(self, info):
144 """Launch a remote process on client and set up an xmlrpc connection.
145
146 Args:
147 info: A dict of remote info, see the definition of self._remote_infos.
148 """
149 assert info['used'], \
150 'Remote %s dependency not installed.' % info['ref_name']
151 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
152 self._launch_ssh_tunnel(info)
153 assert info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None, \
Craig Harrison91944552011-08-04 14:09:55 -0700154 'The SSH tunnel is not up.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800155
156 # Launch RPC server remotely.
157 self._kill_remote_process(info)
158 logging.info('Client command: %s' % info['remote_command'])
159 info['remote_process'] = subprocess.Popen([
Craig Harrison63e9c6a2011-08-10 17:13:57 -0700160 'ssh -o "StrictHostKeyChecking no" -n root@%s \'%s\'' %
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800161 (self._client.ip, info['remote_command'])], shell=True)
162
163 # Connect to RPC object.
164 logging.info('Connecting to client RPC server...')
165 remote_url = 'http://localhost:%s' % info['port']
166 setattr(self, info['ref_name'],
167 xmlrpclib.ServerProxy(remote_url, allow_none=True))
168 logging.info('Server proxy: %s' % remote_url)
169
Craig Harrison91944552011-08-04 14:09:55 -0700170 # Poll for client RPC server to come online.
171 timeout = 10
172 succeed = False
173 while timeout > 0 and not succeed:
174 time.sleep(2)
175 try:
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800176 remote_object = getattr(self, info['ref_name'])
177 polling_rpc = getattr(remote_object, info['polling_rpc'])
178 polling_rpc()
Craig Harrison91944552011-08-04 14:09:55 -0700179 succeed = True
180 except:
181 timeout -= 1
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800182 assert succeed, 'Timed out connecting to client RPC server.'
Craig Harrison91944552011-08-04 14:09:55 -0700183
184
185 def wait_for_client(self):
186 """Wait for the client to come back online.
187
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800188 New remote processes will be launched if their used flags are enabled.
Craig Harrison91944552011-08-04 14:09:55 -0700189 """
190 timeout = 10
191 # Ensure old ssh connections are terminated.
192 self._terminate_all_ssh()
193 # Wait for the client to come up.
194 while timeout > 0 and not self.ping_test(self._client.ip):
195 time.sleep(5)
196 timeout -= 1
197 assert timeout, 'Timed out waiting for client to reboot.'
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800198 logging.info('Server: Client machine is up.')
199 # Relaunch remote clients.
200 for name, info in self._remote_infos.iteritems():
201 if info['used']:
202 self.launch_client(info)
203 logging.info('Server: Relaunched remote %s.' % name)
Craig Harrison91944552011-08-04 14:09:55 -0700204
205
Craig Harrison2b6c6fc2011-06-23 10:34:02 -0700206 def cleanup(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800207 """Delete the Servo object, call remote cleanup, and kill ssh."""
Craig Harrison91944552011-08-04 14:09:55 -0700208 if self.servo:
209 del self.servo
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800210 for info in self._remote_infos.itervalues():
211 if info['remote_process'] and info['remote_process'].poll() is None:
212 remote_object = getattr(self, info['ref_name'])
213 remote_object.cleanup()
Craig Harrison91944552011-08-04 14:09:55 -0700214 self._terminate_all_ssh()
215
216
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800217 def _launch_ssh_tunnel(self, info):
218 """Establish an ssh tunnel for connecting to the remote RPC server.
219
220 Args:
221 info: A dict of remote info, see the definition of self._remote_infos.
222 """
223 if not info['ssh_tunnel'] or info['ssh_tunnel'].poll() is not None:
224 info['ssh_tunnel'] = subprocess.Popen(['ssh', '-N', '-n', '-L',
225 '%s:localhost:%s' % (info['port'], info['port']),
Craig Harrison91944552011-08-04 14:09:55 -0700226 'root@%s' % self._client.ip])
227
228
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800229 def _kill_remote_process(self, info):
230 """Ensure the remote process and local ssh process are terminated.
231
232 Args:
233 info: A dict of remote info, see the definition of self._remote_infos.
234 """
235 kill_cmd = 'pkill -f %s' % info['remote_command_short']
Craig Harrison63e9c6a2011-08-10 17:13:57 -0700236 subprocess.call(['ssh -n -o "StrictHostKeyChecking no" root@%s \'%s\'' %
Craig Harrison91944552011-08-04 14:09:55 -0700237 (self._client.ip, kill_cmd)],
238 shell=True)
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800239 if info['remote_process'] and info['remote_process'].poll() is None:
240 info['remote_process'].terminate()
Craig Harrison91944552011-08-04 14:09:55 -0700241
242
243 def _terminate_all_ssh(self):
Tom Wai-Hong Tambea57b32011-09-02 18:27:47 +0800244 """Terminate all ssh connections associated with remote processes."""
245 for info in self._remote_infos.itervalues():
246 if info['ssh_tunnel'] and info['ssh_tunnel'].poll() is None:
247 info['ssh_tunnel'].terminate()
248 self._kill_remote_process(info)