diff --git a/drydock_provisioner/orchestrator/util.py b/drydock_provisioner/orchestrator/util.py index 4c150c7f..7b257cb5 100644 --- a/drydock_provisioner/orchestrator/util.py +++ b/drydock_provisioner/orchestrator/util.py @@ -18,7 +18,7 @@ import drydock_provisioner.error as errors class SimpleBytes(): - def calulate_bytes(size_str): + def calculate_bytes(size_str): """ Calculate the size in bytes of a size_str. diff --git a/drydock_provisioner/orchestrator/validations/boot_storage_rational.py b/drydock_provisioner/orchestrator/validations/boot_storage_rational.py new file mode 100644 index 00000000..f257926f --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/boot_storage_rational.py @@ -0,0 +1,112 @@ +# 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 + +import drydock_provisioner.error as errors +from drydock_provisioner.orchestrator.util import SimpleBytes +from drydock_provisioner.objects.task import TaskStatusMessage + +class BootStorageRational(Validators): + def __init__(self): + super().__init__('Boot Storage Rational', 1001) + + def execute(self, site_design, orchestrator=None): + """ + Ensures that root volume is defined and is at least 20GB and that boot volume is at least 1 GB + """ + message_list = [] + site_design = site_design.obj_to_simple() + BYTES_IN_GB = SimpleBytes.calculate_bytes('1GB') + + baremetal_node_list = site_design.get('baremetal_nodes', []) + + for baremetal_node in baremetal_node_list: + storage_devices_list = baremetal_node.get('storage_devices', []) + + root_set = False + + for storage_device in storage_devices_list: + partitions_list = storage_device.get('partitions', []) + + for host_partition in partitions_list: + if host_partition.get('name') == 'root': + size = host_partition.get('size') + try: + cal_size = SimpleBytes.calculate_bytes(size) + root_set = True + # check if size < 20GB + if cal_size < 20 * BYTES_IN_GB: + msg = ( + 'Boot Storage Error: Root volume must be > 20GB on BaremetalNode ' + '%s' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + except errors.InvalidSizeFormat as e: + msg = ( + 'Boot Storage Error: Root volume has an invalid size format on BaremetalNode' + '%s.' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + + # check make sure root has been defined and boot volume > 1GB + if root_set and host_partition.get('name') == 'boot': + size = host_partition.get('size') + + try: + cal_size = SimpleBytes.calculate_bytes(size) + # check if size < 1GB + if cal_size < BYTES_IN_GB: + msg = ( + 'Boot Storage Error: Boot volume must be > 1GB on BaremetalNode ' + '%s' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + except errors.InvalidSizeFormat as e: + msg = ( + 'Boot Storage Error: Boot volume has an invalid size format on BaremetalNode ' + '%s.' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + + # This must be set + if not root_set: + msg = ( + 'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode ' + '%s' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Boot Storage', error=False, ctx_type='NA', ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/ip_locality_check.py b/drydock_provisioner/orchestrator/validations/ip_locality_check.py new file mode 100644 index 00000000..0123e049 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/ip_locality_check.py @@ -0,0 +1,120 @@ +# 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 + +from netaddr import IPNetwork, IPAddress + +from drydock_provisioner.objects.task import TaskStatusMessage + +class IpLocalityCheck(Validators): + def __init__(self): + super().__init__('IP Locality Check', 1002) + + def execute(self, site_design, orchestrator=None): + """ + 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 = [] + + site_design = site_design.obj_to_simple() + baremetal_nodes_list = site_design.get('baremetal_nodes', []) + network_list = site_design.get('networks', []) + + if not network_list: + msg = 'No networks found.' + message_list.append( + TaskStatusMessage( + msg=msg, error=False, ctx_type='NA', ctx='NA')) + else: + for net in network_list: + name = net.get('name') + cidr = net.get('cidr') + routes = net.get('routes', []) + + cidr_range = IPNetwork(cidr) + network_dict[name] = cidr_range + + if routes: + for r in routes: + gateway = r.get('gateway') + + if not gateway: + msg = 'No gateway found for route %s.' % routes + 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')) + if not baremetal_nodes_list: + msg = 'No baremetal_nodes found.' + 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', []) + + for ip_address in addressing_list: + ip_address_network_name = ip_address.get('network') + address = ip_address.get('address') + ip_type = ip_address.get('type') + + if ip_type is not 'dhcp': + 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')) + 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')) + if not message_list: + msg = 'IP Locality Success' + message_list.append( + TaskStatusMessage( + msg=msg, error=False, ctx_type='NA', ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/mtu_rational.py b/drydock_provisioner/orchestrator/validations/mtu_rational.py new file mode 100644 index 00000000..8f177ed1 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/mtu_rational.py @@ -0,0 +1,78 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class MtuRational(Validators): + def __init__(self): + super().__init__('MTU Rational', 1003) + + def execute(self, site_design, orchestrator=None): + """ + Ensure that the MTU for each network is equal or less than the MTU defined + for the parent NetworkLink for that network. + + Ensure that each defined MTU is a rational size, say > 1400 and < 64000 + """ + message_list = [] + site_design = site_design.obj_to_simple() + + network_links = site_design.get('network_links', []) + networks = site_design.get('networks', []) + + parent_mtu_check = {} + + for network_link in network_links: + mtu = network_link.get('mtu') + # check mtu > 1400 and < 64000 + if mtu and (mtu < 1400 or mtu > 64000): + msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network Link %s.' % network_link.get( + 'name') + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + # add assigned network to dict with parent mtu + assigned_network = network_link.get('native_network') + parent_mtu_check[assigned_network] = mtu + + for network in networks: + network_mtu = network.get('mtu') + + # check mtu > 1400 and < 64000 + if network_mtu and (network_mtu < 1400 or network_mtu > 64000): + msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network %s.' % network.get( + 'name') + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + name = network.get('name') + parent_mtu = parent_mtu_check.get(name) + if network_mtu and parent_mtu: + # check to make sure mtu for network is <= parent network link + if network_mtu > parent_mtu: + msg = 'Mtu Error: Mtu must be <= the parent Network Link; for Network %s' % ( + network.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Mtu', error=False, ctx_type='NA', ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/network_trunking_rational.py b/drydock_provisioner/orchestrator/validations/network_trunking_rational.py new file mode 100644 index 00000000..96ef9eb9 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/network_trunking_rational.py @@ -0,0 +1,70 @@ +# 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 + +import drydock_provisioner.objects.fields as hd_fields +from drydock_provisioner.objects.task import TaskStatusMessage + +class NetworkTrunkingRational(Validators): + def __init__(self): + super().__init__('Network Trunking Rational', 1004) + + def execute(self, site_design, orchestrator=None): + """ + 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 = [] + site_design = site_design.obj_to_simple() + + network_link_list = site_design.get('network_links', []) + + for network_link in network_link_list: + allowed_networks = network_link.get('allowed_networks', []) + # if allowed networks > 1 trunking must be enabled + if (len(allowed_networks) > 1 and network_link.get('trunk_mode') == + hd_fields.NetworkLinkTrunkingMode.Disabled): + + msg = ( + 'Rational Network Trunking Error: If there is more than 1 allowed network,' + 'trunking mode must be enabled; on NetworkLink %s' % + network_link.get('name')) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + # trunking mode is disabled, default_network must be defined + if (network_link.get( + 'trunk_mode') == hd_fields.NetworkLinkTrunkingMode.Disabled + and network_link.get('native_network') is None): + + msg = ( + 'Rational Network Trunking Error: Trunking mode is disabled, a trunking' + 'default_network must be defined; on NetworkLink %s' % + network_link.get('name')) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Rational Network Trunking', + error=False, + ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/no_duplicate_ips_check.py b/drydock_provisioner/orchestrator/validations/no_duplicate_ips_check.py new file mode 100644 index 00000000..2e9c7f4d --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/no_duplicate_ips_check.py @@ -0,0 +1,63 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class NoDuplicateIpsCheck(Validators): + def __init__(self): + super().__init__('No Duplicate IPs Check', 1005) + + def execute(self, site_design, orchestrator=None): + """ + 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 = [] + + site_design = site_design.obj_to_simple() + baremetal_nodes_list = site_design.get('baremetal_nodes', []) + + if not baremetal_nodes_list: + msg = 'No BaremetalNodes Found.' + 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', []) + + for ip_address in addressing_list: + address = ip_address.get('address') + node_name = node.get('name') + + 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')) + 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')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/platform_selection.py b/drydock_provisioner/orchestrator/validations/platform_selection.py new file mode 100644 index 00000000..152d805f --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/platform_selection.py @@ -0,0 +1,78 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class PlatformSelection(Validators): + def __init__(self): + super().__init__('Platform Selection', 1006) + + def execute(self, site_design, orchestrator=None): + """Validate that the platform selection for all nodes is valid. + + Each node specifies an ``image`` and a ``kernel`` to use for + deployment. Check that these are valid for the image repository + configured in MAAS. + """ + message_list = list() + + try: + node_driver = orchestrator.enabled_drivers['node'] + except KeyError: + message_list.append( + TaskStatusMessage( + msg="Platform Validation: No enabled node driver, image" + "and kernel selections not validated.", + error=False, + ctx_type='NA', + ctx='NA')) + return Validators.report_results(self, message_list) + + valid_images = node_driver.get_available_images() + + valid_kernels = dict() + + for i in valid_images: + valid_kernels[i] = node_driver.get_available_kernels(i) + + for n in site_design.baremetal_nodes: + if n.image in valid_images: + if n.kernel in valid_kernels[n.image]: + continue + message_list.append( + TaskStatusMessage( + msg="Platform Validation: invalid kernel %s for node %s." + % (n.kernel, n.name), + error=True, + ctx_type='NA', + ctx='NA')) + continue + message_list.append( + TaskStatusMessage( + msg="Platform Validation: invalid image %s for node %s." % + (n.image, n.name), + error=True, + ctx_type='NA', + ctx='NA')) + if not message_list: + message_list.append( + TaskStatusMessage( + msg="Platform Validation: all nodes have valid " + "image and kernel selections.", + error=False, + ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/rational_network_bond.py b/drydock_provisioner/orchestrator/validations/rational_network_bond.py new file mode 100644 index 00000000..86ca2602 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/rational_network_bond.py @@ -0,0 +1,109 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class RationalNetworkBond(Validators): + def __init__(self): + super().__init__('Rational Network Bond', 1007) + + def execute(self, site_design, orchestrator=None): + """ + 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() + + network_links = site_design.get('network_links', []) + + for network_link in network_links: + bonding_mode = network_link.get('bonding_mode', []) + + if bonding_mode == 'disabled': + # check to make sure nothing else is specified + if any([ + network_link.get(x) for x in [ + 'bonding_peer_rate', 'bonding_xmit_hash', + 'bonding_mon_rate', 'bonding_up_delay', + 'bonding_down_delay' + ] + ]): + + msg = ( + 'Network Link Bonding Error: If bonding mode is disabled no other bond option can be' + 'specified; on BaremetalNode %s' % + network_link.get('name')) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + elif bonding_mode == '802.3ad': + # check if up_delay and down_delay are >= mon_rate + mon_rate = network_link.get('bonding_mon_rate') + if network_link.get('bonding_up_delay') < mon_rate: + msg = ('Network Link Bonding Error: Up delay is less ' + 'than mon rate on BaremetalNode %s' % + (network_link.get('name'))) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if network_link.get('bonding_down_delay') < mon_rate: + msg = ('Network Link Bonding Error: Down delay is ' + 'less than mon rate on BaremetalNode %s' % + (network_link.get('name'))) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + elif bonding_mode in ['active-backup', 'balanced-rr']: + # make sure hash and peer_rate are NOT defined + if network_link.get('bonding_xmit_hash'): + msg = ( + 'Network Link Bonding Error: Hash cannot be defined if bond mode is ' + '%s, on BaremetalNode %s' % (bonding_mode, + network_link.get('name'))) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if network_link.get('bonding_peer_rate'): + msg = ( + 'Network Link Bonding Error: Peer rate cannot be defined if bond mode is ' + '%s, on BaremetalNode %s' % (bonding_mode, + network_link.get('name'))) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Network Link Bonding', + error=False, + ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/storage_partititioning.py b/drydock_provisioner/orchestrator/validations/storage_partititioning.py new file mode 100644 index 00000000..8990592e --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/storage_partititioning.py @@ -0,0 +1,102 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class StoragePartitioning(Validators): + def __init__(self): + super().__init__('Storage Partitioning', 1008) + + def execute(self, site_design, orchestrator=None): + """ + 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() + + baremetal_nodes = site_design.get('baremetal_nodes', []) + + volume_group_check_list = [] + + for baremetal_node in baremetal_nodes: + storage_devices_list = baremetal_node.get('storage_devices', []) + + for storage_device in storage_devices_list: + partitions_list = storage_device.get('partitions') + volume_group = storage_device.get('volume_group') + + # error if both or neither is defined + if all([partitions_list, volume_group + ]) or not any([partitions_list, volume_group]): + msg = ('Storage Partitioning Error: Either a volume group ' + 'OR partitions must be defined for each storage ' + 'device; on BaremetalNode ' + '%s' % baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + # if there is a volume group add to list + if volume_group is not None: + volume_group_check_list.append(volume_group) + + if partitions_list is not None: + for partition in partitions_list: + partition_volume_group = partition.get('volume_group') + fstype = partition.get('fstype') + + # error if both are defined + if all([fstype, partition_volume_group]): + msg = ( + 'Storage Partitioning Error: Both a volume group AND file system cannot be ' + 'defined in a sigle partition; on BaremetalNode %s' + % baremetal_node.get('name')) + + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + + # if there is a volume group add to list + if partition_volume_group is not None: + volume_group_check_list.append(volume_group) + + # checks all volume groups are assigned to a partition or storage device + # if one exist that wasn't found earlier it is unassigned + all_volume_groups = baremetal_node.get('volume_groups', []) + for volume_group in all_volume_groups: + 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'))) + + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Storage Partitioning', + error=False, + ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/storage_sizing.py b/drydock_provisioner/orchestrator/validations/storage_sizing.py new file mode 100644 index 00000000..5b730d4c --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/storage_sizing.py @@ -0,0 +1,103 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class StorageSizing(Validators): + def __init__(self): + super().__init__('Storage Sizing', 1009) + + def execute(self, site_design, orchestrator=None): + """ + Ensures that for a partitioned physical device or logical volumes + in a volume group, if sizing is a percentage then those percentages + do not sum > 99% and have no negative values + """ + message_list = [] + site_design = site_design.obj_to_simple() + + baremetal_nodes = site_design.get('baremetal_nodes', []) + + for baremetal_node in baremetal_nodes: + storage_device_list = baremetal_node.get('storage_devices', []) + + for storage_device in storage_device_list: + partition_list = storage_device.get('partitions', []) + partition_sum = 0 + for partition in partition_list: + size = partition.get('size') + percent = size.split('%') + if len(percent) == 2: + if int(percent[0]) < 0: + msg = ( + 'Storage Sizing Error: Storage partition size is < 0 ' + 'on Baremetal Node %s' % + baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + + partition_sum += int(percent[0]) + + if partition_sum > 99: + msg = ( + 'Storage Sizing Error: Storage partition size is greater than ' + '99 on Baremetal Node %s' % + baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + volume_groups = baremetal_node.get('volume_groups', []) + volume_sum = 0 + for volume_group in volume_groups: + logical_volume_list = volume_group.get( + 'logical_volumes', []) + for logical_volume in logical_volume_list: + size = logical_volume.get('size') + percent = size.split('%') + if len(percent) == 2: + if int(percent[0]) < 0: + msg = ( + 'Storage Sizing Error: Storage volume size is < 0 ' + 'on Baremetal Node %s' % + baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, + error=True, + ctx_type='NA', + ctx='NA')) + volume_sum += int(percent[0]) + + if volume_sum > 99: + msg = ( + 'Storage Sizing Error: Storage volume size is greater ' + 'than 99 on Baremetal Node %s.' % + baremetal_node.get('name')) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Storage Sizing', error=False, ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/unique_network_check.py b/drydock_provisioner/orchestrator/validations/unique_network_check.py new file mode 100644 index 00000000..550b1969 --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/unique_network_check.py @@ -0,0 +1,70 @@ +# 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 + +from drydock_provisioner.objects.task import TaskStatusMessage + +class UniqueNetworkCheck(Validators): + def __init__(self): + super().__init__('Unique Network Check', 1010) + + def execute(self, site_design, orchestrator=None): + """ + Ensures that each network name appears at most once between all NetworkLink + allowed networks + """ + message_list = [] + site_design = site_design.obj_to_simple() + network_link_list = site_design.get('network_links', []) + compare = {} + + for network_link in network_link_list: + allowed_network_list = network_link.get('allowed_networks', []) + compare[network_link.get('name')] = allowed_network_list + + # This checks the allowed networks for each network link against + # the other allowed networks + checked_pairs = [] + for network_link_name in compare: + allowed_network_list_1 = compare[network_link_name] + + for network_link_name_2 in compare: + if (network_link_name is not network_link_name_2 + and sorted([network_link_name, network_link_name_2 + ]) not in checked_pairs): + checked_pairs.append( + sorted([network_link_name, network_link_name_2])) + allowed_network_list_2 = compare[network_link_name_2] + # creates a list of duplicated allowed networks + duplicated_names = [ + i for i in allowed_network_list_1 + if i in allowed_network_list_2 + ] + + for name in duplicated_names: + msg = ( + 'Unique Network Error: Allowed network %s duplicated on NetworkLink %s and NetworkLink ' + '%s' % (name, network_link_name, + network_link_name_2)) + message_list.append( + TaskStatusMessage( + msg=msg, error=True, ctx_type='NA', ctx='NA')) + + if not message_list: + message_list.append( + TaskStatusMessage( + msg='Unique Network', error=False, ctx_type='NA', + ctx='NA')) + + return Validators.report_results(self, message_list) diff --git a/drydock_provisioner/orchestrator/validations/validator.py b/drydock_provisioner/orchestrator/validations/validator.py index 0581bbc2..265ea6d7 100644 --- a/drydock_provisioner/orchestrator/validations/validator.py +++ b/drydock_provisioner/orchestrator/validations/validator.py @@ -14,12 +14,19 @@ """Business Logic Validation""" import drydock_provisioner.objects.fields as hd_fields -import drydock_provisioner.error as errors -from netaddr import IPNetwork, IPAddress -from drydock_provisioner.orchestrator.util import SimpleBytes -from drydock_provisioner.objects.task import TaskStatus, TaskStatusMessage +from drydock_provisioner.objects.task import TaskStatus +from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational +from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck +from drydock_provisioner.orchestrator.validations.mtu_rational import MtuRational +from drydock_provisioner.orchestrator.validations.network_trunking_rational import NetworkTrunkingRational +from drydock_provisioner.orchestrator.validations.no_duplicate_ips_check import NoDuplicateIpsCheck +from drydock_provisioner.orchestrator.validations.platform_selection import PlatformSelection +from drydock_provisioner.orchestrator.validations.rational_network_bond import RationalNetworkBond +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 class Validator(): def __init__(self, orchestrator): @@ -29,7 +36,7 @@ class Validator(): """ self.orchestrator = orchestrator - def validate_design(self, site_design, result_status=None): + def validate_design(self, site_design, result_status=None, include_output=False): """Validate the design in site_design passes all validation rules. Apply all validation rules to the design in site_design. If result_status is @@ -43,10 +50,13 @@ class Validator(): result_status = TaskStatus() validation_error = False + message_lists = [] for rule in rule_set: - output = rule(site_design, orchestrator=self.orchestrator) - result_status.message_list.extend(output) - error_msg = [m for m in output if m.error] + results, message_list = rule.execute(site_design=site_design, orchestrator=self.orchestrator) + for item in message_list: + message_lists.append(item) + result_status.message_list.extend(results) + error_msg = [m for m in results if m.error] result_status.error_count = result_status.error_count + len( error_msg) if len(error_msg) > 0: @@ -57,723 +67,38 @@ class Validator(): else: result_status.set_status(hd_fields.ValidationResult.Success) + if include_output: + output = { + "kind": "Status", + "api_version": "v1.0", + "metadata": {}, + "status": "Success", + "message": "Drydock validations succeeded", + "reason": "Validation", + "details": { + "error_count": 0, + "message_list": [] + }, + "code": 200 + } + if len(message_lists) > 0: + output['status'] = "Failure" + output['details']['error_count'] = len(message_lists) + output['details']['message_list'] = message_lists + output['code'] = 400 + return output return result_status - @classmethod - def valid_platform_selection(cls, site_design, orchestrator=None): - """Validate that the platform selection for all nodes is valid. - - Each node specifies an ``image`` and a ``kernel`` to use for - deployment. Check that these are valid for the image repoistory - configured in MAAS. - """ - message_list = list() - - try: - node_driver = orchestrator.enabled_drivers['node'] - except KeyError: - message_list.append( - TaskStatusMessage( - msg="Platform Validation: No enabled node driver, image" - "and kernel selections not validated.", - error=False, - ctx_type='NA', - ctx='NA')) - return message_list - - valid_images = node_driver.get_available_images() - - valid_kernels = dict() - - for i in valid_images: - valid_kernels[i] = node_driver.get_available_kernels(i) - - for n in site_design.baremetal_nodes: - if n.image in valid_images: - if n.kernel in valid_kernels[n.image]: - continue - message_list.append( - TaskStatusMessage( - msg="Platform Validation: invalid kernel %s for node %s." - % (n.kernel, n.name), - error=True, - ctx_type='NA', - ctx='NA')) - continue - message_list.append( - TaskStatusMessage( - msg="Platform Validation: invalid image %s for node %s." % - (n.image, n.name), - error=True, - ctx_type='NA', - ctx='NA')) - if not message_list: - message_list.append( - TaskStatusMessage( - msg="Platform Validation: all nodes have valid " - "image and kernel selections.", - error=False, - ctx_type='NA', - ctx='NA')) - - return message_list - - @classmethod - def rational_network_bond(cls, site_design, orchestrator=None): - """ - 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() - - network_links = site_design.get('network_links', []) - - for network_link in network_links: - bonding_mode = network_link.get('bonding_mode', []) - - if bonding_mode == 'disabled': - # check to make sure nothing else is specified - if any([ - network_link.get(x) for x in [ - 'bonding_peer_rate', 'bonding_xmit_hash', - 'bonding_mon_rate', 'bonding_up_delay', - 'bonding_down_delay' - ] - ]): - - msg = ( - 'Network Link Bonding Error: If bonding mode is disabled no other bond option can be' - 'specified; on BaremetalNode %s' % - network_link.get('name')) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - elif bonding_mode == '802.3ad': - # check if up_delay and down_delay are >= mon_rate - mon_rate = network_link.get('bonding_mon_rate') - if network_link.get('bonding_up_delay') < mon_rate: - msg = ('Network Link Bonding Error: Up delay is less ' - 'than mon rate on BaremetalNode %s' % - (network_link.get('name'))) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if network_link.get('bonding_down_delay') < mon_rate: - msg = ('Network Link Bonding Error: Down delay is ' - 'less than mon rate on BaremetalNode %s' % - (network_link.get('name'))) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - elif bonding_mode in ['active-backup', 'balanced-rr']: - # make sure hash and peer_rate are NOT defined - if network_link.get('bonding_xmit_hash'): - msg = ( - 'Network Link Bonding Error: Hash cannot be defined if bond mode is ' - '%s, on BaremetalNode %s' % (bonding_mode, - network_link.get('name'))) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if network_link.get('bonding_peer_rate'): - msg = ( - 'Network Link Bonding Error: Peer rate cannot be defined if bond mode is ' - '%s, on BaremetalNode %s' % (bonding_mode, - network_link.get('name'))) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Network Link Bonding', - error=False, - ctx_type='NA', - ctx='NA')) - return message_list - - @classmethod - def network_trunking_rational(cls, site_design, orchestrator=None): - """ - 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 = [] - site_design = site_design.obj_to_simple() - - network_link_list = site_design.get('network_links', []) - - for network_link in network_link_list: - allowed_networks = network_link.get('allowed_networks', []) - # if allowed networks > 1 trunking must be enabled - if (len(allowed_networks) > 1 and network_link.get('trunk_mode') == - hd_fields.NetworkLinkTrunkingMode.Disabled): - - msg = ( - 'Rational Network Trunking Error: If there is more than 1 allowed network,' - 'trunking mode must be enabled; on NetworkLink %s' % - network_link.get('name')) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - # trunking mode is disabled, default_network must be defined - if (network_link.get( - 'trunk_mode') == hd_fields.NetworkLinkTrunkingMode.Disabled - and network_link.get('native_network') is None): - - msg = ( - 'Rational Network Trunking Error: Trunking mode is disabled, a trunking' - 'default_network must be defined; on NetworkLink %s' % - network_link.get('name')) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Rational Network Trunking', - error=False, - ctx_type='NA', - ctx='NA')) - return message_list - - @classmethod - def storage_partitioning(cls, site_design, orchestrator=None): - """ - 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() - - baremetal_nodes = site_design.get('baremetal_nodes', []) - - volume_group_check_list = [] - - for baremetal_node in baremetal_nodes: - storage_devices_list = baremetal_node.get('storage_devices', []) - - for storage_device in storage_devices_list: - partitions_list = storage_device.get('partitions') - volume_group = storage_device.get('volume_group') - - # error if both or neither is defined - if all([partitions_list, volume_group - ]) or not any([partitions_list, volume_group]): - msg = ('Storage Partitioning Error: Either a volume group ' - 'OR partitions must be defined for each storage ' - 'device; on BaremetalNode ' - '%s' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - # if there is a volume group add to list - if volume_group is not None: - volume_group_check_list.append(volume_group) - - if partitions_list is not None: - for partition in partitions_list: - partition_volume_group = partition.get('volume_group') - fstype = partition.get('fstype') - - # error if both are defined - if all([fstype, partition_volume_group]): - msg = ( - 'Storage Partitioning Error: Both a volume group AND file system cannot be ' - 'defined in a sigle partition; on BaremetalNode %s' - % baremetal_node.get('name')) - - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - - # if there is a volume group add to list - if partition_volume_group is not None: - volume_group_check_list.append(volume_group) - - # checks all volume groups are assigned to a partition or storage device - # if one exist that wasn't found earlier it is unassigned - all_volume_groups = baremetal_node.get('volume_groups', []) - for volume_group in all_volume_groups: - 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'))) - - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Storage Partitioning', - error=False, - ctx_type='NA', - ctx='NA')) - return message_list - - @classmethod - def unique_network_check(cls, site_design, orchestrator=None): - """ - Ensures that each network name appears at most once between all NetworkLink - allowed networks - """ - - message_list = [] - site_design = site_design.obj_to_simple() - network_link_list = site_design.get('network_links', []) - compare = {} - - for network_link in network_link_list: - allowed_network_list = network_link.get('allowed_networks', []) - compare[network_link.get('name')] = allowed_network_list - - # This checks the allowed networks for each network link aginst - # the other allowed networks - checked_pairs = [] - for network_link_name in compare: - allowed_network_list_1 = compare[network_link_name] - - for network_link_name_2 in compare: - if (network_link_name is not network_link_name_2 - and sorted([network_link_name, network_link_name_2 - ]) not in checked_pairs): - checked_pairs.append( - sorted([network_link_name, network_link_name_2])) - allowed_network_list_2 = compare[network_link_name_2] - # creates a list of duplicated allowed networks - duplicated_names = [ - i for i in allowed_network_list_1 - if i in allowed_network_list_2 - ] - - for name in duplicated_names: - msg = ( - 'Unique Network Error: Allowed network %s duplicated on NetworkLink %s and NetworkLink ' - '%s' % (name, network_link_name, - network_link_name_2)) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Unique Network', error=False, ctx_type='NA', - ctx='NA')) - return message_list - - @classmethod - def mtu_rational(cls, site_design, orchestrator=None): - """ - Ensure that the MTU for each network is equal or less than the MTU defined - for the parent NetworkLink for that network. - - Ensure that each defined MTU is a rational size, say > 1400 and < 64000 - """ - message_list = [] - site_design = site_design.obj_to_simple() - - network_links = site_design.get('network_links', []) - networks = site_design.get('networks', []) - - parent_mtu_check = {} - - for network_link in network_links: - mtu = network_link.get('mtu') - # check mtu > 1400 and < 64000 - if mtu and (mtu < 1400 or mtu > 64000): - msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network Link %s.' % network_link.get( - 'name') - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - # add assigned network to dict with parent mtu - assigned_network = network_link.get('native_network') - parent_mtu_check[assigned_network] = mtu - - for network in networks: - network_mtu = network.get('mtu') - - # check mtu > 1400 and < 64000 - if network_mtu and (network_mtu < 1400 or network_mtu > 64000): - msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network %s.' % network.get( - 'name') - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - name = network.get('name') - parent_mtu = parent_mtu_check.get(name) - if network_mtu and parent_mtu: - # check to make sure mtu for network is <= parent network link - if network_mtu > parent_mtu: - msg = 'Mtu Error: Mtu must be <= the parent Network Link; for Network %s' % ( - network.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Mtu', error=False, ctx_type='NA', ctx='NA')) - return message_list - - @classmethod - def storage_sizing(cls, site_design, orchestrator=None): - """ - Ensures that for a partitioned physical device or logical volumes - in a volume group, if sizing is a percentage then those percentages - do not sum > 99% and have no negitive values - """ - - message_list = [] - site_design = site_design.obj_to_simple() - - baremetal_nodes = site_design.get('baremetal_nodes', []) - - for baremetal_node in baremetal_nodes: - storage_device_list = baremetal_node.get('storage_devices', []) - - for storage_device in storage_device_list: - partition_list = storage_device.get('partitions', []) - partition_sum = 0 - for partition in partition_list: - size = partition.get('size') - percent = size.split('%') - if len(percent) == 2: - if int(percent[0]) < 0: - msg = ( - 'Storage Sizing Error: Storage partition size is < 0 ' - 'on Baremetal Node %s' % - baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - - partition_sum += int(percent[0]) - - if partition_sum > 99: - msg = ( - 'Storage Sizing Error: Storage partition size is greater than ' - '99 on Baremetal Node %s' % - baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - volume_groups = baremetal_node.get('volume_groups', []) - volume_sum = 0 - for volume_group in volume_groups: - logical_volume_list = volume_group.get( - 'logical_volumes', []) - for logical_volume in logical_volume_list: - size = logical_volume.get('size') - percent = size.split('%') - if len(percent) == 2: - if int(percent[0]) < 0: - msg = ( - 'Storage Sizing Error: Storage volume size is < 0 ' - 'on Baremetal Node %s' % - baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - volume_sum += int(percent[0]) - - if volume_sum > 99: - msg = ( - 'Storage Sizing Error: Storage volume size is greater ' - 'than 99 on Baremetal Node %s.' % - baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Storage Sizing', error=False, ctx_type='NA', - ctx='NA')) - return message_list - - @classmethod - def no_duplicate_IPs_check(cls, site_design, orchestrator=None): - """ - 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 = [] - - site_design = site_design.obj_to_simple() - baremetal_nodes_list = site_design.get('baremetal_nodes', []) - - if not baremetal_nodes_list: - msg = 'No BaremetalNodes Found.' - 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', []) - - for ip_address in addressing_list: - address = ip_address.get('address') - node_name = node.get('name') - - 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')) - 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')) - - return message_list - - @classmethod - def boot_storage_rational(cls, site_design, orchestrator=None): - """ - Ensures that root volume is defined and is at least 20GB and that boot volume is at least 1 GB - """ - message_list = [] - site_design = site_design.obj_to_simple() - BYTES_IN_GB = SimpleBytes.calulate_bytes('1GB') - - baremetal_node_list = site_design.get('baremetal_nodes', []) - - for baremetal_node in baremetal_node_list: - storage_devices_list = baremetal_node.get('storage_devices', []) - - root_set = False - - for storage_device in storage_devices_list: - partitions_list = storage_device.get('partitions', []) - - for host_partition in partitions_list: - if host_partition.get('name') == 'root': - size = host_partition.get('size') - try: - cal_size = SimpleBytes.calulate_bytes(size) - root_set = True - # check if size < 20GB - if cal_size < 20 * BYTES_IN_GB: - msg = ( - 'Boot Storage Error: Root volume must be > 20GB on BaremetalNode ' - '%s' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - except errors.InvalidSizeFormat as e: - msg = ( - 'Boot Storage Error: Root volume has an invalid size format on BaremetalNode' - '%s.' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - - # check make sure root has been defined and boot volume > 1GB - if root_set and host_partition.get('name') == 'boot': - size = host_partition.get('size') - - try: - cal_size = SimpleBytes.calulate_bytes(size) - # check if size < 1GB - if cal_size < BYTES_IN_GB: - msg = ( - 'Boot Storage Error: Boot volume must be > 1GB on BaremetalNode ' - '%s' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - except errors.InvalidSizeFormat as e: - msg = ( - 'Boot Storage Error: Boot volume has an invalid size format on BaremetalNode ' - '%s.' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, - error=True, - ctx_type='NA', - ctx='NA')) - - # This must be set - if not root_set: - msg = ( - 'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode ' - '%s' % baremetal_node.get('name')) - message_list.append( - TaskStatusMessage( - msg=msg, error=True, ctx_type='NA', ctx='NA')) - - if not message_list: - message_list.append( - TaskStatusMessage( - msg='Boot Storage', error=False, ctx_type='NA', ctx='NA')) - return message_list - - @classmethod - def ip_locality_check(cls, site_design, orchestrator=None): - """ - 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 = [] - - site_design = site_design.obj_to_simple() - baremetal_nodes_list = site_design.get('baremetal_nodes', []) - network_list = site_design.get('networks', []) - - if not network_list: - msg = 'No networks found.' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) - else: - for net in network_list: - name = net.get('name') - cidr = net.get('cidr') - routes = net.get('routes', []) - - cidr_range = IPNetwork(cidr) - network_dict[name] = cidr_range - - if routes: - for r in routes: - gateway = r.get('gateway') - - if not gateway: - msg = 'No gateway found for route %s.' % routes - 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')) - if not baremetal_nodes_list: - msg = 'No baremetal_nodes found.' - 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', []) - - for ip_address in addressing_list: - ip_address_network_name = ip_address.get('network') - address = ip_address.get('address') - ip_type = ip_address.get('type') - - if ip_type is not 'dhcp': - 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')) - 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')) - if not message_list: - msg = 'IP Locality Success' - message_list.append( - TaskStatusMessage( - msg=msg, error=False, ctx_type='NA', ctx='NA')) - return message_list - rule_set = [ - Validator.rational_network_bond, - Validator.network_trunking_rational, - Validator.storage_partitioning, - Validator.unique_network_check, - Validator.mtu_rational, - Validator.storage_sizing, - Validator.ip_locality_check, - Validator.no_duplicate_IPs_check, - Validator.boot_storage_rational, - Validator.valid_platform_selection, + BootStorageRational(), + IpLocalityCheck(), + MtuRational(), + NetworkTrunkingRational(), + NoDuplicateIpsCheck(), + PlatformSelection(), + RationalNetworkBond(), + StoragePartitioning(), + StorageSizing(), + UniqueNetworkCheck(), ] diff --git a/drydock_provisioner/orchestrator/validations/validators.py b/drydock_provisioner/orchestrator/validations/validators.py new file mode 100644 index 00000000..f7506dea --- /dev/null +++ b/drydock_provisioner/orchestrator/validations/validators.py @@ -0,0 +1,40 @@ +# 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. +"""Business Logic Validation""" + +class Validators: + def __init__(self, name, code): + self.name = name + self.code = code + + def report_results(self, results): + # https://github.com/att-comdev/ucp-integration/blob/master/docs/source/api-conventions.rst#output-structure + message_list = [] + for result in results: + rd = result.to_dict() + if isinstance(rd, dict) and rd['error']: + item = { + "message": rd['message'], + "error": True, + "name": self.name, + "documents": [], + "level": "Error", + "diagnostic": "Context Type = %s, Context = %s" % (rd['context_type'], rd['context']), + "kind": "ValidationMessage" + } + message_list.append(item) + return results, message_list + + def execute(site_design, orchestrator=None): + pass diff --git a/tests/unit/test_validation_rule_boot_storage.py b/tests/unit/test_validation_rule_boot_storage.py index 92d8b89e..956558ca 100644 --- a/tests/unit/test_validation_rule_boot_storage.py +++ b/tests/unit/test_validation_rule_boot_storage.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational class TestRationalBootStorage(object): @@ -31,12 +31,13 @@ class TestRationalBootStorage(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.boot_storage_rational(site_design) - msg = message_list[0].to_dict() + validator = BootStorageRational() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'Boot Storage' assert msg.get('error') is False - assert len(message_list) == 1 + assert len(results) == 1 def test_invalid_boot_storage_small(self, deckhand_ingester, drydock_state, input_files, mock_get_build_data): @@ -49,17 +50,18 @@ class TestRationalBootStorage(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.boot_storage_rational(site_design) + validator = BootStorageRational() + results, message_list = validator.execute(site_design) regex = re.compile( 'Boot Storage Error: .+ volume must be > .+GB on BaremetalNode .+') - for msg in message_list: + for msg in results: msg = msg.to_dict() assert regex.match(msg.get('message')) is not None assert msg.get('error') - assert len(message_list) == 4 + assert len(results) == 4 def test_invalid_boot_storage_root_not_set(self, deckhand_ingester, drydock_state, input_files): @@ -72,15 +74,16 @@ class TestRationalBootStorage(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.boot_storage_rational(site_design) + validator = BootStorageRational() + results, message_list = validator.execute(site_design) regex = re.compile( 'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode .+' ) - for msg in message_list: + for msg in results: msg = msg.to_dict() assert regex.match(msg.get('message')) is not None assert msg.get('error') - assert len(message_list) == 2 + assert len(results) == 2 diff --git a/tests/unit/test_validation_rule_ip_locality.py b/tests/unit/test_validation_rule_ip_locality.py index eecfbbd2..472eae6f 100644 --- a/tests/unit/test_validation_rule_ip_locality.py +++ b/tests/unit/test_validation_rule_ip_locality.py @@ -14,7 +14,7 @@ import re -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck from drydock_provisioner.orchestrator.orchestrator import Orchestrator @@ -28,8 +28,9 @@ class TestIPLocality(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.ip_locality_check(site_design) - msg = message_list[0].to_dict() + validator = IpLocalityCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'IP Locality Success' assert msg.get('error') is False @@ -44,8 +45,9 @@ class TestIPLocality(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.ip_locality_check(site_design) - msg = message_list[0].to_dict() + validator = IpLocalityCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'No networks found.' assert msg.get('error') is False @@ -60,8 +62,9 @@ class TestIPLocality(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.ip_locality_check(site_design) - msg = message_list[0].to_dict() + validator = IpLocalityCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert 'No gateway found' in msg.get('message') assert msg.get('error') is True @@ -76,8 +79,9 @@ class TestIPLocality(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.ip_locality_check(site_design) - msg = message_list[0].to_dict() + validator = IpLocalityCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'No baremetal_nodes found.' assert msg.get('error') is False @@ -92,7 +96,8 @@ class TestIPLocality(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.ip_locality_check(site_design) + validator = IpLocalityCheck() + results, message_list = validator.execute(site_design) regex = re.compile( 'IP Locality Error: The gateway IP Address .+ is not within the defined CIDR: .+ of .+' @@ -102,8 +107,8 @@ class TestIPLocality(object): 'IP Locality Error: The IP Address .+ is not within the defined CIDR: .+ of .+ .' ) - assert len(message_list) == 3 - for msg in message_list: + assert len(results) == 3 + for msg in results: msg = msg.to_dict() assert msg.get('error') assert (regex.match(msg.get('message')) is not None diff --git a/tests/unit/test_validation_rule_mtu_rational.py b/tests/unit/test_validation_rule_mtu_rational.py index 790a2e67..98324b1c 100644 --- a/tests/unit/test_validation_rule_mtu_rational.py +++ b/tests/unit/test_validation_rule_mtu_rational.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.mtu_rational import MtuRational class TestMtu(object): @@ -30,12 +30,13 @@ class TestMtu(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.mtu_rational(site_design) - msg = message_list[0].to_dict() + validator = MtuRational() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'Mtu' assert msg.get('error') is False - assert len(message_list) == 1 + assert len(results) == 1 def test_invalid_mtu(self, mocker, deckhand_ingester, drydock_state, input_files): @@ -48,7 +49,8 @@ class TestMtu(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.mtu_rational(site_design) + validator = MtuRational() + results, message_list = validator.execute(site_design) regex = re.compile( 'Mtu Error: Mtu must be between 1400 and 64000; on Network .+') @@ -56,11 +58,11 @@ class TestMtu(object): 'Mtu Error: Mtu must be <= the parent Network Link; for Network .+' ) - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') assert regex.match( msg.get('message')) is not None or regex_1.match( msg.get('message')) is not None - assert len(message_list) == 4 + assert len(results) == 4 diff --git a/tests/unit/test_validation_rule_network_bond.py b/tests/unit/test_validation_rule_network_bond.py index 8b7d2559..a85f44d2 100644 --- a/tests/unit/test_validation_rule_network_bond.py +++ b/tests/unit/test_validation_rule_network_bond.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.rational_network_bond import RationalNetworkBond class TestRationalNetworkLinkBond(object): @@ -30,12 +30,13 @@ class TestRationalNetworkLinkBond(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.rational_network_bond(site_design) - msg = message_list[0].to_dict() + validator = RationalNetworkBond() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'Network Link Bonding' assert msg.get('error') is False - assert len(message_list) == 1 + assert len(results) == 1 def test_invalid_rational_network_bond(self, mocker, deckhand_ingester, drydock_state, input_files): @@ -48,7 +49,8 @@ class TestRationalNetworkLinkBond(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.rational_network_bond(site_design) + validator = RationalNetworkBond() + results, message_list = validator.execute(site_design) regex = re.compile( 'Network Link Bonding Error: Down delay is less than mon rate on BaremetalNode .+' @@ -57,11 +59,11 @@ class TestRationalNetworkLinkBond(object): 'Network Link Bonding Error: Up delay is less than mon rate on BaremetalNode .+' ) - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') is True assert regex.match( msg.get('message')) is not None or regex_1.match( msg.get('message')) is not None - assert len(message_list) == 2 + assert len(results) == 2 diff --git a/tests/unit/test_validation_rule_network_trunking.py b/tests/unit/test_validation_rule_network_trunking.py index 2680d245..9c928aa3 100644 --- a/tests/unit/test_validation_rule_network_trunking.py +++ b/tests/unit/test_validation_rule_network_trunking.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.network_trunking_rational import NetworkTrunkingRational class TestRationalNetworkTrunking(object): @@ -30,8 +30,9 @@ class TestRationalNetworkTrunking(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.network_trunking_rational(site_design) - msg = message_list[0].to_dict() + validator = NetworkTrunkingRational() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'Rational Network Trunking' assert msg.get('error') is False @@ -46,7 +47,8 @@ class TestRationalNetworkTrunking(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.network_trunking_rational(site_design) + validator = NetworkTrunkingRational() + results, message_list = validator.execute(site_design) regex = re.compile( 'Rational Network Trunking Error: Trunking mode is disabled, a trunking' @@ -56,11 +58,11 @@ class TestRationalNetworkTrunking(object): 'Rational Network Trunking Error: If there is more than 1 allowed network,' 'trunking mode must be enabled; on NetworkLink .+') - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') assert regex.match( msg.get('message')) is not None or regex_1.match( msg.get('message')) is not None - assert len(message_list) == 2 + assert len(results) == 2 diff --git a/tests/unit/test_validation_rule_no_duplicate_IPs.py b/tests/unit/test_validation_rule_no_duplicate_IPs.py index 20adfed8..d2dd0621 100644 --- a/tests/unit/test_validation_rule_no_duplicate_IPs.py +++ b/tests/unit/test_validation_rule_no_duplicate_IPs.py @@ -14,7 +14,7 @@ import re -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.no_duplicate_ips_check import NoDuplicateIpsCheck from drydock_provisioner.orchestrator.orchestrator import Orchestrator @@ -29,8 +29,9 @@ class TestDuplicateIPs(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.no_duplicate_IPs_check(site_design) - msg = message_list[0].to_dict() + validator = NoDuplicateIpsCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'No Duplicate IP Addresses.' assert msg.get('error') is False @@ -45,8 +46,9 @@ class TestDuplicateIPs(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.no_duplicate_IPs_check(site_design) - msg = message_list[0].to_dict() + validator = NoDuplicateIpsCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'No BaremetalNodes Found.' assert msg.get('error') is False @@ -61,8 +63,9 @@ class TestDuplicateIPs(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.no_duplicate_IPs_check(site_design) - msg = message_list[0].to_dict() + validator = NoDuplicateIpsCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'No BaremetalNodes Found.' assert msg.get('error') is False @@ -77,12 +80,13 @@ class TestDuplicateIPs(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.no_duplicate_IPs_check(site_design) + validator = NoDuplicateIpsCheck() + results, message_list = validator.execute(site_design) regex = re.compile( 'Error! Duplicate IP Address Found: .+ is in use by both .+ and .+.' ) - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') is True assert regex.match(msg.get('message')) is not None diff --git a/tests/unit/test_validation_rule_storage_partitioning.py b/tests/unit/test_validation_rule_storage_partitioning.py index a90c91b1..b8f77a35 100644 --- a/tests/unit/test_validation_rule_storage_partitioning.py +++ b/tests/unit/test_validation_rule_storage_partitioning.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.storage_partititioning import StoragePartitioning class TestRationalNetworkTrunking(object): @@ -30,10 +30,11 @@ class TestRationalNetworkTrunking(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.storage_partitioning(site_design) - msg = message_list[0].to_dict() + validator = StoragePartitioning() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() - assert len(message_list) == 1 + assert len(results) == 1 assert msg.get('message') == 'Storage Partitioning' assert msg.get('error') is False @@ -48,10 +49,11 @@ class TestRationalNetworkTrunking(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.storage_partitioning(site_design) - msg = message_list[0].to_dict() + validator = StoragePartitioning() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() - assert len(message_list) == 1 + assert len(results) == 1 assert msg.get('message') == 'Storage Partitioning' assert msg.get('error') is False @@ -67,15 +69,16 @@ class TestRationalNetworkTrunking(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.storage_partitioning(site_design) + validator = StoragePartitioning() + results, message_list = validator.execute(site_design) regex = re.compile( 'Storage Partitioning Error: A volume group must be assigned to a storage device or ' 'partition; volume group .+ on BaremetalNode .+') - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') assert regex.match(msg.get('message')) is not None - assert len(message_list) == 2 + assert len(results) == 2 diff --git a/tests/unit/test_validation_rule_storage_sizing.py b/tests/unit/test_validation_rule_storage_sizing.py index ac6d0c0a..267ad75f 100644 --- a/tests/unit/test_validation_rule_storage_sizing.py +++ b/tests/unit/test_validation_rule_storage_sizing.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing class TestStorageSizing(object): @@ -31,10 +31,11 @@ class TestStorageSizing(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.storage_sizing(site_design) - msg = message_list[0].to_dict() + validator = StorageSizing() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() - assert len(message_list) == 1 + assert len(results) == 1 assert msg.get('message') == 'Storage Sizing' assert msg.get('error') is False @@ -49,7 +50,8 @@ class TestStorageSizing(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.storage_sizing(site_design) + validator = StorageSizing() + results, message_list = validator.execute(site_design) regex = re.compile( 'Storage Sizing Error: Storage .+ size is < 0 on Baremetal Node .+' @@ -58,8 +60,8 @@ class TestStorageSizing(object): 'Storage Sizing Error: Storage .+ size is greater than 99 on Baremetal Node .+' ) - assert len(message_list) == 6 - for msg in message_list: + assert len(results) == 6 + for msg in results: msg = msg.to_dict() assert regex.match( msg.get('message')) is not None or regex_1.match( diff --git a/tests/unit/test_validation_rule_unique_network.py b/tests/unit/test_validation_rule_unique_network.py index f2200d1d..fe45c59d 100644 --- a/tests/unit/test_validation_rule_unique_network.py +++ b/tests/unit/test_validation_rule_unique_network.py @@ -16,7 +16,7 @@ import re from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck class TestUniqueNetwork(object): @@ -31,12 +31,13 @@ class TestUniqueNetwork(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.unique_network_check(site_design) - msg = message_list[0].to_dict() + validator = UniqueNetworkCheck() + results, message_list = validator.execute(site_design) + msg = results[0].to_dict() assert msg.get('message') == 'Unique Network' assert msg.get('error') is False - assert len(message_list) == 1 + assert len(results) == 1 def test_invalid_unique_network(self, mocker, deckhand_ingester, drydock_state, input_files): @@ -49,15 +50,16 @@ class TestUniqueNetwork(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.unique_network_check(site_design) + validator = UniqueNetworkCheck() + results, message_list = validator.execute(site_design) regex = re.compile( 'Unique Network Error: Allowed network .+ duplicated on NetworkLink .+ and NetworkLink .+' ) - for msg in message_list: + for msg in results: msg = msg.to_dict() assert msg.get('error') assert regex.match(msg.get('message')) is not None - assert len(message_list) == 1 + assert len(results) == 1 diff --git a/tests/unit/test_validation_rule_valid_platform.py b/tests/unit/test_validation_rule_valid_platform.py index 3c3091fc..62b4aa1a 100644 --- a/tests/unit/test_validation_rule_valid_platform.py +++ b/tests/unit/test_validation_rule_valid_platform.py @@ -16,7 +16,7 @@ import drydock_provisioner.config as config from drydock_provisioner.orchestrator.orchestrator import Orchestrator -from drydock_provisioner.orchestrator.validations.validator import Validator +from drydock_provisioner.orchestrator.validations.platform_selection import PlatformSelection class TestValidPlatform(object): @@ -41,16 +41,17 @@ class TestValidPlatform(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.valid_platform_selection( + validator = PlatformSelection() + results, message_list = validator.execute( site_design, orchestrator=orch) - for m in message_list: - print(m.to_dict()) + for r in results: + print(r.to_dict()) - msg = message_list[0].to_dict() + msg = results[0].to_dict() assert 'all nodes have valid' in msg.get('message') assert msg.get('error') is False - assert len(message_list) == 1 + assert len(results) == 1 def test_invalid_platform(self, mocker, deckhand_ingester, drydock_state, input_files): @@ -73,13 +74,14 @@ class TestValidPlatform(object): status, site_design = Orchestrator.get_effective_site(orch, design_ref) - message_list = Validator.valid_platform_selection( + validator = PlatformSelection() + results, message_list = validator.execute( site_design, orchestrator=orch) - for m in message_list: - print(m.to_dict()) + for r in results: + print(r.to_dict()) - msg = message_list[0].to_dict() + msg = results[0].to_dict() assert 'invalid kernel lts' in msg.get('message') assert msg.get('error') - assert len(message_list) == 1 + assert len(results) == 1