Allow soft job dependencies
A "soft" dependency can be used to indicate that a job must run after another completes, but only if it runs at all. For example, a deployment job which depends on a build job with different file matcher criteria. Change-Id: I4d7fc2b40942569323da273c4529fdb365a3b11a
This commit is contained in:
parent
967828b1f0
commit
db3388688a
|
@ -1070,6 +1070,27 @@ Here is an example of two job definitions:
|
||||||
completed successfully, and if one or more of them fail, this
|
completed successfully, and if one or more of them fail, this
|
||||||
job will not be run.
|
job will not be run.
|
||||||
|
|
||||||
|
The format for this attribute is either a list of strings or
|
||||||
|
dictionaries. Strings are interpreted as job names,
|
||||||
|
dictionaries, if used, may have the following attributes:
|
||||||
|
|
||||||
|
.. attr:: name
|
||||||
|
:required:
|
||||||
|
|
||||||
|
The name of the required job.
|
||||||
|
|
||||||
|
.. attr:: soft
|
||||||
|
:default: false
|
||||||
|
|
||||||
|
A boolean value which indicates whether this job is a *hard*
|
||||||
|
or *soft* dependency. A *hard* dependency will cause an
|
||||||
|
error if the specified job is not run. That is, if job B
|
||||||
|
depends on job A, but job A is not run for any reason (for
|
||||||
|
example, it containes a file matcher which does not match),
|
||||||
|
then Zuul will not run any jobs and report an error. A
|
||||||
|
*soft* dependency will simply be ignored if the dependent job
|
||||||
|
is not run.
|
||||||
|
|
||||||
.. attr:: allowed-projects
|
.. attr:: allowed-projects
|
||||||
|
|
||||||
A list of Zuul projects which may use this job. By default, a
|
A list of Zuul projects which may use this job. By default, a
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- The :attr:`job.dependencies` attribute may now be used to express
|
||||||
|
"soft" dependencies -- that is, to indicate a job should run
|
||||||
|
after another completes, but only if it runs at all. For example,
|
||||||
|
a deployment job which should always run, but depends on a build
|
||||||
|
job which only runs if the source code is changed.
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
- pipeline:
|
||||||
|
name: check
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: patchset-created
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
Verified: 1
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
Verified: -1
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: base
|
||||||
|
parent: null
|
||||||
|
run: playbooks/base.yaml
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: build
|
||||||
|
files: main.c
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: deploy
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: org/project
|
||||||
|
check:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- deploy:
|
||||||
|
dependencies:
|
||||||
|
- name: project-merge
|
||||||
|
soft: true
|
|
@ -0,0 +1,34 @@
|
||||||
|
- pipeline:
|
||||||
|
name: check
|
||||||
|
manager: independent
|
||||||
|
trigger:
|
||||||
|
gerrit:
|
||||||
|
- event: patchset-created
|
||||||
|
success:
|
||||||
|
gerrit:
|
||||||
|
Verified: 1
|
||||||
|
failure:
|
||||||
|
gerrit:
|
||||||
|
Verified: -1
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: base
|
||||||
|
parent: null
|
||||||
|
run: playbooks/base.yaml
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: build
|
||||||
|
files: main.c
|
||||||
|
|
||||||
|
- job:
|
||||||
|
name: deploy
|
||||||
|
|
||||||
|
- project:
|
||||||
|
name: org/project
|
||||||
|
check:
|
||||||
|
jobs:
|
||||||
|
- build
|
||||||
|
- deploy:
|
||||||
|
dependencies:
|
||||||
|
- name: build
|
||||||
|
soft: true
|
|
@ -441,7 +441,8 @@ class TestGraph(BaseTestCase):
|
||||||
prevjob = None
|
prevjob = None
|
||||||
for j in jobs[:3]:
|
for j in jobs[:3]:
|
||||||
if prevjob:
|
if prevjob:
|
||||||
j.dependencies = frozenset([prevjob.name])
|
j.dependencies = frozenset([
|
||||||
|
model.JobDependency(prevjob.name)])
|
||||||
graph.addJob(j)
|
graph.addJob(j)
|
||||||
prevjob = j
|
prevjob = j
|
||||||
# 0 triggers 1 triggers 2 triggers 3...
|
# 0 triggers 1 triggers 2 triggers 3...
|
||||||
|
@ -451,32 +452,95 @@ class TestGraph(BaseTestCase):
|
||||||
Exception,
|
Exception,
|
||||||
"Dependency cycle detected in job jobX"):
|
"Dependency cycle detected in job jobX"):
|
||||||
j = model.Job('jobX')
|
j = model.Job('jobX')
|
||||||
j.dependencies = frozenset([j.name])
|
j.dependencies = frozenset([model.JobDependency(j.name)])
|
||||||
graph.addJob(j)
|
graph.addJob(j)
|
||||||
|
|
||||||
# Disallow circular dependencies
|
# Disallow circular dependencies
|
||||||
with testtools.ExpectedException(
|
with testtools.ExpectedException(
|
||||||
Exception,
|
Exception,
|
||||||
"Dependency cycle detected in job job3"):
|
"Dependency cycle detected in job job3"):
|
||||||
jobs[4].dependencies = frozenset([jobs[3].name])
|
jobs[4].dependencies = frozenset([
|
||||||
|
model.JobDependency(jobs[3].name)])
|
||||||
graph.addJob(jobs[4])
|
graph.addJob(jobs[4])
|
||||||
jobs[3].dependencies = frozenset([jobs[4].name])
|
jobs[3].dependencies = frozenset([
|
||||||
|
model.JobDependency(jobs[4].name)])
|
||||||
graph.addJob(jobs[3])
|
graph.addJob(jobs[3])
|
||||||
|
|
||||||
jobs[5].dependencies = frozenset([jobs[4].name])
|
jobs[5].dependencies = frozenset([model.JobDependency(jobs[4].name)])
|
||||||
graph.addJob(jobs[5])
|
graph.addJob(jobs[5])
|
||||||
|
|
||||||
with testtools.ExpectedException(
|
with testtools.ExpectedException(
|
||||||
Exception,
|
Exception,
|
||||||
"Dependency cycle detected in job job3"):
|
"Dependency cycle detected in job job3"):
|
||||||
jobs[3].dependencies = frozenset([jobs[5].name])
|
jobs[3].dependencies = frozenset([
|
||||||
|
model.JobDependency(jobs[5].name)])
|
||||||
graph.addJob(jobs[3])
|
graph.addJob(jobs[3])
|
||||||
|
|
||||||
jobs[3].dependencies = frozenset([jobs[2].name])
|
jobs[3].dependencies = frozenset([
|
||||||
|
model.JobDependency(jobs[2].name)])
|
||||||
graph.addJob(jobs[3])
|
graph.addJob(jobs[3])
|
||||||
jobs[6].dependencies = frozenset([jobs[2].name])
|
jobs[6].dependencies = frozenset([
|
||||||
|
model.JobDependency(jobs[2].name)])
|
||||||
graph.addJob(jobs[6])
|
graph.addJob(jobs[6])
|
||||||
|
|
||||||
|
def test_job_graph_allows_soft_dependencies(self):
|
||||||
|
parent = model.Job('parent')
|
||||||
|
child = model.Job('child')
|
||||||
|
child.dependencies = frozenset([
|
||||||
|
model.JobDependency(parent.name, True)])
|
||||||
|
|
||||||
|
# With the parent
|
||||||
|
graph = model.JobGraph()
|
||||||
|
graph.addJob(parent)
|
||||||
|
graph.addJob(child)
|
||||||
|
self.assertEqual(graph.getParentJobsRecursively(child.name),
|
||||||
|
[parent])
|
||||||
|
|
||||||
|
# Skip the parent
|
||||||
|
graph = model.JobGraph()
|
||||||
|
graph.addJob(child)
|
||||||
|
self.assertEqual(graph.getParentJobsRecursively(child.name), [])
|
||||||
|
|
||||||
|
def test_job_graph_allows_soft_dependencies4(self):
|
||||||
|
# A more complex scenario with multiple parents at each level
|
||||||
|
parents = [model.Job('parent%i' % i) for i in range(6)]
|
||||||
|
child = model.Job('child')
|
||||||
|
child.dependencies = frozenset([
|
||||||
|
model.JobDependency(parents[0].name, True),
|
||||||
|
model.JobDependency(parents[1].name)])
|
||||||
|
parents[0].dependencies = frozenset([
|
||||||
|
model.JobDependency(parents[2].name),
|
||||||
|
model.JobDependency(parents[3].name, True)])
|
||||||
|
parents[1].dependencies = frozenset([
|
||||||
|
model.JobDependency(parents[4].name),
|
||||||
|
model.JobDependency(parents[5].name)])
|
||||||
|
# Run them all
|
||||||
|
graph = model.JobGraph()
|
||||||
|
for j in parents:
|
||||||
|
graph.addJob(j)
|
||||||
|
graph.addJob(child)
|
||||||
|
self.assertEqual(set(graph.getParentJobsRecursively(child.name)),
|
||||||
|
set(parents))
|
||||||
|
|
||||||
|
# Skip first parent, therefore its recursive dependencies don't appear
|
||||||
|
graph = model.JobGraph()
|
||||||
|
for j in parents:
|
||||||
|
if j is not parents[0]:
|
||||||
|
graph.addJob(j)
|
||||||
|
graph.addJob(child)
|
||||||
|
self.assertEqual(set(graph.getParentJobsRecursively(child.name)),
|
||||||
|
set(parents) -
|
||||||
|
set([parents[0], parents[2], parents[3]]))
|
||||||
|
|
||||||
|
# Skip a leaf node
|
||||||
|
graph = model.JobGraph()
|
||||||
|
for j in parents:
|
||||||
|
if j is not parents[3]:
|
||||||
|
graph.addJob(j)
|
||||||
|
graph.addJob(child)
|
||||||
|
self.assertEqual(set(graph.getParentJobsRecursively(child.name)),
|
||||||
|
set(parents) - set([parents[3]]))
|
||||||
|
|
||||||
|
|
||||||
class TestTenant(BaseTestCase):
|
class TestTenant(BaseTestCase):
|
||||||
def test_add_project(self):
|
def test_add_project(self):
|
||||||
|
|
|
@ -5673,6 +5673,25 @@ class TestDependencyGraph(ZuulTestCase):
|
||||||
self.assertEqual(change.data['status'], 'NEW')
|
self.assertEqual(change.data['status'], 'NEW')
|
||||||
self.assertEqual(change.reported, 2)
|
self.assertEqual(change.reported, 2)
|
||||||
|
|
||||||
|
@simple_layout('layouts/soft-dependencies-error.yaml')
|
||||||
|
def test_soft_dependencies_error(self):
|
||||||
|
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||||
|
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertHistory([])
|
||||||
|
self.assertEqual(len(A.messages), 1)
|
||||||
|
self.assertTrue('Job project-merge not defined' in A.messages[0])
|
||||||
|
print(A.messages)
|
||||||
|
|
||||||
|
@simple_layout('layouts/soft-dependencies.yaml')
|
||||||
|
def test_soft_dependencies(self):
|
||||||
|
A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A')
|
||||||
|
self.fake_gerrit.addEvent(A.getPatchsetCreatedEvent(1))
|
||||||
|
self.waitUntilSettled()
|
||||||
|
self.assertHistory([
|
||||||
|
dict(name='deploy', result='SUCCESS', changes='1,1'),
|
||||||
|
], ordered=False)
|
||||||
|
|
||||||
|
|
||||||
class TestDuplicatePipeline(ZuulTestCase):
|
class TestDuplicatePipeline(ZuulTestCase):
|
||||||
tenant_config_file = 'config/duplicate-pipeline/main.yaml'
|
tenant_config_file = 'config/duplicate-pipeline/main.yaml'
|
||||||
|
|
|
@ -517,7 +517,8 @@ class TestWeb(BaseTestWeb):
|
||||||
[{'abstract': False,
|
[{'abstract': False,
|
||||||
'attempts': 3,
|
'attempts': 3,
|
||||||
'branches': [],
|
'branches': [],
|
||||||
'dependencies': ['project-merge'],
|
'dependencies': [{'name': 'project-merge',
|
||||||
|
'soft': False}],
|
||||||
'description': None,
|
'description': None,
|
||||||
'files': [],
|
'files': [],
|
||||||
'final': False,
|
'final': False,
|
||||||
|
@ -547,7 +548,8 @@ class TestWeb(BaseTestWeb):
|
||||||
[{'abstract': False,
|
[{'abstract': False,
|
||||||
'attempts': 3,
|
'attempts': 3,
|
||||||
'branches': [],
|
'branches': [],
|
||||||
'dependencies': ['project-merge'],
|
'dependencies': [{'name': 'project-merge',
|
||||||
|
'soft': False}],
|
||||||
'description': None,
|
'description': None,
|
||||||
'files': [],
|
'files': [],
|
||||||
'final': False,
|
'final': False,
|
||||||
|
@ -577,7 +579,8 @@ class TestWeb(BaseTestWeb):
|
||||||
[{'abstract': False,
|
[{'abstract': False,
|
||||||
'attempts': 3,
|
'attempts': 3,
|
||||||
'branches': [],
|
'branches': [],
|
||||||
'dependencies': ['project-merge'],
|
'dependencies': [{'name': 'project-merge',
|
||||||
|
'soft': False}],
|
||||||
'description': None,
|
'description': None,
|
||||||
'files': [],
|
'files': [],
|
||||||
'final': False,
|
'final': False,
|
||||||
|
|
|
@ -533,6 +533,9 @@ class JobParser(object):
|
||||||
'override-branch': str,
|
'override-branch': str,
|
||||||
'override-checkout': str}
|
'override-checkout': str}
|
||||||
|
|
||||||
|
job_dependency = {vs.Required('name'): str,
|
||||||
|
'soft': bool}
|
||||||
|
|
||||||
secret = {vs.Required('name'): str,
|
secret = {vs.Required('name'): str,
|
||||||
vs.Required('secret'): str,
|
vs.Required('secret'): str,
|
||||||
'pass-to-parent': bool}
|
'pass-to-parent': bool}
|
||||||
|
@ -575,7 +578,7 @@ class JobParser(object):
|
||||||
'extra-vars': dict,
|
'extra-vars': dict,
|
||||||
'host-vars': {str: dict},
|
'host-vars': {str: dict},
|
||||||
'group-vars': {str: dict},
|
'group-vars': {str: dict},
|
||||||
'dependencies': to_list(str),
|
'dependencies': to_list(vs.Any(job_dependency, str)),
|
||||||
'allowed-projects': to_list(str),
|
'allowed-projects': to_list(str),
|
||||||
'override-branch': str,
|
'override-branch': str,
|
||||||
'override-checkout': str,
|
'override-checkout': str,
|
||||||
|
@ -764,6 +767,20 @@ class JobParser(object):
|
||||||
new_projects[project.canonical_name] = job_project
|
new_projects[project.canonical_name] = job_project
|
||||||
job.required_projects = new_projects
|
job.required_projects = new_projects
|
||||||
|
|
||||||
|
if 'dependencies' in conf:
|
||||||
|
new_dependencies = []
|
||||||
|
dependencies = as_list(conf.get('dependencies', []))
|
||||||
|
for dep in dependencies:
|
||||||
|
if isinstance(dep, dict):
|
||||||
|
dep_name = dep['name']
|
||||||
|
dep_soft = dep.get('soft', False)
|
||||||
|
else:
|
||||||
|
dep_name = dep
|
||||||
|
dep_soft = False
|
||||||
|
job_dependency = model.JobDependency(dep_name, dep_soft)
|
||||||
|
new_dependencies.append(job_dependency)
|
||||||
|
job.dependencies = new_dependencies
|
||||||
|
|
||||||
if 'semaphore' in conf:
|
if 'semaphore' in conf:
|
||||||
semaphore = conf.get('semaphore')
|
semaphore = conf.get('semaphore')
|
||||||
if isinstance(semaphore, str):
|
if isinstance(semaphore, str):
|
||||||
|
@ -773,7 +790,7 @@ class JobParser(object):
|
||||||
semaphore.get('name'),
|
semaphore.get('name'),
|
||||||
semaphore.get('resources-first', False))
|
semaphore.get('resources-first', False))
|
||||||
|
|
||||||
for k in ('tags', 'requires', 'provides', 'dependencies'):
|
for k in ('tags', 'requires', 'provides'):
|
||||||
v = frozenset(as_list(conf.get(k)))
|
v = frozenset(as_list(conf.get(k)))
|
||||||
if v:
|
if v:
|
||||||
setattr(job, k, v)
|
setattr(job, k, v)
|
||||||
|
|
|
@ -1213,7 +1213,7 @@ class Job(ConfigObject):
|
||||||
d['tags'] = list(self.tags)
|
d['tags'] = list(self.tags)
|
||||||
d['provides'] = list(self.provides)
|
d['provides'] = list(self.provides)
|
||||||
d['requires'] = list(self.requires)
|
d['requires'] = list(self.requires)
|
||||||
d['dependencies'] = list(self.dependencies)
|
d['dependencies'] = list(map(lambda x: x.toDict(), self.dependencies))
|
||||||
d['attempts'] = self.attempts
|
d['attempts'] = self.attempts
|
||||||
d['roles'] = list(map(lambda x: x.toDict(), self.roles))
|
d['roles'] = list(map(lambda x: x.toDict(), self.roles))
|
||||||
d['run'] = list(map(lambda x: x.toSchemaDict(), self.run))
|
d['run'] = list(map(lambda x: x.toSchemaDict(), self.run))
|
||||||
|
@ -1649,12 +1649,25 @@ class JobList(ConfigObject):
|
||||||
joblist.append(job)
|
joblist.append(job)
|
||||||
|
|
||||||
|
|
||||||
|
class JobDependency(ConfigObject):
|
||||||
|
""" A reference to another job in the project-pipeline-config. """
|
||||||
|
def __init__(self, name, soft=False):
|
||||||
|
super(JobDependency, self).__init__()
|
||||||
|
self.name = name
|
||||||
|
self.soft = soft
|
||||||
|
|
||||||
|
def toDict(self):
|
||||||
|
return {'name': self.name,
|
||||||
|
'soft': self.soft}
|
||||||
|
|
||||||
|
|
||||||
class JobGraph(object):
|
class JobGraph(object):
|
||||||
""" A JobGraph represents the dependency graph between Job."""
|
""" A JobGraph represents the dependency graph between Job."""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.jobs = OrderedDict() # job_name -> Job
|
self.jobs = OrderedDict() # job_name -> Job
|
||||||
self._dependencies = {} # dependent_job_name -> set(parent_job_names)
|
# dependent_job_name -> dict(parent_job_name -> soft)
|
||||||
|
self._dependencies = {}
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self):
|
||||||
return '<JobGraph %s>' % (self.jobs)
|
return '<JobGraph %s>' % (self.jobs)
|
||||||
|
@ -1666,17 +1679,18 @@ class JobGraph(object):
|
||||||
raise Exception("Job %s already added" % (job.name,))
|
raise Exception("Job %s already added" % (job.name,))
|
||||||
self.jobs[job.name] = job
|
self.jobs[job.name] = job
|
||||||
# Append the dependency information
|
# Append the dependency information
|
||||||
self._dependencies.setdefault(job.name, set())
|
self._dependencies.setdefault(job.name, {})
|
||||||
try:
|
try:
|
||||||
for dependency in job.dependencies:
|
for dependency in job.dependencies:
|
||||||
# Make sure a circular dependency is never created
|
# Make sure a circular dependency is never created
|
||||||
ancestor_jobs = self._getParentJobNamesRecursively(
|
ancestor_jobs = self._getParentJobNamesRecursively(
|
||||||
dependency, soft=True)
|
dependency.name, soft=True)
|
||||||
ancestor_jobs.add(dependency)
|
ancestor_jobs.add(dependency.name)
|
||||||
if any((job.name == anc_job) for anc_job in ancestor_jobs):
|
if any((job.name == anc_job) for anc_job in ancestor_jobs):
|
||||||
raise Exception("Dependency cycle detected in job %s" %
|
raise Exception("Dependency cycle detected in job %s" %
|
||||||
(job.name,))
|
(job.name,))
|
||||||
self._dependencies[job.name].add(dependency)
|
self._dependencies[job.name][dependency.name] = \
|
||||||
|
dependency.soft
|
||||||
except Exception:
|
except Exception:
|
||||||
del self.jobs[job.name]
|
del self.jobs[job.name]
|
||||||
del self._dependencies[job.name]
|
del self._dependencies[job.name]
|
||||||
|
@ -1703,25 +1717,34 @@ class JobGraph(object):
|
||||||
all_dependent_jobs |= new_dependent_jobs
|
all_dependent_jobs |= new_dependent_jobs
|
||||||
return [self.jobs[name] for name in all_dependent_jobs]
|
return [self.jobs[name] for name in all_dependent_jobs]
|
||||||
|
|
||||||
def getParentJobsRecursively(self, dependent_job, soft=False):
|
def getParentJobsRecursively(self, dependent_job, layout=None):
|
||||||
return [self.jobs[name] for name in
|
return [self.jobs[name] for name in
|
||||||
self._getParentJobNamesRecursively(dependent_job, soft)]
|
self._getParentJobNamesRecursively(dependent_job,
|
||||||
|
layout=layout)]
|
||||||
|
|
||||||
def _getParentJobNamesRecursively(self, dependent_job, soft=False):
|
def _getParentJobNamesRecursively(self, dependent_job, soft=False,
|
||||||
|
layout=None):
|
||||||
all_parent_jobs = set()
|
all_parent_jobs = set()
|
||||||
jobs_to_iterate = set([dependent_job])
|
jobs_to_iterate = set([(dependent_job, False)])
|
||||||
while len(jobs_to_iterate) > 0:
|
while len(jobs_to_iterate) > 0:
|
||||||
current_job = jobs_to_iterate.pop()
|
(current_job, current_soft) = jobs_to_iterate.pop()
|
||||||
current_parent_jobs = self._dependencies.get(current_job)
|
current_parent_jobs = self._dependencies.get(current_job)
|
||||||
if current_parent_jobs is None:
|
if current_parent_jobs is None:
|
||||||
if soft:
|
if soft or current_soft:
|
||||||
current_parent_jobs = set()
|
if layout:
|
||||||
|
# If the caller supplied a layout, verify that
|
||||||
|
# the job exists to provide a helpful error
|
||||||
|
# message. Called for exception side effect:
|
||||||
|
layout.getJob(current_job)
|
||||||
|
current_parent_jobs = {}
|
||||||
else:
|
else:
|
||||||
raise Exception("Job %s depends on %s which was not run." %
|
raise Exception("Job %s depends on %s which was not run." %
|
||||||
(dependent_job, current_job))
|
(dependent_job, current_job))
|
||||||
new_parent_jobs = current_parent_jobs - all_parent_jobs
|
elif dependent_job != current_job:
|
||||||
jobs_to_iterate |= new_parent_jobs
|
all_parent_jobs.add(current_job)
|
||||||
all_parent_jobs |= new_parent_jobs
|
new_parent_jobs = set(current_parent_jobs.keys()) - all_parent_jobs
|
||||||
|
for j in new_parent_jobs:
|
||||||
|
jobs_to_iterate.add((j, current_parent_jobs[j]))
|
||||||
return all_parent_jobs
|
return all_parent_jobs
|
||||||
|
|
||||||
|
|
||||||
|
@ -2066,7 +2089,7 @@ class QueueItem(object):
|
||||||
for job in job_graph.getJobs():
|
for job in job_graph.getJobs():
|
||||||
# Ensure that each jobs's dependencies are fully
|
# Ensure that each jobs's dependencies are fully
|
||||||
# accessible. This will raise an exception if not.
|
# accessible. This will raise an exception if not.
|
||||||
job_graph.getParentJobsRecursively(job.name)
|
job_graph.getParentJobsRecursively(job.name, self.layout)
|
||||||
self.job_graph = job_graph
|
self.job_graph = job_graph
|
||||||
except Exception:
|
except Exception:
|
||||||
self.project_pipeline_config = None
|
self.project_pipeline_config = None
|
||||||
|
@ -2645,7 +2668,7 @@ class QueueItem(object):
|
||||||
|
|
||||||
ret['jobs'].append({
|
ret['jobs'].append({
|
||||||
'name': job.name,
|
'name': job.name,
|
||||||
'dependencies': list(job.dependencies),
|
'dependencies': [x.name for x in job.dependencies],
|
||||||
'elapsed_time': elapsed,
|
'elapsed_time': elapsed,
|
||||||
'remaining_time': remaining,
|
'remaining_time': remaining,
|
||||||
'url': build_url,
|
'url': build_url,
|
||||||
|
|
Loading…
Reference in New Issue