Chromium Code Reviews| OLD | NEW |
|---|---|
| (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() | |
| OLD | NEW |