Skip to content

Commit

Permalink
Allow forcing on multiple builders at once.
Browse files Browse the repository at this point in the history
The web status currently does this by forcing multiple times, once for each builder,
creating a separate buildset for each. Instead, teach ForceScheduler to support
starting multiple builders in a single buildset, and refactor the webstatus code to
support that.

Fixes #2342.
  • Loading branch information
tomprince committed Sep 27, 2012
1 parent 92231c0 commit 3be5a31
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 49 deletions.
14 changes: 8 additions & 6 deletions master/buildbot/schedulers/forcesched.py
Expand Up @@ -542,14 +542,16 @@ def gatherPropertiesAndChanges(self, **kwargs):
defer.returnValue((real_properties, changeids, sourcestamps))

@defer.inlineCallbacks
def force(self, owner, builder_name, **kwargs):
def force(self, owner, builderNames=None, **kwargs):
"""
We check the parameters, and launch the build, if everything is correct
"""
if not builder_name in self.builderNames:
# in the case of buildAll, this method will be called several times
# for all the builders
# we just do nothing on a builder that is not in our builderNames
if builderNames is None:
builderNames = self.builderNames
else:
builderNames = set(builderNames).intersection(self.builderNames)

if not builderNames:
defer.returnValue(None)
return

Expand All @@ -576,7 +578,7 @@ def force(self, owner, builder_name, **kwargs):
reason = r,
sourcestamps = sourcestamps,
properties = properties,
builderNames = [ builder_name ],
builderNames = builderNames,
)

defer.returnValue(res)
84 changes: 44 additions & 40 deletions master/buildbot/status/web/builder.py
Expand Up @@ -31,7 +31,42 @@
from buildbot import util
import collections

class ForceAllBuildsActionResource(ActionResource):
class ForceAction(ActionResource):
@defer.inlineCallbacks
def force(self, req, builderNames):
master = self.getBuildmaster(req)
owner = self.getAuthz(req).getUsernameFull(req)
schedulername = req.args.get("forcescheduler", ["<unknown>"])[0]
if schedulername == "<unknown>":
defer.returnValue((path_to_builder(req, self.builder_status),
"forcescheduler arg not found"))
return

args = {}
# decode all of the args
encoding = getRequestCharset(req)
for name, argl in req.args.iteritems():
if name == "checkbox":
# damn html's ungeneric checkbox implementation...
for cb in argl:
args[cb.decode(encoding)] = True
else:
args[name] = [ arg.decode(encoding) for arg in argl ]

for sch in master.allSchedulers():
if schedulername == sch.name:
try:
yield sch.force(owner, builderNames, **args)
msg = ""
except ValidationError, e:
msg = html.escape(e.message.encode('ascii','ignore'))
break

# send the user back to the builder page
defer.returnValue(msg)


class ForceAllBuildsActionResource(ForceAction):

def __init__(self, status, selectedOrAll):
self.status = status
Expand All @@ -47,19 +82,15 @@ def performAction(self, req):
defer.returnValue(path_to_authzfail(req))
return

builders = None
if self.selectedOrAll == 'all':
builders = self.status.getBuilderNames()
builderNames = None
elif self.selectedOrAll == 'selected':
builders = [b for b in req.args.get("selected", []) if b]
builderNames = [b for b in req.args.get("selected", []) if b]

msg = yield self.force(req, builderNames)

for bname in builders:
builder_status = self.status.getBuilder(bname)
ar = ForceBuildActionResource(builder_status)
d = ar.performAction(req)
d.addErrback(log.err, "(ignored) while trying to force build")
# back to the welcome page
defer.returnValue(path_to_root(req))
defer.returnValue((path_to_root(req) + "builders", msg))

class StopAllBuildsActionResource(ActionResource):

Expand Down Expand Up @@ -119,7 +150,7 @@ def performAction(self, req):
# send the user back to the builder page
defer.returnValue(path_to_builder(req, self.builder_status))

class ForceBuildActionResource(ActionResource):
class ForceBuildActionResource(ForceAction):

def __init__(self, builder_status):
self.builder_status = builder_status
Expand All @@ -135,36 +166,9 @@ def performAction(self, req):
defer.returnValue(path_to_authzfail(req))
return

master = self.getBuildmaster(req)
owner = self.getAuthz(req).getUsernameFull(req)
schedulername = req.args.get("forcescheduler", ["<unknown>"])[0]
if schedulername == "<unknown>":
defer.returnValue((path_to_builder(req, self.builder_status),
"forcescheduler arg not found"))
return
builderName = self.builder_status.getName()

