buildbot / buildbot
Showing 22 of 34 files from the diff.

@@ -102,7 +102,7 @@
Loading
102 102
    name = "build_data"
103 103
    plural = "build_data"
104 104
    endpoints = [BuildDatasNoValueEndpoint, BuildDataNoValueEndpoint, BuildDataEndpoint]
105 -
    keyFields = []
105 +
    keyField = "name"
106 106
107 107
    class EntityType(types.Entity):
108 108
        buildid = types.Integer()

@@ -67,7 +67,7 @@
Loading
67 67
    name = "test_result"
68 68
    plural = "test_results"
69 69
    endpoints = [TestResultsEndpoint]
70 -
    keyFields = ['test_resultid']
70 +
    keyField = 'test_resultid'
71 71
    eventPathPatterns = """
72 72
        /test_result_sets/:test_result_setid/results
73 73
    """

@@ -27,7 +27,7 @@
Loading
27 27
    name = None
28 28
    plural = None
29 29
    endpoints = []
30 -
    keyFields = []
30 +
    keyField = None
31 31
    eventPathPatterns = ""
32 32
    entityType = None
33 33
    subresources = []
@@ -68,7 +68,7 @@
Loading
68 68
    @functools.lru_cache(1)
69 69
    def getCollectionEndpoint(self):
70 70
        for ep in self.getEndpoints():
71 -
            if ep.isCollection:
71 +
            if ep.isCollection or ep.isPseudoCollection:
72 72
                return ep
73 73
        return None
74 74
@@ -101,7 +101,9 @@
Loading
101 101
    pathPatterns = ""
102 102
    rootLinkName = None
103 103
    isCollection = False
104 +
    isPseudoCollection = False
104 105
    isRaw = False
106 +
    parentMapping = {}
105 107
106 108
    def __init__(self, rtype, master):
107 109
        self.rtype = rtype
@@ -117,17 +119,34 @@
Loading
117 119
            raise exceptions.InvalidControlException("action: {} is not supported".format(action))
118 120
        return action_method(args, kwargs)
119 121
122 +
    def get_kwargs_from_graphql_parent(self, parent, parent_type):
123 +
        if parent_type not in self.parentMapping:
124 +
            rtype = self.master.data.getResourceTypeForGraphQlType(parent_type)
125 +
            if rtype.keyField in parent:
126 +
                parentid = rtype.keyField
127 +
            else:
128 +
                raise NotImplementedError(
129 +
                    "Collection endpoint should implement "
130 +
                    "get_kwargs_from_graphql or parentMapping"
131 +
                )
132 +
        else:
133 +
            parentid = self.parentMapping[parent_type]
134 +
        ret = {}
135 +
        ret[parentid] = parent[parentid]
136 +
        return ret
137 +
120 138
    def get_kwargs_from_graphql(self, parent, resolve_info, args):
121 -
        if self.isCollection:
139 +
        if self.isCollection or self.isPseudoCollection:
122 140
            if parent is not None:
123 -
                raise NotImplementedError(
124 -
                    "Collection endpoint should implement get_kwargs_from_graphql")
141 +
                return self.get_kwargs_from_graphql_parent(
142 +
                    parent, resolve_info.parent_type.name
143 +
                )
125 144
            return {}
126 145
        ret = {}
127 -
        for k in self.rtype.keyFields:
128 -
            v = args.pop(k)
129 -
            if v is not None:
130 -
                ret[k] = v
146 +
        k = self.rtype.keyField
147 +
        v = args.pop(k)
148 +
        if v is not None:
149 +
            ret[k] = v
131 150
        return ret
132 151
133 152
    def __repr__(self):

@@ -43,11 +43,16 @@
Loading
43 43
    with as_future()
