Skip to content

Commit

Permalink
Merge pull request #898 from tardyp/forcesched
Browse files Browse the repository at this point in the history
Forcesched UI
  • Loading branch information
djmitche committed Oct 16, 2013
2 parents b8cecfe + ddc90d4 commit 3ed72e7
Show file tree
Hide file tree
Showing 32 changed files with 501 additions and 179 deletions.
1 change: 1 addition & 0 deletions master/buildbot/data/connector.py
Expand Up @@ -45,6 +45,7 @@ class DataConnector(service.Service):
'buildbot.data.masters',
'buildbot.data.sourcestamps',
'buildbot.data.schedulers',
'buildbot.data.forceschedulers',
'buildbot.data.root',
]

Expand Down
70 changes: 70 additions & 0 deletions master/buildbot/data/forceschedulers.py
@@ -0,0 +1,70 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

from buildbot.data import base, types
from buildbot.schedulers import forcesched


def forceScheduler2Data(sched):
ret = dict(all_fields = [],
name = sched.name,
builder_names = sched.builderNames)
for field in sched.all_fields:
ret["all_fields"].append(field.toJsonDict())
return ret

class ForceSchedulerEndpoint(base.Endpoint):

isCollection = False
pathPatterns = """
/forceschedulers/i:schedulername
"""

def get(self, resultSpec, kwargs):
for sched in self.master.allSchedulers():
if sched.name == kwargs['schedulername'] and isinstance(sched, forcesched.ForceScheduler):
return forceScheduler2Data(sched)


class ForceSchedulersEndpoint(base.Endpoint):

isCollection = True
pathPatterns = """
/forceschedulers
"""
rootLinkName = 'schedulers'

def get(self, resultSpec, kwargs):
l = []
for sched in self.master.allSchedulers():
print sched, isinstance(sched, forcesched.ForceScheduler)
if isinstance(sched, forcesched.ForceScheduler):
l.append(forceScheduler2Data(sched))
return l


class ForceScheduler(base.ResourceType):

name = "forcescheduler"
plural = "forceschedulers"
endpoints = [ ForceSchedulerEndpoint, ForceSchedulersEndpoint ]
keyFields = [ ]

class EntityType(types.Entity):
name = types.String()
builder_names = types.List(of=types.Identifier())
all_fields = types.List(of=types.JsonObject())
entityType = EntityType(name)

57 changes: 41 additions & 16 deletions master/buildbot/schedulers/forcesched.py
Expand Up @@ -15,6 +15,7 @@

import traceback
import re
import copy
from twisted.internet import defer
import email.utils as email_utils

Expand All @@ -34,13 +35,15 @@ class BaseParameter(object):
name = ""
parentName = None
label = ""
type = []
type = ""
subtype = ""
default = ""
required = False
multiple = False
regex = None
debug=True
hide = False
css_class = ""

@property
def fullName(self):
Expand Down Expand Up @@ -130,10 +133,17 @@ def parse_from_args(self, l):
def parse_from_arg(self, s):
return s

def toJsonDict(self):
ret = {}
for i in dir(self):
v = getattr(self, i)
if not callable(v) and not i.startswith("__"):
ret[i] = v
return ret

class FixedParameter(BaseParameter):
"""A fixed parameter that cannot be modified by the user."""
type = ["fixed"]
type = "fixed"
hide = True
default = ""

Expand All @@ -143,7 +153,7 @@ def parse_from_args(self, l):

class StringParameter(BaseParameter):
"""A simple string parameter"""
type = ["text"]
type = "text"
size = 10

def parse_from_arg(self, s):
Expand All @@ -152,7 +162,7 @@ def parse_from_arg(self, s):

class TextParameter(StringParameter):
"""A generic string parameter that may span multiple lines"""
type = ["textarea"]
type = "textarea"
cols = 80
rows = 20

Expand All @@ -162,22 +172,22 @@ def value_to_text(self, value):

class IntParameter(StringParameter):
"""An integer parameter"""
type = ["int"]
type = "int"

parse_from_arg = int # will throw an exception if parse fail


class BooleanParameter(BaseParameter):
"""A boolean parameter"""
type = ["bool"]
type = "bool"

def getFromKwargs(self, kwargs):
return kwargs.get(self.fullName, None) == [True]


