Use new validator model for validation

- Update all validator rules to publish messages using
  new format
- Update unit tests to follow new validation framework
- Add validation for no tagged VLANs on non-trunked links
- Add validation for node interfaces attaching to networks
  not allowed on the interface link
- Unit test change to work around an issue where coverage testing
  and unit testing show different results

- Update tox to skip E126 to match YAPF formatting

Change-Id: Ifef21112896b88c2bd2b361630e041806ebb6663
This commit is contained in:
Scott Hussey 2018-03-06 13:41:13 -06:00
parent 8c2540c3f2
commit c27c8e6c9b
40 changed files with 649 additions and 713 deletions

View File

@ -56,10 +56,12 @@ def start_api(state_manager=None, ingester=None, orchestrator=None):
# v1.0 of Drydock API
v1_0_routes = [
# API for managing orchestrator tasks
('/health', HealthResource(state_manager=state_manager,
orchestrator=orchestrator)),
('/health/extended', HealthExtendedResource(state_manager=state_manager,
orchestrator=orchestrator)),
('/health',
HealthResource(
state_manager=state_manager, orchestrator=orchestrator)),
('/health/extended',
HealthExtendedResource(
state_manager=state_manager, orchestrator=orchestrator)),
('/tasks',
TasksResource(state_manager=state_manager,
orchestrator=orchestrator)),

View File

@ -27,6 +27,7 @@ class HealthResource(StatefulResource):
"""
Returns empty response body that Drydock is healthy
"""
def __init__(self, orchestrator=None, **kwargs):
"""Object initializer.
@ -39,15 +40,18 @@ class HealthResource(StatefulResource):
"""
Returns 204 on healthy, otherwise 503, without response body.
"""
hc = HealthCheckCombined(state_manager=self.state_manager,
orchestrator=self.orchestrator,
extended=False)
hc = HealthCheckCombined(
state_manager=self.state_manager,
orchestrator=self.orchestrator,
extended=False)
return hc.get(req, resp)
class HealthExtendedResource(StatefulResource):
"""
Returns response body that Drydock is healthy
"""
def __init__(self, orchestrator=None, **kwargs):
"""Object initializer.
@ -61,15 +65,18 @@ class HealthExtendedResource(StatefulResource):
"""
Returns 200 on success, otherwise 503, with a response body.
"""
hc = HealthCheckCombined(state_manager=self.state_manager,
orchestrator=self.orchestrator,
extended=True)
hc = HealthCheckCombined(
state_manager=self.state_manager,
orchestrator=self.orchestrator,
extended=True)
return hc.get(req, resp)
class HealthCheckCombined(object):
"""
Returns Drydock health check status.
"""
def __init__(self, state_manager=None, orchestrator=None, extended=False):
"""Object initializer.
@ -90,18 +97,22 @@ class HealthCheckCombined(object):
if now is None:
raise Exception('None received from database for now()')
except Exception as ex:
hcm = HealthCheckMessage(msg='Unable to connect to database', error=True)
hcm = HealthCheckMessage(
msg='Unable to connect to database', error=True)
health_check.add_detail_msg(msg=hcm)
# Test MaaS connection
try:
task = self.orchestrator.create_task(action=hd_fields.OrchestratorAction.Noop)
maas_validation = ValidateNodeServices(task, self.orchestrator, self.state_manager)
task = self.orchestrator.create_task(
action=hd_fields.OrchestratorAction.Noop)
maas_validation = ValidateNodeServices(task, self.orchestrator,
self.state_manager)
maas_validation.start()
if maas_validation.task.get_status() == ActionResult.Failure:
raise Exception('MaaS task failure')
except Exception as ex:
hcm = HealthCheckMessage(msg='Unable to connect to MaaS', error=True)
hcm = HealthCheckMessage(
msg='Unable to connect to MaaS', error=True)
health_check.add_detail_msg(msg=hcm)
if self.extended:

View File

@ -17,6 +17,8 @@ import json
from drydock_provisioner import policy
from drydock_provisioner.control.base import StatefulResource
from drydock_provisioner.objects import fields as hd_fields
import drydock_provisioner.error as errors
@ -36,21 +38,6 @@ class ValidationResource(StatefulResource):
@policy.ApiEnforcer('physical_provisioner:validate_site_design')
def on_post(self, req, resp):
# create resp message
resp_message = {
'kind': 'Status',
'apiVersion': 'v1.0',
'metaData': {},
'status': '',
'message': '',
'reason': 'Validation',
'details': {
'errorCount': 0,
'messageList': []
},
'code': '',
}
try:
json_data = self.req_json(req)
@ -68,29 +55,25 @@ class ValidationResource(StatefulResource):
self.error(req.context, err_message)
return self.return_error(resp, falcon.HTTP_400, err_message)
message, design_data = self.orchestrator.get_effective_site(
validation, design_data = self.orchestrator.get_effective_site(
design_ref)
resp_message['details']['errorCount'] = message.error_count
resp_message['details']['messageList'] = [
m.to_dict() for m in message.message_list
]
if message.error_count == 0:
resp_message['status'] = 'Success'
resp_message['message'] = 'Drydock Validations succeeded'
if validation.status == hd_fields.ValidationResult.Success:
resp_message = validation.to_dict()
resp_message['code'] = 200
resp.status = falcon.HTTP_200
resp.body = json.dumps(resp_message)
else:
resp_message['status'] = 'Failure'
resp_message['message'] = 'Drydock Validations failed'
resp_message = validation.to_dict()
resp_message['code'] = 400
resp.status = falcon.HTTP_400
resp.body = json.dumps(resp_message)
except errors.InvalidFormat as e:
err_message = str(e)
resp.status = falcon.HTTP_400
self.error(req.context, err_message)
self.return_error(resp, falcon.HTTP_400, err_message)
except Exception as ex:
err_message = str(ex)
self.error(req.context, err_message)
self.return_error(resp, falcon.HTTP_500, err_message)

View File

@ -53,6 +53,7 @@ class InvalidDesignReference(DesignError):
"""
pass
class UnsupportedDocumentType(DesignError):
"""
**Message:** *Site definition document in an unknown format*.

View File

@ -13,6 +13,7 @@
# limitations under the License.
"""Models for representing health check status."""
class HealthCheck(object):
"""Specialized status for health check status."""

View File

@ -60,7 +60,13 @@ class Validation(TaskStatus):
class ValidationMessage(TaskStatusMessage):
"""Message describing details of a validation."""
def __init__(self, msg, name, error=False, level=None, docs=None, diagnostic=None):
def __init__(self,
msg,
name,
error=False,
level=None,
docs=None,
diagnostic=None):
self.name = name
self.message = msg
self.error = error
@ -100,7 +106,8 @@ class DocumentReference(base.DrydockObject):
super().__init__(**kwargs)
if (self.doc_type == hd_fields.DocumentType.Deckhand):
if not all([self.doc_schema, self.doc_name]):
raise ValueError("doc_schema and doc_name required for Deckhand sources.")
raise ValueError(
"doc_schema and doc_name required for Deckhand sources.")
else:
raise errors.UnsupportedDocumentType(
"Document type %s not supported." % self.doc_type)

View File

@ -285,7 +285,7 @@ class Orchestrator(object):
val = Validator(self)
try:
status, site_design = self.get_described_site(design_ref)
if status.status == hd_fields.ActionResult.Success:
if status.status == hd_fields.ValidationResult.Success:
self.compute_model_inheritance(site_design)
self.compute_bootaction_targets(site_design)
self.render_route_domains(site_design)

View File

@ -15,98 +15,81 @@ 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)
super().__init__('Rational Boot Storage', 'DD1001')
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
baremetal_node_list = site_design.baremetal_nodes or []
for baremetal_node in baremetal_node_list:
storage_devices_list = baremetal_node.get('storage_devices', [])
storage_devices_list = baremetal_node.storage_devices or []
root_set = False
for storage_device in storage_devices_list:
partitions_list = storage_device.get('partitions', [])
partitions_list = storage_device.partitions or []
for host_partition in partitions_list:
if host_partition.get('name') == 'root':
size = host_partition.get('size')
if host_partition.name == 'root':
size = host_partition.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'))
'Root volume must be > 20GB on BaremetalNode '
'%s' % baremetal_node.name)
self.report_error(
msg, [baremetal_node.doc_ref],
"Configure a larger root volume")
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'))
'Root volume has an invalid size format on BaremetalNode'
'%s.' % baremetal_node.name)
self.report_error(msg, [
baremetal_node.doc_ref
], "Use a valid root volume storage specification."
)
# 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')
if root_set and host_partition.name == 'boot':
size = host_partition.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'))
'Boot volume must be > 1GB on BaremetalNode '
'%s' % baremetal_node.name)
self.report_error(
msg, [baremetal_node.doc_ref],
"Configure a larger boot volume.")
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'))
'Boot volume has an invalid size format on BaremetalNode '
'%s.' % baremetal_node.name)
self.report_error(msg, [
baremetal_node.doc_ref
], "Use a valid boot volume storage specification."
)
# 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'))
'Root volume has to be set and must be > 20GB on BaremetalNode '
'%s' % baremetal_node.name)
self.report_error(msg, [
baremetal_node.doc_ref
], "All nodes require a defined root volume at least 20GB in size."
)
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)
return

View File

@ -15,34 +15,31 @@ 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)
super().__init__('IP Locality Check', "DD2002")
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
baremetal_nodes_list = site_design.baremetal_nodes or []
network_list = site_design.networks or []
if not network_list:
msg = 'No networks found.'
message_list.append(
TaskStatusMessage(
msg=msg, error=False, ctx_type='NA', ctx='NA'))
self.report_warn(
msg, [],
'Site design likely incomplete without defined networks')
else:
for net in network_list:
name = net.get('name')
cidr = net.get('cidr')
routes = net.get('routes', [])
name = net.name
cidr = net.cidr
routes = net.routes or []
cidr_range = IPNetwork(cidr)
network_dict[name] = cidr_range
@ -53,68 +50,52 @@ class IpLocalityCheck(Validators):
if not gateway:
msg = 'No gateway found for route %s.' % routes
message_list.append(
TaskStatusMessage(
msg=msg,
error=True,
ctx_type='NA',
ctx='NA'))
self.report_error(
msg, [net.doc_ref],
diagnostic=
"Define a network-local gateway for the route."
)
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.'
'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'))
self.report_error(
msg, [net.doc_ref],
"Route gateways must reside on the local network. Check "
"gateway IP and CIDR netmask.")
if not baremetal_nodes_list:
msg = 'No baremetal_nodes found.'
message_list.append(
TaskStatusMessage(
msg=msg, error=False, ctx_type='NA', ctx='NA'))
self.report_warn(
msg, [],
"site design likely incomplete without defined networks.")
else:
for node in baremetal_nodes_list:
addressing_list = node.get('addressing', [])
addressing_list = node.addressing or []
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')
ip_address_network_name = ip_address.network
address = ip_address.address
ip_type = ip_address.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.' \
msg = '%s is not a valid network.' \
% (ip_address_network_name)
message_list.append(
TaskStatusMessage(
msg=msg,
error=True,
ctx_type='NA',
ctx='NA'))
self.report_error(msg, [
node.doc_ref
], "Define network or correct address definition.")
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 .'
'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'))
self.report_error(
msg, [node.doc_ref],
"Define a valid address for this network.")
return Validators.report_results(self, message_list)
return

View File

@ -13,66 +13,63 @@
# 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)
MIN_MTU_SIZE = 1280
MAX_MTU_SIZE = 65536
def execute(self, site_design, orchestrator=None):
def __init__(self):
super().__init__('MTU Rationality', 'DD2003')
def run_validation(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', [])
network_links = site_design.network_links or []
networks = site_design.networks or []
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'))
mtu = network_link.mtu
if mtu and (mtu < MtuRational.MIN_MTU_SIZE
or mtu > MtuRational.MAX_MTU_SIZE):
msg = ("MTU must be between %d and %d, value is %d" %
(MtuRational.MIN_MTU_SIZE, MtuRational.MAX_MTU_SIZE,
mtu))
self.report_error(
msg, [network_link.doc_ref],
"Define a valid MTU. Standard is 1500, Jumbo is 9100.")
# add assigned network to dict with parent mtu
assigned_network = network_link.get('native_network')
assigned_network = network_link.native_network
parent_mtu_check[assigned_network] = mtu
for network in networks:
network_mtu = network.get('mtu')
network_mtu = network.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'))
if network_mtu and (network_mtu < MtuRational.MIN_MTU_SIZE
or network_mtu > MtuRational.MAX_MTU_SIZE):
msg = ("MTU must be between %d and %d, value is %d" %
(MtuRational.MIN_MTU_SIZE, MtuRational.MAX_MTU_SIZE,
mtu))
self.report_error(
msg, [network.doc_ref],
"Define a valid MTU. Standard is 1500, Jumbo is 9100.")
name = network.get('name')
name = network.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'))
msg = 'MTU must be <= the parent Network Link; for Network %s' % (
network.name)
self.report_error(msg, [
network.doc_ref
], "Define a MTU less than or equal to that of the carrying network link."
)
if not message_list:
message_list.append(
TaskStatusMessage(
msg='Mtu', error=False, ctx_type='NA', ctx='NA'))
return Validators.report_results(self, message_list)
return

View File

@ -14,57 +14,49 @@
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)
super().__init__('Network Trunking Rationalty', "DD2004")
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
network_link_list = site_design.network_links or []
for network_link in network_link_list:
allowed_networks = network_link.get('allowed_networks', [])
allowed_networks = network_link.allowed_networks
# if allowed networks > 1 trunking must be enabled
if (len(allowed_networks) > 1 and network_link.get('trunk_mode') ==
if (len(allowed_networks) > 1 and network_link.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'))
msg = ('If there is more than 1 allowed network,'
'trunking mode must be enabled')
self.report_error(msg, [
network_link.doc_ref
], "Reduce the allowed network list to 1 or enable trunking on the link."
)
# 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):
if (network_link.trunk_mode ==
hd_fields.NetworkLinkTrunkingMode.Disabled
and network_link.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'))
msg = 'Trunking mode is disabled, a trunking default_network must be defined'
self.report_error(
msg, [network_link.doc_ref],
"Non-trunked links must have a native network defined.")
elif (network_link.trunk_mode ==
hd_fields.NetworkLinkTrunkingMode.Disabled
and network_link.native_network is not None):
network = site_design.get_network(network_link.native_network)
if network and network.vlan_id:
msg = "Network link native network has a defined VLAN tag."
self.report_error(msg, [
network.doc_ref, network_link.doc_ref
], "Tagged network not allowed on non-trunked network links."
)
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)
return

View File

@ -13,51 +13,40 @@
# 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)
super().__init__('Duplicated IP Check', "DD2005")
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
baremetal_nodes_list = site_design.baremetal_nodes or []
if not baremetal_nodes_list:
msg = 'No BaremetalNodes Found.'
message_list.append(
TaskStatusMessage(
msg=msg, error=False, ctx_type='NA', ctx='NA'))
self.report_warn(
msg, [],
"Site design unlikely complete with no defined baremetal nodes."
)
else:
for node in baremetal_nodes_list:
addressing_list = node.get('addressing', [])
addressing_list = node.addressing or []
for ip_address in addressing_list:
address = ip_address.get('address')
node_name = node.get('name')
address = ip_address.address
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'))
msg = ('Duplicate IP Address Found: %s ' % address)
self.report_error(
msg, [node.doc_ref, found_ips[address].doc_ref],
"Select unique IP addresses for each node.")
elif address is not None:
found_ips[address] = node_name
found_ips[address] = node
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)
return

View File

@ -13,32 +13,28 @@
# 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)
super().__init__('Platform Selection', 'DD3001')
def execute(self, site_design, orchestrator=None):
def run_validation(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)
msg = ("Platform Validation: No enabled node driver, image"
"and kernel selections not validated.")
self.report_warn(
msg, [],
"Cannot validate platform selection without accessing the node provisioner."
)
return
valid_images = node_driver.get_available_images()
@ -51,28 +47,14 @@ class PlatformSelection(Validators):
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'))
msg = "Platform Validation: invalid kernel %s" % (n.kernel)
self.report_error(msg, [n.doc_ref],
"Select a valid kernel from: %s" % ",".join(
valid_kernels[n.image]))
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'))
msg = "Platform Validation: invalid image %s" % (n.image)
self.report_error(
msg, [n.doc_ref],
"Select a valid image from: %s" % ",".join(valid_images))
return Validators.report_results(self, message_list)
return

View File

@ -13,13 +13,12 @@
# 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)
super().__init__('Network Bond Rationality', 'DD1006')
def execute(self, site_design, orchestrator=None):
def run_validation(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.
@ -28,82 +27,58 @@ class RationalNetworkBond(Validators):
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', [])
network_links = site_design.network_links or []
for network_link in network_links:
bonding_mode = network_link.get('bonding_mode', [])
bonding_mode = network_link.bonding_mode
if bonding_mode == 'disabled':
# check to make sure nothing else is specified
if any([
network_link.get(x) for x in [
getattr(network_link, 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'))
msg = 'If bonding mode is disabled no other bond option can be specified'
self.report_error(
msg, [network_link.doc_ref],
"Enable a bonding mode or remove the bond options.")
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')))
mon_rate = network_link.bonding_mon_rate
if network_link.bonding_up_delay < mon_rate:
msg = ('Up delay %d is less than mon rate %d' %
(network_link.bonding_up_delay, mon_rate))
self.report_error(msg, [
network_link.doc_ref
], "Link up delay must be equal or greater than the mon_rate"
)
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'))
if network_link.bonding_down_delay < mon_rate:
msg = ('Down delay %d is less than mon rate %d' %
(network_link.bonding_down_delay, mon_rate))
self.report_error(msg, [
network_link.doc_ref
], "Link down delay must be equal or greater than the mon_rate"
)
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'))
msg = ('Hash cannot be defined if bond mode is %s' %
(bonding_mode))
self.report_error(
msg, [network_link.doc_ref],
"Hash mode is only applicable to LACP (802.3ad)")
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')))
msg = ('Peer rate cannot be defined if bond mode is %s' %
(bonding_mode))
self.report_error(
msg, [network_link.doc_ref],
"Peer rate is only applicable to LACP (802.3ad)")
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)
return

View File

@ -13,90 +13,69 @@
# 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)
super().__init__('Storage Partitioning', "DD2002")
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
baremetal_nodes = site_design.baremetal_nodes or []
volume_group_check_list = []
for baremetal_node in baremetal_nodes:
storage_devices_list = baremetal_node.get('storage_devices', [])
storage_devices_list = baremetal_node.storage_devices or []
for storage_device in storage_devices_list:
partitions_list = storage_device.get('partitions')
volume_group = storage_device.get('volume_group')
partitions_list = storage_device.partitions or []
volume_group = storage_device.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'))
msg = (
'Either a volume group OR partitions must be defined for each storage '
'device.')
self.report_error(
msg, [baremetal_node.doc_ref],
"A storage device must be used for exactly one of a volume group "
"physical volume or carved into partitions.")
# 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')
for partition in partitions_list:
partition_volume_group = partition.volume_group
fstype = partition.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'))
# error if both are defined
if all([fstype, partition_volume_group]):
msg = ('Both a volume group AND file system cannot be '
'defined in a single partition')
self.report_error(
msg, [baremetal_node.doc_ref],
"A partition can be used for only one of a volume group "
"physical volume or formatted as a filesystem.")
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)
# 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', [])
all_volume_groups = baremetal_node.volume_groups or []
for volume_group in all_volume_groups:
if volume_group.get('name') not in volume_group_check_list:
if volume_group.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')))
'Volume group %s not assigned any physical volumes' %
(volume_group.name))
self.report_error(msg, [
baremetal_node.doc_ref
], "Each volume group should be assigned at least one storage device "
"or partition as a physical volume.")
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)
return

View File

@ -13,91 +13,70 @@
# 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)
super().__init__('Storage Sizing', 'DD2003')
def execute(self, site_design, orchestrator=None):
def run_validation(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', [])
baremetal_nodes = site_design.baremetal_nodes or []
for baremetal_node in baremetal_nodes:
storage_device_list = baremetal_node.get('storage_devices', [])
storage_device_list = baremetal_node.storage_devices or []
for storage_device in storage_device_list:
partition_list = storage_device.get('partitions', [])
partition_list = storage_device.partitions or []
partition_sum = 0
for partition in partition_list:
size = partition.get('size')
size = partition.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'))
'Storage partition %s on device %s size is < 0'
% (partition.name, storage_device.name))
self.report_error(
msg, [baremetal_node.doc_ref],
"Partition size must be a positive number.")
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'))
'Cumulative partition sizes on device %s is greater than 99%%.'
% (storage_device.name))
self.report_error(msg, [
baremetal_node.doc_ref
], "Percentage-based sizes must sum to less than 100%."
)
volume_groups = baremetal_node.get('volume_groups', [])
volume_groups = baremetal_node.volume_groups or []
volume_sum = 0
for volume_group in volume_groups:
logical_volume_list = volume_group.get(
'logical_volumes', [])
logical_volume_list = volume_group.logical_volumes or []
for logical_volume in logical_volume_list:
size = logical_volume.get('size')
size = logical_volume.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'))
msg = ('Logical Volume %s size is < 0 ' %
(logical_volume.name))
self.report_error(msg,
[baremetal_node.doc_ref], "")
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'))
msg = ('Cumulative logical volume size is greater '
'than 99% in volume group %s' %
(volume_group.name))
self.report_error(msg, [
baremetal_node.doc_ref
], "Percentage-based sizes must sum to less than 100%."
)
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)
return

View File

@ -13,39 +13,37 @@
# 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)
super().__init__('Allowed Network Check', 'DD1007')
def execute(self, site_design, orchestrator=None):
def run_validation(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 = {}
network_link_list = site_design.network_links or []
link_allowed_nets = {}
for network_link in network_link_list:
allowed_network_list = network_link.get('allowed_networks', [])
compare[network_link.get('name')] = allowed_network_list
allowed_network_list = network_link.allowed_networks
link_allowed_nets[network_link.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 in link_allowed_nets:
allowed_network_list_1 = link_allowed_nets[network_link_name]
for network_link_name_2 in compare:
for network_link_name_2 in link_allowed_nets:
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]
allowed_network_list_2 = link_allowed_nets[
network_link_name_2]
# creates a list of duplicated allowed networks
duplicated_names = [
i for i in allowed_network_list_1
@ -54,17 +52,36 @@ class UniqueNetworkCheck(Validators):
for name in duplicated_names:
msg = (
'Unique Network Error: Allowed network %s duplicated on NetworkLink %s and NetworkLink '
'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'))
self.report_error(
msg, [],
"Each network is only allowed to cross a single network link."
)
if not message_list:
message_list.append(
TaskStatusMessage(
msg='Unique Network', error=False, ctx_type='NA',
ctx='NA'))
node_list = site_design.baremetal_nodes or []
return Validators.report_results(self, message_list)
for n in node_list:
node_interfaces = n.interfaces or []
for i in node_interfaces:
nic_link = i.network_link
for nw in i.networks:
try:
if nw not in link_allowed_nets[nic_link]:
msg = (
"Interface %s attached to network %s not allowed on interface link"
% (i.get_name(), nw))
self.report_error(msg, [
n.doc_ref
], "Interfaces can only be attached to networks allowed on the network link "
"connected to the interface.")
except KeyError:
msg = (
"Interface %s connected to undefined network link %s."
% (i.get_name(), nic_link))
self.report_error(msg, [
n.doc_ref
], "Define the network link attached to this interface."
)
return

View File

@ -15,7 +15,7 @@
import drydock_provisioner.objects.fields as hd_fields
from drydock_provisioner.objects.task import TaskStatus
from drydock_provisioner.objects.validation import Validation
from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational
from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck
@ -28,6 +28,7 @@ from drydock_provisioner.orchestrator.validations.storage_partititioning import
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):
"""Create a validator with a reference to the orchestrator.
@ -36,7 +37,10 @@ class Validator():
"""
self.orchestrator = orchestrator
def validate_design(self, site_design, result_status=None, include_output=False):
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
@ -47,16 +51,14 @@ class Validator():
:param result_status: instance of objects.TaskStatus
"""
if result_status is None:
result_status = TaskStatus()
result_status = Validation()
validation_error = False
message_lists = []
for rule in rule_set:
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]
message_list = rule.execute(
site_design=site_design, orchestrator=self.orchestrator)
result_status.message_list.extend(message_list)
error_msg = [m for m in message_list if m.error]
result_status.error_count = result_status.error_count + len(
error_msg)
if len(error_msg) > 0:
@ -64,29 +66,12 @@ class Validator():
if validation_error:
result_status.set_status(hd_fields.ValidationResult.Failure)
result_status.message = "Site design failed validation."
result_status.reason = "See detail messages."
else:
result_status.set_status(hd_fields.ValidationResult.Success)
result_status.message = "Site design passed validation"
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