args = {}
# decode all of the args
encoding = getRequestCharset(req)
for name, argl in req.args.iteritems():
name = name.decode(encoding)
if name == 'checkbox':
# damn html's ungeneric checkbox implementation...
for cb in argl:
args[cb] = True
else:
args[name] = [ arg.decode(encoding) for arg in argl ]

builder_name = self.builder_status.getName()

for sch in master.allSchedulers():
if schedulername == sch.name:
try:
yield sch.force(owner, builder_name, **args)
msg = ""
except ValidationError, e:
msg = html.escape(e.message.encode('ascii','ignore'))
break
msg = yield self.force(req, [builderName])

# send the user back to the builder page
defer.returnValue((path_to_builder(req, self.builder_status), msg))
Expand Down
3 changes: 3 additions & 0 deletions master/buildbot/test/fake/fakedb.py
Expand Up @@ -747,6 +747,9 @@ def assertBuildset(self, bsid, expected_buildset, expected_sourcestamps):
if 'brids' in expected_buildset:
buildset['brids'] = self.allBuildRequests(bsid)

if 'builders' in expected_buildset:
buildset['builders'] = self.allBuildRequests(bsid).keys()

for ss in dictOfssDict.itervalues():
if 'id' in ss:
del ss['id']
Expand Down
61 changes: 58 additions & 3 deletions master/buildbot/test/unit/test_schedulers_forcesched.py
Expand Up @@ -112,7 +112,7 @@ def test_compare_codebases(self):
def test_basicForce(self):
sched = self.makeScheduler()

res = yield sched.force('user', 'a', branch='a', reason='because',revision='c',
res = yield sched.force('user', builderNames=['a'], branch='a', reason='because',revision='c',
repository='d', project='p',
property1_name='p1',property1_value='e',
property2_name='p2',property2_value='f',
Expand Down Expand Up @@ -143,6 +143,61 @@ def test_basicForce(self):
project='p', sourcestampsetid=100)
})

@defer.inlineCallbacks
def test_force_allBuilders(self):
sched = self.makeScheduler()

res = yield sched.force('user', branch='a', reason='because',revision='c',
repository='d', project='p',
)
bsid,brids = res

self.assertEqual(len(brids), 2)

self.db.buildsets.assertBuildset\
(bsid,
dict(reason="A build was forced by 'user': because",
brids=brids,
builders = ['a', 'b'],
external_idstring=None,
properties=[ ('owner', ('user', 'Force Build Form')),
('reason', ('because', 'Force Build Form')),
('scheduler', ('testsched', 'Scheduler')),
],
sourcestampsetid=100),
{'':
dict(branch='a', revision='c', repository='d', codebase='',
project='p', sourcestampsetid=100)
})

@defer.inlineCallbacks
def test_force_someBuilders(self):
sched = self.makeScheduler(builderNames=['a','b','c'])

res = yield sched.force('user', builderNames=['a','b'],
branch='a', reason='because',revision='c',
repository='d', project='p',
)
bsid,brids = res

self.assertEqual(len(brids), 2)

self.db.buildsets.assertBuildset\
(bsid,
dict(reason="A build was forced by 'user': because",
brids=brids,
builders = ['a', 'b'],
external_idstring=None,
properties=[ ('owner', ('user', 'Force Build Form')),
('reason', ('because', 'Force Build Form')),
('scheduler', ('testsched', 'Scheduler')),
],
sourcestampsetid=100),
{'':
dict(branch='a', revision='c', repository='d', codebase='',
project='p', sourcestampsetid=100)
})

def test_bad_codebases(self):
# cant specify both codebases and branch/revision/project/repository:
self.assertRaises(ValidationError, ForceScheduler,
Expand Down Expand Up @@ -174,7 +229,7 @@ def test_bad_codebases(self):
@defer.inlineCallbacks
def test_good_codebases(self):
sched = self.makeScheduler(codebases=['foo', CodebaseParameter('bar')])
res = yield sched.force('user', 'a', reason='because',
res = yield sched.force('user', builderNames=['a'], reason='because',
foo_branch='a', foo_revision='c', foo_repository='d', foo_project='p',
bar_branch='a2', bar_revision='c2', bar_repository='d2', bar_project='p2',
property1_name='p1',property1_value='e',
Expand Down Expand Up @@ -233,7 +288,7 @@ def do_ParameterTest(self,
if not req:
req = {name:value, 'reason':'because'}
try:
bsid, brids = yield sched.force(owner, 'a', **req)
bsid, brids = yield sched.force(owner, builderNames=['a'], **req)
except Exception,e:
if expectKind is not Exception:
# an exception is not expected
Expand Down

0 comments on commit 3be5a31

Please sign in to comment.