Merge "Add API endpoint to get frozen jobs"

This commit is contained in:
Zuul 2019-03-25 22:25:33 +00:00 committed by Gerrit Code Review
commit 65596aaafd
4 changed files with 113 additions and 6 deletions

View File

@ -710,6 +710,53 @@ class TestWeb(BaseTestWeb):
job = self.get_url("api/tenant/tenant-one/job/noop").json()
self.assertEqual("noop", job[0]["name"])
def test_freeze_jobs(self):
# Test can get a list of the jobs for a given project+pipeline+branch.
resp = self.get_url(
"api/tenant/tenant-one/pipeline/check"
"/project/org/project1/branch/master/freeze-jobs")
freeze_jobs = [{
'name': 'project-merge',
'dependencies': [],
}, {
'name': 'project-test1',
'dependencies': [{
'name': 'project-merge',
'soft': False,
}],
}, {
'name': 'project-test2',
'dependencies': [{
'name': 'project-merge',
'soft': False,
}],
}, {
'name': 'project1-project2-integration',
'dependencies': [{
'name': 'project-merge',
'soft': False,
}],
}]
self.assertEqual(freeze_jobs, resp.json())
def test_freeze_jobs_set_includes_all_jobs(self):
# When freezing a job set we want to include all jobs even if they
# have certain matcher requirements (such as required files) since we
# can't otherwise evaluate them.
resp = self.get_url(
"api/tenant/tenant-one/pipeline/gate"
"/project/org/project/branch/master/freeze-jobs")
expected = {
'name': 'project-testfile',
'dependencies': [{
'name': 'project-merge',
'soft': False,
}],
}
self.assertIn(expected, resp.json())
class TestWebSecrets(BaseTestWeb):
tenant_config_file = 'config/secrets/main.yaml'

View File

@ -2079,7 +2079,7 @@ class QueueItem(object):
def warning(self, msg):
self.current_build_set.warning_messages.append(msg)
def freezeJobGraph(self):
def freezeJobGraph(self, skip_file_matcher=False):
"""Find or create actual matching jobs for this item's change and
store the resulting job tree."""
@ -2091,7 +2091,8 @@ class QueueItem(object):
if ppc:
for msg in ppc.debug_messages:
self.debug(msg)
job_graph = self.layout.createJobGraph(self, ppc)
job_graph = self.layout.createJobGraph(
self, ppc, skip_file_matcher)
for job in job_graph.getJobs():
# Ensure that each jobs's dependencies are fully
# accessible. This will raise an exception if not.
@ -3782,7 +3783,7 @@ class Layout(object):
raise NoMatchingParentError()
return jobs
def _createJobGraph(self, item, ppc, job_graph):
def _createJobGraph(self, item, ppc, job_graph, skip_file_matcher):
job_list = ppc.job_list
change = item.change
pipeline = item.pipeline
@ -3845,7 +3846,8 @@ class Layout(object):
item.debug("No matching pipeline variants for {jobname}".
format(jobname=jobname), indent=2)
continue
if not frozen_job.changeMatchesFiles(change):
if not skip_file_matcher and \
not frozen_job.changeMatchesFiles(change):
self.log.debug("Job %s did not match files in %s",
repr(frozen_job), change)
item.debug("Job {jobname} did not match files".
@ -3879,12 +3881,12 @@ class Layout(object):
job_graph.addJob(frozen_job)
def createJobGraph(self, item, ppc):
def createJobGraph(self, item, ppc, skip_file_matcher=False):
# NOTE(pabelanger): It is possible for a foreign project not to have a
# configured pipeline, if so return an empty JobGraph.
ret = JobGraph()
if ppc:
self._createJobGraph(item, ppc, ret)
self._createJobGraph(item, ppc, ret, skip_file_matcher)
return ret

View File

@ -75,6 +75,7 @@ class RPCListener(object):
self.worker.registerFunction("zuul:job_list")
self.worker.registerFunction("zuul:project_get")
self.worker.registerFunction("zuul:project_list")
self.worker.registerFunction("zuul:project_freeze_jobs")
self.worker.registerFunction("zuul:pipeline_list")
self.worker.registerFunction("zuul:key_get")
self.worker.registerFunction("zuul:config_errors_list")
@ -448,6 +449,37 @@ class RPCListener(object):
job.sendWorkComplete(json.dumps(
sorted(output, key=lambda project: project["name"])))
def handle_project_freeze_jobs(self, gear_job):
args = json.loads(gear_job.arguments)
tenant = self.sched.abide.tenants.get(args.get("tenant"))
project = None
pipeline = None
if tenant:
(trusted, project) = tenant.getProject(args.get("project"))
pipeline = tenant.layout.pipelines.get(args.get("pipeline"))
if not project or not pipeline:
gear_job.sendWorkComplete(json.dumps(None))
return
change = model.Branch(project)
change.branch = args.get("branch", "master")
queue = model.ChangeQueue(pipeline)
item = model.QueueItem(queue, change)
item.layout = tenant.layout
item.freezeJobGraph(skip_file_matcher=True)
output = []
for job in item.job_graph.getJobs():
job.setBase(tenant.layout)
output.append({
'name': job.name,
'dependencies':
list(map(lambda x: x.toDict(), job.dependencies)),
})
gear_job.sendWorkComplete(json.dumps(output))
def handle_pipeline_list(self, job):
args = json.loads(job.arguments)
tenant = self.sched.abide.tenants.get(args.get("tenant"))

View File

@ -572,6 +572,26 @@ class ZuulWebAPI(object):
def console_stream(self, tenant):
cherrypy.request.ws_handler.zuulweb = self.zuulweb
@cherrypy.expose
@cherrypy.tools.save_params()
@cherrypy.tools.json_out(content_type='application/json; charset=utf-8')
def project_freeze_jobs(self, tenant, pipeline, project, branch):
job = self.rpc.submitJob(
'zuul:project_freeze_jobs',
{
'tenant': tenant,
'project': project,
'pipeline': pipeline,
'branch': branch
}
)
ret = json.loads(job.data[0])
if not ret:
raise cherrypy.HTTPError(404)
resp = cherrypy.response
resp.headers['Access-Control-Allow-Origin'] = '*'
return ret
class StaticHandler(object):
def __init__(self, root):
@ -707,6 +727,12 @@ class ZuulWeb(object):
controller=api, action='projects')
route_map.connect('api', '/api/tenant/{tenant}/project/{project:.*}',
controller=api, action='project')
route_map.connect(
'api',
'/api/tenant/{tenant}/pipeline/{pipeline}'
'/project/{project:.*}/branch/{branch:.*}/freeze-jobs',
controller=api, action='project_freeze_jobs'
)
route_map.connect('api', '/api/tenant/{tenant}/pipelines',
controller=api, action='pipelines')
route_map.connect('api', '/api/tenant/{tenant}/labels',