View File

@ -13,28 +13,58 @@
# limitations under the License.
"""Business Logic Validation"""
from drydock_provisioner import objects
from drydock_provisioner.objects import fields as hd_fields
class Validators:
def __init__(self, name, code):
def __init__(self, long_name, name):
self.name = name
self.code = code
self.long_name = long_name
self.reset_message_list()
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 report_msg(self, msg, docs, diagnostic, error, level):
"""Add a validation message to the result list.
def execute(site_design, orchestrator=None):
pass
:param msg: String - msg, will be prepended with validator long name
:param docs: List - List of document references related to this validation message
:param diagnostic: String - Diagnostic information for t/s this error
:param error: Bool - Whether this message indicates an error
:param level: String - More detailed of the severity level of this message
"""
fmt_msg = "%s: %s" % (self.long_name, msg)
msg_obj = objects.ValidationMessage(
fmt_msg,
self.name,
error=error,
level=level,
docs=docs,
diagnostic=diagnostic)
self.messages.append(msg_obj)
def report_error(self, msg, docs, diagnostic):
self.report_msg(msg, docs, diagnostic, True,
hd_fields.MessageLevels.ERROR)
def report_warn(self, msg, docs, diagnostic):
self.report_msg(msg, docs, diagnostic, False,
hd_fields.MessageLevels.WARN)
def report_info(self, msg, docs, diagnostic):
self.report_msg(msg, docs, diagnostic, False,
hd_fields.MessageLevels.INFO)
def error_count(self):
errors = [x for x in self.messages if x.error]
return len(errors)
def reset_message_list(self):
self.messages = []
def execute(self, site_design, orchestrator=None):
self.reset_message_list()
self.run_validation(site_design, orchestrator=orchestrator)
if self.error_count() == 0:
self.report_info("Validation successful.", [], "")
return self.messages

