From 3b4186880246cdc9ace304d5b2608dd017b03dc4 Mon Sep 17 00:00:00 2001 From: Scott Hussey Date: Fri, 27 Apr 2018 11:35:33 -0500 Subject: [PATCH] [373577] Fix hostnames with underscores Hostnames with underscores caused a deployment failure. Update to use a double underscore as a delimiter and provide a design validator to check that hostnames do not contain it. Closes #78 Change-Id: Ib148aed5cffe7fd8bc08441eaef8a45af6601bdd --- docs/source/topology.rst | 1 + .../drivers/node/maasdriver/actions/node.py | 11 ++-- .../drivers/node/maasdriver/models/machine.py | 1 + .../validations/hostname_validity.py | 32 ++++++++++ .../orchestrator/validations/validator.py | 2 + drydock_provisioner/statemgmt/state.py | 4 +- .../test_validation_rule_hostname_validity.py | 64 +++++++++++++++++++ tests/yaml_samples/invalid_validation.yaml | 4 +- 8 files changed, 112 insertions(+), 7 deletions(-) create mode 100644 drydock_provisioner/orchestrator/validations/hostname_validity.py create mode 100644 tests/unit/test_validation_rule_hostname_validity.py diff --git a/docs/source/topology.rst b/docs/source/topology.rst index 98763006..f2c1b421 100644 --- a/docs/source/topology.rst +++ b/docs/source/topology.rst @@ -227,6 +227,7 @@ reference to the particular physical node. The ``BaremetalNode`` definition will reference a ``HostProfile`` and can then extend or override any of the configuration values. +NOTE: Drydock does not support hostnames containing '__' (double underscoe) Hardware Profile ---------------- diff --git a/drydock_provisioner/drivers/node/maasdriver/actions/node.py b/drydock_provisioner/drivers/node/maasdriver/actions/node.py index 56dfa9f3..08a16ce7 100644 --- a/drydock_provisioner/drivers/node/maasdriver/actions/node.py +++ b/drydock_provisioner/drivers/node/maasdriver/actions/node.py @@ -660,7 +660,9 @@ class ConfigureUserCredentials(BaseMaasAction): if key_list: for k in key_list: try: - if len(current_keys.query({'key': k.replace("\n", "")})) == 0: + if len(current_keys.query({ + 'key': k.replace("\n", "") + })) == 0: new_key = maas_keys.SshKey(self.maas_client, key=k) new_key = current_keys.add(new_key) msg = "Added SSH key %s to MaaS user profile. Will be installed on all deployed nodes." % ( @@ -1814,7 +1816,8 @@ class ApplyNodeStorage(BaseMaasAction): raise errors.NotEnoughStorage() if match.group(1) == '>': - computed_size = int(context.available_size) - ApplyNodeStorage.PART_TABLE_RESERVATION + computed_size = int(context.available_size + ) - ApplyNodeStorage.PART_TABLE_RESERVATION return computed_size @@ -1919,7 +1922,7 @@ class DeployNode(BaseMaasAction): tag_list = maas_tag.Tags(self.maas_client) tag_list.refresh() - node_id_tags = tag_list.startswith("%s_baid-" % (n.name)) + node_id_tags = tag_list.startswith("%s__baid__" % (n.name)) for t in node_id_tags: t.delete() @@ -1929,7 +1932,7 @@ class DeployNode(BaseMaasAction): self.logger.debug(msg) node_baid_tag = maas_tag.Tag( self.maas_client, - name="%s_baid-%s" % (n.name, ba_key.hex())) + name="%s__baid__%s" % (n.name, ba_key.hex())) node_baid_tag = tag_list.add(node_baid_tag) node_baid_tag.apply_to_node(machine.resource_id) self.task.add_status_msg( diff --git a/drydock_provisioner/drivers/node/maasdriver/models/machine.py b/drydock_provisioner/drivers/node/maasdriver/models/machine.py index 2c71592d..d8177ecc 100644 --- a/drydock_provisioner/drivers/node/maasdriver/models/machine.py +++ b/drydock_provisioner/drivers/node/maasdriver/models/machine.py @@ -24,6 +24,7 @@ from bson import BSON LOG = logging.getLogger(__name__) + class Machine(model_base.ResourceBase): resource_url = 'machines/{resource_id}/' diff --git a/drydock_provisioner/orchestrator/validations/hostname_validity.py b/drydock_provisioner/orchestrator/validations/hostname_validity.py new file mode 100644 index 00000000..b671b412 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/hostname_validity.py @@ -0,0 +1,32 @@ +# Copyright 2018 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from drydock_provisioner.orchestrator.validations.validators import Validators + + +class HostnameValidity(Validators): + def __init__(self): + super().__init__('Hostname Validity', 'DD3003') + + def run_validation(self, site_design, orchestrator=None): + """Validate that node hostnames do not contain '__' """ + node_list = site_design.baremetal_nodes or [] + + invalid_nodes = [n for n in node_list if '__' in n.name] + + for n in invalid_nodes: + msg = "Hostname %s invalid." % n.name + self.report_error( + msg, [n.doc_ref], + "Hostnames cannot contain '__' (double underscore)") + return diff --git a/drydock_provisioner/orchestrator/validations/validator.py b/drydock_provisioner/orchestrator/validations/validator.py index 3f8df563..e5bd51d7 100644 --- a/drydock_provisioner/orchestrator/validations/validator.py +++ b/drydock_provisioner/orchestrator/validations/validator.py @@ -27,6 +27,7 @@ from drydock_provisioner.orchestrator.validations.rational_network_bond import R from drydock_provisioner.orchestrator.validations.storage_partititioning import StoragePartitioning from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck +from drydock_provisioner.orchestrator.validations.hostname_validity import HostnameValidity class Validator(): @@ -86,4 +87,5 @@ rule_set = [ StoragePartitioning(), StorageSizing(), UniqueNetworkCheck(), + HostnameValidity(), ] diff --git a/drydock_provisioner/statemgmt/state.py b/drydock_provisioner/statemgmt/state.py index d5b2fbf6..42ef8018 100644 --- a/drydock_provisioner/statemgmt/state.py +++ b/drydock_provisioner/statemgmt/state.py @@ -282,8 +282,8 @@ class DrydockState(object): """ try: conn = self.db_engine.connect() - query = self.tasks_tbl.insert().values(**( - task.to_db(include_id=True))) + query = self.tasks_tbl.insert().values( + **(task.to_db(include_id=True))) conn.execute(query) conn.close() return True diff --git a/tests/unit/test_validation_rule_hostname_validity.py b/tests/unit/test_validation_rule_hostname_validity.py new file mode 100644 index 00000000..19d2c0b0 --- /dev/null +++ b/tests/unit/test_validation_rule_hostname_validity.py @@ -0,0 +1,64 @@ +# Copyright 2017 AT&T Intellectual Property. All other rights reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Test Validation Rule Hostname Validity""" + +import logging + +from drydock_provisioner.orchestrator.orchestrator import Orchestrator +from drydock_provisioner.orchestrator.validations.hostname_validity import HostnameValidity + +LOG = logging.getLogger(__name__) + + +class TestHostnameValidity(object): + def test_hostname(self, mocker, deckhand_ingester, drydock_state, + input_files): + input_file = input_files.join("validation.yaml") + design_ref = "file://%s" % str(input_file) + + orch = Orchestrator( + state_manager=drydock_state, ingester=deckhand_ingester) + + status, site_design = Orchestrator.get_effective_site(orch, design_ref) + + validator = HostnameValidity() + message_list = validator.execute(site_design, orchestrator=orch) + msg = message_list[0].to_dict() + + assert 'Hostname' in msg.get('message') + assert msg.get('error') is False + assert len(message_list) == 1 + + def test_invalid_hostname(self, mocker, deckhand_ingester, drydock_state, + input_files): + + input_file = input_files.join("invalid_validation.yaml") + design_ref = "file://%s" % str(input_file) + + orch = Orchestrator( + state_manager=drydock_state, ingester=deckhand_ingester) + + status, site_design = Orchestrator.get_effective_site(orch, design_ref) + + validator = HostnameValidity() + message_list = validator.execute(site_design, orchestrator=orch) + + for msg in message_list: + msg = msg.to_dict() + LOG.debug(msg) + assert msg.get('error') + assert len(msg.get('documents')) > 0 + assert "bad__name" in msg.get('message') + + assert len(message_list) == 1 diff --git a/tests/yaml_samples/invalid_validation.yaml b/tests/yaml_samples/invalid_validation.yaml index d2ff779b..3cb07d4f 100644 --- a/tests/yaml_samples/invalid_validation.yaml +++ b/tests/yaml_samples/invalid_validation.yaml @@ -358,7 +358,9 @@ data: schema: 'drydock/BaremetalNode/v1' metadata: schema: 'metadata/Document/v1' - name: compute01 +##### +# Invalid hostname contains '__' + name: bad__name storagePolicy: 'cleartext' labels: application: 'drydock'