diff options
-rw-r--r-- | tests/unit/test_tracing.py | 5 | ||||
-rw-r--r-- | tests/unit/test_v3.py | 58 | ||||
-rw-r--r-- | web/src/containers/build/Console.jsx | 51 | ||||
-rw-r--r-- | zuul/configloader.py | 14 | ||||
-rw-r--r-- | zuul/nodepool.py | 8 |
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() |