View File

@ -126,13 +126,12 @@ class DrydockPolicy(object):
'path': '/api/v1.0/designs/{design_id}/parts',
'method': 'POST'
}]),
policy.DocumentedRuleDefault(
'physical_provisioner:health_data', 'role:admin',
'et health status',
[{
'path': '/api/v1.0/health/extended',
'method': 'GET'
}])
policy.DocumentedRuleDefault('physical_provisioner:health_data',
'role:admin', 'et health status',
[{
'path': '/api/v1.0/health/extended',
'method': 'GET'
}])
]
# Validate Design Policy

View File

@ -684,7 +684,5 @@ class DrydockState(object):
return None
except Exception as ex:
self.logger.error(str(ex))
self.logger.error(
"Error querying for now()",
exc_info=True)
self.logger.error("Error querying for now()", exc_info=True)
return None

View File

@ -19,7 +19,8 @@ import falcon
def test_get_health(mocker, deckhand_orchestrator, drydock_state):
api = HealthResource(state_manager=drydock_state, orchestrator=deckhand_orchestrator)
api = HealthResource(
state_manager=drydock_state, orchestrator=deckhand_orchestrator)
# Configure mocked request and response
req = mocker.MagicMock(spec=falcon.Request)

View File

@ -20,13 +20,13 @@ import drydock_provisioner.objects.fields as hd_fields
class TestClass(object):
def test_task_complete(self, yaml_ingester, input_files, setup,
def test_task_complete(self, deckhand_ingester, input_files, setup,
blank_state, mock_get_build_data):
input_file = input_files.join("fullsite.yaml")
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
orchestrator = orch.Orchestrator(
state_manager=blank_state, ingester=yaml_ingester)
state_manager=blank_state, ingester=deckhand_ingester)
orch_task = orchestrator.create_task(
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
orch_task.set_status(hd_fields.TaskStatus.Queued)
@ -45,13 +45,13 @@ class TestClass(object):
orchestrator.stop_orchestrator()
orch_thread.join(10)
def test_task_termination(self, input_files, yaml_ingester, setup,
def test_task_termination(self, input_files, deckhand_ingester, setup,
blank_state):
input_file = input_files.join("fullsite.yaml")
input_file = input_files.join("deckhand_fullsite.yaml")
design_ref = "file://%s" % str(input_file)
orchestrator = orch.Orchestrator(
state_manager=blank_state, ingester=yaml_ingester)
state_manager=blank_state, ingester=deckhand_ingester)
orch_task = orchestrator.create_task(
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)

View File

@ -16,12 +16,15 @@ from falcon import testing
import pytest
import json
import logging
from drydock_provisioner import policy
from drydock_provisioner.control.api import start_api
import falcon
LOG = logging.getLogger(__name__)
class TestValidationApi(object):
def test_post_validation_resp(self, input_files, falcontest, drydock_state,
@ -46,6 +49,7 @@ class TestValidationApi(object):
result = falcontest.simulate_post(
url, headers=hdr, body=json.dumps(body))
LOG.debug(result.text)
assert result.status == falcon.HTTP_200
def test_href_error(self, input_files, falcontest):
@ -65,6 +69,7 @@ class TestValidationApi(object):
result = falcontest.simulate_post(
url, headers=hdr, body=json.dumps(body))
LOG.debug(result.text)
assert result.status == falcon.HTTP_400
def test_json_data_error(self, input_files, falcontest):
@ -80,6 +85,7 @@ class TestValidationApi(object):
result = falcontest.simulate_post(
url, headers=hdr, body=json.dumps(body))
LOG.debug(result.text)
assert result.status == falcon.HTTP_400
def test_invalid_post_resp(self, input_files, falcontest, drydock_state,

View File

@ -31,7 +31,8 @@ class TestClass(object):
assert len(design_data.host_profiles) == 2
assert len(design_data.baremetal_nodes) == 2
def test_ingest_deckhand_docref_exists(self, input_files, setup, deckhand_ingester):
def test_ingest_deckhand_docref_exists(self, input_files, setup,
deckhand_ingester):
"""Test that each processed document has a doc_ref."""
input_file = input_files.join('deckhand_fullsite.yaml')

View File

@ -33,7 +33,4 @@ class TestDesignValidator(object):
val = Validator(orch)
response = val.validate_design(site_design)
for msg in response.message_list:
assert msg.error is False
assert response.error_count == 0
assert response.status == hd_fields.ValidationResult.Success

View File

@ -14,15 +14,17 @@
"""Test Validation Rule Rational Boot Storage"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational
LOG = logging.getLogger(__name__)
class TestRationalBootStorage(object):
def test_boot_storage_rational(self, deckhand_ingester, drydock_state,
input_files, mock_get_build_data):
input_file = input_files.join("validation.yaml")
design_ref = "file://%s" % str(input_file)
@ -32,16 +34,14 @@ class TestRationalBootStorage(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = BootStorageRational()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'Boot Storage'
assert msg.get('error') is False
assert len(results) == 1
assert len(message_list) == 1
def test_invalid_boot_storage_small(self, deckhand_ingester, drydock_state,
input_files, mock_get_build_data):
input_file = input_files.join("invalid_boot_storage_small.yaml")
design_ref = "file://%s" % str(input_file)
@ -51,21 +51,21 @@ class TestRationalBootStorage(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = BootStorageRational()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Boot Storage Error: .+ volume must be > .+GB on BaremetalNode .+')
regex = re.compile('.+ volume must be > .+GB')
for msg in results:
for msg in message_list:
msg = msg.to_dict()
assert regex.match(msg.get('message')) is not None
LOG.debug(msg)
assert len(msg.get('documents')) > 0
assert regex.search(msg.get('message')) is not None
assert msg.get('error')
assert len(results) == 4
assert len(message_list) == 4
def test_invalid_boot_storage_root_not_set(self, deckhand_ingester,
drydock_state, input_files):
input_file = input_files.join("invalid_validation.yaml")
design_ref = "file://%s" % str(input_file)
@ -75,15 +75,15 @@ class TestRationalBootStorage(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = BootStorageRational()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design)
regex = re.compile(
'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode .+'
)
regex = re.compile('Root volume has to be set and must be > 20GB')
for msg in results:
for msg in message_list:
msg = msg.to_dict()
assert regex.match(msg.get('message')) is not None
LOG.debug(msg)
assert len(msg.get('documents')) > 0
assert regex.search(msg.get('message')) is not None
assert msg.get('error')
assert len(results) == 2
assert len(message_list) == 2

View File

@ -13,10 +13,13 @@
# limitations under the License.
import re
import logging
from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
LOG = logging.getLogger(__name__)
class TestIPLocality(object):
def test_ip_locality(self, input_files, drydock_state, deckhand_ingester):
@ -29,10 +32,9 @@ class TestIPLocality(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = IpLocalityCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'IP Locality Success'
assert msg.get('error') is False
def test_ip_locality_no_networks(self, input_files, drydock_state,
@ -46,10 +48,10 @@ class TestIPLocality(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = IpLocalityCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'No networks found.'
assert 'No networks found' in msg.get('message')
assert msg.get('error') is False
def test_ip_locality_no_gateway(self, input_files, drydock_state,
@ -63,8 +65,8 @@ class TestIPLocality(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = IpLocalityCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert 'No gateway found' in msg.get('message')
assert msg.get('error') is True
@ -80,10 +82,10 @@ class TestIPLocality(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = IpLocalityCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'No baremetal_nodes found.'
assert 'No baremetal_nodes found' in msg.get('message')
assert msg.get('error') is False
def test_invalid_ip_locality_invalid_network(
@ -97,20 +99,24 @@ class TestIPLocality(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = IpLocalityCheck()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'IP Locality Error: The gateway IP Address .+ is not within the defined CIDR: .+ of .+'
'The gateway IP Address .+ is not within the defined CIDR: .+ of .+'
)
regex_1 = re.compile('IP Locality Error: .+ is not a valid network.')
regex_1 = re.compile('.+ is not a valid network.')
regex_2 = re.compile(
'IP Locality Error: The IP Address .+ is not within the defined CIDR: .+ of .+ .'
)
'The IP Address .+ is not within the defined CIDR: .+ of .+ .')
assert len(results) == 3
for msg in results:
assert len(message_list) == 3
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
assert len(msg.get('documents')) > 0
assert msg.get('error')
assert (regex.match(msg.get('message')) is not None
or regex_1.match(msg.get('message')) is not None
or regex_2.match(msg.get('message')) is not None)
assert any([
regex.search(msg.get('message')),
regex_1.search(msg.get('message')),
regex_2.search(msg.get('message'))
])

View File

@ -14,14 +14,16 @@
"""Test Validation Rule Unique Network"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.mtu_rational import MtuRational
LOG = logging.getLogger(__name__)
class TestMtu(object):
def test_mtu(self, mocker, deckhand_ingester, drydock_state, input_files):
input_file = input_files.join("validation.yaml")
design_ref = "file://%s" % str(input_file)
@ -31,12 +33,12 @@ class TestMtu(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = MtuRational()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'Mtu'
assert 'MTU' in msg.get('message')
assert msg.get('error') is False
assert len(results) == 1
assert len(message_list) == 1
def test_invalid_mtu(self, mocker, deckhand_ingester, drydock_state,
input_files):
@ -50,19 +52,19 @@ class TestMtu(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = MtuRational()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Mtu Error: Mtu must be between 1400 and 64000; on Network .+')
regex_1 = re.compile(
'Mtu Error: Mtu must be <= the parent Network Link; for Network .+'
)
regex = re.compile('MTU must be between \d+ and \d+')
regex_1 = re.compile('MTU must be <= the parent Network Link')
for msg in results:
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
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(msg.get('documents')) > 0
assert any([
regex.search(msg.get('message')),
regex_1.search(msg.get('message'))
])
assert len(results) == 4
assert len(message_list) == 4

View File

@ -14,10 +14,13 @@
"""Test Validation Rule Rational Network Bond"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.rational_network_bond import RationalNetworkBond
LOG = logging.getLogger(__name__)
class TestRationalNetworkLinkBond(object):
def test_rational_network_bond(self, mocker, deckhand_ingester,
@ -31,12 +34,12 @@ class TestRationalNetworkLinkBond(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = RationalNetworkBond()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'Network Link Bonding'
assert msg.get('error') is False
assert len(results) == 1
assert len(message_list) == 1
assert len(msg.get('documents')) == 0
def test_invalid_rational_network_bond(self, mocker, deckhand_ingester,
drydock_state, input_files):
@ -50,20 +53,19 @@ class TestRationalNetworkLinkBond(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = RationalNetworkBond()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Network Link Bonding Error: Down delay is less than mon rate on BaremetalNode .+'
)
regex_1 = re.compile(
'Network Link Bonding Error: Up delay is less than mon rate on BaremetalNode .+'
)
regex = re.compile('Down delay \S+ is less than mon rate \S+')
regex_1 = re.compile('Up delay \S+ is less than mon rate \S+')
for msg in results:
for msg in message_list:
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
LOG.debug(msg)
assert msg.get('error')
assert len(msg.get('documents')) > 0
assert any([
regex.search(msg.get('message')),
regex_1.search(msg.get('message'))
])
assert len(results) == 2
assert len(message_list) == 2

View File

@ -14,10 +14,13 @@
"""Test Validation Rule Rational Network Trunking"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.network_trunking_rational import NetworkTrunkingRational
LOG = logging.getLogger(__name__)
class TestRationalNetworkTrunking(object):
def test_rational_network_trunking(self, deckhand_ingester, drydock_state,
@ -31,10 +34,9 @@ class TestRationalNetworkTrunking(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NetworkTrunkingRational()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'Rational Network Trunking'
assert msg.get('error') is False
def test_invalid_rational_network_trunking(self, deckhand_ingester,
@ -48,21 +50,27 @@ class TestRationalNetworkTrunking(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NetworkTrunkingRational()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Rational Network Trunking Error: Trunking mode is disabled, a trunking'
'default_network must be defined; on NetworkLink .+')
'Trunking mode is disabled, a trunking default_network must be defined'
)
regex_1 = re.compile(
'Rational Network Trunking Error: If there is more than 1 allowed network,'
'trunking mode must be enabled; on NetworkLink .+')
'If there is more than 1 allowed network,trunking mode must be enabled'
)
for msg in results:
regex_2 = re.compile('native network has a defined VLAN tag')
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
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(msg.get('documents')) > 0
assert any([
regex.search(msg.get('message')),
regex_1.search(msg.get('message')),
regex_2.search(msg.get('message'))
])
assert len(results) == 2
assert len(message_list) == 3

View File

@ -13,10 +13,13 @@
# limitations under the License.
import re
import logging
from drydock_provisioner.orchestrator.validations.no_duplicate_ips_check import NoDuplicateIpsCheck
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
LOG = logging.getLogger(__name__)
class TestDuplicateIPs(object):
def test_no_duplicate_IPs(self, input_files, drydock_state,
@ -30,10 +33,9 @@ class TestDuplicateIPs(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NoDuplicateIpsCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'No Duplicate IP Addresses.'
assert msg.get('error') is False
def test_no_duplicate_IPs_no_baremetal_node(
@ -47,10 +49,10 @@ class TestDuplicateIPs(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NoDuplicateIpsCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'No BaremetalNodes Found.'
assert 'No BaremetalNodes Found.' in msg.get('message')
assert msg.get('error') is False
def test_no_duplicate_IPs_no_addressing(self, input_files, drydock_state,
@ -64,10 +66,10 @@ class TestDuplicateIPs(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NoDuplicateIpsCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'No BaremetalNodes Found.'
assert 'No BaremetalNodes Found.' in msg.get('message')
assert msg.get('error') is False
def test_invalid_no_duplicate_IPs(self, input_files, drydock_state,
@ -81,12 +83,13 @@ class TestDuplicateIPs(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = NoDuplicateIpsCheck()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design)
regex = re.compile(
'Error! Duplicate IP Address Found: .+ is in use by both .+ and .+.'
)
for msg in results:
regex = re.compile('Duplicate IP Address Found: [0-9.]+')
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
assert len(msg.get('documents')) > 0
assert msg.get('error') is True
assert regex.match(msg.get('message')) is not None
assert regex.search(msg.get('message')) is not None

View File

@ -14,10 +14,13 @@
"""Test Validation Rule Storage Partitioning"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.storage_partititioning import StoragePartitioning
LOG = logging.getLogger(__name__)
class TestRationalNetworkTrunking(object):
def test_storage_partitioning(self, deckhand_ingester, drydock_state,
@ -31,11 +34,10 @@ class TestRationalNetworkTrunking(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = StoragePartitioning()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert len(results) == 1
assert msg.get('message') == 'Storage Partitioning'
assert len(message_list) == 1
assert msg.get('error') is False
def test_storage_partitioning_unassigned_partition(
@ -50,11 +52,10 @@ class TestRationalNetworkTrunking(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = StoragePartitioning()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert len(results) == 1
assert msg.get('message') == 'Storage Partitioning'
assert len(message_list) == 1
assert msg.get('error') is False
def test_invalid_storage_partitioning(self, deckhand_ingester,
@ -70,15 +71,15 @@ class TestRationalNetworkTrunking(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = StoragePartitioning()
results, message_list = validator.execute(site_design)
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 .+')
regex = re.compile('Volume group .+ not assigned any physical volumes')
for msg in results:
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
assert len(msg.get('documents')) > 0
assert msg.get('error')
assert regex.match(msg.get('message')) is not None
assert regex.search(msg.get('message')) is not None
assert len(results) == 2
assert len(message_list) == 2

View File

@ -14,10 +14,13 @@
"""Test Validation Rule Rational Network Trunking"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing
LOG = logging.getLogger(__name__)
class TestStorageSizing(object):
def test_storage_sizing(self, deckhand_ingester, drydock_state,
@ -32,11 +35,10 @@ class TestStorageSizing(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = StorageSizing()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert len(results) == 1
assert msg.get('message') == 'Storage Sizing'
assert len(message_list) == 1
assert msg.get('error') is False
def test_invalid_storage_sizing(self, deckhand_ingester, drydock_state,
@ -51,19 +53,17 @@ class TestStorageSizing(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = StorageSizing()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Storage Sizing Error: Storage .+ size is < 0 on Baremetal Node .+'
)
regex_1 = re.compile(
'Storage Sizing Error: Storage .+ size is greater than 99 on Baremetal Node .+'
)
'(Storage partition)|(Logical Volume) .+ size is < 0')
regex_1 = re.compile('greater than 99%')
assert len(results) == 6
for msg in results:
assert len(message_list) == 6
for msg in message_list:
msg = msg.to_dict()
assert regex.match(
msg.get('message')) is not None or regex_1.match(
LOG.debug(msg)
assert regex.search(
msg.get('message')) is not None or regex_1.search(
msg.get('message')) is not None
assert msg.get('error') is True

View File

@ -14,10 +14,13 @@
"""Test Validation Rule Unique Network"""
import re
import logging
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck
LOG = logging.getLogger(__name__)
class TestUniqueNetwork(object):
def test_unique_network(self, mocker, deckhand_ingester, drydock_state,
@ -32,12 +35,11 @@ class TestUniqueNetwork(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = UniqueNetworkCheck()
results, message_list = validator.execute(site_design)
msg = results[0].to_dict()
message_list = validator.execute(site_design, orchestrator=orch)
msg = message_list[0].to_dict()
assert msg.get('message') == 'Unique Network'
assert msg.get('error') is False
assert len(results) == 1
assert len(message_list) == 1
def test_invalid_unique_network(self, mocker, deckhand_ingester,
drydock_state, input_files):
@ -51,15 +53,22 @@ class TestUniqueNetwork(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = UniqueNetworkCheck()
results, message_list = validator.execute(site_design)
message_list = validator.execute(site_design, orchestrator=orch)
regex = re.compile(
'Unique Network Error: Allowed network .+ duplicated on NetworkLink .+ and NetworkLink .+'
'Allowed network .+ duplicated on NetworkLink .+ and NetworkLink .+'
)
regex_1 = re.compile(
'Interface \S+ attached to network \S+ not allowed on interface link'
)
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) == 1
for msg in message_list:
msg = msg.to_dict()
LOG.debug(msg)
assert msg.get('error')
assert any([
regex.search(msg.get('message')),
regex_1.search(msg.get('message'))
])

View File

@ -13,11 +13,15 @@
# limitations under the License.
"""Test Validation Rule Rational Boot Storage"""
import logging
import drydock_provisioner.config as config
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
from drydock_provisioner.orchestrator.validations.platform_selection import PlatformSelection
LOG = logging.getLogger(__name__)
class TestValidPlatform(object):
def test_valid_platform(self, mocker, deckhand_ingester, drydock_state,
@ -42,16 +46,15 @@ class TestValidPlatform(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = PlatformSelection()
results, message_list = validator.execute(
site_design, orchestrator=orch)
for r in results:
print(r.to_dict())
message_list = validator.execute(site_design, orchestrator=orch)
msg = results[0].to_dict()
for r in message_list:
LOG.debug(r.to_dict())
msg = message_list[0].to_dict()
assert 'all nodes have valid' in msg.get('message')
assert msg.get('error') is False
assert len(results) == 1
assert len(message_list) == 1
def test_invalid_platform(self, mocker, deckhand_ingester, drydock_state,
input_files):
@ -75,13 +78,13 @@ class TestValidPlatform(object):
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
validator = PlatformSelection()
results, message_list = validator.execute(
site_design, orchestrator=orch)
message_list = validator.execute(site_design, orchestrator=orch)
for r in results:
print(r.to_dict())
for r in message_list:
LOG.debug(r.to_dict())
msg = results[0].to_dict()
msg = message_list[0].to_dict()
assert 'invalid kernel lts' in msg.get('message')
assert msg.get('error')
assert len(results) == 1
assert len(msg.get('documents')) > 0
assert len(message_list) == 1

View File

@ -93,6 +93,7 @@ data:
default_network: mgmt
allowed_networks:
- public
- private
- mgmt
---
schema: 'drydock/Rack/v1'

View File

@ -308,6 +308,10 @@ data:
networks:
- mgmt
- private
############
### This fails here
############
- foo
metadata:
tags:
- 'test'

View File

@ -93,6 +93,7 @@ data:
default_network: mgmt
allowed_networks:
- public
- private
- mgmt
---
schema: 'drydock/Rack/v1'

View File

@ -69,7 +69,7 @@ commands = flake8 \
commands = bandit -r drydock_provisioner -n 5
[flake8]
ignore=E302,H306,H304,W503,E251
ignore=E302,H306,H304,W503,E251,E126
exclude= venv,.venv,.git,.idea,.tox,*.egg-info,*.eggs,bin,dist,./build/,alembic/
max-line-length=119