Merge lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt into lp:openerp-tools

Proposed by Vo Minh Thu
Status: Merged
Merged at revision: 197
Proposed branch: lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt
Merge into: lp:openerp-tools
Diff against target: 1776 lines (+684/-606)
13 files modified
openerp-runbot/openerp-runbot (+6/-1)
openerp-runbot/openerprunbot/__init__.py (+1/-0)
openerp-runbot/openerprunbot/core.py (+154/-578)
openerp-runbot/openerprunbot/jobs/__init__.py (+7/-0)
openerp-runbot/openerprunbot/jobs/install_all.py (+366/-0)
openerp-runbot/openerprunbot/jobs/migrate_script.py (+61/-0)
openerp-runbot/openerprunbot/misc.py (+54/-1)
openerp-runbot/openerprunbot/templates/branches.html.mako (+2/-1)
openerp-runbot/openerprunbot/templates/defs.html.mako (+24/-18)
openerp-runbot/openerprunbot/templates/nginx.conf.mako (+1/-1)
openerp-runbot/starting.html (+1/-0)
openerp-runbot/stopped.html (+1/-0)
openerp-runbot/try-template.py (+6/-6)
To merge this branch: bzr merge lp:~openerp-dev/openerp-tools/trunk-configured-branches-vmt
Reviewer Review Type Date Requested Status
OpenERP R&D Team Pending
Review via email: mp+113346@code.launchpad.net
To post a comment you must log in.
201. By OpenERP Online

