Refactoring validations into individual classes
- Updated validations and tests for new classes. Change-Id: I66cfd8cc5857a63d6764804b71c00384fd3d1bcf
This commit is contained in:
parent
47a27981ec
commit
6c83117f2c
@ -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.
|
||||
|
||||
|
@ -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)
|
@ -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)
|
78
drydock_provisioner/orchestrator/validations/mtu_rational.py
Normal file
78
drydock_provisioner/orchestrator/validations/mtu_rational.py
Normal file
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
@ -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)
|
103
drydock_provisioner/orchestrator/validations/storage_sizing.py
Normal file
103
drydock_provisioner/orchestrator/validations/storage_sizing.py
Normal file
@ -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)
|
@ -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)
|
@ -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(),
|
||||
]
|
||||
|
40
drydock_provisioner/orchestrator/validations/validators.py
Normal file
40
drydock_provisioner/orchestrator/validations/validators.py
Normal file
@ -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
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user