Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(217)

Side by Side Diff: testing/legion/client_lib.py

Issue 890773003: Adding the initial code for Omnibot multi-machine support (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 5 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(Empty)
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.
4 import argparse
Marc-Antoine Ruel (Google) 2015/01/30 18:00:21 In general we keep an empty line in between
Mike Meade 2015/01/30 21:04:04 Done.
5 import logging
6 import os
7 import socket
8 import subprocess
9 import tempfile
10 import threading
11 import uuid
12 import xmlrpclib
13
14 #pylint: disable=relative-import
15 import common_lib
16 import client_rpc_server
17
18 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
19 SWARMING_DIR = os.path.join(THIS_DIR, '../../tools/swarming_client')
Marc-Antoine Ruel (Google) 2015/01/30 18:00:21 if you plan this to work on windows, use os.path.j
Mike Meade 2015/01/30 21:04:04 Done.
20 ISOLATE_PY = os.path.join(SWARMING_DIR, 'isolate.py')
21 SWARMING_PY = os.path.join(SWARMING_DIR, 'swarming.py')
22 # ISOLATE_SERVER = 'omnibot-isolate-server.appspot.com'
23 # SWARMING_SERVER = 'https://omnibot-swarming-server.appspot.com/'
24 CLIENT_CONNECTION_TIMEOUT = 30 * 60 # 30 minutes
25 args = None
26
27
28 class Error(Exception):
29 pass
30
31
32 class ConnectionTimeoutError(Error):
33 pass
34
35
36 def GetArgs():
37 """Parse command line args args.
38
39 Returns:
40 Parsed command line args.
41 """
42 global args
Marc-Antoine Ruel (Google) 2015/01/30 18:00:21 not a fan
Mike Meade 2015/01/30 21:04:04 Moved into the class.
43 if not args:
44 parser = argparse.ArgumentParser()
45 parser.add_argument('--isolate-server')
46 parser.add_argument('--swarming-server')
47 parser.add_argument('--client-connection-timeout',
48 default=CLIENT_CONNECTION_TIMEOUT)
49 args, _ = parser.parse_known_args()
50 return args
51
52
53 class Client(object):
54 """The main client class.
55
56 This class is used to create clients, connect to their RPC servers, and
57 run RPC commands.
58 """
59
60 _client_count = 0
61
62 def __init__(self, isolate_file, discovery_server, name=None):
63 """ctor.
64
65 Args:
66 isolate_file: The path to the client isolate file.
67 discovery_server: The discovery server to register with
68 name: A name for the client.
69 """
70 self._IncreaseCount()
71 self._discovery_server = discovery_server
72 self._name = name or self._CreateName()
73 self._priority = 100
74 self._isolate_file = isolate_file
75 self._isolated_file = isolate_file + 'd'
76 self._connected = False
77 self._connect_event = threading.Event()
78 self._ip_address = None
79 self._config_vars = []
80 self._dimensions = []
81 self._rpc_timeout = None
82 self._otp = str(uuid.uuid1())
Marc-Antoine Ruel (Google) 2015/01/30 18:00:21 eventually it'd be nice for this to be determinist
Mike Meade 2015/01/30 21:04:04 Maybe HOST_FQDN\CLIENT_NAME\SHA-1_LEGION_FILES? T
83 self._rpc = None
84 self._verbose = False
85
86 @property
87 def connected(self):
88 """Return the value of self._connected."""
89 return self._connected
90
91 @property
92 def connect_event(self):
93 return self._connect_event
94
95 @property
96 def rpc(self):
97 """Return the rpc object."""
98 return self._rpc
99
100 @property
101 def priority(self):
102 return self._priority
103
104 @property
105 def rpc_idle_timeout(self):
106 return self._rpc_idle_timeout
107
108 @property
109 def name(self):
110 return self._name
111
112 @classmethod
113 def _IncreaseCount(cls):
114 """Increase the client_count parameter."""
115 cls._client_count += 1
116
117 @classmethod
118 def _CreateName(cls):
119 """Create a name for this client.
120
121 By default the name is "Client%s" where %s is the number of clients that
122 currently exist.
123 """
124 return 'Client%d' % cls._client_count
125
126 def SetPriority(self, priority):
Marc-Antoine Ruel (Google) 2015/01/30 18:00:21 Why not @priority.setter def priority(self, value)
Mike Meade 2015/01/30 21:04:04 The only reason I added the method like this was f
127 """Sets the priority of the client task.
128
129 Args:
130 priority: The priority to set.
131 """
132 logging.debug('Setting %s priority to %s', self._name, priority)
133 self._priority = priority
134
135 def SetVerbose(self):
136 """Set the client verbosity to debug."""
137 logging.debug('Setting %s --verbosity', self._name)
138 self._verbose = True
139
140 def AddConfigVars(self, key, value):
141 """Add a set of config vars to isolate.py.
142
143 Args:
144 key: The config vars key.
145 value: The config vars value.
146 """
147 logging.debug('Adding --config-var %s %s to %s', key, value,
148 self._name)
149 self._config_vars.append((key, value))
150
151 def AddDimension(self, key, value):
152 """Add a set of dimensions to swarming.py.
153
154 Args:
155 key: The dimension key.
156 value: The dimension value.
157 """
158 logging.debug('Adding --dimension %s %s to %s', key, value,
159 self._name)
160 self._dimensions.append((key, value))
161
162 def SetRPCTimeout(self, timeout):
163 """Sets the client's RPC timeout value.
164
165 If the RPC server does not receive an RPC request within this time the
166 client controller will exit.
167
168 Args:
169 timeout: The RPC server timeout in seconds.
170 """
171 logging.debug('Setting %s RPC timeout to %s', self._name, timeout)
172 self._rpc_timeout = timeout
173
174 def Create(self, wait=False, timeout=None):
175 """Create the client machine and wait for it to be created if specified.
176
177 Args:
178 wait: True to block until created, False to return immediately.
179 timeout: The timeout to block before raising a ConnectionTimeoutError.
180 """
181 logging.info('Creating %s', self._name)
182 self._connect_event.clear()
183 self._RegisterOnConnectCallback()
184 self._ExecuteIsolate()
185 self._ExecuteSwarming()
186 if wait:
187 self.WaitForConnection(timeout)
188
189 def WaitForConnection(self, timeout=None):
190 """Connect to the client machine.
191
192 This method waits for the client machine to register itself
193 with the discovery server.
194
195 Args:
196 timeout: The timeout in seconds.
197
198 Raises:
199 TimeoutError if the client doesn't connect in time.
200 """
201 timeout = timeout or GetArgs().client_connection_timeout
202 msg = ('Waiting for %s to connect with a timeout of %d seconds' %
203 (self._name, timeout))
204 logging.info(msg)
205 self._connect_event.wait(timeout)
206 if not self._connect_event.is_set():
207 raise ConnectionTimeoutError()
208
209 def Release(self):
210 """Quit the client's RPC server so it can release the machine."""
211 if self._rpc is not None and self._connected:
212 logging.info('Releasing %s', self._name)
213 try:
214 self._rpc.Quit()
215 except (socket.error, xmlrpclib.Fault):
216 logging.error('Unable to connect to %s to call Quit', self._name)
217 self._rpc = None
218 self._connected = False
219
220 def _ExecuteIsolate(self):
221 """Execute isolate.py with the given args."""
222 cmd = [
223 'python',
224 ISOLATE_PY,
225 'archive',
226 '--isolate=' + self._isolate_file,
227 '--isolated=' + self._isolated_file,
228 ]
229
230 if GetArgs().isolate_server:
231 cmd += ['--isolate-server', GetArgs().isolate_server]
232 for key, value in self._config_vars:
233 cmd += ['--config-var', key, value]
234
235 logging.debug('Running %s', ' '.join(cmd))
236 if subprocess.call(cmd, stdout=subprocess.PIPE) != 0:
237 raise Error('Error calling isolate.py')
238
239 def _ExecuteSwarming(self):
240 """Execute swarming.py with the vars."""
241 cmd = [
242 'python',
243 SWARMING_PY,
244 'trigger',
245 self._isolated_file,
246 '--priority=' + str(self._priority)
247 ]
248
249 if GetArgs().isolate_server:
250 cmd += ['--isolate-server', GetArgs().isolate_server]
251 if GetArgs().swarming_server:
252 cmd += ['--swarming', GetArgs().swarming_server]
253 for key, value in self._dimensions:
254 cmd += ['--dimension', key, value]
255
256 cmd += ['--', '--host', str(common_lib.MY_IP), '--otp', self._otp]
257 if self._rpc_timeout:
258 cmd += ['--idle-timeout', str(self._rpc_timeout)]
259 if self._verbose:
260 cmd += ['--verbose']
261
262 logging.debug('Running %s', ' '.join(cmd))
263 if subprocess.call(cmd, stdout=subprocess.PIPE) != 0:
264 raise Error('Error calling swarming.py')
265
266 def _RegisterOnConnectCallback(self):
267 """Register a callback with the discovery server.
268
269 This callback is used to receive the client's IP address once it starts
270 and contacts the discovery server.
271 """
272 self._discovery_server.RegisterClientCallback(self._otp, self._OnConnect)
273
274 def _OnConnect(self, ip_address):
275 """The OnConnect callback method.
276
277 This method receives the ip address received by the discovery server from
278 the client and sets the object's connected state to True.
279 """
280 self._ip_address = ip_address
281 self._connected = True
282 self._rpc = client_rpc_server.RPCServer.Connect(self._ip_address)
283 logging.info('%s connected from %s', self._name, ip_address)
284 self._connect_event.set()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698