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

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: Addressing initial comments 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 """Client library module."""
5
6 import argparse
7 import logging
8 import os
9 import socket
10 import subprocess
11 import tempfile
12 import threading
13 import uuid
14 import xmlrpclib
15
16 #pylint: disable=relative-import
17 import common_lib
18 import client_rpc_server
19
20 THIS_DIR = os.path.dirname(os.path.abspath(__file__))
21 SWARMING_DIR = os.path.join(THIS_DIR, '..', '..', 'tools/swarming_client')
22 ISOLATE_PY = os.path.join(SWARMING_DIR, 'isolate.py')
23 SWARMING_PY = os.path.join(SWARMING_DIR, 'swarming.py')
24 # ISOLATE_SERVER = 'omnibot-isolate-server.appspot.com'
25 # SWARMING_SERVER = 'https://omnibot-swarming-server.appspot.com/'
26 CLIENT_CONNECTION_TIMEOUT = 30 * 60 # 30 minutes
27
28
29 class Error(Exception):
30 pass
31
32
33 class ConnectionTimeoutError(Error):
34 pass
35
36
37 class Client(object):
38 """The main client class.
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 Few issues: - The class name is misleading, it's n
Mike Meade 2015/02/03 01:18:08 Done.
39
40 This class is used to create clients, wait for them to connect to the host,
41 connect to their RPC servers, and run RPC commands.
42 """
43
44 _client_count = 0
45
46 def __init__(self, isolate_file, discovery_server, name=None):
47 """ctor.
48
49 Args:
50 isolate_file: The path to the client isolate file.
51 discovery_server: The discovery server to register with
52 name: A name for the client.
53 """
54 self._IncreaseCount()
55 self.name = name or self._CreateName()
56 self.priority = 100
57 self.isolate_file = isolate_file
58 self.isolated_file = isolate_file + 'd'
59 self.rpc_timeout = None
60 self._connect_event = threading.Event()
61 self._args = self._ParseArgs()
62 self._discovery_server = discovery_server
63 self._connected = False
64 self._ip_address = None
65 self._config_vars = []
66 self._dimensions = []
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 this must be a dict, since duplicate keys are not
Mike Meade 2015/02/03 01:18:08 Done.
67 self._otp = str(uuid.uuid1())
68 self._rpc = None
69 self._verbose = False
70
71 @property
72 def connected(self):
73 """Return the value of self._connected."""
74 return self._connected
75
76 @property
77 def connect_event(self):
78 return self._connect_event
79
80 @property
81 def rpc(self):
82 return self._rpc
83
84 @classmethod
85 def _IncreaseCount(cls):
Marc-Antoine Ruel (Google) 2015/01/30 21:58:38 Why a function if it is used exactly at one place?
Mike Meade 2015/02/03 01:18:08 Changed it to type(self)._client_count += 1 in the
86 """Increase the client_count parameter."""
87 cls._client_count += 1
88
89 @classmethod
90 def _CreateName(cls):
91 """Create a name for this client.
92
93 By default the name is "Client%d" where %d is the number of clients that
94 currently exist.
95 """
96 return 'Client%d' % cls._client_count
97
98 def _ParseArgs(self):
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 Why is this a method?
Mike Meade 2015/02/03 01:18:08 Moved it into __init__
99 """Parse command line args."""
100 parser = argparse.ArgumentParser()
101 parser.add_argument('--isolate-server')
102 parser.add_argument('--swarming-server')
103 parser.add_argument('--client-connection-timeout',
104 default=CLIENT_CONNECTION_TIMEOUT)
105 args, _ = parser.parse_known_args()
106 return args
107
108 def AddConfigVars(self, key, value):
109 """Add a set of config vars to isolate.py.
110
111 Args:
112 key: The config vars key.
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 Frankly, I don't see the value of this documentati
Mike Meade 2015/02/03 01:18:08 Done.
113 value: The config vars value.
114 """
115 logging.debug('Adding --config-var %s %s to %s', key, value,
116 self.name)
117 self._config_vars.append((key, value))
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 The fact previous keys are not overridden IS somet
Mike Meade 2015/02/03 01:18:07 I added a note, but should this also be a dict?
118
119 def AddDimension(self, key, value):
120 """Add a set of dimensions to swarming.py.
121
122 Args:
123 key: The dimension key.
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 same
Mike Meade 2015/02/03 01:18:08 Done.
124 value: The dimension value.
125 """
126 logging.debug('Adding --dimension %s %s to %s', key, value,
127 self.name)
128 self._dimensions.append((key, value))
129
130 def Create(self, wait=False, timeout=None):
Marc-Antoine Ruel (Google) 2015/01/30 21:58:38 Why options? Will there be code that will use all
Mike Meade 2015/02/03 01:18:07 In thinking about it more this is probably over-en
131 """Create the client machine and wait for it to be created if specified.
132
133 Args:
134 wait: True to block until created, False to return immediately.
135 timeout: The timeout to block before raising a ConnectionTimeoutError.
136 """
137 logging.info('Creating %s', self.name)
138 self._connect_event.clear()
139 self._RegisterOnConnectCallback()
140 self._ExecuteIsolate()
141 self._ExecuteSwarming()
142 if wait:
143 self.WaitForConnection(timeout)
144
145 def WaitForConnection(self, timeout=None):
146 """Connect to the client machine.
147
148 This method waits for the client machine to register itself
149 with the discovery server.
150
151 Args:
152 timeout: The timeout in seconds.
153
154 Raises:
155 TimeoutError if the client doesn't connect in time.
156 """
157 timeout = timeout or self._args.client_connection_timeout
158 msg = ('Waiting for %s to connect with a timeout of %d seconds' %
159 (self.name, timeout))
160 logging.info(msg)
161 self._connect_event.wait(timeout)
162 if not self._connect_event.is_set():
163 raise ConnectionTimeoutError()
164
165 def Release(self):
166 """Quit the client's RPC server so it can release the machine."""
167 if self._rpc is not None and self._connected:
168 logging.info('Releasing %s', self.name)
169 try:
170 self._rpc.Quit()
171 except (socket.error, xmlrpclib.Fault):
172 logging.error('Unable to connect to %s to call Quit', self.name)
173 self._rpc = None
174 self._connected = False
175
176 def _ExecuteIsolate(self):
177 """Execute isolate.py with the given args."""
178 cmd = [
179 'python',
180 ISOLATE_PY,
181 'archive',
182 '--isolate=' + self.isolate_file,
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 Trust me, that's a recipe for bugs. Use instead: '
Mike Meade 2015/02/03 01:18:07 Done.
183 '--isolated=' + self.isolated_file,
184 ]
185
186 if self._args.isolate_server:
187 cmd += ['--isolate-server', self._args.isolate_server]
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 I generally prefer cmd.extend(...)
Mike Meade 2015/02/03 01:18:07 Done.
188 for key, value in self._config_vars:
189 cmd += ['--config-var', key, value]
190
191 logging.debug('Running %s', ' '.join(cmd))
192 if subprocess.call(cmd, stdout=subprocess.PIPE) != 0:
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 This may hang BTW since you are not reading stdout
Mike Meade 2015/02/03 01:18:08 Done.
193 raise Error('Error calling isolate.py')
194
195 def _ExecuteSwarming(self):
196 """Execute swarming.py with the vars."""
197 cmd = [
198 'python',
199 SWARMING_PY,
200 'trigger',
201 self.isolated_file,
202 '--priority=' + str(self.priority)
203 ]
204
205 if self._args.isolate_server:
206 cmd += ['--isolate-server', self._args.isolate_server]
207 if self._args.swarming_server:
208 cmd += ['--swarming', self._args.swarming_server]
209 for key, value in self._dimensions:
210 cmd += ['--dimension', key, value]
211
212 cmd += ['--', '--host', str(common_lib.MY_IP), '--otp', self._otp]
213 if self.rpc_timeout:
214 cmd += ['--idle-timeout', str(self.rpc_timeout)]
215 if self._verbose:
216 cmd += ['--verbose']
217
218 logging.debug('Running %s', ' '.join(cmd))
219 if subprocess.call(cmd, stdout=subprocess.PIPE) != 0:
220 raise Error('Error calling swarming.py')
221
222 def _RegisterOnConnectCallback(self):
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 Same thing, it's used exactly at one place, I don'
Mike Meade 2015/02/03 01:18:08 Done.
223 """Register a callback with the discovery server.
224
225 This callback is used to receive the client's IP address once it starts
Marc-Antoine Ruel (Google) 2015/01/30 21:58:39 What the callback does is not relevant documentati
Mike Meade 2015/02/03 01:18:07 Acknowledged.
226 and contacts the discovery server.
227 """
228 self._discovery_server.RegisterClientCallback(self._otp, self._OnConnect)
229
230 def _OnConnect(self, ip_address):
231 """The OnConnect callback method.
232
233 This method receives the ip address received by the discovery server from
234 the client and sets the object's connected state to True.
235 """
236 self._ip_address = ip_address
237 self._connected = True
238 self._rpc = client_rpc_server.RPCServer.Connect(self._ip_address)
239 logging.info('%s connected from %s', self.name, ip_address)
240 self._connect_event.set()
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698