From 48959d6e5388c0628905703c8cd8d250cdd722a0 Mon Sep 17 00:00:00 2001 From: Tristan Cacqueray Date: Thu, 21 Feb 2019 03:32:27 +0000 Subject: [PATCH] web: add /{tenant}/buildset/{uuid} route This change adds a /buildset/{uuid} route to return a single buildset. Change-Id: Id703d337caa68278dfed25aa0ff07371d0e81a1b --- tests/unit/test_web.py | 46 +++++++++++++++++++++++++++++ zuul/driver/sql/sqlconnection.py | 31 ++++++++++++++++++++ zuul/web/__init__.py | 50 ++++++++++++++++++++++---------- 3 files changed, 112 insertions(+), 15 deletions(-) diff --git a/tests/unit/test_web.py b/tests/unit/test_web.py index 58c5640133..b3f34b03c1 100755 --- a/tests/unit/test_web.py +++ b/tests/unit/test_web.py @@ -725,6 +725,23 @@ class TestBuildInfo(ZuulDBTestCase, BaseTestWeb): buildsets = self.get_url("api/tenant/tenant-one/buildsets").json() self.assertEqual(2, len(buildsets)) + project_bs = [x for x in buildsets if x["project"] == "org/project"][0] + + buildset = self.get_url( + "api/tenant/tenant-one/buildset/%s" % project_bs['uuid']).json() + self.assertEqual(3, len(buildset["builds"])) + + project_test1_build = [x for x in buildset["builds"] + if x["job_name"] == "project-test1"][0] + self.assertEqual('SUCCESS', project_test1_build['result']) + + project_test2_build = [x for x in buildset["builds"] + if x["job_name"] == "project-test2"][0] + self.assertEqual('SUCCESS', project_test2_build['result']) + + project_merge_build = [x for x in buildset["builds"] + if x["job_name"] == "project-merge"][0] + self.assertEqual('SUCCESS', project_merge_build['result']) class TestArtifacts(ZuulDBTestCase, BaseTestWeb, AnsibleZuulTestCase): @@ -754,3 +771,32 @@ class TestArtifacts(ZuulDBTestCase, BaseTestWeb, AnsibleZuulTestCase): {'url': 'http://example.com/tarball', 'name': 'tarball'}, ]) + + def test_buildset_artifacts(self): + self.add_base_changes() + self.executor_server.hold_jobs_in_build = False + self.executor_server.release() + self.waitUntilSettled() + + buildsets = self.get_url("api/tenant/tenant-one/buildsets").json() + project_bs = [x for x in buildsets if x["project"] == "org/project"][0] + buildset = self.get_url( + "api/tenant/tenant-one/buildset/%s" % project_bs['uuid']).json() + print("X" * 120) + print(buildset) + print("Y" * 120) + self.assertEqual(3, len(buildset["builds"])) + + test1_build = [x for x in buildset["builds"] + if x["job_name"] == "project-test1"][0] + arts = test1_build['artifacts'] + arts.sort(key=lambda x: x['name']) + self.assertEqual([ + {'url': 'http://example.com/docs', + 'name': 'docs'}, + {'url': 'http://logs.example.com/build/relative/docs', + 'name': 'relative', + 'metadata': {'foo': 'bar'}}, + {'url': 'http://example.com/tarball', + 'name': 'tarball'}, + ], test1_build['artifacts']) diff --git a/zuul/driver/sql/sqlconnection.py b/zuul/driver/sql/sqlconnection.py index 8652e46e55..c99187fbf4 100644 --- a/zuul/driver/sql/sqlconnection.py +++ b/zuul/driver/sql/sqlconnection.py @@ -31,6 +31,9 @@ PROVIDES_TABLE = 'zuul_provides' class DatabaseSession(object): + + log = logging.getLogger("zuul.DatabaseSession") + def __init__(self, connection): self.connection = connection self.session = connection.session @@ -134,6 +137,29 @@ class DatabaseSession(object): except sqlalchemy.orm.exc.NoResultFound: return [] + def getBuildset(self, tenant, uuid): + """Get one buildset with its builds""" + + buildset_table = self.connection.zuul_buildset_table + + q = self.session().query(self.connection.buildSetModel).\ + options(orm.joinedload(self.connection.buildSetModel.builds). + subqueryload(self.connection.buildModel.artifacts)).\ + options(orm.joinedload(self.connection.buildSetModel.builds). + subqueryload(self.connection.buildModel.provides)).\ + with_hint(buildset_table, 'USE INDEX (PRIMARY)', 'mysql') + + q = self.listFilter(q, buildset_table.c.tenant, tenant) + q = self.listFilter(q, buildset_table.c.uuid, uuid) + + try: + return q.one() + except sqlalchemy.orm.exc.NoResultFound: + return None + except sqlalchemy.orm.exc.MultipleResultsFound: + self.log.error("Multiple buildset found with uuid %s", uuid) + return None + class SQLConnection(BaseConnection): driver_name = 'sql' @@ -319,3 +345,8 @@ class SQLConnection(BaseConnection): """Return a list of BuildSet objects""" with self.getSession() as db: return db.getBuildsets(*args, **kw) + + def getBuildset(self, *args, **kw): + """Return a BuildSet objects""" + with self.getSession() as db: + return db.getBuildset(*args, **kw) diff --git a/zuul/web/__init__.py b/zuul/web/__init__.py index 8684e1c35e..cd5860385f 100755 --- a/zuul/web/__init__.py +++ b/zuul/web/__init__.py @@ -431,6 +431,8 @@ class ZuulWebAPI(object): 'voting': build.voting, 'log_url': build.log_url, 'node_name': build.node_name, + 'artifacts': [], + 'provides': [], } if buildset: @@ -443,22 +445,20 @@ class ZuulWebAPI(object): 'ref': buildset.ref, 'newrev': buildset.newrev, 'ref_url': buildset.ref_url, - 'artifacts': [], - 'provides': [], }) - for artifact in build.artifacts: - art = { - 'name': artifact.name, - 'url': artifact.url, - } - if artifact.meta: - art['metadata'] = json.loads(artifact.meta) - ret['artifacts'].append(art) - for provides in build.provides: - ret['provides'].append({ - 'name': provides.name, - }) + for artifact in build.artifacts: + art = { + 'name': artifact.name, + 'url': artifact.url, + } + if artifact.meta: + art['metadata'] = json.loads(artifact.meta) + ret['artifacts'].append(art) + for provides in build.provides: + ret['provides'].append({ + 'name': provides.name, + }) return ret def _get_connection(self, tenant): @@ -505,7 +505,7 @@ class ZuulWebAPI(object): resp.headers['Access-Control-Allow-Origin'] = '*' return data - def buildsetToDict(self, buildset): + def buildsetToDict(self, buildset, builds=[]): ret = { 'uuid': buildset.uuid, 'result': buildset.result, @@ -519,6 +519,10 @@ class ZuulWebAPI(object): 'newrev': buildset.newrev, 'ref_url': buildset.ref_url, } + if builds: + ret['builds'] = [] + for build in builds: + ret['builds'].append(self.buildToDict(build)) return ret @cherrypy.expose @@ -539,6 +543,20 @@ class ZuulWebAPI(object): resp.headers['Access-Control-Allow-Origin'] = '*' return [self.buildsetToDict(b) for b in buildsets] + @cherrypy.expose + @cherrypy.tools.save_params() + @cherrypy.tools.json_out(content_type='application/json; charset=utf-8') + def buildset(self, tenant, uuid): + connection = self._get_connection(tenant) + + data = connection.getBuildset(tenant, uuid) + if not data: + raise cherrypy.HTTPError(404, "Buildset not found") + data = self.buildsetToDict(data, data.builds) + resp = cherrypy.response + resp.headers['Access-Control-Allow-Origin'] = '*' + return data + @cherrypy.expose @cherrypy.tools.save_params() @cherrypy.tools.websocket(handler_cls=LogStreamHandler) @@ -697,6 +715,8 @@ class ZuulWeb(object): controller=api, action='build') route_map.connect('api', '/api/tenant/{tenant}/buildsets', controller=api, action='buildsets') + route_map.connect('api', '/api/tenant/{tenant}/buildset/{uuid}', + controller=api, action='buildset') route_map.connect('api', '/api/tenant/{tenant}/config-errors', controller=api, action='config_errors')