44 44
    """
45 45
    data = None
46 +
    asyncio_loop = None
47 +
48 +
    # asyncio will create an event loop if none exists yet in get_event_loop(). We need to set it
49 +
    # back via set_event_loop() if we want it to be properly closed.
50 +
    _saved_event_loop = None
46 51
47 52
    def reconfigServiceWithBuildbotConfig(self, new_config):
48 53
        if self.data is None:
49 54
            self.data = self.master.data
50 -
        config = new_config.get('www', {}).get('graphql')
55 +
        config = new_config.www.get('graphql')
51 56
        self.enabled = False
52 57
        if config is None:
53 58
            return
@@ -57,14 +62,32 @@
Loading
57 62
58 63
        self.enabled = True
59 64
        self.config = config
60 -
        if not isinstance(asyncio.get_event_loop(), AsyncIOLoopWithTwisted):
61 -
            asyncio_loop = AsyncIOLoopWithTwisted(self.master.reactor)
62 -
            asyncio.set_event_loop(asyncio_loop)
63 -
            asyncio_loop.start()
65 +
        loop = None
66 +
        try:
67 +
            if self._saved_event_loop is None:
68 +
                loop = asyncio.get_event_loop()
69 +
        except RuntimeError:
70 +
            pass
71 +
72 +
        if self._saved_event_loop is None and not isinstance(loop, AsyncIOLoopWithTwisted):
73 +
            self._saved_event_loop = loop
74 +
            self.asyncio_loop = AsyncIOLoopWithTwisted(self.master.reactor)
75 +
            asyncio.set_event_loop(self.asyncio_loop)
76 +
            self.asyncio_loop.start()
64 77
65 78
        self.debug = self.config.get("debug")
66 79
        self.schema = graphql.build_schema(self.get_schema())
67 80
81 +
    def stopService(self):
82 +
        if self.asyncio_loop:
83 +
            self.asyncio_loop.stop()
84 +
            self.asyncio_loop.close()
85 +
            if self._saved_event_loop is not None:
86 +
                asyncio.set_event_loop(self._saved_event_loop)
87 +
            self.asyncio_loop = None
88 +
89 +
        return super().stopService()
90 +
68 91
    @functools.lru_cache(1)
69 92
    def get_schema(self):
70 93
        """Return the graphQL Schema of the buildbot data model"""
@@ -81,7 +104,9 @@
Loading
81 104
        # type dependencies must be added recursively
82 105
        def add_dependent_types(ent):
83 106
            typename = ent.toGraphQLTypeName()
84 -
            if typename not in types and isinstance(ent, Entity):
107 +
            if typename in types:
108 +
                return
109 +
            if isinstance(ent, Entity):
85 110
                types[typename] = ent
86 111
            for dtyp in ent.graphQLDependentTypes():
87 112
                add_dependent_types(dtyp)
@@ -89,7 +114,8 @@
Loading
89 114
            rtype = self.data.getResourceType(ent.name)
90 115
            if rtype is not None:
91 116
                for subresource in rtype.subresources:
92 -
                    add_dependent_types(subresource.rtype.entityType)
117 +
                    rtype = self.data.getResourceTypeForGraphQlType(subresource)
118 +
                    add_dependent_types(rtype.entityType)
93 119
94 120
        # root query contain the list of item available directly
95 121
        # mapped against the rootLinks
@@ -118,16 +144,23 @@
Loading
118 144
                    query_fields.append(f"{field}__{op}: {field_type_graphql}")
119 145
120 146
            query_fields.extend(["order: String", "limit: Int", "offset: Int"])
147 +
            ep = self.data.getEndPointForResourceName(rtype.plural)
148 +
            if ep is None or not ep.isPseudoCollection:
149 +
                plural_typespec = f"[{typename}]"
150 +
            else:
151 +
                plural_typespec = typename
121 152
            queries_schema += (
122 -
                f"  {rtype.plural}{format_query_fields(query_fields)}: [{typename}]!\n"
153 +
                f"  {rtype.plural}{format_query_fields(query_fields)}: {plural_typespec}!\n"
123 154
            )
124 155
125 -
            # build the queriable parameters, via keyFields
156 +
            # build the queriable parameter, via keyField
126 157
            keyfields = []
127 -
            for field in sorted(rtype.keyFields):
128 -
                field_type = rtype.entityType.fields[field]
129 -
                field_type_graphql = field_type.toGraphQLTypeName()
130 -
                keyfields.append(f"{field}: {field_type_graphql}")
158 +
            field = rtype.keyField
159 +
            if field not in rtype.entityType.fields:
160 +
                raise RuntimeError(f"bad keyField {field} not in entityType {rtype.entityType}")
161 +
            field_type = rtype.entityType.fields[field]
162 +
            field_type_graphql = field_type.toGraphQLTypeName()
163 +
            keyfields.append(f"{field}: {field_type_graphql}")
131 164
132 165
            queries_schema += (
133 166
                f"  {rtype.name}{format_query_fields(keyfields)}: {typename}\n"
@@ -155,7 +188,8 @@
Loading
155 188
            rtype = self.data.getResourceType(typ.name)
156 189
            if rtype is not None:
157 190
                for subresource in rtype.subresources:
158 -
                    schema += format_subresource(subresource.rtype)
191 +
                    rtype = self.data.getResourceTypeForGraphQlType(subresource)
192 +
                    schema += format_subresource(rtype)
159 193
            schema += "}\n"
160 194
        return schema
161 195
@@ -181,7 +215,7 @@
Loading
181 215
            rspec = None
182 216
            kwargs = ep.get_kwargs_from_graphql(parent, resolve_info, args)
183 217
184 -
            if ep.isCollection:
218 +
            if ep.isCollection or ep.isPseudoCollection:
185 219
                args = {k: [v] for k, v in args.items()}
186 220
                rspec = self.data.resultspec_from_jsonapi(args, ep.rtype.entityType, True)
187 221

@@ -25,7 +25,7 @@
Loading
25 25
    name = "patch"
26 26
    plural = "patches"
27 27
    endpoints = []
28 -
    keyFields = ['patchid']
28 +
    keyField = 'patchid'
29 29
30 30
    class EntityType(types.Entity):
31 31
        patchid = types.Integer()

@@ -87,6 +87,8 @@
Loading
87 87
    eventPathPatterns = """