class UserNameParameter(StringParameter):
"""A username parameter to supply the 'owner' of a build"""
type = ["text"]
type = "text"
default = ""
size = 30
need_email = True
Expand All @@ -200,7 +210,7 @@ class ChoiceStringParameter(BaseParameter):
"""A list of strings, allowing the selection of one of the predefined values.
The 'strict' parameter controls whether values outside the predefined list
of choices are allowed"""
type = ["list"]
type = "list"
choices = []
strict = True

Expand All @@ -214,7 +224,8 @@ def getChoices(self, master, scheduler, buildername):

class InheritBuildParameter(ChoiceStringParameter):
"""A parameter that takes its values from another build"""
type = ChoiceStringParameter.type + ["inherit"]
type = ChoiceStringParameter.type
subtype = "inherit"
name = "inherit"
compatible_builds = None

Expand Down Expand Up @@ -298,12 +309,20 @@ class NestedParameter(BaseParameter):
The result of a NestedParameter is typically a dictionary, with the key/value
being the name/value of the children.
"""
type = ['nested']
type = 'nested'
fields = None

columns = None
def __init__(self, name, fields, **kwargs):
BaseParameter.__init__(self, fields=fields, name=name, **kwargs)

# reasonable defaults for the number of columns
if self.columns is None:
if len(fields) >= 4:
self.columns = 2
else:
self.columns = 1
if self.columns > 4:
config.error("UI only support up to 4 columns in nested parameters")

# fix up the child nodes with the parent (use None for now):
self.setParent(None)

Expand Down Expand Up @@ -338,11 +357,17 @@ def updateFromKwargs(self, kwargs, properties, **kw):
# if there's no name, collapse this nest all the way
d = properties
d.update(kwargs[self.fullName])


def toJsonDict(self):
ret = BaseParameter.toJsonDict(self)
ret['fields'] = [ field.toJsonDict() for field in self.fields ]
return ret

class AnyPropertyParameter(NestedParameter):
"""A generic property parameter, where both the name and value of the property
must be given."""
type = NestedParameter.type + ["any"]
type = NestedParameter.type
subtype = "any"

def __init__(self, name, **kw):
fields = [
Expand Down Expand Up @@ -374,10 +399,10 @@ def updateFromKwargs(self, master, properties, kwargs, **kw):
raise ValidationError("bad property name='%s', value='%s'" % (pname, pvalue))
properties[pname] = pvalue


class CodebaseParameter(NestedParameter):
"""A parameter whose result is a codebase specification instead of a property"""
type = NestedParameter.type + ["codebase"]
type = NestedParameter.type
subtype = "codebase"
codebase = ''

def __init__(self,
Expand Down
75 changes: 58 additions & 17 deletions master/buildbot/test/unit/test_schedulers_forcesched.py
Expand Up @@ -24,6 +24,7 @@
from buildbot.schedulers.forcesched import CodebaseParameter, BaseParameter
from buildbot.test.util import scheduler
from buildbot.test.util.config import ConfigErrorsMixin
from buildbot.util import json

class TestForceScheduler(scheduler.SchedulerMixin, ConfigErrorsMixin, unittest.TestCase):

Expand Down Expand Up @@ -317,6 +318,7 @@ def do_ParameterTest(self,
expectKind=None, # None=one prop, Exception=exception, dict=many props
owner='user',
value=None, req=None,
expectJson=None,
**kwargs):

name = kwargs.setdefault('name', 'p1')
Expand All @@ -329,6 +331,8 @@ def do_ParameterTest(self,

self.assertEqual(prop.name, name)
self.assertEqual(prop.label, kwargs.get('label', prop.name))
if expectJson is not None:
self.assertEqual(json.dumps(prop.toJsonDict()), expectJson)

sched = self.makeScheduler(properties=[prop])

Expand Down Expand Up @@ -373,35 +377,54 @@ def do_ParameterTest(self,

def test_StringParameter(self):
self.do_ParameterTest(value="testedvalue", expect="testedvalue",
klass=StringParameter)
klass=StringParameter,
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "default": "", "css_class": "", '
'"parentName": null, "label": "p1", "subtype": "", '
'"debug": true, "multiple": false, "fullName": "p1", "type": "text", '
'"size": 10}')

def test_IntParameter(self):
self.do_ParameterTest(value="123", expect=123, klass=IntParameter)
self.do_ParameterTest(value="123", expect=123, klass=IntParameter,
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "default": "", "css_class": "", '
'"parentName": null, "label": "p1", "subtype": "", '
'"debug": true, "multiple": false, "fullName": "p1", "type": "int", '
'"size": 10}')


def test_FixedParameter(self):
self.do_ParameterTest(value="123", expect="321", klass=FixedParameter,
default="321")

default="321",
expectJson='{"regex": null, "required": false, "hide": true, '
'"name": "p1", "default": "321", "css_class": "", '
'"parentName": null, "label": "p1", "subtype": "", '
'"debug": true, "multiple": false, "fullName": "p1", "type": "fixed"}')

def test_BooleanParameter_True(self):
req = dict(p1=True,reason='because')
self.do_ParameterTest(value="123", expect=True, klass=BooleanParameter,
req=req)

req=req,
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "default": "", "css_class": "", '
'"parentName": null, "label": "p1", "subtype": "", '
'"debug": true, "multiple": false, "fullName": "p1", "type": "bool"}')

def test_BooleanParameter_False(self):
req = dict(p2=True,reason='because')
self.do_ParameterTest(value="123", expect=False,
klass=BooleanParameter, req=req)

klass=BooleanParameter, req=req)

def test_UserNameParameter(self):
email = "test <test@buildbot.net>"
self.do_ParameterTest(value=email, expect=email,
klass=UserNameParameter(),
name="username", label="Your name:")

klass=UserNameParameter(),
name="username", label="Your name:",
expectJson='{"regex": null, "parentName": null, "hide": false, '
'"name": "username", "default": "", "css_class": "", '
'"need_email": true, "label": "Your name:", "subtype": "", '
'"debug": true, "multiple": false, "fullName": "username", '
'"size": 30, "type": "text", "required": false}')

def test_UserNameParameterError(self):
for value in ["test","test@buildbot.net","<test@buildbot.net>"]:
Expand All @@ -411,11 +434,14 @@ def test_UserNameParameterError(self):
klass=UserNameParameter(debug=False),
name="username", label="Your name:")


def test_ChoiceParameter(self):
self.do_ParameterTest(value='t1', expect='t1',
klass=ChoiceStringParameter, choices=['t1','t2'])

klass=ChoiceStringParameter, choices=['t1','t2'],
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "subtype": "", "default": "", "css_class": "", '
'"parentName": null, "choices": ["t1", "t2"], "strict": true, '
'"debug": true, "multiple": false, "fullName": "p1", '
'"label": "p1", "type": "list"}')

def test_ChoiceParameterError(self):
self.do_ParameterTest(value='t3',
Expand All @@ -430,9 +456,16 @@ def test_ChoiceParameterError_notStrict(self):
klass=ChoiceStringParameter, choices=['t1','t2'])



def test_ChoiceParameterMultiple(self):
self.do_ParameterTest(value=['t1','t2'], expect=['t1','t2'],
klass=ChoiceStringParameter,choices=['t1','t2'], multiple=True)
klass=ChoiceStringParameter,choices=['t1','t2'], multiple=True,
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "subtype": "", "default": "", "css_class": "", '
'"parentName": null, "choices": ["t1", "t2"], "strict": true, '
'"debug": true, "multiple": true, "fullName": "p1", '
'"label": "p1", "type": "list"}')



def test_ChoiceParameterMultipleError(self):
Expand All @@ -442,14 +475,22 @@ def test_ChoiceParameterMultipleError(self):
klass=ChoiceStringParameter, choices=['t1','t2'],
multiple=True, debug=False)


def test_NestedParameter(self):
fields = [
IntParameter(name="foo")
]
self.do_ParameterTest(req=dict(p1_foo='123', reason="because"),
expect=dict(foo=123),
klass=NestedParameter, fields=fields)
klass=NestedParameter, fields=fields,
expectJson='{"regex": null, "required": false, "hide": false, '
'"name": "p1", "default": "", "css_class": "", '
'"parentName": null, "label": "p1", "subtype": "", '
'"fields": [{"regex": null, "required": false, "hide": false, '
'"name": "foo", "default": "", "css_class": "", '
'"parentName": "p1", "label": "foo", "subtype": "", "debug": true, '
'"multiple": false, "fullName": "p1_foo", "type": "int", '
'"size": 10}], "debug": true, "multiple": false, '
'"fullName": "p1", "type": "nested", "columns": 1}')

def test_NestedNestedParameter(self):
fields = [
Expand Down

0 comments on commit 3ed72e7

Please sign in to comment.