Merge lp:~david-schwarz/lava-dispatcher/multi-target into lp:lava-dispatcher
- multi-target
- Merge into trunk
Proposed by
David Schwarz
Status: | Rejected |
---|---|
Rejected by: | Michael Hudson-Doyle |
Proposed branch: | lp:~david-schwarz/lava-dispatcher/multi-target |
Merge into: | lp:lava-dispatcher |
Diff against target: |
672 lines (+464/-64) (has conflicts) 9 files modified
doc/multi-target_test.json (+46/-0) doc/multi-target_test_2.json (+81/-0) lava-dispatch (+3/-0) lava_dispatcher/__init__.py (+162/-45) lava_dispatcher/actions/launch_control.py (+53/-19) lava_dispatcher/actions/shell_commands.py (+42/-0) lava_dispatcher/actions/sync_to_label.py (+22/-0) lava_dispatcher/sync.py (+47/-0) lava_dispatcher/utils.py (+8/-0) Text conflict in lava_dispatcher/__init__.py |
To merge this branch: | bzr merge lp:~david-schwarz/lava-dispatcher/multi-target |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Linaro Validation Team | Pending | ||
Review via email: mp+68917@code.launchpad.net |
Commit message
Description of the change
The changes on this branch give lava-dispatcher the capability to manage and coordinate tests on multiple target machines concurrently. This allows the possibility of tests requiring coordination between multiple machines or multiple groups of machines.
Note that the Python module IPy must be installed on the dispatcher host.
To post a comment you must log in.
Unmerged revisions
- 80. By David Schwarz
-
actions: Add scp and verify_file_present actions and example
- 79. By David Schwarz
-
multi-target: Run tests involving multiple target machines
Each target machines' action sequence runs in a separate thread.
At the end of the run, results are pooled from all threads and
sent as a single bundle to the dashboard server.Note that package python-ipy must be installed on the
system running the dispatcher.
Preview Diff
[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1 | === added file 'doc/multi-target_test.json' |
2 | --- doc/multi-target_test.json 1970-01-01 00:00:00 +0000 |
3 | +++ doc/multi-target_test.json 2011-07-22 22:31:22 +0000 |
4 | @@ -0,0 +1,46 @@ |
5 | +{ |
6 | + "job_name": "multi-target_test", |
7 | + "client_groups": [ |
8 | + { |
9 | + "name": "group1", |
10 | + "target_hosts": ["192.168.10.1-192.168.10.10"] |
11 | + }, |
12 | + { |
13 | + "name": "group2", |
14 | + "target_hosts": ["192.168.85.11", |
15 | + "192.168.87.28", |
16 | + "host1.linaro.org", |
17 | + "host2", |
18 | + "host16.hyphenated-domain.com"] |
19 | + } |
20 | + ], |
21 | + "timeout": 18000, |
22 | + "actions": [ |
23 | + { |
24 | + "command": "ls", |
25 | + "target_groups": ["group1"] |
26 | + }, |
27 | + { |
28 | + "command": "sync_to_label", |
29 | + "target_groups": ["group1","group2"], |
30 | + "parameters": |
31 | + { |
32 | + "label": "ls_done", |
33 | + "block": true |
34 | + } |
35 | + }, |
36 | + { |
37 | + "command": "date", |
38 | + "target_groups": ["group1", "group2"] |
39 | + }, |
40 | + { |
41 | + "command": "submit_results", |
42 | + "parameters": |
43 | + { |
44 | + "server": "http://validation.linaro.org/launch-control", |
45 | + "stream": "/anonymous/ls-test/", |
46 | + "skip_remote": true |
47 | + } |
48 | + } |
49 | + ] |
50 | +} |
51 | |
52 | === added file 'doc/multi-target_test_2.json' |
53 | --- doc/multi-target_test_2.json 1970-01-01 00:00:00 +0000 |
54 | +++ doc/multi-target_test_2.json 2011-07-22 22:31:22 +0000 |
55 | @@ -0,0 +1,81 @@ |
56 | +{ |
57 | + "job_name": "multi-target_test_2", |
58 | + "client_groups": [ |
59 | + { |
60 | + "name": "group1", |
61 | + "target_hosts": ["panda01"] |
62 | + }, |
63 | + { |
64 | + "name": "group2", |
65 | + "target_hosts": ["panda02"] |
66 | + } |
67 | + ], |
68 | + "timeout": 18000, |
69 | + "actions": [ |
70 | + { |
71 | + "command": "sync_to_label", |
72 | + "target_groups": ["group1","group2"], |
73 | + "parameters": |
74 | + { |
75 | + "label": "all_clients_up", |
76 | + "block": true |
77 | + } |
78 | + }, |
79 | + { |
80 | + "target_groups": ["group1"], |
81 | + "command": "scp", |
82 | + "parameters": |
83 | + { |
84 | + "username": "tester", |
85 | + "host": "10.1.1.1", |
86 | + "filename": "~/exists.txt", |
87 | + "target_path": "" |
88 | + } |
89 | + }, |
90 | + { |
91 | + "target_groups": ["group1"], |
92 | + "command": "scp", |
93 | + "parameters": |
94 | + { |
95 | + "username": "tester", |
96 | + "host": "10.1.1.1", |
97 | + "filename": "~/doesnotexist.txt", |
98 | + "target_path": "" |
99 | + } |
100 | + }, |
101 | + { |
102 | + "command": "sync_to_label", |
103 | + "target_groups": ["group1","group2"], |
104 | + "parameters": |
105 | + { |
106 | + "label": "wait_for_scp", |
107 | + "block": true |
108 | + } |
109 | + }, |
110 | + { |
111 | + "command": "verify_file_present", |
112 | + "target_groups": ["group2"], |
113 | + "parameters": |
114 | + { |
115 | + "file": "~/exists.txt" |
116 | + } |
117 | + }, |
118 | + { |
119 | + "command": "verify_file_present", |
120 | + "target_groups": ["group2"], |
121 | + "parameters": |
122 | + { |
123 | + "file": "~/doesnotexist.txt" |
124 | + } |
125 | + }, |
126 | + { |
127 | + "command": "submit_results", |
128 | + "parameters": |
129 | + { |
130 | + "server": "http://validation.linaro.org/launch-control", |
131 | + "stream": "/anonymous/panda_multi_scp/", |
132 | + "skip_remote": true |
133 | + } |
134 | + } |
135 | + ] |
136 | +} |
137 | |
138 | === modified file 'lava-dispatch' |
139 | --- lava-dispatch 2011-06-27 04:55:08 +0000 |
140 | +++ lava-dispatch 2011-07-22 22:31:22 +0000 |
141 | @@ -20,6 +20,7 @@ |
142 | # with this program; if not, see <http://www.gnu.org/licenses>. |
143 | |
144 | import sys |
145 | +import threading |
146 | |
147 | from lava_dispatcher import LavaTestJob |
148 | |
149 | @@ -28,6 +29,8 @@ |
150 | print >> sys.stderr, "Usage:\n lava-dispatch <json job file>" |
151 | sys.exit(status) |
152 | |
153 | +threading.stack_size(256000); |
154 | + |
155 | if len(sys.argv) != 2: |
156 | usage(1) |
157 | |
158 | |
159 | === modified file 'lava_dispatcher/__init__.py' |
160 | --- lava_dispatcher/__init__.py 2011-07-21 16:55:47 +0000 |
161 | +++ lava_dispatcher/__init__.py 2011-07-22 22:31:22 +0000 |
162 | @@ -25,51 +25,185 @@ |
163 | from uuid import uuid1 |
164 | import base64 |
165 | import pexpect |
166 | +import re |
167 | +from operator import add |
168 | |
169 | from lava_dispatcher.actions import get_all_cmds |
170 | from lava_dispatcher.client import LavaClient, CriticalError, GeneralError |
171 | from lava_dispatcher.android_client import LavaAndroidClient |
172 | +<<<<<<< TREE |
173 | |
174 | __version__ = "0.1.0" |
175 | |
176 | class LavaTestJob(object): |
177 | +======= |
178 | +from threading import Thread |
179 | +from lava_dispatcher.utils import ip_addr_range |
180 | +from lava_dispatcher.sync import SyncMaster |
181 | + |
182 | +class LavaTestJob(SyncMaster): |
183 | + |
184 | +>>>>>>> MERGE-SOURCE |
185 | def __init__(self, job_json): |
186 | + super(LavaTestJob, self).__init__() |
187 | self.job_status = 'pass' |
188 | self.load_job_data(job_json) |
189 | - self.context = LavaContext(self.target, self.image_type) |
190 | + self.client_group_list = dict() |
191 | + self._init_client_threads() |
192 | + self.lava_commands = get_all_cmds() |
193 | |
194 | def load_job_data(self, job_json): |
195 | self.job_data = json.loads(job_json) |
196 | |
197 | + def _init_client_threads(self): |
198 | + if not self.client_groups: |
199 | + thread = ClientThread(self.target, self.image_type, |
200 | + self.target_type, self) |
201 | + self.client_group_list['default'] = thread |
202 | + else: |
203 | + for group in self.client_groups: |
204 | + group_name = group['name'] |
205 | + self.client_group_list[group_name] = [] |
206 | + for hostname in self._gen_host(group): |
207 | + image_type = group.get('image_type') |
208 | + target_type = group.get('target_type') |
209 | + thread = ClientThread(hostname, image_type, target_type, |
210 | + self) |
211 | + self.client_group_list[group_name].append(thread) |
212 | + |
213 | + def _gen_host(self, group): |
214 | + for host_str in group['target_hosts']: |
215 | + if re.match('^[\d\.]+-[\d\.]+$', host_str): |
216 | + range = host_str.split('-') |
217 | + assert len(range) == 2 |
218 | + for host in ip_addr_range(*range): |
219 | + yield host |
220 | + else: |
221 | + yield host_str |
222 | + |
223 | + def _iter_threads(self, thread_group_list): |
224 | + for thread_group in thread_group_list.itervalues(): |
225 | + for thread in thread_group: |
226 | + yield thread |
227 | + |
228 | @property |
229 | def target(self): |
230 | - return self.job_data['target'] |
231 | + return self.job_data.get('target') |
232 | |
233 | @property |
234 | def image_type(self): |
235 | - if self.job_data.has_key('image_type'): |
236 | - return self.job_data['image_type'] |
237 | + return self.job_data.get('image_type') |
238 | + |
239 | + @property |
240 | + def target_type(self): |
241 | + return self.job_data.get('target_type') |
242 | + |
243 | + @property |
244 | + def client_groups(self): |
245 | + return self.job_data.get('client_groups') |
246 | |
247 | def run(self): |
248 | - lava_commands = get_all_cmds() |
249 | - |
250 | if self.job_data['actions'][-1]['command'] == 'submit_results': |
251 | submit_results = self.job_data['actions'].pop(-1) |
252 | else: |
253 | submit_results = None |
254 | |
255 | + for cmd in self.job_data['actions']: |
256 | + target_groups = cmd.get('target_groups') |
257 | + if not target_groups: |
258 | + target_groups = {'default' : self.job_data['target']} |
259 | + params = cmd.get('parameters', {}) |
260 | + metadata = cmd.get('metadata', {}) |
261 | + |
262 | + if cmd['command'] == 'sync_to_label': |
263 | + self.init_sync_label(target_groups, params) |
264 | + |
265 | + for group_name in target_groups: |
266 | + for client_thread in self.client_group_list[group_name]: |
267 | + client_thread.add_action(self.lava_commands[cmd['command']], |
268 | + cmd['command'], |
269 | + params, |
270 | + metadata) |
271 | + thread_iter = self._iter_threads(self.client_group_list) |
272 | + for client_thread in thread_iter: |
273 | + client_thread.start() |
274 | + thread_iter = self._iter_threads(self.client_group_list) |
275 | + for client_thread in thread_iter: |
276 | + client_thread.join() |
277 | + |
278 | + if submit_results: |
279 | + results_context = LavaContext(self) |
280 | + thread_iter = self._iter_threads(self.client_group_list) |
281 | + for client_thread in thread_iter: |
282 | + results_context.subcontext_list.append(client_thread.context) |
283 | + |
284 | + params = submit_results.get('parameters', {}) |
285 | + action = self.lava_commands[submit_results['command']]( |
286 | + results_context) |
287 | + action.run(**params) |
288 | + |
289 | + def init_sync_label(self, host_groups, params): |
290 | + label = params.get('label') |
291 | + if not label: |
292 | + print 'Job Error in sync_to_label action: No label' |
293 | + raise |
294 | + |
295 | + groups = map(self.client_group_list.get, host_groups) |
296 | + size = reduce(add, map(len, groups)) |
297 | + self.add_sync_label(label, size) |
298 | + |
299 | + |
300 | +class LavaContext(object): |
301 | + def __init__(self, sync_master): |
302 | + self.test_data = LavaTestData() |
303 | + self.sync_master = sync_master |
304 | + self.subcontext_list = [] |
305 | + |
306 | + @property |
307 | + def client(self): |
308 | + return self._client |
309 | + |
310 | + |
311 | +class ActionThunk(object): |
312 | + def __init__(self, action, params, metadata, action_name): |
313 | + self.action = action |
314 | + self.params = params |
315 | + self.metadata = metadata |
316 | + self.action_name = action_name |
317 | + |
318 | + def run(self): |
319 | + self.action.context.test_data.add_metadata(self.metadata) |
320 | + self.action.run(**self.params) |
321 | + |
322 | + |
323 | +class ClientThread(Thread): |
324 | + def __init__(self, hostname, image_type, target_type, sync_master): |
325 | + super(ClientThread, self).__init__() |
326 | + self.action_list = [] |
327 | + self.context = LavaContext(sync_master) |
328 | + if image_type == "android": |
329 | + self.context.client_class = LavaAndroidClient |
330 | + self.context.image_type = image_type |
331 | + self.context.hostname = hostname |
332 | + else: |
333 | + # conmux / serial |
334 | + self.context.client_class = LavaClient |
335 | + self.context.hostname = hostname |
336 | + |
337 | + def add_action(self, action_class, action_name, params, metadata): |
338 | + action = action_class(self.context) |
339 | + self.action_list.append(ActionThunk(action, params, metadata, action_name)) |
340 | + |
341 | + def run(self): |
342 | + self.context._client = self.context.client_class(self.context.hostname) |
343 | + |
344 | try: |
345 | - for cmd in self.job_data['actions']: |
346 | - params = cmd.get('parameters', {}) |
347 | - metadata = cmd.get('metadata', {}) |
348 | - metadata['target.hostname'] = self.target |
349 | - self.context.test_data.add_metadata(metadata) |
350 | - action = lava_commands[cmd['command']](self.context) |
351 | + for action in self.action_list: |
352 | try: |
353 | status = 'fail' |
354 | - action.run(**params) |
355 | + action.run() |
356 | except CriticalError, err: |
357 | - raise err |
358 | + raise |
359 | except (pexpect.TIMEOUT, GeneralError), err: |
360 | pass |
361 | except Exception, err: |
362 | @@ -77,45 +211,28 @@ |
363 | else: |
364 | status = 'pass' |
365 | finally: |
366 | + err_msg = "" |
367 | + command = action.action_name |
368 | if status == 'fail': |
369 | - err_msg = "Lava failed at action " + cmd['command'] \ |
370 | - + " with error: " + str(err) + "\n" |
371 | - if cmd['command'] == 'lava_test_run': |
372 | - err_msg = err_msg + "Lava failed with test: " \ |
373 | - + test_name |
374 | + err_msg = "Lava failed at action %s with error: %s\n" %\ |
375 | + (command, str(err)) |
376 | + if command == 'lava_test_run': |
377 | + err_msg += "Lava failed on test: %s" %\ |
378 | + action.params.get('test_name') |
379 | exc_type, exc_value, exc_traceback = sys.exc_info() |
380 | - err_msg = err_msg + repr(traceback.format_tb(exc_traceback)) |
381 | + err_msg += repr(traceback.format_tb(exc_traceback)) |
382 | print >> sys.stderr, err_msg |
383 | - else: |
384 | - err_msg = "" |
385 | - self.context.test_data.add_result(cmd['command'], |
386 | + self.context.test_data.add_result(action.action_name, |
387 | status, err_msg) |
388 | except: |
389 | - #Capture all user-defined and non-user-defined critical errors |
390 | + # Capture all user-defined and non-user-defined critical errors |
391 | + # Do not raise--allow thread to exit gracefully |
392 | + print "Exception in thread for %s. "\ |
393 | + "Exiting thread." % self.context.hostname |
394 | self.context.test_data.job_status='fail' |
395 | - raise |
396 | - finally: |
397 | - if submit_results: |
398 | - params = submit_results.get('parameters', {}) |
399 | - action = lava_commands[submit_results['command']]( |
400 | - self.context) |
401 | - action.run(**params) |
402 | - |
403 | - |
404 | -class LavaContext(object): |
405 | - def __init__(self, target, image_type): |
406 | - if image_type != "android": |
407 | - self._client = LavaClient(target) |
408 | - else: |
409 | - self._client = LavaAndroidClient(target) |
410 | - self.test_data = LavaTestData() |
411 | - |
412 | - @property |
413 | - def client(self): |
414 | - return self._client |
415 | - |
416 | |
417 | class LavaTestData(object): |
418 | + |
419 | def __init__(self, test_id='lava'): |
420 | self.job_status = 'pass' |
421 | self.metadata = {} |
422 | |
423 | === modified file 'lava_dispatcher/actions/launch_control.py' |
424 | --- lava_dispatcher/actions/launch_control.py 2011-06-27 04:55:08 +0000 |
425 | +++ lava_dispatcher/actions/launch_control.py 2011-07-22 22:31:22 +0000 |
426 | @@ -32,8 +32,8 @@ |
427 | class cmd_submit_results_on_host(BaseAction): |
428 | def run(self, server, stream): |
429 | xmlrpc_url = "%s/xml-rpc/" % server |
430 | - srv = xmlrpclib.ServerProxy(xmlrpc_url, |
431 | - allow_none=True, use_datetime=True) |
432 | + srv = xmlrpclib.ServerProxy(xmlrpc_url, allow_none=True, |
433 | + use_datetime=True) |
434 | |
435 | client = self.client |
436 | call("cd /tmp/%s/; ls *.bundle > bundle.lst" % LAVA_RESULT_DIR, |
437 | @@ -68,16 +68,7 @@ |
438 | class cmd_submit_results(BaseAction): |
439 | all_bundles = [] |
440 | |
441 | - def run(self, server, stream, result_disk="testrootfs"): |
442 | - """Submit test results to a launch-control server |
443 | - :param server: URL of the launch-control server |
444 | - :param stream: Stream on the launch-control server to save the result to |
445 | - """ |
446 | - #Create l-c server connection |
447 | - xmlrpc_url = "%s/xml-rpc/" % server |
448 | - srv = xmlrpclib.ServerProxy(xmlrpc_url, |
449 | - allow_none=True, use_datetime=True) |
450 | - |
451 | + def retrieve_remote_results(self, client): |
452 | client = self.client |
453 | try: |
454 | self.in_master_shell() |
455 | @@ -133,17 +124,60 @@ |
456 | |
457 | self.all_bundles.append(json.loads(content)) |
458 | |
459 | + def run(self, server, stream, result_disk="testrootfs", skip_remote=False): |
460 | + """Submit test results to a launch-control server |
461 | + :param server: URL of the launch-control server |
462 | + :param stream: Stream on the launch-control server to save the result to |
463 | + """ |
464 | + #Create l-c server connection |
465 | + xmlrpc_url = "%s/xml-rpc/" % server |
466 | + srv = xmlrpclib.ServerProxy(xmlrpc_url, |
467 | + allow_none=True, use_datetime=True) |
468 | + |
469 | main_bundle = self.combine_bundles() |
470 | - self.context.test_data.add_seriallog( |
471 | - self.context.client.get_seriallog()) |
472 | - main_bundle['test_runs'].append(self.context.test_data.get_test_run()) |
473 | - for test_run in main_bundle['test_runs']: |
474 | - attributes = test_run.get('attributes',{}) |
475 | - attributes.update(self.context.test_data.get_metadata()) |
476 | - test_run['attributes'] = attributes |
477 | + main_test_runs = main_bundle['test_runs'] |
478 | + counter=0 |
479 | + |
480 | + if len(self.context.subcontext_list) > 0: |
481 | + for context in self.context.subcontext_list: |
482 | + if not skip_remote: |
483 | + self.retrieve_remote_results(context.client) |
484 | + |
485 | + client_bundle = { |
486 | + "test_runs": [], |
487 | + "format": "Dashboard Bundle Format 1.2" |
488 | + } |
489 | + context.test_data.add_seriallog( |
490 | + context.client.get_seriallog()) |
491 | + client_bundle['test_runs'].append(context.test_data.get_test_run()) |
492 | + for test_run in client_bundle['test_runs']: |
493 | + attributes = test_run.get('attributes',{}) |
494 | + attributes.update(context.test_data.get_metadata()) |
495 | + test_run['attributes'] = attributes |
496 | + if len(main_test_runs) == 0: |
497 | + main_test_runs.append(test_run.copy()) |
498 | + main_test_runs[0]['test_results'] = [] |
499 | + main_test_runs[0]['attachments'] = [] |
500 | + self.merge_thread_run(main_test_runs, |
501 | + test_run, |
502 | + context.client.hostname, |
503 | + counter) |
504 | + counter += 1 |
505 | json_bundle = json.dumps(main_bundle) |
506 | srv.put(json_bundle, 'lava-dispatcher.bundle', stream) |
507 | |
508 | + def merge_thread_run(self, main_run, new_run, hostname, serial_number): |
509 | + for test_case in new_run['test_results']: |
510 | + new_id = "%s_%s_%d" % (hostname, test_case['test_case_id'], serial_number) |
511 | + new_test_case = test_case |
512 | + new_test_case['test_case_id'] = new_id |
513 | + main_run[0]['test_results'].append(new_test_case) |
514 | + |
515 | + for attachment in new_run['attachments']: |
516 | + new_attachment = attachment |
517 | + new_attachment['pathname'] = "%s_%s_%d" % (hostname, attachment['pathname'], serial_number) |
518 | + main_run[0]['attachments'].append(new_attachment) |
519 | + |
520 | def combine_bundles(self): |
521 | if not self.all_bundles: |
522 | return { |
523 | |
524 | === added file 'lava_dispatcher/actions/shell_commands.py' |
525 | --- lava_dispatcher/actions/shell_commands.py 1970-01-01 00:00:00 +0000 |
526 | +++ lava_dispatcher/actions/shell_commands.py 2011-07-22 22:31:22 +0000 |
527 | @@ -0,0 +1,42 @@ |
528 | +# Copyright (C) 2011 Calxeda, Inc. |
529 | +# |
530 | +# This file is part of LAVA Dispatcher. |
531 | +# |
532 | +# LAVA Dispatcher is free software; you can redistribute it and/or modify |
533 | +# it under the terms of the GNU General Public License as published by |
534 | +# the Free Software Foundation; either version 2 of the License, or |
535 | +# (at your option) any later version. |
536 | +# |
537 | +# LAVA Dispatcher is distributed in the hope that it will be useful, |
538 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
539 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
540 | +# GNU General Public License for more details. |
541 | +# |
542 | +# You should have received a copy of the GNU General Public License |
543 | +# along with this program; if not, see <http://www.gnu.org/licenses>. |
544 | + |
545 | +from lava_dispatcher.actions import BaseAction |
546 | +from lava_dispatcher.client import OperationFailed |
547 | +from lava_dispatcher.config import MASTER_STR |
548 | + |
549 | +class cmd_scp(BaseAction): |
550 | + |
551 | + def run(self, username, host, filename, target_path): |
552 | + client = self.client |
553 | + cmd = 'scp %s %s@%s:%s' % (filename, username, host, target_path) |
554 | + client.run_shell_command(cmd, MASTER_STR) |
555 | + # TODO XXX: This does not handle authentication issues (password, |
556 | + # new SSH key, changed SSH key, etc.) |
557 | + |
558 | + |
559 | +# TODO XXX: This class should derive from TestAction, as defined in |
560 | +# the result-reporting branch, in order to report pass/fail results |
561 | +#based on pattern matching |
562 | +class cmd_verify_file_present(BaseAction): |
563 | + |
564 | +# fail_patterns = ['No such file or directory'] |
565 | + |
566 | + def run(self, file): |
567 | + client = self.client |
568 | + cmd = 'ls %s' % (file) |
569 | + client.run_shell_command(cmd, MASTER_STR) |
570 | |
571 | === added file 'lava_dispatcher/actions/sync_to_label.py' |
572 | --- lava_dispatcher/actions/sync_to_label.py 1970-01-01 00:00:00 +0000 |
573 | +++ lava_dispatcher/actions/sync_to_label.py 2011-07-22 22:31:22 +0000 |
574 | @@ -0,0 +1,22 @@ |
575 | +# Copyright (C) 2011 Calxeda, Inc. |
576 | +# |
577 | +# This file is part of LAVA Dispatcher. |
578 | +# |
579 | +# LAVA Dispatcher is free software; you can redistribute it and/or modify |
580 | +# it under the terms of the GNU General Public License as published by |
581 | +# the Free Software Foundation; either version 2 of the License, or |
582 | +# (at your option) any later version. |
583 | +# |
584 | +# LAVA Dispatcher is distributed in the hope that it will be useful, |
585 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
586 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
587 | +# GNU General Public License for more details. |
588 | +# |
589 | +# You should have received a copy of the GNU General Public License |
590 | +# along with this program; if not, see <http://www.gnu.org/licenses>. |
591 | + |
592 | +from lava_dispatcher.actions import BaseAction |
593 | + |
594 | +class cmd_sync_to_label(BaseAction): |
595 | + def run(self, label, block): |
596 | + self.context.sync_master.sync_to(label, block) |
597 | |
598 | === added file 'lava_dispatcher/sync.py' |
599 | --- lava_dispatcher/sync.py 1970-01-01 00:00:00 +0000 |
600 | +++ lava_dispatcher/sync.py 2011-07-22 22:31:22 +0000 |
601 | @@ -0,0 +1,47 @@ |
602 | +# Copyright (C) 2011 Calxeda, Inc. |
603 | +# |
604 | +# This file is part of LAVA Dispatcher. |
605 | +# |
606 | +# LAVA Dispatcher is free software; you can redistribute it and/or modify |
607 | +# it under the terms of the GNU General Public License as published by |
608 | +# the Free Software Foundation; either version 2 of the License, or |
609 | +# (at your option) any later version. |
610 | +# |
611 | +# LAVA Dispatcher is distributed in the hope that it will be useful, |
612 | +# but WITHOUT ANY WARRANTY; without even the implied warranty of |
613 | +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
614 | +# GNU General Public License for more details. |
615 | +# |
616 | +# You should have received a copy of the GNU General Public License |
617 | +# along with this program; if not, see <http://www.gnu.org/licenses>. |
618 | + |
619 | +from threading import Event, Lock |
620 | + |
621 | +class SyncMaster(object): |
622 | + def __init__(self): |
623 | + self.eventlist_lock = Lock() |
624 | + self.eventlist = dict() |
625 | + |
626 | + class EventCount(object): |
627 | + def __init__(self): |
628 | + self.count = 0 |
629 | + self.event = Event() |
630 | + |
631 | + def add_sync_label(self, label, count): |
632 | + if not self.eventlist.get(label): |
633 | + self.eventlist[label] = self.EventCount() |
634 | + |
635 | + self.eventlist[label].count += count |
636 | + |
637 | + def sync_to(self, label, block): |
638 | + self.eventlist_lock.acquire() |
639 | + try: |
640 | + self.eventlist[label].count -= 1 |
641 | + if self.eventlist[label].count == 0: |
642 | + self.eventlist[label].event.set() |
643 | + finally: |
644 | + self.eventlist_lock.release() |
645 | + if block: |
646 | + self.eventlist[label].event.wait() |
647 | + |
648 | + |
649 | \ No newline at end of file |
650 | |
651 | === modified file 'lava_dispatcher/utils.py' |
652 | --- lava_dispatcher/utils.py 2011-06-27 04:55:08 +0000 |
653 | +++ lava_dispatcher/utils.py 2011-07-22 22:31:22 +0000 |
654 | @@ -22,6 +22,7 @@ |
655 | import shutil |
656 | import urllib2 |
657 | import urlparse |
658 | +from IPy import IP |
659 | |
660 | from lava_dispatcher.config import LAVA_CACHEDIR |
661 | |
662 | @@ -66,3 +67,10 @@ |
663 | path = os.path.join(LAVA_CACHEDIR, url_parts.netloc, |
664 | url_parts.path.lstrip(os.sep)) |
665 | return path |
666 | + |
667 | +def ip_addr_range(a, b): |
668 | + int_a = IP(a).int() |
669 | + int_b = IP(b).int() |
670 | + |
671 | + return [IP(x) for x in range(int_a, int_b + 1)] |
672 | + |