88 88
        /masters/:masterid
89 89
    """
90 +
    keyField = "masterid"
91 +
    subresources = ["Builder"]
90 92
91 93
    class EntityType(types.Entity):
92 94
        masterid = types.Integer()

@@ -79,7 +79,7 @@
Loading
79 79
    name = "changesource"
80 80
    plural = "changesources"
81 81
    endpoints = [ChangeSourceEndpoint, ChangeSourcesEndpoint]
82 -
    keyFields = ['changesourceid']
82 +
    keyField = 'changesourceid'
83 83
84 84
    class EntityType(types.Entity):
85 85
        changesourceid = types.Integer()

@@ -132,7 +132,9 @@
Loading
132 132
        return None
133 133
134 134
    def getResourceTypeForGraphQlType(self, type):
135 -
        return self.graphql_rtypes.get(type.name)
135 +
        if type not in self.graphql_rtypes:
136 +
            raise RuntimeError(f"Can't get rtype for {type}: {self.graphql_rtypes.keys()}")
137 +
        return self.graphql_rtypes.get(type)
136 138
137 139
    def get(self, path, filters=None, fields=None, order=None,
138 140
            limit=None, offset=None):
@@ -186,23 +188,23 @@
Loading
186 188
        filters, properties = [], []
187 189
        for arg in req_args:
188 190
            argStr = bytes2unicode(arg)
189 -
            if arg == b'order':
191 +
            if argStr == 'order':
190 192
                order = tuple([bytes2unicode(o) for o in req_args[arg]])
191 193
                checkFields(order, True)
192 -
            elif arg == b'field':
194 +
            elif argStr == 'field':
193 195
                fields = req_args[arg]
194 196
                checkFields(fields, False)
195 -
            elif arg == b'limit':
197 +
            elif argStr == 'limit':
196 198
                try:
197 199
                    limit = int(req_args[arg][0])
198 200
                except Exception as e:
199 201
                    raise exceptions.InvalidQueryParameter('invalid limit') from e
200 -
            elif arg == b'offset':
202 +
            elif argStr == 'offset':
201 203
                try:
202 204
                    offset = int(req_args[arg][0])
203 205
                except Exception as e:
204 206
                    raise exceptions.InvalidQueryParameter('invalid offset') from e
205 -
            elif arg == b'property':
207 +
            elif argStr == 'property':
206 208
                try:
207 209
                    props = []
208 210
                    for v in req_args[arg]:

@@ -46,7 +46,9 @@
Loading
46 46
    # Note that this is a singular endpoint, even though it overrides the
47 47
    # offset/limit query params in ResultSpec
48 48
    isCollection = False
49 +
    isPseudoCollection = True
49 50
    pathPatterns = """
51 +
        /logchunks
50 52
        /logs/n:logid/contents
51 53
        /steps/n:stepid/logs/i:log_slug/contents
52 54
        /builds/n:buildid/steps/i:step_name/logs/i:log_slug/contents
@@ -54,6 +56,7 @@
Loading
54 56
        /builders/n:builderid/builds/n:build_number/steps/i:step_name/logs/i:log_slug/contents
55 57
        /builders/n:builderid/builds/n:build_number/steps/n:step_number/logs/i:log_slug/contents
