diff --git a/docs/source/API.rst b/docs/source/API.rst index b74d4164..5c6c6d6c 100644 --- a/docs/source/API.rst +++ b/docs/source/API.rst @@ -73,3 +73,9 @@ object in details can be extended with additional fields as needed. Once a POST containing the ``status`` field is made to a bootaction-id, that bootaction-id can no longer be updated with status changes nor additional detailed status messages. + +validatedesign API +------------------ + +The Validatedesign API is used for validating documents before they will be used by Drydock. See +:ref:`validatedesign` for more details on validating documents. diff --git a/docs/source/index.rst b/docs/source/index.rst index 0e4afb2d..29a91f1b 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,6 +44,7 @@ API Documentation API task bootaction + validatedesign Client Documentation -------------------- diff --git a/docs/source/validatedesign.rst b/docs/source/validatedesign.rst new file mode 100644 index 00000000..07531fd7 --- /dev/null +++ b/docs/source/validatedesign.rst @@ -0,0 +1,31 @@ +.. _validatedesign: + +Validate Design +=============== + +The DryDock Validation API is a set of logic checks that must be passed before any information from the YAMLs will be +processed by Drydock. These checks are performed synchronously and will return a message list with a success or +failures for each check. + +Formatting +---------- + +This document can be POSTed to the Drydock validatedesign to validate a set of documents that have been +processed by Deckhand:: + + { + rel : "design", + href: "deckhand+https://{{deckhand_url}}/revisions/{{revision_id}}/rendered-documents", + type: "application/x-yaml" + } + +v1.0 +---- + +Validation Checks +^^^^^^^^^^^^^^^^^ + +These checks are meant to check the business logic of documents sent to the validatedesign API. + +.. autoclass:: drydock_provisioner.orchestrator.validations.validator.Validator + :members: diff --git a/drydock_provisioner/orchestrator/validations/validator.py b/drydock_provisioner/orchestrator/validations/validator.py index 0ddcb603..1364c954 100644 --- a/drydock_provisioner/orchestrator/validations/validator.py +++ b/drydock_provisioner/orchestrator/validations/validator.py @@ -56,7 +56,12 @@ class Validator(): @classmethod def rational_network_bond(cls, site_design): """ - Ensures that NetworkLink 'bonding' is rational + This check ensures that each NetworkLink has a rational bonding setup. + If the bonding mode is set to 'disabled' then it ensures that no other options are specified. + If the bonding mode it set to '802.3ad' then it ensures that the bonding up delay and the bonding down delay + are both greater then or equal to the mon rate. + If the bonding mode is set to active-backup or balanced-rr then it ensures that the bonding hash and the + bonding peer rate are both NOT defined. """ message_list = [] site_design = site_design.obj_to_simple() @@ -141,7 +146,8 @@ class Validator(): @classmethod def network_trunking_rational(cls, site_design): """ - Ensures that Network Trunking is Rational + This check ensures that for each NetworkLink if the allowed networks are greater then 1 trunking mode is + enabled. It also makes sure that if trunking mode is disabled then a default network is defined. """ message_list = [] @@ -190,7 +196,8 @@ class Validator(): @classmethod def storage_partitioning(cls, site_design): """ - Checks storage partitioning + This checks that for each storage device a partition list OR volume group is defined. Also for each partition + list it ensures that a file system and partition volume group are not defined in the same partition. """ message_list = [] site_design = site_design.obj_to_simple() @@ -249,8 +256,8 @@ class Validator(): if volume_group.get('name') not in volume_group_check_list: msg = ('Storage Partitioning Error: A volume group must be assigned to a storage device or ' - 'partition; volume group %s on BaremetalNode %s' % - (volume_group.get('name'), baremetal_node.get('name'))) + 'partition; volume group %s on BaremetalNode %s' % (volume_group.get('name'), + baremetal_node.get('name'))) message_list.append( TaskStatusMessage( @@ -420,7 +427,9 @@ class Validator(): @classmethod def no_duplicate_IPs_check(cls, site_design): """ - Ensures that the same IP is not assigned to multiple baremetal nodes. + Ensures that the same IP is not assigned to multiple baremetal node definitions by checking each new IP against + the list of known IPs. If the IP is unique no error is thrown and the new IP will be added to the list to be + checked against in the future. """ found_ips = {} # Dictionary Format - IP address: BaremetalNode name message_list = [] @@ -430,9 +439,7 @@ class Validator(): if not baremetal_nodes_list: msg = 'No BaremetalNodes Found.' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=False, ctx_type='NA', ctx='NA')) else: for node in baremetal_nodes_list: addressing_list = node.get('addressing', []) @@ -443,19 +450,14 @@ class Validator(): if address in found_ips and address is not None: msg = ('Error! Duplicate IP Address Found: %s ' - 'is in use by both %s and %s.' - % (address, found_ips[address], node_name)) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) + 'is in use by both %s and %s.' % (address, found_ips[address], node_name)) + message_list.append(TaskStatusMessage(msg=msg, error=True, ctx_type='NA', ctx='NA')) elif address is not None: found_ips[address] = node_name if not message_list: msg = 'No Duplicate IP Addresses.' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=False, ctx_type='NA', ctx='NA')) return message_list @@ -523,7 +525,8 @@ class Validator(): @classmethod def ip_locality_check(cls, site_design): """ - Ensures that IP addresses are within defined CIDR ranges. + Ensures that each IP addresses assigned to a baremetal node is within the defined CIDR for the network. Also + verifies that the gateway IP for each static route of a network is within that network's CIDR. """ network_dict = {} # Dictionary Format - network name: cidr message_list = [] @@ -534,9 +537,7 @@ class Validator(): if not network_list: msg = 'No networks found.' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=False, ctx_type='NA', ctx='NA')) else: for net in network_list: name = net.get('name') @@ -552,23 +553,16 @@ class Validator(): if not gateway: msg = 'No gateway found for route %s.' % routes - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=True, ctx_type='NA', ctx='NA')) else: ip = IPAddress(gateway) if ip not in cidr_range: msg = ('IP Locality Error: The gateway IP Address %s ' - 'is not within the defined CIDR: %s of %s.' - % (gateway, cidr, name)) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) + 'is not within the defined CIDR: %s of %s.' % (gateway, cidr, name)) + message_list.append(TaskStatusMessage(msg=msg, error=True, ctx_type='NA', ctx='NA')) if not baremetal_nodes_list: msg = 'No baremetal_nodes found.' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=False, ctx_type='NA', ctx='NA')) else: for node in baremetal_nodes_list: addressing_list = node.get('addressing', []) @@ -582,23 +576,16 @@ class Validator(): if ip_address_network_name not in network_dict: msg = 'IP Locality Error: %s is not a valid network.' \ % (ip_address_network_name) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=True, ctx_type='NA', ctx='NA')) else: if IPAddress(address) not in IPNetwork(network_dict[ip_address_network_name]): msg = ('IP Locality Error: The IP Address %s ' - 'is not within the defined CIDR: %s of %s .' - % (address, network_dict[ip_address_network_name], - ip_address_network_name)) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) + 'is not within the defined CIDR: %s of %s .' % + (address, network_dict[ip_address_network_name], ip_address_network_name)) + message_list.append(TaskStatusMessage(msg=msg, error=True, ctx_type='NA', ctx='NA')) if not message_list: msg = 'IP Locality Success' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) + message_list.append(TaskStatusMessage(msg=msg, error=False, ctx_type='NA', ctx='NA')) return message_list