Merge lp:~david-schwarz/lava-dispatcher/multi-target into lp:lava-dispatcher

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
Reviewer Review Type Date Requested Status
Linaro Validation Team Pending
Review via email: mp+68917@code.launchpad.net

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
=== added file 'doc/multi-target_test.json'
--- doc/multi-target_test.json 1970-01-01 00:00:00 +0000
+++ doc/multi-target_test.json 2011-07-22 22:31:22 +0000
@@ -0,0 +1,46 @@
1{
2 "job_name": "multi-target_test",
3 "client_groups": [
4 {
5 "name": "group1",
6 "target_hosts": ["192.168.10.1-192.168.10.10"]
7 },
8 {
9 "name": "group2",
10 "target_hosts": ["192.168.85.11",
11 "192.168.87.28",
12 "host1.linaro.org",
13 "host2",
14 "host16.hyphenated-domain.com"]
15 }
16 ],
17 "timeout": 18000,
18 "actions": [
19 {
20 "command": "ls",
21 "target_groups": ["group1"]
22 },
23 {
24 "command": "sync_to_label",
25 "target_groups": ["group1","group2"],
26 "parameters":
27 {
28 "label": "ls_done",
29 "block": true
30 }
31 },
32 {
33 "command": "date",
34 "target_groups": ["group1", "group2"]
35 },
36 {
37 "command": "submit_results",
38 "parameters":
39 {
40 "server": "http://validation.linaro.org/launch-control",
41 "stream": "/anonymous/ls-test/",
42 "skip_remote": true
43 }
44 }
45 ]
46}
047
=== added file 'doc/multi-target_test_2.json'
--- doc/multi-target_test_2.json 1970-01-01 00:00:00 +0000
+++ doc/multi-target_test_2.json 2011-07-22 22:31:22 +0000
@@ -0,0 +1,81 @@
1{
2 "job_name": "multi-target_test_2",
3 "client_groups": [
4 {
5 "name": "group1",
6 "target_hosts": ["panda01"]
7 },
8 {
9 "name": "group2",
10 "target_hosts": ["panda02"]
11 }
12 ],
13 "timeout": 18000,
14 "actions": [
15 {
16 "command": "sync_to_label",
17 "target_groups": ["group1","group2"],
18 "parameters":
19 {
20 "label": "all_clients_up",
21 "block": true
22 }
23 },
24 {
25 "target_groups": ["group1"],
26 "command": "scp",
27 "parameters":
28 {
29 "username": "tester",
30 "host": "10.1.1.1",
31 "filename": "~/exists.txt",
32 "target_path": ""
33 }
34 },
35 {
36 "target_groups": ["group1"],
37 "command": "scp",
38 "parameters":
39 {
40 "username": "tester",
41 "host": "10.1.1.1",
42 "filename": "~/doesnotexist.txt",
43 "target_path": ""
44 }
45 },
46 {
47 "command": "sync_to_label",
48 "target_groups": ["group1","group2"],
49 "parameters":
50 {
51 "label": "wait_for_scp",
52 "block": true
53 }
54 },
55 {
56 "command": "verify_file_present",
57 "target_groups": ["group2"],
58 "parameters":
59 {
60 "file": "~/exists.txt"
61 }
62 },
63 {
64 "command": "verify_file_present",
65 "target_groups": ["group2"],
66 "parameters":
67 {
68 "file": "~/doesnotexist.txt"
69 }
70 },
71 {
72 "command": "submit_results",
73 "parameters":
74 {
75 "server": "http://validation.linaro.org/launch-control",
76 "stream": "/anonymous/panda_multi_scp/",
77 "skip_remote": true
78 }
79 }
80 ]
81}
082
=== modified file 'lava-dispatch'
--- lava-dispatch 2011-06-27 04:55:08 +0000
+++ lava-dispatch 2011-07-22 22:31:22 +0000
@@ -20,6 +20,7 @@
20# with this program; if not, see <http://www.gnu.org/licenses>.20# with this program; if not, see <http://www.gnu.org/licenses>.
2121
22import sys22import sys
23import threading
2324
24from lava_dispatcher import LavaTestJob25from lava_dispatcher import LavaTestJob
2526
@@ -28,6 +29,8 @@
28 print >> sys.stderr, "Usage:\n lava-dispatch <json job file>"29 print >> sys.stderr, "Usage:\n lava-dispatch <json job file>"
29 sys.exit(status)30 sys.exit(status)
3031
32threading.stack_size(256000);
33
31if len(sys.argv) != 2:34if len(sys.argv) != 2:
32 usage(1)35 usage(1)
3336
3437
=== modified file 'lava_dispatcher/__init__.py'
--- lava_dispatcher/__init__.py 2011-07-21 16:55:47 +0000
+++ lava_dispatcher/__init__.py 2011-07-22 22:31:22 +0000
@@ -25,51 +25,185 @@
25from uuid import uuid125from uuid import uuid1
26import base6426import base64
27import pexpect27import pexpect
28import re
29from operator import add
2830
29from lava_dispatcher.actions import get_all_cmds31from lava_dispatcher.actions import get_all_cmds
30from lava_dispatcher.client import LavaClient, CriticalError, GeneralError32from lava_dispatcher.client import LavaClient, CriticalError, GeneralError
31from lava_dispatcher.android_client import LavaAndroidClient33from lava_dispatcher.android_client import LavaAndroidClient
34<<<<<<< TREE
3235
33__version__ = "0.1.0"36__version__ = "0.1.0"
3437
35class LavaTestJob(object):38class LavaTestJob(object):
39=======
40from threading import Thread
41from lava_dispatcher.utils import ip_addr_range
42from lava_dispatcher.sync import SyncMaster
43
44class LavaTestJob(SyncMaster):
45
46>>>>>>> MERGE-SOURCE
36 def __init__(self, job_json):47 def __init__(self, job_json):
48 super(LavaTestJob, self).__init__()
37 self.job_status = 'pass'49 self.job_status = 'pass'
38 self.load_job_data(job_json)50 self.load_job_data(job_json)
39 self.context = LavaContext(self.target, self.image_type)51 self.client_group_list = dict()
52 self._init_client_threads()
53 self.lava_commands = get_all_cmds()
4054
41 def load_job_data(self, job_json):55 def load_job_data(self, job_json):
42 self.job_data = json.loads(job_json)56 self.job_data = json.loads(job_json)
4357
58 def _init_client_threads(self):
59 if not self.client_groups:
60 thread = ClientThread(self.target, self.image_type,
61 self.target_type, self)
62 self.client_group_list['default'] = thread
63 else:
64 for group in self.client_groups:
65 group_name = group['name']
66 self.client_group_list[group_name] = []
67 for hostname in self._gen_host(group):
68 image_type = group.get('image_type')
69 target_type = group.get('target_type')
70 thread = ClientThread(hostname, image_type, target_type,
71 self)
72 self.client_group_list[group_name].append(thread)
73
74 def _gen_host(self, group):
75 for host_str in group['target_hosts']:
76 if re.match('^[\d\.]+-[\d\.]+$', host_str):
77 range = host_str.split('-')
78 assert len(range) == 2
79 for host in ip_addr_range(*range):
80 yield host
81 else:
82 yield host_str
83
84 def _iter_threads(self, thread_group_list):
85 for thread_group in thread_group_list.itervalues():
86 for thread in thread_group:
87 yield thread
88
44 @property89 @property
45 def target(self):90 def target(self):
46 return self.job_data['target']91 return self.job_data.get('target')
4792
48 @property93 @property
49 def image_type(self):94 def image_type(self):
50 if self.job_data.has_key('image_type'):95 return self.job_data.get('image_type')
51 return self.job_data['image_type']96
97 @property
98 def target_type(self):
99 return self.job_data.get('target_type')
100
101 @property
102 def client_groups(self):
103 return self.job_data.get('client_groups')
52104
53 def run(self):105 def run(self):
54 lava_commands = get_all_cmds()
55
56 if self.job_data['actions'][-1]['command'] == 'submit_results':106 if self.job_data['actions'][-1]['command'] == 'submit_results':
57 submit_results = self.job_data['actions'].pop(-1)107 submit_results = self.job_data['actions'].pop(-1)
58 else:108 else:
59 submit_results = None109 submit_results = None
60110
111 for cmd in self.job_data['actions']:
112 target_groups = cmd.get('target_groups')
113 if not target_groups:
114 target_groups = {'default' : self.job_data['target']}
115 params = cmd.get('parameters', {})
116 metadata = cmd.get('metadata', {})
117
118 if cmd['command'] == 'sync_to_label':
119 self.init_sync_label(target_groups, params)
120
121 for group_name in target_groups:
122 for client_thread in self.client_group_list[group_name]:
123 client_thread.add_action(self.lava_commands[cmd['command']],
124 cmd['command'],
125 params,
126 metadata)
127 thread_iter = self._iter_threads(self.client_group_list)
128 for client_thread in thread_iter:
129 client_thread.start()
130 thread_iter = self._iter_threads(self.client_group_list)
131 for client_thread in thread_iter:
132 client_thread.join()
133
134 if submit_results:
135 results_context = LavaContext(self)
136 thread_iter = self._iter_threads(self.client_group_list)
137 for client_thread in thread_iter:
138 results_context.subcontext_list.append(client_thread.context)
139
140 params = submit_results.get('parameters', {})
141 action = self.lava_commands[submit_results['command']](
142 results_context)
143 action.run(**params)
144
145 def init_sync_label(self, host_groups, params):
146 label = params.get('label')
147 if not label:
148 print 'Job Error in sync_to_label action: No label'
149 raise
150
151 groups = map(self.client_group_list.get, host_groups)
152 size = reduce(add, map(len, groups))
153 self.add_sync_label(label, size)
154
155
156class LavaContext(object):
157 def __init__(self, sync_master):
158 self.test_data = LavaTestData()
159 self.sync_master = sync_master
160 self.subcontext_list = []
161
162 @property
163 def client(self):
164 return self._client
165
166
167class ActionThunk(object):
168 def __init__(self, action, params, metadata, action_name):
169 self.action = action
170 self.params = params
171 self.metadata = metadata
172 self.action_name = action_name
173
174 def run(self):
175 self.action.context.test_data.add_metadata(self.metadata)
176 self.action.run(**self.params)
177
178
179class ClientThread(Thread):
180 def __init__(self, hostname, image_type, target_type, sync_master):
181 super(ClientThread, self).__init__()
182 self.action_list = []
183 self.context = LavaContext(sync_master)
184 if image_type == "android":
185 self.context.client_class = LavaAndroidClient
186 self.context.image_type = image_type
187 self.context.hostname = hostname
188 else:
189 # conmux / serial
190 self.context.client_class = LavaClient
191 self.context.hostname = hostname
192
193 def add_action(self, action_class, action_name, params, metadata):
194 action = action_class(self.context)
195 self.action_list.append(ActionThunk(action, params, metadata, action_name))
196
197 def run(self):
198 self.context._client = self.context.client_class(self.context.hostname)
199
61 try:200 try:
62 for cmd in self.job_data['actions']:201 for action in self.action_list:
63 params = cmd.get('parameters', {})
64 metadata = cmd.get('metadata', {})
65 metadata['target.hostname'] = self.target
66 self.context.test_data.add_metadata(metadata)
67 action = lava_commands[cmd['command']](self.context)
68 try:202 try:
69 status = 'fail'203 status = 'fail'
70 action.run(**params)204 action.run()
71 except CriticalError, err:205 except CriticalError, err:
72 raise err206 raise
73 except (pexpect.TIMEOUT, GeneralError), err:207 except (pexpect.TIMEOUT, GeneralError), err:
74 pass208 pass
75 except Exception, err:209 except Exception, err:
@@ -77,45 +211,28 @@
77 else:211 else:
78 status = 'pass'212 status = 'pass'
79 finally:213 finally:
214 err_msg = ""
215 command = action.action_name
80 if status == 'fail':216 if status == 'fail':
81 err_msg = "Lava failed at action " + cmd['command'] \217 err_msg = "Lava failed at action %s with error: %s\n" %\
82 + " with error: " + str(err) + "\n"218 (command, str(err))
83 if cmd['command'] == 'lava_test_run':219 if command == 'lava_test_run':
84 err_msg = err_msg + "Lava failed with test: " \220 err_msg += "Lava failed on test: %s" %\
85 + test_name221 action.params.get('test_name')
86 exc_type, exc_value, exc_traceback = sys.exc_info()222 exc_type, exc_value, exc_traceback = sys.exc_info()
87 err_msg = err_msg + repr(traceback.format_tb(exc_traceback))223 err_msg += repr(traceback.format_tb(exc_traceback))
88 print >> sys.stderr, err_msg224 print >> sys.stderr, err_msg
89 else:225 self.context.test_data.add_result(action.action_name,
90 err_msg = ""
91 self.context.test_data.add_result(cmd['command'],
92 status, err_msg)226 status, err_msg)
93 except:227 except:
94 #Capture all user-defined and non-user-defined critical errors228 # Capture all user-defined and non-user-defined critical errors
229 # Do not raise--allow thread to exit gracefully
230 print "Exception in thread for %s. "\
231 "Exiting thread." % self.context.hostname
95 self.context.test_data.job_status='fail'232 self.context.test_data.job_status='fail'
96 raise
97 finally:
98 if submit_results:
99 params = submit_results.get('parameters', {})
100 action = lava_commands[submit_results['command']](
101 self.context)
102 action.run(**params)
103
104
105class LavaContext(object):
106 def __init__(self, target, image_type):
107 if image_type != "android":
108 self._client = LavaClient(target)
109 else:
110 self._client = LavaAndroidClient(target)
111 self.test_data = LavaTestData()
112
113 @property
114 def client(self):
115 return self._client
116
117233
118class LavaTestData(object):234class LavaTestData(object):
235
119 def __init__(self, test_id='lava'):236 def __init__(self, test_id='lava'):
120 self.job_status = 'pass'237 self.job_status = 'pass'
121 self.metadata = {}238 self.metadata = {}
122239
=== modified file 'lava_dispatcher/actions/launch_control.py'
--- lava_dispatcher/actions/launch_control.py 2011-06-27 04:55:08 +0000
+++ lava_dispatcher/actions/launch_control.py 2011-07-22 22:31:22 +0000
@@ -32,8 +32,8 @@
32class cmd_submit_results_on_host(BaseAction):32class cmd_submit_results_on_host(BaseAction):
33 def run(self, server, stream):33 def run(self, server, stream):
34 xmlrpc_url = "%s/xml-rpc/" % server34 xmlrpc_url = "%s/xml-rpc/" % server
35 srv = xmlrpclib.ServerProxy(xmlrpc_url,35 srv = xmlrpclib.ServerProxy(xmlrpc_url, allow_none=True,
36 allow_none=True, use_datetime=True)36 use_datetime=True)
3737
38 client = self.client38 client = self.client
39 call("cd /tmp/%s/; ls *.bundle > bundle.lst" % LAVA_RESULT_DIR,39 call("cd /tmp/%s/; ls *.bundle > bundle.lst" % LAVA_RESULT_DIR,
@@ -68,16 +68,7 @@
68class cmd_submit_results(BaseAction):68class cmd_submit_results(BaseAction):
69 all_bundles = []69 all_bundles = []
7070
71 def run(self, server, stream, result_disk="testrootfs"):71 def retrieve_remote_results(self, client):
72 """Submit test results to a launch-control server
73 :param server: URL of the launch-control server
74 :param stream: Stream on the launch-control server to save the result to
75 """
76 #Create l-c server connection
77 xmlrpc_url = "%s/xml-rpc/" % server
78 srv = xmlrpclib.ServerProxy(xmlrpc_url,
79 allow_none=True, use_datetime=True)
80
81 client = self.client72 client = self.client
82 try:73 try:
83 self.in_master_shell()74 self.in_master_shell()
@@ -133,17 +124,60 @@
133124
134 self.all_bundles.append(json.loads(content))125 self.all_bundles.append(json.loads(content))
135126
127 def run(self, server, stream, result_disk="testrootfs", skip_remote=False):
128 """Submit test results to a launch-control server
129 :param server: URL of the launch-control server
130 :param stream: Stream on the launch-control server to save the result to
131 """
132 #Create l-c server connection
133 xmlrpc_url = "%s/xml-rpc/" % server
134 srv = xmlrpclib.ServerProxy(xmlrpc_url,
135 allow_none=True, use_datetime=True)
136
136 main_bundle = self.combine_bundles()137 main_bundle = self.combine_bundles()
137 self.context.test_data.add_seriallog(138 main_test_runs = main_bundle['test_runs']
138 self.context.client.get_seriallog())139 counter=0
139 main_bundle['test_runs'].append(self.context.test_data.get_test_run())140
140 for test_run in main_bundle['test_runs']:141 if len(self.context.subcontext_list) > 0:
141 attributes = test_run.get('attributes',{})142 for context in self.context.subcontext_list:
142 attributes.update(self.context.test_data.get_metadata())143 if not skip_remote:
143 test_run['attributes'] = attributes144 self.retrieve_remote_results(context.client)
145
146 client_bundle = {
147 "test_runs": [],
148 "format": "Dashboard Bundle Format 1.2"
149 }
150 context.test_data.add_seriallog(
151 context.client.get_seriallog())
152 client_bundle['test_runs'].append(context.test_data.get_test_run())
153 for test_run in client_bundle['test_runs']:
154 attributes = test_run.get('attributes',{})
155 attributes.update(context.test_data.get_metadata())
156 test_run['attributes'] = attributes
157 if len(main_test_runs) == 0:
158 main_test_runs.append(test_run.copy())
159 main_test_runs[0]['test_results'] = []
160 main_test_runs[0]['attachments'] = []
161 self.merge_thread_run(main_test_runs,
162 test_run,
163 context.client.hostname,
164 counter)
165 counter += 1
144 json_bundle = json.dumps(main_bundle)166 json_bundle = json.dumps(main_bundle)
145 srv.put(json_bundle, 'lava-dispatcher.bundle', stream)167 srv.put(json_bundle, 'lava-dispatcher.bundle', stream)
146168
169 def merge_thread_run(self, main_run, new_run, hostname, serial_number):
170 for test_case in new_run['test_results']:
171 new_id = "%s_%s_%d" % (hostname, test_case['test_case_id'], serial_number)
172 new_test_case = test_case
173 new_test_case['test_case_id'] = new_id
174 main_run[0]['test_results'].append(new_test_case)
175
176 for attachment in new_run['attachments']:
177 new_attachment = attachment
178 new_attachment['pathname'] = "%s_%s_%d" % (hostname, attachment['pathname'], serial_number)
179 main_run[0]['attachments'].append(new_attachment)
180
147 def combine_bundles(self):181 def combine_bundles(self):
148 if not self.all_bundles:182 if not self.all_bundles:
149 return {183 return {
150184
=== added file 'lava_dispatcher/actions/shell_commands.py'
--- lava_dispatcher/actions/shell_commands.py 1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/shell_commands.py 2011-07-22 22:31:22 +0000
@@ -0,0 +1,42 @@
1# Copyright (C) 2011 Calxeda, Inc.
2#
3# This file is part of LAVA Dispatcher.
4#
5# LAVA Dispatcher is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LAVA Dispatcher is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, see <http://www.gnu.org/licenses>.
17
18from lava_dispatcher.actions import BaseAction
19from lava_dispatcher.client import OperationFailed
20from lava_dispatcher.config import MASTER_STR
21
22class cmd_scp(BaseAction):
23
24 def run(self, username, host, filename, target_path):
25 client = self.client
26 cmd = 'scp %s %s@%s:%s' % (filename, username, host, target_path)
27 client.run_shell_command(cmd, MASTER_STR)
28 # TODO XXX: This does not handle authentication issues (password,
29 # new SSH key, changed SSH key, etc.)
30
31
32# TODO XXX: This class should derive from TestAction, as defined in
33# the result-reporting branch, in order to report pass/fail results
34#based on pattern matching
35class cmd_verify_file_present(BaseAction):
36
37# fail_patterns = ['No such file or directory']
38
39 def run(self, file):
40 client = self.client
41 cmd = 'ls %s' % (file)
42 client.run_shell_command(cmd, MASTER_STR)
043
=== added file 'lava_dispatcher/actions/sync_to_label.py'
--- lava_dispatcher/actions/sync_to_label.py 1970-01-01 00:00:00 +0000
+++ lava_dispatcher/actions/sync_to_label.py 2011-07-22 22:31:22 +0000
@@ -0,0 +1,22 @@
1# Copyright (C) 2011 Calxeda, Inc.
2#
3# This file is part of LAVA Dispatcher.
4#
5# LAVA Dispatcher is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LAVA Dispatcher is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, see <http://www.gnu.org/licenses>.
17
18from lava_dispatcher.actions import BaseAction
19
20class cmd_sync_to_label(BaseAction):
21 def run(self, label, block):
22 self.context.sync_master.sync_to(label, block)
023
=== added file 'lava_dispatcher/sync.py'
--- lava_dispatcher/sync.py 1970-01-01 00:00:00 +0000
+++ lava_dispatcher/sync.py 2011-07-22 22:31:22 +0000
@@ -0,0 +1,47 @@
1# Copyright (C) 2011 Calxeda, Inc.
2#
3# This file is part of LAVA Dispatcher.
4#
5# LAVA Dispatcher is free software; you can redistribute it and/or modify
6# it under the terms of the GNU General Public License as published by
7# the Free Software Foundation; either version 2 of the License, or
8# (at your option) any later version.
9#
10# LAVA Dispatcher is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU General Public License for more details.
14#
15# You should have received a copy of the GNU General Public License
16# along with this program; if not, see <http://www.gnu.org/licenses>.
17
18from threading import Event, Lock
19
20class SyncMaster(object):
21 def __init__(self):
22 self.eventlist_lock = Lock()
23 self.eventlist = dict()
24
25 class EventCount(object):
26 def __init__(self):
27 self.count = 0
28 self.event = Event()
29
30 def add_sync_label(self, label, count):
31 if not self.eventlist.get(label):
32 self.eventlist[label] = self.EventCount()
33
34 self.eventlist[label].count += count
35
36 def sync_to(self, label, block):
37 self.eventlist_lock.acquire()
38 try:
39 self.eventlist[label].count -= 1
40 if self.eventlist[label].count == 0:
41 self.eventlist[label].event.set()
42 finally:
43 self.eventlist_lock.release()
44 if block:
45 self.eventlist[label].event.wait()
46
47
0\ No newline at end of file48\ No newline at end of file
149
=== modified file 'lava_dispatcher/utils.py'
--- lava_dispatcher/utils.py 2011-06-27 04:55:08 +0000
+++ lava_dispatcher/utils.py 2011-07-22 22:31:22 +0000
@@ -22,6 +22,7 @@
22import shutil22import shutil
23import urllib223import urllib2
24import urlparse24import urlparse
25from IPy import IP
2526
26from lava_dispatcher.config import LAVA_CACHEDIR27from lava_dispatcher.config import LAVA_CACHEDIR
2728
@@ -66,3 +67,10 @@
66 path = os.path.join(LAVA_CACHEDIR, url_parts.netloc,67 path = os.path.join(LAVA_CACHEDIR, url_parts.netloc,
67 url_parts.path.lstrip(os.sep))68 url_parts.path.lstrip(os.sep))
68 return path69 return path
70
71def ip_addr_range(a, b):
72 int_a = IP(a).int()
73 int_b = IP(b).int()
74
75 return [IP(x) for x in range(int_a, int_b + 1)]
76

Subscribers

People subscribed via source and target branches