[FIX] runbot: the completing branches were used in place of the original ones.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'openerp-runbot/openerp-runbot'
--- openerp-runbot/openerp-runbot 2012-04-19 15:34:05 +0000
+++ openerp-runbot/openerp-runbot 2012-07-04 11:46:17 +0000
@@ -57,6 +57,7 @@
57 parser.add_option("--test", metavar="INT", default=1, help="run tests flag (%default)")57 parser.add_option("--test", metavar="INT", default=1, help="run tests flag (%default)")
58 parser.add_option("--workers", metavar="INT", default=4, help="number of workers (%default)")58 parser.add_option("--workers", metavar="INT", default=4, help="number of workers (%default)")
59 parser.add_option("--start-job-id", metavar="INT", default=0, help="initial job id (%default)")59 parser.add_option("--start-job-id", metavar="INT", default=0, help="initial job id (%default)")
60 parser.add_option("--job-type", metavar="JOB_TYPE", default='install_all', help="name of the default job type (%default)")
60 parser.add_option("--debug", action="store_true", help="ease debugging by e.g. limiting branches")61 parser.add_option("--debug", action="store_true", help="ease debugging by e.g. limiting branches")
61 o, a = parser.parse_args(sys.argv)62 o, a = parser.parse_args(sys.argv)
62 if o.init:63 if o.init:
@@ -64,6 +65,9 @@
64 elif o.clean:65 elif o.clean:
65 runbot_clean(o.dir)66 runbot_clean(o.dir)
66 elif o.run:67 elif o.run:
68 assert o.job_type in openerprunbot.jobs.JOBS
69 path = os.path.dirname(sys.modules['__main__'].__file__)
70 openerprunbot.core.run(["cp", os.path.join(path, "starting.html"), "static/index.html"])
67 openerprunbot.server.read_state()71 openerprunbot.server.read_state()
68 openerprunbot.server.start_server(int(o.nginx_port)-1)72 openerprunbot.server.start_server(int(o.nginx_port)-1)
69 server_net_port = int(o.nginx_port) + int(o.number) * 273 server_net_port = int(o.nginx_port) + int(o.number) * 2
@@ -76,10 +80,11 @@
76 r = openerprunbot.core.RunBot(o.dir, o.poll, server_net_port,80 r = openerprunbot.core.RunBot(o.dir, o.poll, server_net_port,
77 server_xml_port, client_web_port, o.number, o.nginx_port,81 server_xml_port, client_web_port, o.number, o.nginx_port,
78 o.nginx_domain, o.test, o.workers, int(o.start_job_id),82 o.nginx_domain, o.test, o.workers, int(o.start_job_id),
79 o.debug, lp)83 o.debug, lp, o.job_type)
80 runbot_kill_msg()84 runbot_kill_msg()
81 r.loop()85 r.loop()
82 runbot_kill_msg()86 runbot_kill_msg()
87 openerprunbot.core.run(["cp", os.path.join(path, "stopped.html"), "static/index.html"])
83 print "Last used job id:", r.current_job_id88 print "Last used job id:", r.current_job_id
84 else:89 else:
85 parser.print_help()90 parser.print_help()
8691
=== modified file 'openerp-runbot/openerprunbot/__init__.py'
--- openerp-runbot/openerprunbot/__init__.py 2011-12-06 23:17:14 +0000
+++ openerp-runbot/openerprunbot/__init__.py 2012-07-04 11:46:17 +0000
@@ -5,6 +5,7 @@
5import re5import re
66
7from . import core7from . import core
8from . import jobs
8from . import launchpad9from . import launchpad
9from . import misc10from . import misc
10from . import server11from . import server
1112
=== modified file 'openerp-runbot/openerprunbot/core.py'
--- openerp-runbot/openerprunbot/core.py 2012-05-07 07:21:32 +0000
+++ openerp-runbot/openerprunbot/core.py 2012-07-04 11:46:17 +0000
@@ -16,6 +16,7 @@
1616
17import openerprunbot17import openerprunbot
18from openerprunbot.misc import *18from openerprunbot.misc import *
19from openerprunbot.jobs.install_all import InstallAllJob
1920
20# Hard-coded branches we always want, used to complement monitored branches21# Hard-coded branches we always want, used to complement monitored branches
21# (i.e. to create branch groups).22# (i.e. to create branch groups).
@@ -31,67 +32,36 @@
31 'web_trunk': '~openerp/openerp-web/trunk',32 'web_trunk': '~openerp/openerp-web/trunk',
32}33}
3334
35migration_scripts_branch = None
36migration_platform_branch = None
37
34# Number of build 'slots' per branch group.38# Number of build 'slots' per branch group.
35POINTS = 539POINTS = 5
3640
41# Job states
42STATE_ALLOCATED = 'allocated'
43STATE_BROKEN = 'broken'
44STATE_PULLING = 'pulling'
45STATE_RUNNING = 'running'
46STATE_TESTING = 'testing'
47
37# This constant matches community_dashboard.py.48# This constant matches community_dashboard.py.
38NEW_MERGE_STATUS = ['Needs review', 'Code failed to merge', 'Approved']49NEW_MERGE_STATUS = ['Needs review', 'Code failed to merge', 'Approved']
3950
40#----------------------------------------------------------51#----------------------------------------------------------
41# OpenERP RunBot misc
42#----------------------------------------------------------
43
44def underscore(s):
45 return s.replace("~","").replace(":","_").replace("/","_").replace(".","_").replace(" ","_")
46
47def get_committer_info(repo_path):
48 committer_name = None
49 committer_xgram = None
50 committer_email = None
51
52 output = run_output(["bzr", "log", "--long", "-r-1"], cwd=repo_path)
53
54 committer_re = re.compile('committer: *(.+)<(.+)@(.+)>')
55 for i in output.split('\n'):
56 m = committer_re.match(i)
57 if m:
58 committer_name = m.group(1).strip()
59 committer_xgram = m.group(2)
60 committer_email = m.group(2) + '@' + m.group(3)
61 break
62
63 return committer_name, committer_xgram, committer_email
64
65def has_test_enable_flag(server_bin_path):
66 """
67 Test whether an openerp-server executable has the --test-enable flag.
68 (When the flag is present, testing for success/failure is done in a
69 different way.)
70 """
71 p1 = subprocess.Popen([server_bin_path, "--help"],
72 stdout=subprocess.PIPE)
73 p2 = subprocess.Popen(["grep", "test-enable"], stdin=p1.stdout,
74 stdout=subprocess.PIPE)
75 p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
76 output = p2.communicate()[0]
77 return output == " --test-enable Enable YAML and unit tests.\n"
78
79#----------------------------------------------------------
80# OpenERP RunBot Branch52# OpenERP RunBot Branch
81#----------------------------------------------------------53#----------------------------------------------------------
8254
83class RunBotBranch(object):55class RunBotBranch(object):
84 def __init__(self, runbot, branch, group=None,56 def __init__(self, runbot, branch, group=None,
85 overriden_repo_path=None, overriden_project_name=None):57 repo_path=None, project_name=None, trigger_build=False):
86 """58 """
87 Represent a single Launchpad branch, e.g.59 Represent a single Launchpad branch, e.g.
88 `~openerp-dev/openobject-server/trunk-foo-bar`.60 `~openerp-dev/openobject-server/trunk-foo-bar`.
89
90 A branch has an overriden_repo_path when it is a trunk or 6.{0,1} branch
91 used to complement another 'real' branch.
92 """61 """
93 self.runbot = runbot62 self.runbot = runbot
94 self.group = group63 self.group = group
64 self.trigger_build = trigger_build
9565
96 # e.g. trunk66 # e.g. trunk
97 self.name = branch.name67 self.name = branch.name
@@ -100,13 +70,10 @@
100 self.unique_name = branch.unique_name70 self.unique_name = branch.unique_name
101 self.unique_name_underscore = underscore(self.unique_name)71 self.unique_name_underscore = underscore(self.unique_name)
102 # server, addons, or web (both for openobject-client-web and openerp-web)72 # server, addons, or web (both for openobject-client-web and openerp-web)
103 if overriden_project_name:73 if project_name:
104 self.project_name = overriden_project_name74 self.project_name = project_name
105 else:75 else:
106 self.project_name = re.search("/(openobject|openerp)-(addons|server|client-web|web)/",self.unique_name).group(2)76 self.project_name = project_name_from_unique_name(self.unique_name)
107 if self.project_name == 'client-web':
108 self.project_name = 'web'
109 assert self.project_name in ('server', 'addons', 'web')
11077
111 self.lp_date_last_modified = branch.date_last_modified78 self.lp_date_last_modified = branch.date_last_modified
112 self.lp_revision_count = branch.revision_count79 self.lp_revision_count = branch.revision_count
@@ -116,10 +83,10 @@
116 self.merge_count = 083 self.merge_count = 0
11784
118 # Repository path <Root>/repo/<branchuniquename>85 # Repository path <Root>/repo/<branchuniquename>
119 self.repo_path=os.path.join(self.runbot.wd,'repo',self.unique_name_underscore)86 if repo_path:
120 self.overriden_repo_path = overriden_repo_path87 self.repo_path=repo_path
121 if overriden_repo_path:88 else:
122 self.repo_path=overriden_repo_path89 self.repo_path=os.path.join(self.runbot.wd,'repo',self.unique_name_underscore)
12390
124 self.committer_name = None91 self.committer_name = None
125 self.committer_xgram = None92 self.committer_xgram = None
@@ -134,7 +101,7 @@
134 if self.local_revision_count != self.lp_revision_count:101 if self.local_revision_count != self.lp_revision_count:
135 self.local_date_last_modified = self.lp_date_last_modified102 self.local_date_last_modified = self.lp_date_last_modified
136 self.local_revision_count = self.lp_revision_count103 self.local_revision_count = self.lp_revision_count
137 if not self.overriden_repo_path or self.group.is_configured():104 if self.trigger_build:
138 name = self.project_name105 name = self.project_name
139 self.group.need_run_reason.append(name)106 self.group.need_run_reason.append(name)
140107
@@ -142,50 +109,87 @@
142# OpenERP RunBot Grouped Branch109# OpenERP RunBot Grouped Branch
143#----------------------------------------------------------110#----------------------------------------------------------
144111
145class RunBotGroupedBranchBase(object):112class RunBotGroupedBranch(object):
146 def __init__(self, runbot, sticky):113 """
114 A single 'branch group' represents a single branch name for all our
115 projects. E.g. trunk is available in server, addons, web, and client-web.
116
117 When the corresponding project doesn't have that branch, trunk is used.
118 For instance, a branch group can represent
119 `~openerp-dev/openobject-server/trunk-foo-bar`
120 `~openerp-dev/openobject-addons/trunk-foo-bar`
121 and
122 `~openerp/openobject-web-client/6.0`
123 `~openerp/openerp-web/trunk`
124 will be used to complete the group, building the four branches together.
125
126 Alternatively a 'branch group' can be configured. In that case, all
127 branches are manually specified; no attempt is done to complete the group.
128 """
129 def __init__(self, runbot, team_name, name, version, sticky,
130 server_branch=None, client_web_branch=None,
131 web_branch=None, addons_branches=None, modules=None, job_type=None):
147 self.runbot = runbot132 self.runbot = runbot
148 self.sticky = sticky133 self.sticky = sticky
134 self.job_type = job_type
135
149 # Points are build 'slots'136 # Points are build 'slots'
150 self.points = [None for x in xrange(POINTS)]137 self.points = [None for x in xrange(POINTS)]
138
139 self.team_name = team_name
140 self.name = name # group name or manually chosen name.
141 self.name_underscore = underscore(self.name)
142 self.version = version # '6.0', '6.1', or 'trunk'
143
151 # Reason to do a build, e.g. because 'addons' was commited to.144 # Reason to do a build, e.g. because 'addons' was commited to.
152 self.need_run_reason = []145 self.need_run_reason = []
153 self.version = None146
147 self.server_branch = server_branch
148 self.web_branch = client_web_branch or web_branch
149 self.addons_branches = addons_branches
150
151 self.server = None
152 self.web = None
153 self.addons = []
154
155 self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-')))
156
157 # List of modules to install instead of `all`.
158 self.modules = modules.split(',') if modules else []
159
160 self.configured = False
161
162 # A normal build_number is maxint, a build_number is assigned when a
163 # build is manually requested or a triggering branch has a new revision.
164 # The build number is used to assign a position in the queue.
165 # The value will be reset to maxint after the build is done.
166 self.build_number = sys.maxint
167
154 # True when it is not possible to provide default branches based on168 # True when it is not possible to provide default branches based on
155 # the name.169 # the name.
156 self.wrong_matching = False170 self.wrong_matching = False
157171
158 # A normal manual_build is maxint, a manual_build is used only when
159 # manually requesting a build with a 'build' command pushed in the
160 # queue. The value will be reset to maxint after the build is done.
161 self.manual_build = sys.maxint
162
163 # List of modules to install instead of `all`.
164 self.modules = []
165
166 def add_point(self, j):172 def add_point(self, j):
167 assert not j.completed173 assert j.state == STATE_ALLOCATED
168 p = Point(self, j)174 self.points = self.points[1:] + [j] # TODO delete db and on-disk data
169 self.points = self.points[1:] + [p] # TODO delete db and on-disk data
170175
171 def complete_point(self, j):176 def complete_point(self, j):
172 assert j.completed177 assert j.state in (STATE_RUNNING, STATE_BROKEN)
173 for p in self.points + [None]:178 for p in self.points + [None]:
174 if p and p.job_id == j.job_id:179 if p and p.job_id == j.job_id:
175 break180 break
176 if p and p.state != 'running':181 if p and p.state != j.state:
177 p.update(j)
178 p.save_json()182 p.save_json()
179183
180 def all_points_completed(self):184 def all_points_completed(self):
181 for p in self.points:185 for p in self.points:
182 if p is not None and p.state != 'running':186 if p is not None and p.state not in (STATE_RUNNING, STATE_BROKEN):
183 return False187 return False
184 return True188 return True
185189
186 def is_sticky_job(self, j):190 def is_sticky_job(self, j):
187 """ Return True if the job is the latest running point of a sticky branch. """191 """ Return True if the job is the latest running point of a sticky branch. """
188 runnings = filter(lambda x: x and x.running_t0, self.points)192 runnings = filter(lambda x: x and x.state == STATE_RUNNING, self.points)
189 return self.sticky and runnings and runnings[-1].job_id == j.job_id193 return self.sticky and runnings and runnings[-1].job_id == j.job_id
190194
191 def update_state(self, state):195 def update_state(self, state):
@@ -198,43 +202,33 @@
198 if self.sticky != previous:202 if self.sticky != previous:
199 log("stickiness changed", team=self.team_name, branch=self.name, now=self.sticky)203 log("stickiness changed", team=self.team_name, branch=self.name, now=self.sticky)
200204
201 def is_configured(self):205 def repo_updates(self):
202 return isinstance(self, ConfiguredGroup)206 return [copy.copy(x) for x in self.all_branches()]
203207
204class ConfiguredGroup(RunBotGroupedBranchBase):208 def all_branches(self):
205 def __init__(self, runbot, team_name, name, version, sticky,209 r = []
206 server_branch=None, client_web_branch=None,210 r.append(self.server)
207 web_branch=None, addons_branches=None, modules=None):211 r.extend([x for x in self.addons])
208 super(ConfiguredGroup, self).__init__(runbot, sticky)212 r.append(self.web)
209 self.team_name = team_name213 if hasattr(self, 'migration_script'):
210 self.name = name # Unique manually-chosen name, not necessarily the group name.214 r.extend([self.migration_script, self.migration_platform])
211 self.name_underscore = underscore(self.name)215 return r
212 self.version = version # '6.0', '6.1', or 'trunk'216
213217 def lp_date_last_modified(self):
214 self.server_branch = server_branch218 return max(p.lp_date_last_modified for p in self.all_branches() if p and p.trigger_build)
215 self.web_branch = client_web_branch or web_branch
216 self.addons_branches = addons_branches
217
218 self.server = None
219 self.web = None
220 self.addons = []
221
222 self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-')))
223
224 self.modules = modules.split(',') if modules else []
225219
226 def is_ok(self):220 def is_ok(self):
227 """221 """
228 Test whether the group is useable, i.e. if self.populate_branches()222 Test whether the group is useable, i.e. if self.configure_branches()
229 didn't early return.223 didn't early return.
230 """224 """
231 if self.server and self.web and self.addons:225 return self.server and self.web and self.addons
232 return True
233 else:
234 return False
235226
236 def populate_branches(self, launchpad):227 # TODO all the xxxx_branch argument in __init__ can be moved here.
228 def configure_branches(self, launchpad):
229 """ Fetch the configured branches. """
237 log("runbot-populate-configured-branches")230 log("runbot-populate-configured-branches")
231 self.configured = True
238 repo = os.path.join(self.runbot.wd, 'repo')232 repo = os.path.join(self.runbot.wd, 'repo')
239 filters = {'status': NEW_MERGE_STATUS}233 filters = {'status': NEW_MERGE_STATUS}
240234
@@ -243,7 +237,7 @@
243 if not b:237 if not b:
244 log("WARNING:no such unique name", name=self.server_branch)238 log("WARNING:no such unique name", name=self.server_branch)
245 return239 return
246 self.server = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='server')240 self.server = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='server', trigger_build=True)
247 self.server.update_launchpad(b)241 self.server.update_launchpad(b)
248 self.server.merge_count = len(list(b.getMergeProposals(**filters)))242 self.server.merge_count = len(list(b.getMergeProposals(**filters)))
249243
@@ -253,7 +247,7 @@
253 if not b:247 if not b:
254 log("WARNING:no such unique name", name=self.web_branch)248 log("WARNING:no such unique name", name=self.web_branch)
255 return249 return
256 self.web = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='web')250 self.web = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='web', trigger_build=True)
257 self.web.update_launchpad(b)251 self.web.update_launchpad(b)
258 self.web.merge_count = len(list(b.getMergeProposals(**filters)))252 self.web.merge_count = len(list(b.getMergeProposals(**filters)))
259 else:253 else:
@@ -267,59 +261,13 @@
267 log("WARNING:no such unique name", name=self.addons_branches[x])261 log("WARNING:no such unique name", name=self.addons_branches[x])
268 self.addons = []262 self.addons = []
269 return263 return
270 bb = RunBotBranch(self.runbot, b, group=self, overriden_repo_path=path, overriden_project_name='addons')264 bb = RunBotBranch(self.runbot, b, group=self, repo_path=path, project_name='addons', trigger_build=True)
271 bb.update_launchpad(b)265 bb.update_launchpad(b)
272 bb.merge_count = len(list(b.getMergeProposals(**filters)))266 bb.merge_count = len(list(b.getMergeProposals(**filters)))
273 self.addons.append(bb)267 self.addons.append(bb)
274268
275 def repo_updates(self):
276 r = []
277 r.append(copy.copy(self.server))
278 for x in self.addons:
279 r.append(copy.copy(x))
280 r.append(copy.copy(self.web))
281 return r
282
283 def lp_date_last_modified(self):
284 lp_date_last_modified = None
285 for p in [self.server, self.web] + self.addons:
286 if p and (not lp_date_last_modified or p.lp_date_last_modified > lp_date_last_modified):
287 lp_date_last_modified = p.lp_date_last_modified
288 assert lp_date_last_modified # at least one project in the group
289 return lp_date_last_modified
290
291class RunBotGroupedBranch(RunBotGroupedBranchBase):
292 """
293 A single 'branch group' represents a single branch name for all our
294 projects. E.g. trunk is available in server, addons, web, and client-web.
295
296 When the corresponding project doesn't have that branch, trunk is used.
297 For instance, a branch group can represent
298 `~openerp-dev/openobject-server/trunk-foo-bar`
299 `~openerp-dev/openobject-addons/trunk-foo-bar`
300 and
301 `~openerp/openobject-web-client/6.0`
302 `~openerp/openerp-web/trunk`
303 will be used to complete the group, building the four branches together.
304
305 Alternatively a 'branch group' can be configured. In that case, all
306 branches are manually specified; no attempt is done to complete the group.
307 """
308
309 def __init__(self, runbot, b, team_name, sticky):
310 """A grouped branch can be created from any branch."""
311 super(RunBotGroupedBranch, self).__init__(runbot, sticky)
312 self.team_name = team_name
313 self.name = b.name
314 self.name_underscore = underscore(self.name)
315 self.server = None
316 self.addons = [] # Only one branch in a non-configured group.
317 self.web = None
318 self.json_path=os.path.join(runbot.wd,'static',"%s-%s.json"%(team_name, self.name.replace('_','-').replace('.','-')))
319
320 self.add_branch(b)
321
322 def add_branch(self, b):269 def add_branch(self, b):
270 """ Add a single branch (when not using configure_branches(). """
323 assert b.name == self.name271 assert b.name == self.name
324 b.group = self272 b.group = self
325 if b.project_name == 'addons':273 if b.project_name == 'addons':
@@ -363,403 +311,28 @@
363 for p in ('server', 'web'):311 for p in ('server', 'web'):
364 if not getattr(self, p):312 if not getattr(self, p):
365 b = self.runbot.main_branches[p+ending]313 b = self.runbot.main_branches[p+ending]
366 setattr(self, p, RunBotBranch(self.runbot,b,group=self,overriden_repo_path=paths[p]))314 setattr(self, p, RunBotBranch(self.runbot,b,group=self,repo_path=paths[p]))
367 getattr(self, p).update_launchpad(b)315 getattr(self, p).update_launchpad(b)
368 elif getattr(self, p).overriden_repo_path:316 elif not getattr(self, p).trigger_build:
369 b = self.runbot.main_branches[p+ending]317 b = self.runbot.main_branches[p+ending]
370 getattr(self, p).update_launchpad(b)318 getattr(self, p).update_launchpad(b)
371 if not self.addons:319 if not self.addons:
372 b = self.runbot.main_branches['addons'+ending]320 b = self.runbot.main_branches['addons'+ending]
373 self.addons = [RunBotBranch(self.runbot,b,group=self,overriden_repo_path=paths['addons'])]321 self.addons = [RunBotBranch(self.runbot,b,group=self,repo_path=paths['addons'])]
374 self.addons[0].update_launchpad(b)322 self.addons[0].update_launchpad(b)
375 elif self.addons[0].overriden_repo_path:323 elif not self.addons[0].trigger_build:
376 b = self.runbot.main_branches['addons'+ending]324 b = self.runbot.main_branches['addons'+ending]
377 self.addons[0].update_launchpad(b)325 self.addons[0].update_launchpad(b)
378326
379 def repo_updates(self):327 if self.job_type == 'migrate_script':
380 r = []328 if not hasattr(self, 'migration_script'):
381 r.append(copy.copy(self.server))329 self.migration_script = RunBotBranch(self.runbot,
382 r.append(copy.copy(self.addons[0]))330 migration_scripts_branch, group=self, repo_path='repo/openerp_migration_script', project_name='migration-scripts', trigger_build=True)
383 r.append(copy.copy(self.web))331 self.migration_platform = RunBotBranch(self.runbot,
384 return r332 migration_platform_branch, group=self, repo_path='repo/openerp_migration_platform', project_name='migration-platform', trigger_build=True)
385333 self.migration_script.update_launchpad(migration_scripts_branch)
386 def lp_date_last_modified(self):334 self.migration_platform.update_launchpad(migration_platform_branch)
387 lp_date_last_modified = None335 print "initialized group with job type <migration_script>"
388 for p in ('server', 'web'):
389 if getattr(self, p) and not getattr(self, p).overriden_repo_path \
390 and (not lp_date_last_modified or getattr(self, p).lp_date_last_modified > lp_date_last_modified):
391 lp_date_last_modified = getattr(self, p).lp_date_last_modified
392 if self.addons and not self.addons[0].overriden_repo_path \
393 and (not lp_date_last_modified or self.addons[0].lp_date_last_modified > lp_date_last_modified):
394 lp_date_last_modified = self.addons[0].lp_date_last_modified
395 assert lp_date_last_modified # at least one project in the group
396 return lp_date_last_modified
397
398#----------------------------------------------------------
399# OpenERP RunBot Worker
400#----------------------------------------------------------
401
402class Job(object):
403 """
404 A Job encapsulates all the necessary data to build a branch group for a
405 given slot. The build is done in its own worker thread.
406 """
407
408 def __init__(self, g, port, test, job_id, debug):
409 self.job_id = job_id
410 self.completed = False
411 self.team_name = g.team_name
412 self.name = g.name
413 self.name_underscore = g.name_underscore
414 self.version = g.version
415 self.debug = debug
416
417 self.repo_updates = g.repo_updates()
418 self.port = port
419 self.test = test
420 self.running_server_pid = 0
421 self.client_web_pid = 0
422 self.running_t0=0
423
424 repo = os.path.join(g.runbot.wd,'repo')
425 self.server_src = g.server.repo_path
426 self.client_web_src = g.web.repo_path if g.version == '6.0' and g.web else None
427 self.web_src = g.web.repo_path if g.web else None
428
429 # if addons is not the full addons branch use trunk
430 self.addons_src = [a.repo_path for a in g.addons]
431
432 # Running path <Root>/static/<domain>
433 self.subdomain = "%s-%s-%s"%(self.team_name, self.name.replace('_','-').replace('.','-'),self.job_id)
434 self.running_path = os.path.join(g.runbot.wd,'static',self.subdomain)
435 self.json_path = g.json_path
436 self.log_path = os.path.join(self.running_path,'logs')
437 self.flags_path = os.path.join(self.running_path,'flags')
438
439 # Server
440 self.server_path=os.path.join(self.running_path,"server")
441 self.server_bin_path=os.path.join(self.server_path,"openerp-server")
442 self.server_log_path=os.path.join(self.log_path,'server.txt')
443 self.server_log_base_path=os.path.join(self.log_path,'test-base.txt')
444 self.server_log_all_path=os.path.join(self.log_path,'test-all.txt')
445
446 # coverage
447 self.coverage_file_path=os.path.join(self.log_path,'coverage.pickle')
448 self.coverage_base_path=os.path.join(self.log_path,'coverage-base')
449 self.coverage_all_path=os.path.join(self.log_path,'coverage-all')
450
451 # Web60
452 self.client_web_pid=None
453 self.client_web_path=os.path.join(self.running_path,"client-web")
454 self.client_web_bin_path=os.path.join(self.client_web_path,"openerp-web.py")
455 self.client_web_doc_path=os.path.join(self.client_web_path,"doc")
456 self.client_web_log_path=os.path.join(self.log_path,'client-web.txt')
457
458 # test
459 self.test_base_result=None
460 self.test_base_path=os.path.join(self.log_path,'test-base.txt')
461 self.test_all_result=None
462 self.test_all_path=os.path.join(self.log_path,'test-all.txt')
463
464 self.server_net_port=g.runbot.server_net_port
465 self.server_xml_port=g.runbot.server_xml_port
466 self.client_web_port=g.runbot.client_web_port
467
468 self.db = self.name_underscore + '_' + str(self.job_id)
469 self.db_all = "%s_all" % self.db
470
471 for p in ('addons', 'server', 'web'):
472 setattr(self, p + '_committer_name', None)
473 setattr(self, p + '_committer_xgram', None)
474 setattr(self, p + '_committer_email', None)
475 self.start_time = 0
476 self.completed_time = 0
477
478 self.modules = g.modules
479
480 def spawn(self):
481 log("runbot-spawn-worker-" + str(self.job_id), group=self.name)
482 t = threading.Thread(target=self.work, name=('runbot-group-worker-' + str(self.job_id)))
483 t.daemon = True
484 t.start()
485
486 def work(self):
487 try:
488 self.pull_branches()
489 self.start_time = time.time()
490 self.start()
491 self.completed_time = time.time()
492 self.completed = True
493 log("runbot-end-worker", job=self.name)
494 except Exception, e:
495 self.completed = True
496 log("runbot-end-worker [with exception]", job=self.name)
497 print traceback.format_exc()
498
499 def pull_branches(self):
500 for b in self.repo_updates:
501 log("branch-update",branch=b.unique_name)
502 if os.path.exists(b.repo_path):
503 run(["bzr", "pull", "-d", b.repo_path, "--overwrite"])
504 else:
505 run(["bzr", "branch", "lp:%s"%b.unique_name, b.repo_path])
506 run(["bzr", "update", "-r", str(b.local_revision_count), b.repo_path])
507
508 committer_name, committer_xgram, committer_email = \
509 get_committer_info(b.repo_path)
510 b.committer_name = committer_name
511 b.committer_xgram = committer_xgram
512 b.committer_email = committer_email
513
514 def start_rsync(self):
515 log("job-start-rsync",branch=self.name)
516 for i in [self.running_path,self.log_path,self.flags_path,self.client_web_doc_path]:
517 if not os.path.exists(i):
518 os.makedirs(i)
519 # copy server
520 run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.server_src, self.server_path])
521
522 # copy addons (6.0 uses bin/addons, 6.1 and trunk use openerp/addons)
523 addons_dest = "openerp/addons"
524 if not os.path.exists(os.path.join(self.server_path,addons_dest)):
525 addons_dest = "bin/addons"
526 addons_path = os.path.join(self.server_path,addons_dest)
527 for a in self.addons_src:
528 run(["rsync","-a","--exclude",".bzr", "%s/"%a, addons_path])
529 if self.web_src and self.version != '6.0':
530 run(["rsync","-a","--exclude",".bzr", "%s/addons/"%self.web_src, addons_path])
531
532 # copy web-client
533 if self.client_web_src:
534 run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.client_web_src, self.client_web_path])
535
536 def start_createdb(self):
537 run(["psql","template1","-c","select pg_terminate_backend(procpid) from pg_stat_activity where datname in ('%s','%s')"%(self.db,self.db_all)])
538 time.sleep(3)
539 run(["dropdb",self.db])
540 run(["dropdb",self.db_all])
541 run(["createdb",self.db])
542 run(["createdb",self.db_all])
543
544 def run_log(self, cmd, logfile, env=None):
545 env = dict(os.environ, **env) if env else None
546 log("run", *cmd, logfile=logfile)
547 out=open(logfile,"w")
548 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True, env=env)
549 self.running_server_pid=p.pid
550 p.communicate()
551 return p
552
553 def resolve_server_bin_path(self):
554 # This can be done only if the files are present otherwise the if
555 # will always fail. Alternatively, server_bin_path could be a property.
556 if not os.path.exists(self.server_bin_path): # for 6.0 branches
557 self.server_bin_path=os.path.join(self.server_path,"bin","openerp-server.py")
558
559 def start_test_base(self):
560 log("job-start-server-base")
561 cmd = [self.server_bin_path,"-d",self.db,"-i","base","--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"]
562 _has_test_enable_flag = False
563 if has_test_enable_flag(self.server_bin_path):
564 cmd.append("--test-enable")
565 _has_test_enable_flag = True
566 cmd = ["coverage","run","--branch"] + cmd
567 self.run_log(cmd, logfile=self.test_base_path,env={'COVERAGE_FILE': self.coverage_file_path})
568 run(["coverage","html","-d",self.coverage_base_path,"--ignore-errors","--include=*.py"],env={'COVERAGE_FILE': self.coverage_file_path})
569 if _has_test_enable_flag:
570 success_message = "openerp.modules.loading: Modules loaded."
571 rc = not bool(run(["grep",success_message,self.test_base_path]))
572 else:
573 rc = bool(run(["grep","Traceback",self.test_base_path]))
574 self.test_base_result = rc
575
576 def start_test_all(self):
577 log("job-start-server-all")
578 mods = []
579 if not os.path.exists(os.path.join(self.server_path,"openerp/addons")):
580 mods = os.listdir(os.path.join(self.server_path,"bin/addons"))
581 elif os.path.exists(os.path.join(self.server_path,"openerp/addons")):
582 mods = os.listdir(os.path.join(self.server_path,"openerp/addons"))
583 bl = [
584 'document_ftp', 'l10n_lu',
585 '.bzrignore', '__init__.pyc', '__init__.py', 'base_quality_interrogation.py',
586 ]
587 mods = [m for m in mods if m not in bl]
588 if self.modules:
589 # TODO feedback when a requested modules does'nt exist.
590 mods = [m for m in mods if m in self.modules]
591 mods = ",".join(mods)
592 cmd = [self.server_bin_path,"-d",self.db_all,"-i",mods,"--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"]
593 _has_test_enable_flag = False
594 if has_test_enable_flag(self.server_bin_path):
595 cmd.append("--test-enable")
596 _has_test_enable_flag = True
597 cmd = ["coverage","run","--branch"] + cmd
598 self.run_log(cmd, logfile=self.test_all_path)
599 run(["coverage","html","-d",self.coverage_all_path,"--ignore-errors","--include=*.py"])
600 if _has_test_enable_flag:
601 success_message = "openerp.modules.loading: Modules loaded."
602 rc = not bool(run(["grep",success_message,self.test_all_path]))
603 else:
604 rc = bool(run(["grep","Traceback",self.test_all_path]))
605 self.test_all_result = rc
606
607 def start_server(self):
608 port = self.port
609 log("job-start-server",branch=self.name,port=port)
610 cmd=[self.server_bin_path,"--no-xmlrpcs","--netrpc-port=%d"%(self.server_net_port+port),"--xmlrpc-port=%d"%(self.server_xml_port+port)]
611 if os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi.py')) \
612 or os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi', 'core.py')):
613 cmd+=["--db-filter=%d(_.*)?$","--load=web"]
614 log("run",*cmd,log=self.server_log_path)
615 out=open(self.server_log_path,"w")
616 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True)
617 self.running_server_pid=p.pid
618
619 def start_client_web(self):
620 if not self.client_web_src:
621 return
622 port = self.port
623 log("job-start-client-web",branch=self.name,port=port)
624 config="""
625 [global]
626 server.environment = "development"
627 server.socket_host = "0.0.0.0"
628 server.socket_port = %d
629 server.thread_pool = 10
630 tools.sessions.on = True
631 log.access_level = "INFO"
632 log.error_level = "INFO"
633 tools.csrf.on = False
634 tools.log_tracebacks.on = False
635 tools.cgitb.on = True
636 openerp.server.host = 'localhost'
637 openerp.server.port = '%d'
638 openerp.server.protocol = 'socket'
639 openerp.server.timeout = 450
640 [openerp-web]
641 dblist.filter = 'BOTH'
642 dbbutton.visible = True
643 company.url = ''
644 openerp.server.host = 'localhost'
645 openerp.server.port = '%d'
646 openerp.server.protocol = 'socket'
647 openerp.server.timeout = 450
648 """%(self.client_web_port+port,self.server_net_port+port,self.server_net_port+port)
649 config=config.replace("\n ","\n")
650 cfgs = [os.path.join(self.client_web_path,"doc","openerp-web.cfg"), os.path.join(self.client_web_path,"openerp-web.cfg")]
651 for i in cfgs:
652 f=open(i,"w")
653 f.write(config)
654 f.close()
655
656 cmd=[self.client_web_bin_path]
657 log("run",*cmd,log=self.client_web_log_path)
658 out=open(self.client_web_log_path,"w")
659 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True)
660 self.client_web_pid=p.pid
661
662 def start(self):
663 log("job-start",branch=self.name,port=self.port)
664 self.start_rsync()
665 self.resolve_server_bin_path()
666 self.start_createdb()
667 try:
668 if self.test:
669 self.start_test_base()
670 if not self.debug:
671 self.start_test_all()
672 else:
673 self.test_all_result = True
674 self.start_server()
675 self.start_client_web()
676 except OSError,e:
677 log("branch-start-error",exception=e)
678 except IOError,e:
679 log("branch-start-error",exception=e)
680 self.running_t0=time.time()
681 log("branch-started",branch=self.name,port=self.port)
682
683 def stop(self):
684 log("Stopping job", id=self.job_id, branch=self.name)
685 if self.running_server_pid:
686 kill(self.running_server_pid)
687 if self.client_web_pid:
688 kill(self.client_web_pid)
689
690#----------------------------------------------------------
691# OpenERP RunBot Build Slot
692#----------------------------------------------------------
693
694class Point(object):
695 """A point is a build slot and associated to a worker thread."""
696 def __init__(self, g, j):
697 """Create a Point from a given group and job."""
698 self.version = g.version
699 self.port = j.port
700 self.job_id = j.job_id
701 self.team_name = j.team_name
702 self.name_underscore = j.name_underscore
703 self.db = j.db
704 self.running_path = j.running_path
705 self.json_path = j.json_path
706 self.subdomain = j.subdomain
707 self.repo_updates = j.repo_updates # committer date not yet available, available after j.work() is called.
708 self.server_rev = g.server.local_revision_count
709 self.addons_rev = [a.local_revision_count for a in g.addons]
710 self.web_rev = g.web.local_revision_count if g.web else 0
711 self.need_run_reason = g.need_run_reason
712 for p in ('addons', 'server', 'web'):
713 setattr(self, p + '_committer_name', getattr(j, p + '_committer_name'))
714 setattr(self, p + '_committer_xgram', getattr(j, p + '_committer_xgram'))
715 setattr(self, p + '_committer_email', getattr(j, p + '_committer_email'))
716 self.update(j)
717 self.manual_build = g.manual_build
718
719 def update(self, j):
720 """
721 Update the Point from a job. The job should be the same than the one
722 used in `__init__()`.
723 """
724 self.state = 'running' if j.completed else 'testing'
725 self.running_t0 = j.running_t0
726 self.test_base_result = j.test_base_result
727 self.test_all_result = j.test_all_result
728
729 def save_json(self):
730 """Append the point data to a JSON file for posterity."""
731 # A job must be saved only once.
732 if hasattr(self, 'json_saved'):
733 log("=== save_json() called more than once. ===", job_id=self.job_id)
734 self.json_saved = True
735
736 path = self.json_path
737 state = {}
738
739 if os.path.exists(path):
740 with open(path, 'r') as h:
741 state = simplejson.loads(h.read())
742
743 value = {}
744 committers = []
745 for p in ('addons', 'server', 'web'):
746 committers += [
747 p + '_committer_name',
748 p + '_committer_xgram',
749 # p + '_committer_email',
750 ]
751 for a in [
752 'job_id', 'db', 'running_path', 'subdomain', 'server_rev',
753 'addons_rev', 'web_rev', 'need_run_reason',
754 'test_base_result', 'test_all_result',
755 ] + committers:
756 value[a] = getattr(self, a)
757 state.setdefault('jobs', [])
758 state['jobs'].append(value)
759
760 with open(path, 'w') as h:
761 data = simplejson.dumps(state, sort_keys=True, indent=4)
762 h.write(data)
763336
764#----------------------------------------------------------337#----------------------------------------------------------
765# OpenERP RunBot Engine338# OpenERP RunBot Engine
@@ -769,7 +342,7 @@
769 """Used as a singleton, to manage almost all the Runbot state and logic."""342 """Used as a singleton, to manage almost all the Runbot state and logic."""
770 def __init__(self, wd, poll, server_net_port, server_xml_port,343 def __init__(self, wd, poll, server_net_port, server_xml_port,
771 client_web_port, number, nginx_port, domain, test, workers,344 client_web_port, number, nginx_port, domain, test, workers,
772 current_job_id, debug, lp):345 current_job_id, debug, lp, default_job_type='install_all'):
773 self.wd=wd346 self.wd=wd
774 self.server_net_port=int(server_net_port)347 self.server_net_port=int(server_net_port)
775 self.server_xml_port=int(server_xml_port)348 self.server_xml_port=int(server_xml_port)
@@ -788,9 +361,10 @@
788 # realizing a queue of groups to be processed).361 # realizing a queue of groups to be processed).
789 self.current_job_id = current_job_id362 self.current_job_id = current_job_id
790 self.debug = debug363 self.debug = debug
791 self.manual_build_count = 0364 self.next_build_number = 0
792 self.launchpad = lp365 self.launchpad = lp
793 self.checked_date_last_modified = None366 self.checked_date_last_modified = None
367 self.default_job_type = default_job_type
794368
795 def registered_teams(self):369 def registered_teams(self):
796 return openerprunbot.state.get('registered-teams', []) + ['openerp-dev']370 return openerprunbot.state.get('registered-teams', []) + ['openerp-dev']
@@ -833,7 +407,7 @@
833 return gs407 return gs
834408
835 def nginx_groups_registered(self,team_name):409 def nginx_groups_registered(self,team_name):
836 gs = [(g.name,g) for g in self.groups.values() if not any(g.points) and g.team_name==team_name and g.manual_build == sys.maxint]410 gs = [(g.name,g) for g in self.groups.values() if not any(g.points) and g.team_name==team_name and g.build_number == sys.maxint]
837 gs.sort()411 gs.sort()
838 return [g for (x,g) in gs]412 return [g for (x,g) in gs]
839413
@@ -897,26 +471,28 @@
897471
898 def process_add(self, b, sticky, team_name):472 def process_add(self, b, sticky, team_name):
899 log("runbot-process-add", b.unique_name)473 log("runbot-process-add", b.unique_name)
900 if not re.search("/(openobject|openerp)-(addons|server|client-web|web)/", b.unique_name):474 if not project_name_from_unique_name(b.unique_name):
901 log("WARNING: can't add branch: project name is not standard.", unique_name=b.unique_name)475 log("WARNING: can't add branch: project name is not standard.", unique_name=b.unique_name)
902 return476 return
903 if b.unique_name not in self.branches:477 if b.unique_name not in self.branches:
904 self.branches[b.unique_name] = RunBotBranch(self, b)478 self.branches[b.unique_name] = RunBotBranch(self, b, trigger_build=True)
905 bb = self.branches[b.unique_name]479 bb = self.branches[b.unique_name]
906 if (team_name, bb.name) not in self.groups:480 if (team_name, bb.name) not in self.groups:
907 self.groups[(team_name, bb.name)] = RunBotGroupedBranch(self, bb, team_name, sticky)481 self.groups[(team_name, bb.name)] = RunBotGroupedBranch(self, team_name, bb.name, None, sticky, job_type=self.default_job_type)
908 else:482 self.groups[(team_name, bb.name)].add_branch(bb)
909 self.groups[(team_name, bb.name)].add_branch(bb)
910 bb.update_launchpad(b)483 bb.update_launchpad(b)
911 filters = {'status': NEW_MERGE_STATUS}484 filters = {'status': NEW_MERGE_STATUS}
912 bb.merge_count = len(list(b.getMergeProposals(**filters)))485 try:
486 bb.merge_count = len(list(b.getMergeProposals(**filters)))
487 except Exception, e:
488 bb.merge_count = 0
913489
914 def register_configured_branches(self):490 def register_configured_branches(self):
915 for team, v in openerprunbot.state.get('configured-branches', {}).items():491 for team, v in openerprunbot.state.get('configured-branches', {}).items():
916 if not team: continue492 if not team: continue
917 for name, c in v.items():493 for name, c in v.items():
918 if not name: continue494 if not name: continue
919 g = ConfiguredGroup(self, team, name, c['version'], 0,495 g = RunBotGroupedBranch(self, team, name, c['version'], 0,
920 server_branch=c['server_branch'],496 server_branch=c['server_branch'],
921 client_web_branch=c.get('client_web_branch'),497 client_web_branch=c.get('client_web_branch'),
922 web_branch=c.get('web_branch'),498 web_branch=c.get('web_branch'),
@@ -925,7 +501,7 @@
925501
926 # TODO or if it is already there but with different branches.502 # TODO or if it is already there but with different branches.
927 if (g.team_name, g.name) not in self.groups:503 if (g.team_name, g.name) not in self.groups:
928 g.populate_branches(self.launchpad)504 g.configure_branches(self.launchpad)
929 if g.is_ok():505 if g.is_ok():
930 log("adding configured group", team=g.team_name, name=g.name)506 log("adding configured group", team=g.team_name, name=g.name)
931 self.groups[(g.team_name, g.name)] = g507 self.groups[(g.team_name, g.name)] = g
@@ -936,6 +512,11 @@
936 def populate_branches(self):512 def populate_branches(self):
937 """Return all LP branches matching our teams and projects."""513 """Return all LP branches matching our teams and projects."""
938 log("runbot-populate-branches")514 log("runbot-populate-branches")
515 # Get migration branches
516 global migration_scripts_branch
517 global migration_platform_branch
518 migration_scripts_branch = self.launchpad.get_branch(unique_name='~openerp-dev/openerp-int/migration-scripts')
519 migration_platform_branch = self.launchpad.get_branch(unique_name='~openerp-dev/openerp-int/migration-platform')
939 # Register main sticky branches520 # Register main sticky branches
940 for k, v in MAIN_BRANCHES.items():521 for k, v in MAIN_BRANCHES.items():
941 b = self.launchpad.get_branch(unique_name=v)522 b = self.launchpad.get_branch(unique_name=v)
@@ -944,6 +525,7 @@
944525
945 # Register other branches526 # Register other branches
946 for v in openerprunbot.state.get('registered-branches', []):527 for v in openerprunbot.state.get('registered-branches', []):
528 break
947 m = openerprunbot.branch_input_re.match(v)529 m = openerprunbot.branch_input_re.match(v)
948 if m:530 if m:
949 b = self.launchpad.get_branch(unique_name=v)531 b = self.launchpad.get_branch(unique_name=v)
@@ -961,26 +543,19 @@
961 if self.debug: break543 if self.debug: break
962 team_branches = self.launchpad.get_team_branches(team_name)544 team_branches = self.launchpad.get_team_branches(team_name)
963 for b in team_branches:545 for b in team_branches:
964 if re.search("/(openobject|openerp)-(addons|server|client-web|web)/",b.unique_name):546 if project_name_from_unique_name(b.unique_name):
965 self.process_add(b, 0, team_name)547 self.process_add(b, 0, team_name)
966548
967 # Register configured branches549 # Register configured branches
968 self.register_configured_branches()550 self.register_configured_branches()
969551
970 for g in self.groups.values():552 for g in self.groups.values():
971 if not g.is_configured():553 if not g.configured:
972 g.add_main_branches()554 g.add_main_branches()
973555
974 self.checked_date_last_modified = max(x.lp_date_last_modified() for x in self.groups.values())556 self.checked_date_last_modified = max(x.lp_date_last_modified() for x in self.groups.values())
975 self.assign_positions()557 self.assign_positions()
976 return self.get_queue()558 return self.get_queue()
977 #return self.sorted_groups()
978
979 def sorted_groups(self):
980 gs = [(g.sticky, -g.manual_build, g.lp_date_last_modified(), g) for g in self.groups.values()]
981 gs.sort(reverse=1)
982 gs = [g for (x,y,z,g) in gs][:self.number]
983 return gs
984559
985 def assign_positions(self):560 def assign_positions(self):
986 # Sort by last modification date, then assign a position in the queue.561 # Sort by last modification date, then assign a position in the queue.
@@ -992,13 +567,13 @@
992 continue567 continue
993 if not g.sticky:568 if not g.sticky:
994 self.checked_date_last_modified = g.lp_date_last_modified()569 self.checked_date_last_modified = g.lp_date_last_modified()
995 if g.need_run_reason and g.manual_build == sys.maxint:570 if g.need_run_reason and g.build_number == sys.maxint:
996 self.manual_build_count +=1571 self.next_build_number +=1
997 g.manual_build = self.manual_build_count572 g.build_number = self.next_build_number
998573
999 def get_queue(self):574 def get_queue(self):
1000 gs = sorted(self.groups.values(), key=lambda x: x.manual_build)575 gs = sorted(self.groups.values(), key=lambda x: x.build_number)
1001 return filter(lambda g: g.manual_build != sys.maxint, gs)576 return filter(lambda g: g.build_number != sys.maxint, gs)
1002577
1003 def available_workers(self):578 def available_workers(self):
1004 return self.workers - len([t for t in threading.enumerate() if t.name.startswith('runbot-group-worker-')])579 return self.workers - len([t for t in threading.enumerate() if t.name.startswith('runbot-group-worker-')])
@@ -1008,32 +583,32 @@
1008 command, params = openerprunbot.queue.get()583 command, params = openerprunbot.queue.get()
1009 if command == 'build':584 if command == 'build':
1010 team_name, group_name = params585 team_name, group_name = params
1011 if (team_name, group_name) in self.groups and self.groups[(team_name, group_name)].manual_build == sys.maxint:586 if (team_name, group_name) in self.groups and self.groups[(team_name, group_name)].build_number == sys.maxint:
1012 self.manual_build_count += 1587 self.next_build_number += 1
1013 self.groups[(team_name, group_name)].manual_build = self.manual_build_count588 self.groups[(team_name, group_name)].build_number = self.next_build_number
1014 self.groups[(team_name, group_name)].need_run_reason.append('build')589 self.groups[(team_name, group_name)].need_run_reason.append('build')
1015 else:590 else:
1016 log("WARNING: unknown command", command)591 log("WARNING: unknown command", command)
1017592
1018 def reset_build_numbers(self):593 def reset_build_numbers(self):
1019 gs = [(g.manual_build, g) for g in self.groups.values()]594 gs = [(g.build_number, g) for g in self.groups.values()]
1020 gs.sort()595 gs.sort()
1021 gs = [g for (x,g) in gs]596 gs = [g for (x,g) in gs]
1022 self.manual_build_count = 0597 self.next_build_number = 0
1023 sticky_branches = 0598 sticky_branches = 0
1024 for g in gs:599 for g in gs:
1025 if g.sticky:600 if g.sticky:
1026 sticky_branches += 1601 sticky_branches += 1
1027 if g.manual_build == sys.maxint:602 if g.build_number == sys.maxint:
1028 break603 break
1029 self.manual_build_count += 1604 self.next_build_number += 1
1030 g.manual_build = self.manual_build_count605 g.build_number = self.next_build_number
1031 self.number = max(sticky_branches + 1, self.number)606 self.number = max(sticky_branches + 1, self.number)
1032607
1033 def complete_jobs(self):608 def complete_jobs(self):
1034 """Update all slots with the completed jobs."""609 """Update all slots with the completed jobs."""
1035 for job in self.jobs.values():610 for job in self.jobs.values():
1036 if job.completed:611 if job.state in (STATE_RUNNING, STATE_BROKEN):
1037 for g in self.groups.values():612 for g in self.groups.values():
1038 if g.name == job.name:613 if g.name == job.name:
1039 g.complete_point(job)614 g.complete_point(job)
@@ -1052,7 +627,7 @@
1052 for job in self.jobs.itervalues():627 for job in self.jobs.itervalues():
1053 if self.is_sticky_job(job):628 if self.is_sticky_job(job):
1054 continue629 continue
1055 if not job.running_t0:630 if not job.running_since:
1056 continue631 continue
1057 if not victim or job.job_id < victim.job_id:632 if not victim or job.job_id < victim.job_id:
1058 victim = job633 victim = job
@@ -1082,10 +657,11 @@
1082 if g.need_run_reason and g.all_points_completed():657 if g.need_run_reason and g.all_points_completed():
1083 port = self.allocate_port()658 port = self.allocate_port()
1084 self.current_job_id += 1659 self.current_job_id += 1
1085 job = Job(g, port, self.test, self.current_job_id, self.debug)660 job_class = openerprunbot.jobs.JOBS[g.job_type] if g.job_type else openerprunbot.jobs.JOBS[self.default_job_type]
661 job = job_class(g, port, self.test, self.current_job_id, self.debug)
1086 g.add_point(job)662 g.add_point(job)
1087 g.need_run_reason = []663 g.need_run_reason = []
1088 g.manual_build = sys.maxint664 g.build_number = sys.maxint
1089 self.jobs[job.job_id] = job665 self.jobs[job.job_id] = job
1090 job.spawn()666 job.spawn()
1091667
1092668
=== added directory 'openerp-runbot/openerprunbot/jobs'
=== added file 'openerp-runbot/openerprunbot/jobs/__init__.py'
--- openerp-runbot/openerprunbot/jobs/__init__.py 1970-01-01 00:00:00 +0000
+++ openerp-runbot/openerprunbot/jobs/__init__.py 2012-07-04 11:46:17 +0000
@@ -0,0 +1,7 @@
1import install_all
2import migrate_script
3
4JOBS = {
5 'install_all': install_all.InstallAllJob,
6 'migrate_script': migrate_script.MigrateScriptJob,
7 }
08
=== added file 'openerp-runbot/openerprunbot/jobs/install_all.py'
--- openerp-runbot/openerprunbot/jobs/install_all.py 1970-01-01 00:00:00 +0000
+++ openerp-runbot/openerprunbot/jobs/install_all.py 2012-07-04 11:46:17 +0000
@@ -0,0 +1,366 @@
1import os
2import subprocess
3import threading
4import time
5import traceback
6
7from ..misc import *
8
9# Job states
10STATE_ALLOCATED = 'allocated'
11STATE_BROKEN = 'broken'
12STATE_PULLING = 'pulling'
13STATE_RUNNING = 'running'
14STATE_TESTING = 'testing'
15
16class InstallAllJob(object):
17 """
18 A Job encapsulates all the necessary data to build a branch group for a
19 given slot. The build is done in its own worker thread.
20 """
21
22 def __init__(self, g, port, test, job_id, debug):
23 self.job_id = job_id
24 self.state = STATE_ALLOCATED
25 self.team_name = g.team_name
26 self.name = g.name
27 self.name_underscore = g.name_underscore
28 self.version = g.version
29 self.debug = debug
30 self.need_run_reason = g.need_run_reason
31
32 self.repo_updates = g.repo_updates()
33 self.port = port
34 self.test = test
35 self.running_server_pid = 0
36 self.client_web_pid = 0
37 self.running_since=0
38
39 repo = os.path.join(g.runbot.wd,'repo')
40 job_suffix = '_job-' + str(self.job_id)
41 self.server_src = g.server.repo_path + job_suffix
42 self.client_web_src = (g.web.repo_path + job_suffix) if g.version == '6.0' and g.web else None
43 self.web_src = (g.web.repo_path + job_suffix) if g.web else None
44
45 # if addons is not the full addons branch use trunk
46 self.addons_src = [a.repo_path + job_suffix for a in g.addons]
47
48 if hasattr(g, 'migration_script'):
49 self.migration_script_src = g.migration_script.repo_path + job_suffix
50 self.migration_platform_src = g.migration_platform.repo_path + job_suffix
51
52 # Running path <Root>/static/<domain>
53 self.subdomain = "%s-%s-%s"%(self.team_name, self.name.replace('_','-').replace('.','-'),self.job_id)
54 self.running_path = os.path.join(g.runbot.wd,'static',self.subdomain)
55 self.json_path = g.json_path
56 self.log_path = os.path.join(self.running_path,'logs')
57 self.flags_path = os.path.join(self.running_path,'flags')
58
59 # Server
60 self.server_path=os.path.join(self.running_path,"server")
61 self.server_bin_path=os.path.join(self.server_path,"openerp-server")
62 self.server_log_path=os.path.join(self.log_path,'server.txt')
63 self.server_log_base_path=os.path.join(self.log_path,'test-base.txt')
64 self.server_log_all_path=os.path.join(self.log_path,'test-all.txt')
65
66 # coverage
67 self.coverage_file_path=os.path.join(self.log_path,'coverage.pickle')
68 self.coverage_base_path=os.path.join(self.log_path,'coverage-base')
69 self.coverage_all_path=os.path.join(self.log_path,'coverage-all')
70
71 # Web60
72 self.client_web_pid=None
73 self.client_web_path=os.path.join(self.running_path,"client-web")
74 self.client_web_bin_path=os.path.join(self.client_web_path,"openerp-web.py")
75 self.client_web_doc_path=os.path.join(self.client_web_path,"doc")
76 self.client_web_log_path=os.path.join(self.log_path,'client-web.txt')
77
78 # test
79 self.test_base_path=os.path.join(self.log_path,'test-base.txt')
80 self.test_result=None
81 self.test_all_path=os.path.join(self.log_path,'test-all.txt')
82
83 self.server_net_port=g.runbot.server_net_port
84 self.server_xml_port=g.runbot.server_xml_port
85 self.client_web_port=g.runbot.client_web_port
86
87 self.db = self.name_underscore + '_' + str(self.job_id)
88 self.db_all = "%s_all" % self.db
89
90 for p in ('addons', 'server', 'web'):
91 setattr(self, p + '_committer_name', None)
92 setattr(self, p + '_committer_xgram', None)
93 setattr(self, p + '_committer_email', None)
94 self.start_time = 0
95 self.completed_time = 0
96
97 self.modules = g.modules
98
99
100 self.server_rev = g.server.local_revision_count
101 self.addons_rev = [a.local_revision_count for a in g.addons]
102 self.web_rev = g.web.local_revision_count if g.web else 0
103 self.build_number = g.build_number
104
105 def spawn(self):
106 log("runbot-spawn-worker-" + str(self.job_id), group=self.name)
107 t = threading.Thread(target=self.work, name=('runbot-group-worker-' + str(self.job_id)))
108 t.daemon = True
109 t.start()
110
111 def work(self):
112 try:
113 self.state = STATE_PULLING
114 r = self.pull_branches()
115 if r:
116 self.state = STATE_TESTING
117 self.start_time = time.time()
118 self.start()
119 self.completed_time = time.time()
120 self.state = STATE_RUNNING
121 log("runbot-end-worker", job=self.name)
122 else:
123 self.state = STATE_BROKEN
124 log("runbot-end-worker [failed pull]", job=self.name)
125 except Exception, e:
126 self.state = STATE_BROKEN
127 log("runbot-end-worker [with exception]", job=self.name)
128 print traceback.format_exc()
129
130 def pull_branches(self):
131 job_suffix = '_job-' + str(self.job_id)
132 for b in self.repo_updates:
133 log("branch-update",branch=b.unique_name)
134 repo_path = b.repo_path + job_suffix
135 if os.path.exists(repo_path):
136 rc = run(["bzr", "clean-tree", "-d", repo_path, "--force", "--quiet"])
137 if rc: return False
138 rc = run(["bzr", "pull", "-d", repo_path, "--overwrite", "--quiet"])
139 if rc: return False
140 else:
141 rc = run(["bzr", "branch", "lp:%s" % b.unique_name, repo_path, "--quiet"])
142 if rc: return False
143 rc = run(["bzr", "update", "-r", str(b.local_revision_count), repo_path, "--quiet"])
144 if rc: return False
145
146 committer_name, committer_xgram, committer_email = \
147 get_committer_info(repo_path)
148 b.committer_name = committer_name
149 b.committer_xgram = committer_xgram
150 b.committer_email = committer_email
151 log("get-commiter-info", name=committer_name, xgram=committer_xgram, email=committer_email)
152 return True
153
154 def start_rsync(self):
155 log("job-start-rsync",branch=self.name)
156 for i in [self.running_path,self.log_path,self.flags_path,self.client_web_doc_path]:
157 if not os.path.exists(i):
158 os.makedirs(i)
159 # copy server
160 run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.server_src, self.server_path])
161
162 # copy addons (6.0 uses bin/addons, 6.1 and trunk use openerp/addons)
163 addons_dest = "openerp/addons"
164 if not os.path.exists(os.path.join(self.server_path,addons_dest)):
165 addons_dest = "bin/addons"
166 addons_path = os.path.join(self.server_path,addons_dest)
167 for a in self.addons_src:
168 run(["rsync","-a","--exclude",".bzr", "%s/"%a, addons_path])
169 if self.web_src and self.version != '6.0':
170 run(["rsync","-a","--exclude",".bzr", "%s/addons/"%self.web_src, addons_path])
171
172 # copy web-client
173 if self.client_web_src:
174 run(["rsync","-a","--exclude",".bzr","--delete", "%s/"%self.client_web_src, self.client_web_path])
175
176 def start_createdb(self):
177 run(["psql","template1","-c","select pg_terminate_backend(procpid) from pg_stat_activity where datname in ('%s','%s')"%(self.db,self.db_all)])
178 time.sleep(3)
179 run(["dropdb",self.db])
180 run(["dropdb",self.db_all])
181 run(["createdb",self.db])
182 run(["createdb",self.db_all])
183
184 def run_log(self, cmd, logfile, env=None):
185 env = dict(os.environ, **env) if env else None
186 log("run", *cmd, logfile=logfile)
187 out=open(logfile,"w")
188 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True, env=env)
189 self.running_server_pid=p.pid
190 p.communicate()
191 return p
192
193 def resolve_server_bin_path(self):
194 # This can be done only if the files are present otherwise the if
195 # will always fail. Alternatively, server_bin_path could be a property.
196 if not os.path.exists(self.server_bin_path): # for 6.0 branches
197 self.server_bin_path=os.path.join(self.server_path,"bin","openerp-server.py")
198
199 def start_test_base(self):
200 log("job-start-server-base")
201 cmd = [self.server_bin_path,"-d",self.db,"-i","base","--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"]
202 _has_test_enable_flag = False
203 if has_test_enable_flag(self.server_bin_path):
204 cmd.append("--test-enable")
205 _has_test_enable_flag = True
206 cmd = ["coverage","run","--branch"] + cmd
207 self.run_log(cmd, logfile=self.test_base_path,env={'COVERAGE_FILE': self.coverage_file_path})
208 run(["coverage","html","-d",self.coverage_base_path,"--ignore-errors","--include=*.py"],env={'COVERAGE_FILE': self.coverage_file_path})
209 if _has_test_enable_flag:
210 success_message = "openerp.modules.loading: Modules loaded."
211 rc = not bool(run(["grep",success_message,self.test_base_path]))
212 else:
213 rc = bool(run(["grep","Traceback",self.test_base_path]))
214 return rc
215
216 def start_test_all(self, without_demo=False):
217 log("job-start-server-all")
218 mods = []
219 if not os.path.exists(os.path.join(self.server_path,"openerp/addons")):
220 mods = os.listdir(os.path.join(self.server_path,"bin/addons"))
221 elif os.path.exists(os.path.join(self.server_path,"openerp/addons")):
222 mods = os.listdir(os.path.join(self.server_path,"openerp/addons"))
223 bl = [
224 'document_ftp', 'l10n_lu',
225 '.bzrignore', '__init__.pyc', '__init__.py', 'base_quality_interrogation.py',
226 ]
227 mods = [m for m in mods if m not in bl]
228 if self.modules:
229 # TODO feedback when a requested modules does'nt exist.
230 mods = [m for m in mods if m in self.modules]
231 mods = ",".join(mods)
232 cmd = [self.server_bin_path,"-d",self.db_all,"-i",mods,"--stop-after-init","--no-xmlrpc","--no-xmlrpcs","--no-netrpc","--log-level=test"]
233 _has_test_enable_flag = False
234 if has_test_enable_flag(self.server_bin_path):
235 cmd.append("--test-enable")
236 _has_test_enable_flag = True
237 if without_demo:
238 cmd.append("--without-demo=True")
239 cmd = ["coverage","run","--branch"] + cmd
240 self.run_log(cmd, logfile=self.test_all_path)
241 run(["coverage","html","-d",self.coverage_all_path,"--ignore-errors","--include=*.py"])
242 if _has_test_enable_flag:
243 success_message = "openerp.modules.loading: Modules loaded."
244 rc = not bool(run(["grep",success_message,self.test_all_path]))
245 else:
246 rc = bool(run(["grep","Traceback",self.test_all_path]))
247 return rc
248
249 def start_server(self):
250 port = self.port
251 log("job-start-server",branch=self.name,port=port)
252 cmd=[self.server_bin_path,"--no-xmlrpcs","--netrpc-port=%d"%(self.server_net_port+port),"--xmlrpc-port=%d"%(self.server_xml_port+port)]
253 if os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi.py')) \
254 or os.path.exists(os.path.join(self.server_path, 'openerp', 'wsgi', 'core.py')):
255 cmd+=["--db-filter=%d(_.*)?$","--load=web"]
256 log("run",*cmd,log=self.server_log_path)
257 out=open(self.server_log_path,"w")
258 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True)
259 self.running_server_pid=p.pid
260
261 def start_client_web(self):
262 if not self.client_web_src:
263 return
264 port = self.port
265 log("job-start-client-web",branch=self.name,port=port)
266 config="""
267 [global]
268 server.environment = "development"
269 server.socket_host = "0.0.0.0"
270 server.socket_port = %d
271 server.thread_pool = 10
272 tools.sessions.on = True
273 log.access_level = "INFO"
274 log.error_level = "INFO"
275 tools.csrf.on = False
276 tools.log_tracebacks.on = False
277 tools.cgitb.on = True
278 openerp.server.host = 'localhost'
279 openerp.server.port = '%d'
280 openerp.server.protocol = 'socket'
281 openerp.server.timeout = 450
282 [openerp-web]
283 dblist.filter = 'BOTH'
284 dbbutton.visible = True
285 company.url = ''
286 openerp.server.host = 'localhost'
287 openerp.server.port = '%d'
288 openerp.server.protocol = 'socket'
289 openerp.server.timeout = 450
290 """%(self.client_web_port+port,self.server_net_port+port,self.server_net_port+port)
291 config=config.replace("\n ","\n")
292 cfgs = [os.path.join(self.client_web_path,"doc","openerp-web.cfg"), os.path.join(self.client_web_path,"openerp-web.cfg")]
293 for i in cfgs:
294 f=open(i,"w")
295 f.write(config)
296 f.close()
297
298 cmd=[self.client_web_bin_path]
299 log("run",*cmd,log=self.client_web_log_path)
300 out=open(self.client_web_log_path,"w")
301 p=subprocess.Popen(cmd, stdout=out, stderr=out, close_fds=True)
302 self.client_web_pid=p.pid
303
304 def start(self):
305 log("job-start",branch=self.name,port=self.port)
306 self.start_rsync()
307 self.resolve_server_bin_path()
308 self.start_createdb()
309 try:
310 if self.test:
311 r = self.start_test_base()
312 if not self.debug:
313 self.test_result = r and self.start_test_all()
314 else:
315 self.test_result = r
316 self.start_server()
317 self.start_client_web()
318 except OSError,e:
319 log("branch-start-error",exception=e)
320 except IOError,e:
321 log("branch-start-error",exception=e)
322 self.running_since=time.time()
323 log("branch-started",branch=self.name,port=self.port)
324
325 def stop(self):
326 log("Stopping job", id=self.job_id, branch=self.name)
327 if self.running_server_pid:
328 kill(self.running_server_pid)
329 if self.client_web_pid:
330 kill(self.client_web_pid)
331
332 def save_json(self):
333 """Append the point data to a JSON file for posterity."""
334 # A job must be saved only once.
335 if hasattr(self, 'json_saved'):
336 log("=== save_json() called more than once. ===", job_id=self.job_id)
337 self.json_saved = True
338
339 path = self.json_path
340 state = {}
341
342 if os.path.exists(path):
343 with open(path, 'r') as h:
344 state = simplejson.loads(h.read())
345
346 value = {}
347 committers = []
348 for p in ('addons', 'server', 'web'):
349 committers += [
350 p + '_committer_name',
351 p + '_committer_xgram',
352 # p + '_committer_email',
353 ]
354 for a in [
355 'job_id', 'db', 'running_path', 'subdomain', 'server_rev',
356 'addons_rev', 'web_rev', 'need_run_reason',
357 'test_result',
358 ] + committers:
359 value[a] = getattr(self, a)
360 state.setdefault('jobs', [])
361 state['jobs'].append(value)
362
363 with open(path, 'w') as h:
364 data = simplejson.dumps(state, sort_keys=True, indent=4)
365 h.write(data)
366
0367
=== added file 'openerp-runbot/openerprunbot/jobs/migrate_script.py'
--- openerp-runbot/openerprunbot/jobs/migrate_script.py 1970-01-01 00:00:00 +0000
+++ openerp-runbot/openerprunbot/jobs/migrate_script.py 2012-07-04 11:46:17 +0000
@@ -0,0 +1,61 @@
1import os
2import subprocess
3import threading
4import time
5import traceback
6
7from install_all import InstallAllJob
8from ..misc import *
9
10# Job states
11STATE_ALLOCATED = 'allocated'
12STATE_BROKEN = 'broken'
13STATE_PULLING = 'pulling'
14STATE_RUNNING = 'running'
15STATE_TESTING = 'testing'
16
17class MigrateScriptJob(InstallAllJob):
18
19 def start(self, migrate=False):
20 log("job-start",branch=self.name,port=self.port)
21 self.migration_log_path=os.path.join(self.log_path,'migration.txt')
22
23 self.start_rsync()
24 self.resolve_server_bin_path()
25 self.start_createdb()
26 try:
27 rc0 = self.start_test_all(without_demo=True)
28
29 # Run the migration towards trunk
30 if False: # if migrate
31 run(['rm', '-rf', 'migration-' + str(self.job_id)])
32 run(['cp', '-r', self.migration_script_src, 'migration-' + str(self.job_id)])
33 run(['cp', self.migration_platform_src + '/Makefile', 'migration-' + str(self.job_id)])
34 cmd = 'make pre update post updatelog reset-demoflag'.split() + [
35 'OPENERP_BIN=' + self.server_bin_path,
36 'DATABASE=' + self.db_all,
37 'VERSION=trunk']
38 run_output(cmd, cwd='migration-' + str(self.job_id))
39
40 # Check the migrated database with an 'update all' and tests enabled.
41 cmd = ['psql', self.db_all, '-c', "update ir_module_module set demo='t'"]
42 run(cmd)
43 cmd = [self.server_bin_path, "-d", self.db_all, "-u", "base", "--stop-after-init", "--no-xmlrpc", "--no-xmlrpcs", "--no-netrpc", "--log-level=test"]
44 _has_test_enable_flag = False
45 if has_test_enable_flag(self.server_bin_path):
46 cmd.append("--test-enable")
47 _has_test_enable_flag = True
48 self.run_log(cmd, logfile=self.test_all_path)
49 if _has_test_enable_flag:
50 success_message = "openerp.modules.loading: Modules loaded."
51 rc = not bool(run(["grep",success_message,self.test_all_path]))
52 else:
53 rc = bool(run(["grep","Traceback",self.test_all_path]))
54 self.test_result = rc0 and rc
55
56 except OSError,e:
57 log("branch-start-error",exception=e)
58 except IOError,e:
59 log("branch-start-error",exception=e)
60 self.running_since=time.time()
61
062
=== modified file 'openerp-runbot/openerprunbot/misc.py'
--- openerp-runbot/openerprunbot/misc.py 2011-11-24 10:09:50 +0000
+++ openerp-runbot/openerprunbot/misc.py 2012-07-04 11:46:17 +0000
@@ -3,6 +3,7 @@
3"""3"""
44
5import fcntl5import fcntl
6import re
6import signal7import signal
7import subprocess8import subprocess
8import threading9import threading
@@ -10,7 +11,7 @@
10import os11import os
1112
12__all__ = [13__all__ = [
13 'kill', 'log', 'run', 'run_output'14 'kill', 'log', 'run', 'run_output', 'get_committer_info', 'underscore', 'has_test_enable_flag', 'project_name_from_unique_name'
14 ]15 ]
1516
16log_lock = threading.Lock()17log_lock = threading.Lock()
@@ -62,4 +63,56 @@
62 pass63 pass
6364
64def run_output(l, cwd=None):65def run_output(l, cwd=None):
66 log("run_output",l)
65 return subprocess.Popen(l, stdout=subprocess.PIPE, cwd=cwd).communicate()[0]67 return subprocess.Popen(l, stdout=subprocess.PIPE, cwd=cwd).communicate()[0]
68
69#----------------------------------------------------------
70# OpenERP RunBot misc
71#----------------------------------------------------------
72
73def underscore(s):
74 return s.replace("~","").replace(":","_").replace("/","_").replace(".","_").replace(" ","_")
75
76def get_committer_info(repo_path):
77 committer_name = None
78 committer_xgram = None
79 committer_email = None
80
81 output = run_output(["bzr", "log", "--long", "-r-1"], cwd=repo_path)
82
83 committer_re = re.compile('committer: *(.+)<(.+)@(.+)>')
84 for i in output.split('\n'):
85 m = committer_re.match(i)
86 if m:
87 committer_name = m.group(1).strip()
88 committer_xgram = m.group(2)
89 committer_email = m.group(2) + '@' + m.group(3)
90 break
91
92 return committer_name, committer_xgram, committer_email
93
94def has_test_enable_flag(server_bin_path):
95 """
96 Test whether an openerp-server executable has the --test-enable flag.
97 (When the flag is present, testing for success/failure is done in a
98 different way.)
99 """
100 p1 = subprocess.Popen([server_bin_path, "--help"],
101 stdout=subprocess.PIPE)
102 p2 = subprocess.Popen(["grep", "test-enable"], stdin=p1.stdout,
103 stdout=subprocess.PIPE)
104 p1.stdout.close() # Allow p1 to receive a SIGPIPE if p2 exits.
105 output = p2.communicate()[0]
106 return output == " --test-enable Enable YAML and unit tests.\n"
107
108def project_name_from_unique_name(unique_name):
109 m = re.search("/(openobject|openerp)-(addons|server|client-web|web)/", unique_name)
110 if m:
111 n = m.group(2)
112 if n == 'client-web':
113 return 'web'
114 else:
115 return n
116 else:
117 return None
118
66119
=== modified file 'openerp-runbot/openerprunbot/templates/branches.html.mako'
--- openerp-runbot/openerprunbot/templates/branches.html.mako 2012-05-23 13:07:13 +0000
+++ openerp-runbot/openerprunbot/templates/branches.html.mako 2012-07-04 11:46:17 +0000
@@ -106,11 +106,12 @@
106 <h2>Runtime data</h2>106 <h2>Runtime data</h2>
107 <div class="stats">107 <div class="stats">
108 (The following data are global to the Runbot.)<br />108 (The following data are global to the Runbot.)<br />
109 Default job type: ${r.default_job_type}.<br />
109 Maximum ${r.number} concurrent branches.<br />110 Maximum ${r.number} concurrent branches.<br />
110 Maximum ${r.workers} concurrent jobs.<br />111 Maximum ${r.workers} concurrent jobs.<br />
111 ${r.current_job_id} processed jobs.<br />112 ${r.current_job_id} processed jobs.<br />
112 ${r.workers - r.available_workers()} ongoing jobs.<br />113 ${r.workers - r.available_workers()} ongoing jobs.<br />
113 ${r.manual_build_count} manual build requests to go.<br />114 ${r.next_build_number} manual build requests to go.<br />
114 </div>115 </div>
115 </div>116 </div>
116117
117118
=== modified file 'openerp-runbot/openerprunbot/templates/defs.html.mako'
--- openerp-runbot/openerprunbot/templates/defs.html.mako 2012-06-21 10:24:31 +0000
+++ openerp-runbot/openerprunbot/templates/defs.html.mako 2012-07-04 11:46:17 +0000
@@ -14,9 +14,9 @@
14 <span class="i action-toggle dropdown-toggle" data-toggle="dropdown">B</span>14 <span class="i action-toggle dropdown-toggle" data-toggle="dropdown">B</span>
15 <ul class="dropdown-menu">15 <ul class="dropdown-menu">
16 <li><a href="http://${r.domain}/${p.subdomain}/logs/test-all.txt">16 <li><a href="http://${r.domain}/${p.subdomain}/logs/test-all.txt">
17 % if p.test_all_result==False:17 % if p.test_result==False:
18 Install logs (failure)18 Install logs (failure)
19 % elif p.test_all_result==True:19 % elif p.test_result==True:
20 Install logs (success)20 Install logs (success)
21 % else:21 % else:
22 Install logs (ongoing)22 Install logs (ongoing)
@@ -30,25 +30,31 @@
30 % if 'build' in p.need_run_reason:30 % if 'build' in p.need_run_reason:
31 <li>(Build manually requested)</li>31 <li>(Build manually requested)</li>
32 % endif32 % endif
33 % if p.manual_build != sys.maxint:33 % if p.build_number != sys.maxint:
34 <li>(${p.manual_build})</li>34 <li>(${p.build_number})</li>
35 % endif35 % endif
36 </ul>36 </ul>
37% if p.state == 'testing':37% if p.state == 'testing':
38 <p><span class="status"> </span>38 <p><span class="status"> </span>
39% elif p.test_base_result==True and p.test_all_result==True:39% elif p.test_result==True:
40 <p><span class="status green"> </span>40 <p><span class="status green"> </span>
41% elif p.test_base_result==False or p.test_all_result==False:41% elif p.test_result==False:
42 <p><span class="status red"> </span>42 <p><span class="status red"> </span>
43% else:43% else:
44 <p><span class="status"> </span>44 <p><span class="status"> </span>
45% endif45% endif
46% if p.state == 'testing':46% if p.state == 'testing':
47 <span class="testing">Testing...</span></p>47 <span class="testing">Testing...</span></p>
48% elif p.running_t0 and p.test_all_result:48% elif p.state == 'pulling':
49 <span class="running-long">Age: ${r.nginx_index_time(t-p.running_t0)}</span></p>49 <span class="testing">Pulling...</span></p>
50% elif p.running_t0:50% elif p.state == 'allocated':
51 <span class="testing">Age: ${r.nginx_index_time(t-p.running_t0)}</span></p>51 <span class="testing">Allocated...</span></p>
52% elif p.state == 'broken':
53 <span class="testing">Internal error</span></p>
54% elif p.running_since and p.test_result:
55 <span class="running-long">Age: ${r.nginx_index_time(t-p.running_since)}</span></p>
56% elif p.running_since:
57 <span class="testing">Age: ${r.nginx_index_time(t-p.running_since)}</span></p>
52% else:58% else:
53 <span class="testing">Internal error</span></p>59 <span class="testing">Internal error</span></p>
54% endif60% endif
@@ -58,7 +64,7 @@
58 ${rev_(r,g,b,p)}64 ${rev_(r,g,b,p)}
59% endfor65% endfor
60 </ul>66 </ul>
61% if ((p.job_id > r.current_job_id - r.number) and p.running_t0) or g.is_sticky_job(p):67% if ((p.job_id > r.current_job_id - r.number) or g.is_sticky_job(p)) and p.running_since:
62 <form target="_blank" method="GET" action="http://${p.db}.${r.domain}/">68 <form target="_blank" method="GET" action="http://${p.db}.${r.domain}/">
63 <button type="submit">Connect</button>69 <button type="submit">Connect</button>
64 </form>70 </form>
@@ -69,7 +75,7 @@
69</%def>75</%def>
7076
71<%def name="branch_(r,g,b)">77<%def name="branch_(r,g,b)">
72% if b.overriden_repo_path:78% if not b.trigger_build:
73<span class="label">79<span class="label">
74% else:80% else:
75<span class="label notice">81<span class="label notice">
@@ -97,8 +103,8 @@
97% if g.wrong_matching:103% if g.wrong_matching:
98 <span class="wrong-matching">These branches are not correctly named (<a href="/#how">see the instructions</a>).</span>104 <span class="wrong-matching">These branches are not correctly named (<a href="/#how">see the instructions</a>).</span>
99% endif105% endif
100% if g.manual_build != sys.maxint:106% if g.build_number != sys.maxint:
101<p><span>(build n.${g.manual_build})</span></p>107<p><span>(build n.${g.build_number})</span></p>
102% elif not g.sticky:108% elif not g.sticky:
103 ${build_button_(r, g)}109 ${build_button_(r, g)}
104% endif110% endif
@@ -126,16 +132,16 @@
126<tr>132<tr>
127 <td>133 <td>
128<form method="POST" action="http://${r.domain}/a?build=${g.name}&amp;team=${g.team_name}">134<form method="POST" action="http://${r.domain}/a?build=${g.name}&amp;team=${g.team_name}">
129% if g.manual_build != sys.maxint:135% if g.build_number != sys.maxint:
130<strong>${g.manual_build}.</strong>136<strong>${g.build_number}.</strong>
131% endif137% endif
132${g.name}138${g.name}
133<span>(${g.team_name})</span>139<span>(${g.team_name})</span>
134% if g.manual_build == sys.maxint:140% if g.build_number == sys.maxint:
135 <button type="submit">Force Build</button>141 <button type="submit">Force Build</button>
136% endif142% endif
137% for b in g.repo_updates():143% for b in g.repo_updates():
138 % if not b.overriden_repo_path:144 % if b.trigger_build:
139 ${branch_(r,g,b)}145 ${branch_(r,g,b)}
140 % endif146 % endif
141% endfor147% endfor
142148
=== modified file 'openerp-runbot/openerprunbot/templates/nginx.conf.mako'
--- openerp-runbot/openerprunbot/templates/nginx.conf.mako 2012-04-27 10:25:23 +0000
+++ openerp-runbot/openerprunbot/templates/nginx.conf.mako 2012-07-04 11:46:17 +0000
@@ -10,7 +10,7 @@
10 client_body_temp_path nginx; proxy_temp_path nginx; fastcgi_temp_path nginx; access_log nginx/access.log; index index.html;10 client_body_temp_path nginx; proxy_temp_path nginx; fastcgi_temp_path nginx; access_log nginx/access.log; index index.html;
11 % for g in r.groups.values():11 % for g in r.groups.values():
12 % for i in g.points:12 % for i in g.points:
13 % if i and (i.job_id > r.current_job_id - r.number) and i.running_t0:13 % if i and (i.job_id > r.current_job_id - r.number or g.is_sticky_job(i)) and i.running_since:
14 % if i.team_name == 'openerp-dev' and g.is_sticky_job(i):14 % if i.team_name == 'openerp-dev' and g.is_sticky_job(i):
15 server { # openerp-dev sticky job (i.e. first job of a sticky branch)15 server { # openerp-dev sticky job (i.e. first job of a sticky branch)
16 listen ${r.nginx_port};16 listen ${r.nginx_port};
1717
=== added file 'openerp-runbot/starting.html'
--- openerp-runbot/starting.html 1970-01-01 00:00:00 +0000
+++ openerp-runbot/starting.html 2012-07-04 11:46:17 +0000
@@ -0,0 +1,1 @@
1The runbot is starting over.
02
=== added file 'openerp-runbot/stopped.html'
--- openerp-runbot/stopped.html 1970-01-01 00:00:00 +0000
+++ openerp-runbot/stopped.html 2012-07-04 11:46:17 +0000
@@ -0,0 +1,1 @@
1The runbot is down for "planned" maintenance.
02
=== modified file 'openerp-runbot/try-template.py'
--- openerp-runbot/try-template.py 2012-04-18 13:00:01 +0000
+++ openerp-runbot/try-template.py 2012-07-04 11:46:17 +0000
@@ -12,8 +12,8 @@
12 unique_name = '~openerp-dev/openobject-server/trunk-dummy'12 unique_name = '~openerp-dev/openobject-server/trunk-dummy'
13 merge_count = 113 merge_count = 1
14 local_revision_count = 33314 local_revision_count = 333
15 overriden_repo_path = None15 repo_path = None
16 overriden_project_name = None16 project_name = None
1717
18 class Point(object):18 class Point(object):
19 """ Dummy Point class to test mako templates. """19 """ Dummy Point class to test mako templates. """
@@ -24,8 +24,8 @@
24 subdomain = 'trunk_dummy_2'24 subdomain = 'trunk_dummy_2'
25 port = 2225 port = 22
26 need_run_reason = ['server']26 need_run_reason = ['server']
27 manual_build = sys.maxint27 build_number = sys.maxint
28 running_t0 = 10000028 running_since = 100000
29 repo_updates = [RunBotBranch()]29 repo_updates = [RunBotBranch()]
30 version = 'trunk'30 version = 'trunk'
3131
@@ -33,7 +33,7 @@
33 """ Dummy Group class to test mako templates. """33 """ Dummy Group class to test mako templates. """
34 name = 'trunk-dummy'34 name = 'trunk-dummy'
35 team_name = 'openerp-dev'35 team_name = 'openerp-dev'
36 manual_build = sys.maxint36 build_number = sys.maxint
37 wrong_matching = False37 wrong_matching = False
38 points = [None, None, Point()]38 points = [None, None, Point()]
39 version = 'trunk'39 version = 'trunk'
@@ -46,7 +46,7 @@
46 number = 55546 number = 555
47 workers = 66647 workers = 666
48 current_job_id = 77748 current_job_id = 777
49 manual_build_count = 88849 next_build_number = 888
50 server_net_port = 1200050 server_net_port = 12000
51 server_xml_port = 1210051 server_xml_port = 12100
52 def nginx_index_time(self, t):52 def nginx_index_time(self, t):