56 58
    """
59 +
    rootLinkName = "logchunks"
57 60
58 61
    @defer.inlineCallbacks
59 62
    def get(self, resultSpec, kwargs):
@@ -83,6 +86,13 @@
Loading
83 86
                'firstline': firstline,
84 87
                'content': logLines}
85 88
89 +
    def get_kwargs_from_graphql(self, parent, resolve_info, args):
90 +
        if parent is not None:
91 +
            return self.get_kwargs_from_graphql_parent(
92 +
                parent, resolve_info.parent_type.name
93 +
            )
94 +
        return {"logid": args["logid"]}
95 +
86 96
87 97
class RawLogChunkEndpoint(LogChunkEndpointBase):
88 98
@@ -124,13 +134,14 @@
Loading
124 134
125 135
class LogChunk(base.ResourceType):
126 136
127 -
    name = "logchunk"
128 -
    plural = "logchunks"
137 +
    name = "log_chunk"
138 +
    plural = "log_chunks"
129 139
    endpoints = [LogChunkEndpoint, RawLogChunkEndpoint]
130 -
    keyFields = ['stepid', 'logid']
140 +
    keyField = "logid"
131 141
132 142
    class EntityType(types.Entity):
133 143
        logid = types.Integer()
134 144
        firstline = types.Integer()
135 145
        content = types.String()
146 +
136 147
    entityType = EntityType(name)

@@ -180,13 +180,15 @@
Loading
180 180
    name = "buildrequest"
181 181
    plural = "buildrequests"
182 182
    endpoints = [BuildRequestEndpoint, BuildRequestsEndpoint]
183 -
    keyFields = ['buildsetid', 'builderid', 'buildrequestid']
183 +
    keyField = 'buildrequestid'
184 184
    eventPathPatterns = """
185 185
        /buildsets/:buildsetid/builders/:builderid/buildrequests/:buildrequestid
186 186
        /buildrequests/:buildrequestid
187 187
        /builders/:builderid/buildrequests/:buildrequestid
188 188
    """
189 189
190 +
    subresources = ["Build"]
191 +
190 192
    class EntityType(types.Entity):
191 193
        buildrequestid = types.Integer()
192 194
        buildsetid = types.Integer()

@@ -101,11 +101,12 @@
Loading
101 101
    name = "step"
102 102
    plural = "steps"
103 103
    endpoints = [StepEndpoint, StepsEndpoint]
104 -
    keyFields = ['builderid', 'stepid']
104 +
    keyField = 'stepid'
105 105
    eventPathPatterns = """
106 106
        /builds/:buildid/steps/:stepid
107 107
        /steps/:stepid
108 108
    """
109 +
    subresources = ["Log"]
109 110
110 111
    class EntityType(types.Entity):
111 112
        stepid = types.Integer()

@@ -114,7 +114,7 @@
Loading
114 114
    name = "buildset"
115 115
    plural = "buildsets"
116 116
    endpoints = [BuildsetEndpoint, BuildsetsEndpoint]
117 -
    keyFields = ['bsid']
117 +
    keyField = 'bsid'
118 118
    eventPathPatterns = """
119 119
        /buildsets/:bsid
120 120
    """

@@ -97,7 +97,7 @@
Loading
97 97
    name = "forcescheduler"
98 98
    plural = "forceschedulers"
99 99
    endpoints = [ForceSchedulerEndpoint, ForceSchedulersEndpoint]
100 -
    keyFields = []
100 +
    keyField = "name"
101 101
102 102
    class EntityType(types.Entity):
103 103
        name = types.Identifier(50)

@@ -95,11 +95,12 @@
Loading
95 95
    name = "log"
96 96
    plural = "logs"
97 97
    endpoints = [LogEndpoint, LogsEndpoint]
98 -
    keyFields = ['stepid', 'logid']
98 +
    keyField = "logid"
99 99
    eventPathPatterns = """
100 100
        /logs/:logid
101 101
        /steps/:stepid/logs/:slug
102 102
    """
103 +
    subresources = ["LogChunk"]
103 104
104 105
    class EntityType(types.Entity):
105 106
        logid = types.Integer()
@@ -109,6 +110,7 @@
Loading
109 110
        complete = types.Boolean()
110 111
        num_lines = types.Integer()
111 112
        type = types.Identifier(1)
113 +
112 114
    entityType = EntityType(name)
113 115
114 116
    @defer.inlineCallbacks

@@ -101,6 +101,7 @@
Loading
101 101
    eventPathPatterns = """
