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:
parent
8c2540c3f2
commit
c27c8e6c9b
@ -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)),
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
@ -53,6 +53,7 @@ class InvalidDesignReference(DesignError):
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class UnsupportedDocumentType(DesignError):
|
||||
"""
|
||||
**Message:** *Site definition document in an unknown format*.
|
||||
|
@ -13,6 +13,7 @@
|
||||
# limitations under the License.
|
||||
"""Models for representing health check status."""
|
||||
|
||||
|
||||
class HealthCheck(object):
|
||||
"""Specialized status for health check status."""
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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')
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
])
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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'))
|
||||
])
|
||||
|
@ -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
|
||||
|
@ -93,6 +93,7 @@ data:
|
||||
default_network: mgmt
|
||||
allowed_networks:
|
||||
- public
|
||||
- private
|
||||
- mgmt
|
||||
---
|
||||
schema: 'drydock/Rack/v1'
|
||||
|
@ -308,6 +308,10 @@ data:
|
||||
networks:
|
||||
- mgmt
|
||||
- private
|
||||
############
|
||||
### This fails here
|
||||
############
|
||||
- foo
|
||||
metadata:
|
||||
tags:
|
||||
- 'test'
|
||||
|
@ -93,6 +93,7 @@ data:
|
||||
default_network: mgmt
|
||||
allowed_networks:
|
||||
- public
|
||||
- private
|
||||
- mgmt
|
||||
---
|
||||
schema: 'drydock/Rack/v1'
|
||||
|
Loading…
x
Reference in New Issue
Block a user