summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--tests/unit/test_tracing.py5
-rw-r--r--tests/unit/test_v3.py58
-rw-r--r--web/src/containers/build/Console.jsx51
-rw-r--r--zuul/configloader.py14
-rw-r--r--zuul/nodepool.py8
5 files changed, 106 insertions, 30 deletions
diff --git a/tests/unit/test_tracing.py b/tests/unit/test_tracing.py
index ff20f84f1..525876f50 100644
--- a/tests/unit/test_tracing.py
+++ b/tests/unit/test_tracing.py
@@ -193,11 +193,14 @@ class TestTracing(ZuulTestCase):
self.log.debug("Received:\n%s", item)
merge_job = self.getSpan('Merge')
self.log.debug("Received:\n%s", merge_job)
+ node_request = self.getSpan('RequestNodes')
+ self.log.debug("Received:\n%s", node_request)
build = self.getSpan('Build')
self.log.debug("Received:\n%s", build)
jobexec = self.getSpan('JobExecution')
self.log.debug("Received:\n%s", jobexec)
self.assertEqual(item.trace_id, buildset.trace_id)
+ self.assertEqual(item.trace_id, node_request.trace_id)
self.assertEqual(item.trace_id, build.trace_id)
self.assertNotEqual(item.span_id, jobexec.span_id)
self.assertTrue(buildset.start_time_unix_nano >=
@@ -210,6 +213,8 @@ class TestTracing(ZuulTestCase):
buildset.end_time_unix_nano)
self.assertEqual(jobexec.parent_span_id,
build.span_id)
+ self.assertEqual(node_request.parent_span_id,
+ buildset.span_id)
self.assertEqual(build.parent_span_id,
buildset.span_id)
self.assertEqual(merge_job.parent_span_id,
diff --git a/tests/unit/test_v3.py b/tests/unit/test_v3.py
index fe5b46e0c..225c6d355 100644
--- a/tests/unit/test_v3.py
+++ b/tests/unit/test_v3.py
@@ -2232,6 +2232,64 @@ class TestInRepoConfig(ZuulTestCase):
self.assertIn('appears multiple times', A.messages[0],
"A should have a syntax error reported")
+ def test_group_in_job_with_invalid_node(self):
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: test job
+ nodeset:
+ nodes: []
+ groups:
+ - name: a_group
+ nodes:
+ - a_node_that_does_not_exist
+ """)
+
+ file_dict = {'.zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+ files=file_dict)
+ A.addApproval('Code-Review', 2)
+ self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'NEW')
+ self.assertEqual(A.reported, 1,
+ "A should report failure")
+ self.assertIn('which is not defined in the nodeset', A.messages[0],
+ "A should have a syntax error reported")
+
+ def test_duplicate_group_in_job(self):
+ in_repo_conf = textwrap.dedent(
+ """
+ - job:
+ name: test job
+ nodeset:
+ nodes:
+ - name: controller
+ label: ubuntu-focal
+ groups:
+ - name: a_duplicate_group
+ nodes:
+ - controller
+ - name: a_duplicate_group
+ nodes:
+ - controller
+ """)
+
+ file_dict = {'.zuul.yaml': in_repo_conf}
+ A = self.fake_gerrit.addFakeChange('org/project', 'master', 'A',
+ files=file_dict)
+ A.addApproval('Code-Review', 2)
+ self.fake_gerrit.addEvent(A.addApproval('Approved', 1))
+ self.waitUntilSettled()
+
+ self.assertEqual(A.data['status'], 'NEW')
+ self.assertEqual(A.reported, 1,
+ "A should report failure")
+ self.assertIn(
+ 'Group names must be unique within a nodeset.',
+ A.messages[0], "A should have a syntax error reported")
+
def test_secret_not_found_error(self):
in_repo_conf = textwrap.dedent(
"""
diff --git a/web/src/containers/build/Console.jsx b/web/src/containers/build/Console.jsx
index f24d9cbc8..9cd10df92 100644
--- a/web/src/containers/build/Console.jsx
+++ b/web/src/containers/build/Console.jsx
@@ -29,6 +29,7 @@ import {
DataListItemCells,
DataListToggle,
DataListContent,
+ Divider,
Flex,
FlexItem,
Label,
@@ -231,35 +232,39 @@ class HostTask extends React.Component {
<DataListCell key='name' width={4}>{name}</DataListCell>
)
- let label = null
+ let labelColor = null
+ let labelString = null
+
if (this.state.failed) {
- label = <Label color='red' onClick={this.open}
- style={{cursor: 'pointer'}}>FAILED</Label>
+ labelColor = 'red'
+ labelString = 'Failed'
} else if (this.state.changed) {
- label = <Label color='orange' onClick={this.open}
- style={{cursor: 'pointer'}}>CHANGED</Label>
+ labelColor = 'orange'
+ labelString = 'Changed'
} else if (this.state.skipped) {
- label = <Label color='grey' onClick={this.open}
- style={{cursor: 'pointer'}}>SKIPPED</Label>
+ labelColor = 'grey'
+ labelString = 'Skipped'
} else if (this.state.ok) {
- label = <Label color='green' onClick={this.open}
- style={{cursor: 'pointer'}}>OK</Label>
+ labelColor = 'green'
+ labelString = 'OK'
}
dataListCells.push(
<DataListCell key='state'>
- <Flex>
- <FlexItem>
- <Tooltip content={<div>Click for details</div>}>
- <SearchPlusIcon style={{cursor: 'pointer'}} onClick={this.open} />
- </Tooltip>
- </FlexItem>
- <FlexItem>
- <Tooltip content={<div>Click for details</div>}>
- {label}
- </Tooltip>
- </FlexItem>
- </Flex>
+ <Tooltip content={<div>Click for details</div>}>
+ <Label color={labelColor} onClick={this.open}
+ style={{cursor: 'pointer'}}>
+ <Flex flexWrap={{default: 'nowrap' }}>
+ <FlexItem style={{minWidth: '7ch'}}>
+ {labelString}
+ </FlexItem>
+ <Divider align={{default: 'alignRight'}} orientation={{default: 'vertical'}} />
+ <FlexItem>
+ <SearchPlusIcon color='var(--pf-global--Color--200)' style={{cursor: 'pointer'}} />
+ </FlexItem>
+ </Flex>
+ </Label>
+ </Tooltip>
</DataListCell>)
dataListCells.push(
@@ -325,7 +330,9 @@ class HostTask extends React.Component {
}
const modalDescription = <Flex>
- <FlexItem>{label}</FlexItem>
+ <FlexItem>
+ <Label color={labelColor}>{labelString}</Label>
+ </FlexItem>
<FlexItem>
<Chip isReadOnly={true} textMaxWidth='50ch'>
<span style={{ fontSize: 'var(--pf-global--FontSize--md)' }}>
diff --git a/zuul/configloader.py b/zuul/configloader.py
index a92f5337c..2b696a832 100644
--- a/zuul/configloader.py
+++ b/zuul/configloader.py
@@ -81,7 +81,7 @@ class ConfigurationSyntaxError(Exception):
class NodeFromGroupNotFoundError(Exception):
def __init__(self, nodeset, node, group):
message = textwrap.dedent("""\
- In nodeset "{nodeset}" the group "{group}" contains a
+ In {nodeset} the group "{group}" contains a
node named "{node}" which is not defined in the nodeset.""")
message = textwrap.fill(message.format(nodeset=nodeset,
node=node, group=group))
@@ -137,7 +137,7 @@ class MaxTimeoutError(Exception):
class DuplicateGroupError(Exception):
def __init__(self, nodeset, group):
message = textwrap.dedent("""\
- In nodeset "{nodeset}" the group "{group}" appears multiple times.
+ In {nodeset} the group "{group}" appears multiple times.
Group names must be unique within a nodeset.""")
message = textwrap.fill(message.format(nodeset=nodeset,
group=group))
@@ -476,6 +476,7 @@ class NodeSetParser(object):
def __init__(self, pcontext):
self.log = logging.getLogger("zuul.NodeSetParser")
self.pcontext = pcontext
+ self.anonymous = False
self.schema = self.getSchema(False)
self.anon_schema = self.getSchema(True)
@@ -513,6 +514,7 @@ class NodeSetParser(object):
def fromYaml(self, conf, anonymous=False):
if anonymous:
self.anon_schema(conf)
+ self.anonymous = True
else:
self.schema(conf)
@@ -565,10 +567,14 @@ class NodeSetParser(object):
raise Exception("Groups named 'localhost' are not allowed.")
for node_name in as_list(conf_group['nodes']):
if node_name not in node_names:
- raise NodeFromGroupNotFoundError(conf['name'], node_name,
+ nodeset_str = 'the nodeset' if self.anonymous else \
+ 'the nodeset "%s"' % conf['name']
+ raise NodeFromGroupNotFoundError(nodeset_str, node_name,
conf_group['name'])
if conf_group['name'] in group_names:
- raise DuplicateGroupError(conf['name'], conf_group['name'])
+ nodeset_str = 'the nodeset' if self.anonymous else \
+ 'the nodeset "%s"' % conf['name']
+ raise DuplicateGroupError(nodeset_str, conf_group['name'])
group = model.Group(conf_group['name'],
as_list(conf_group['nodes']))
ns.addGroup(group)
diff --git a/zuul/nodepool.py b/zuul/nodepool.py
index 1118fe0c2..6a6afce58 100644
--- a/zuul/nodepool.py
+++ b/zuul/nodepool.py
@@ -84,6 +84,10 @@ class Nodepool(object):
del self.election
def _sendNodesProvisionedEvent(self, request):
+ tracing.endSavedSpan(request.span_info, attributes={
+ "request_id": request.id,
+ "state": request.state,
+ })
tenant_name = request.tenant_name
pipeline_name = request.pipeline_name
event = model.NodesProvisionedEvent(request.id, request.build_set_uuid)
@@ -101,10 +105,6 @@ class Nodepool(object):
continue
if (request.state in {model.STATE_FULFILLED,
model.STATE_FAILED}):
- tracing.endSavedSpan(request.span_info, attributes={
- "request_id": request.id,
- "state": request.state,
- })
self._sendNodesProvisionedEvent(request)
# Now resume normal event processing.
self.stop_watcher_event.wait()