102 102
        /changes/:changeid
103 103
    """
104 +
    keyField = "changeid"
104 105
105 106
    class EntityType(types.Entity):
106 107
        changeid = types.Integer()
@@ -120,6 +121,7 @@
Loading
120 121
        codebase = types.String()
121 122
        sourcestamp = sourcestamps.SourceStamp.entityType
122 123
    entityType = EntityType(name)
124 +
    subresources = ["Build"]
123 125
124 126
    @base.updateMethod
125 127
    @defer.inlineCallbacks

@@ -117,10 +117,11 @@
Loading
117 117
    name = "worker"
118 118
    plural = "workers"
119 119
    endpoints = [WorkerEndpoint, WorkersEndpoint]
120 -
    keyFields = ['workerid']
120 +
    keyField = 'workerid'
121 121
    eventPathPatterns = """
122 122
        /workers/:workerid
123 123
    """
124 +
    subresources = ["Build"]
124 125
125 126
    class EntityType(types.Entity):
126 127
        workerid = types.Integer()

@@ -50,7 +50,7 @@
Loading
50 50
    name = "property"
51 51
    plural = "properties"
52 52
    endpoints = [BuildsetPropertiesEndpoint, BuildPropertiesEndpoint]
53 -
    keyFields = []
53 +
    keyField = "name"
54 54
55 55
    entityType = types.SourcedProperties()
56 56

@@ -68,16 +68,22 @@
Loading
68 68
                     tags=bd['tags'])
69 69
               for bd in bdicts]
70 70
71 +
    def get_kwargs_from_graphql(self, parent, resolve_info, args):
72 +
        if parent is not None:
73 +
            return {'masterid': parent['masterid']}
74 +
        return {}
75 +
71 76
72 77
class Builder(base.ResourceType):
73 78
74 79
    name = "builder"
75 80
    plural = "builders"
76 81
    endpoints = [BuilderEndpoint, BuildersEndpoint]
77 -
    keyFields = ['builderid']
82 +
    keyField = 'builderid'
78 83
    eventPathPatterns = """
79 84
        /builders/:builderid
80 85
    """
86 +
    subresources = ["Build", "Forcescheduler", "Scheduler", "Buildrequest"]
81 87
82 88
    class EntityType(types.Entity):
83 89
        builderid = types.Integer()

@@ -89,7 +89,7 @@
Loading
89 89
    name = "scheduler"
90 90
    plural = "schedulers"
91 91
    endpoints = [SchedulerEndpoint, SchedulersEndpoint]
92 -
    keyFields = ['schedulerid']
92 +
    keyField = 'schedulerid'
93 93
    eventPathPatterns = """
94 94
        /schedulers/:schedulerid
95 95
    """

@@ -77,7 +77,8 @@
Loading
77 77
    name = "sourcestamp"
78 78
    plural = "sourcestamps"
79 79
    endpoints = [SourceStampEndpoint, SourceStampsEndpoint]
80 -
    keyFields = ['ssid']
80 +
    keyField = 'ssid'
81 +
    subresources = ["Change"]
81 82
82 83
    class EntityType(types.Entity):
83 84
        ssid = types.Integer()

@@ -101,7 +101,7 @@
Loading
101 101
    name = "test_result_set"
102 102
    plural = "test_result_sets"
103 103
    endpoints = [TestResultSetsEndpoint, TestResultSetEndpoint]
104 -
    keyFields = ['test_result_setid']
104 +
    keyField = 'test_result_setid'
105 105
    eventPathPatterns = """
106 106
        /test_result_sets/:test_result_setid
107 107
    """

@@ -189,12 +189,13 @@
Loading
189 189
    name = "build"
190 190
    plural = "builds"
191 191
    endpoints = [BuildEndpoint, BuildsEndpoint]
192 -
    keyFields = ['builderid', 'buildid', 'workerid']
192 +
    keyField = "buildid"
193 193
    eventPathPatterns = """
194 194
        /builders/:builderid/builds/:number
195 195
        /builds/:buildid
196 196
        /workers/:workerid/builds/:buildid
197 197
    """
198 +
    subresources = ["Step"]
198 199
199 200
    class EntityType(types.Entity):
200 201
        buildid = types.Integer()
Files Coverage
master/buildbot 92.29%
worker/buildbot_worker 85.52%
Project Totals (331 files) 91.88%
Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading