1 # Copyright 2015 The Chromium 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.
5 """Defines the client library."""
18 #pylint: disable=relative-import
21 THIS_DIR
= os
.path
.dirname(os
.path
.abspath(__file__
))
22 SWARMING_DIR
= os
.path
.join(THIS_DIR
, '..', '..', 'tools/swarming_client')
23 ISOLATE_PY
= os
.path
.join(SWARMING_DIR
, 'isolate.py')
24 SWARMING_PY
= os
.path
.join(SWARMING_DIR
, 'swarming.py')
27 class Error(Exception):
31 class ConnectionTimeoutError(Error
):
35 class ClientController(object):
36 """Creates, configures, and controls a client machine."""
41 def __init__(self
, isolate_file
, config_vars
, dimensions
, priority
=100,
42 idle_timeout_secs
=common_lib
.DEFAULT_TIMEOUT_SECS
,
43 connection_timeout_secs
=common_lib
.DEFAULT_TIMEOUT_SECS
,
44 verbosity
='ERROR', name
=None):
45 assert isinstance(config_vars
, dict)
46 assert isinstance(dimensions
, dict)
47 type(self
)._controllers
.append(self
)
48 type(self
)._client
_count
+= 1
49 self
.verbosity
= verbosity
50 self
._name
= name
or 'Client%d' % type(self
)._client
_count
51 self
._priority
= priority
52 self
._isolate
_file
= isolate_file
53 self
._isolated
_file
= isolate_file
+ 'd'
54 self
._idle
_timeout
_secs
= idle_timeout_secs
55 self
._config
_vars
= config_vars
56 self
._dimensions
= dimensions
57 self
._connect
_event
= threading
.Event()
58 self
._connected
= False
59 self
._ip
_address
= None
60 self
._otp
= self
._CreateOTP
()
63 parser
= argparse
.ArgumentParser()
64 parser
.add_argument('--isolate-server')
65 parser
.add_argument('--swarming-server')
66 parser
.add_argument('--client-connection-timeout-secs',
67 default
=common_lib
.DEFAULT_TIMEOUT_SECS
)
68 args
, _
= parser
.parse_known_args()
70 self
._isolate
_server
= args
.isolate_server
71 self
._swarming
_server
= args
.swarming_server
72 self
._connection
_timeout
_secs
= (connection_timeout_secs
or
73 args
.client_connection_timeout_secs
)
85 return self
._connected
88 def connect_event(self
):
89 return self
._connect
_event
97 return self
._verbosity
100 def verbosity(self
, level
):
101 """Sets the verbosity level as a string.
103 Either a string ('INFO', 'DEBUG', etc) or a logging level (logging.INFO,
104 logging.DEBUG, etc) is allowed.
106 assert isinstance(level
, (str, int))
107 if isinstance(level
, int):
108 level
= logging
.getLevelName(level
)
109 self
._verbosity
= level
#pylint: disable=attribute-defined-outside-init
112 def ReleaseAllControllers(cls
):
113 for controller
in cls
._controllers
:
116 def _CreateOTP(self
):
117 """Creates the OTP."""
118 host_name
= socket
.gethostname()
119 test_name
= os
.path
.basename(sys
.argv
[0])
120 creation_time
= datetime
.datetime
.utcnow()
121 otp
= 'client:%s-host:%s-test:%s-creation:%s' % (
122 self
._name
, host_name
, test_name
, creation_time
)
126 """Creates the client machine."""
127 logging
.info('Creating %s', self
.name
)
128 self
._connect
_event
.clear()
129 self
._ExecuteIsolate
()
130 self
._ExecuteSwarming
()
132 def WaitForConnection(self
):
133 """Waits for the client machine to connect.
136 ConnectionTimeoutError if the client doesn't connect in time.
138 logging
.info('Waiting for %s to connect with a timeout of %d seconds',
139 self
._name
, self
._connection
_timeout
_secs
)
140 self
._connect
_event
.wait(self
._connection
_timeout
_secs
)
141 if not self
._connect
_event
.is_set():
142 raise ConnectionTimeoutError('%s failed to connect' % self
.name
)
145 """Quits the client's RPC server so it can release the machine."""
146 if self
._rpc
is not None and self
._connected
:
147 logging
.info('Releasing %s', self
._name
)
150 except (socket
.error
, xmlrpclib
.Fault
):
151 logging
.error('Unable to connect to %s to call Quit', self
.name
)
153 self
._connected
= False
155 def _ExecuteIsolate(self
):
156 """Executes isolate.py."""
161 '--isolate', self
._isolate
_file
,
162 '--isolated', self
._isolated
_file
,
165 if self
._isolate
_server
:
166 cmd
.extend(['--isolate-server', self
._isolate
_server
])
167 for key
, value
in self
._config
_vars
.iteritems():
168 cmd
.extend(['--config-var', key
, value
])
170 self
._ExecuteProcess
(cmd
)
172 def _ExecuteSwarming(self
):
173 """Executes swarming.py."""
179 '--priority', str(self
._priority
),
182 if self
._isolate
_server
:
183 cmd
.extend(['--isolate-server', self
._isolate
_server
])
184 if self
._swarming
_server
:
185 cmd
.extend(['--swarming', self
._swarming
_server
])
186 for key
, value
in self
._dimensions
.iteritems():
187 cmd
.extend(['--dimension', key
, value
])
191 '--host', common_lib
.MY_IP
,
193 '--verbosity', self
._verbosity
,
194 '--idle-timeout', str(self
._idle
_timeout
_secs
),
197 self
._ExecuteProcess
(cmd
)
199 def _ExecuteProcess(self
, cmd
):
200 """Executes a process, waits for it to complete, and checks for success."""
201 logging
.debug('Running %s', ' '.join(cmd
))
202 p
= subprocess
.Popen(cmd
, stdout
=subprocess
.PIPE
, stderr
=subprocess
.PIPE
)
203 _
, stderr
= p
.communicate()
204 if p
.returncode
!= 0:
207 def OnConnect(self
, ip_address
):
208 """Receives client ip address on connection."""
209 self
._ip
_address
= ip_address
210 self
._connected
= True
211 self
._rpc
= common_lib
.ConnectToServer(self
._ip
_address
)
212 logging
.info('%s connected from %s', self
._name
, ip_address
)
213 self
._connect
_event
.set()