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 of Drydock API
|
||||||
v1_0_routes = [
|
v1_0_routes = [
|
||||||
# API for managing orchestrator tasks
|
# API for managing orchestrator tasks
|
||||||
('/health', HealthResource(state_manager=state_manager,
|
('/health',
|
||||||
orchestrator=orchestrator)),
|
HealthResource(
|
||||||
('/health/extended', HealthExtendedResource(state_manager=state_manager,
|
state_manager=state_manager, orchestrator=orchestrator)),
|
||||||
orchestrator=orchestrator)),
|
('/health/extended',
|
||||||
|
HealthExtendedResource(
|
||||||
|
state_manager=state_manager, orchestrator=orchestrator)),
|
||||||
('/tasks',
|
('/tasks',
|
||||||
TasksResource(state_manager=state_manager,
|
TasksResource(state_manager=state_manager,
|
||||||
orchestrator=orchestrator)),
|
orchestrator=orchestrator)),
|
||||||
|
|
|
@ -27,6 +27,7 @@ class HealthResource(StatefulResource):
|
||||||
"""
|
"""
|
||||||
Returns empty response body that Drydock is healthy
|
Returns empty response body that Drydock is healthy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, orchestrator=None, **kwargs):
|
def __init__(self, orchestrator=None, **kwargs):
|
||||||
"""Object initializer.
|
"""Object initializer.
|
||||||
|
|
||||||
|
@ -39,15 +40,18 @@ class HealthResource(StatefulResource):
|
||||||
"""
|
"""
|
||||||
Returns 204 on healthy, otherwise 503, without response body.
|
Returns 204 on healthy, otherwise 503, without response body.
|
||||||
"""
|
"""
|
||||||
hc = HealthCheckCombined(state_manager=self.state_manager,
|
hc = HealthCheckCombined(
|
||||||
orchestrator=self.orchestrator,
|
state_manager=self.state_manager,
|
||||||
extended=False)
|
orchestrator=self.orchestrator,
|
||||||
|
extended=False)
|
||||||
return hc.get(req, resp)
|
return hc.get(req, resp)
|
||||||
|
|
||||||
|
|
||||||
class HealthExtendedResource(StatefulResource):
|
class HealthExtendedResource(StatefulResource):
|
||||||
"""
|
"""
|
||||||
Returns response body that Drydock is healthy
|
Returns response body that Drydock is healthy
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, orchestrator=None, **kwargs):
|
def __init__(self, orchestrator=None, **kwargs):
|
||||||
"""Object initializer.
|
"""Object initializer.
|
||||||
|
|
||||||
|
@ -61,15 +65,18 @@ class HealthExtendedResource(StatefulResource):
|
||||||
"""
|
"""
|
||||||
Returns 200 on success, otherwise 503, with a response body.
|
Returns 200 on success, otherwise 503, with a response body.
|
||||||
"""
|
"""
|
||||||
hc = HealthCheckCombined(state_manager=self.state_manager,
|
hc = HealthCheckCombined(
|
||||||
orchestrator=self.orchestrator,
|
state_manager=self.state_manager,
|
||||||
extended=True)
|
orchestrator=self.orchestrator,
|
||||||
|
extended=True)
|
||||||
return hc.get(req, resp)
|
return hc.get(req, resp)
|
||||||
|
|
||||||
|
|
||||||
class HealthCheckCombined(object):
|
class HealthCheckCombined(object):
|
||||||
"""
|
"""
|
||||||
Returns Drydock health check status.
|
Returns Drydock health check status.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, state_manager=None, orchestrator=None, extended=False):
|
def __init__(self, state_manager=None, orchestrator=None, extended=False):
|
||||||
"""Object initializer.
|
"""Object initializer.
|
||||||
|
|
||||||
|
@ -90,18 +97,22 @@ class HealthCheckCombined(object):
|
||||||
if now is None:
|
if now is None:
|
||||||
raise Exception('None received from database for now()')
|
raise Exception('None received from database for now()')
|
||||||
except Exception as ex:
|
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)
|
health_check.add_detail_msg(msg=hcm)
|
||||||
|
|
||||||
# Test MaaS connection
|
# Test MaaS connection
|
||||||
try:
|
try:
|
||||||
task = self.orchestrator.create_task(action=hd_fields.OrchestratorAction.Noop)
|
task = self.orchestrator.create_task(
|
||||||
maas_validation = ValidateNodeServices(task, self.orchestrator, self.state_manager)
|
action=hd_fields.OrchestratorAction.Noop)
|
||||||
|
maas_validation = ValidateNodeServices(task, self.orchestrator,
|
||||||
|
self.state_manager)
|
||||||
maas_validation.start()
|
maas_validation.start()
|
||||||
if maas_validation.task.get_status() == ActionResult.Failure:
|
if maas_validation.task.get_status() == ActionResult.Failure:
|
||||||
raise Exception('MaaS task failure')
|
raise Exception('MaaS task failure')
|
||||||
except Exception as ex:
|
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)
|
health_check.add_detail_msg(msg=hcm)
|
||||||
|
|
||||||
if self.extended:
|
if self.extended:
|
||||||
|
|
|
@ -17,6 +17,8 @@ import json
|
||||||
|
|
||||||
from drydock_provisioner import policy
|
from drydock_provisioner import policy
|
||||||
from drydock_provisioner.control.base import StatefulResource
|
from drydock_provisioner.control.base import StatefulResource
|
||||||
|
from drydock_provisioner.objects import fields as hd_fields
|
||||||
|
|
||||||
import drydock_provisioner.error as errors
|
import drydock_provisioner.error as errors
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,21 +38,6 @@ class ValidationResource(StatefulResource):
|
||||||
@policy.ApiEnforcer('physical_provisioner:validate_site_design')
|
@policy.ApiEnforcer('physical_provisioner:validate_site_design')
|
||||||
def on_post(self, req, resp):
|
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:
|
try:
|
||||||
json_data = self.req_json(req)
|
json_data = self.req_json(req)
|
||||||
|
|
||||||
|
@ -68,29 +55,25 @@ class ValidationResource(StatefulResource):
|
||||||
self.error(req.context, err_message)
|
self.error(req.context, err_message)
|
||||||
return self.return_error(resp, falcon.HTTP_400, 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)
|
design_ref)
|
||||||
|
|
||||||
resp_message['details']['errorCount'] = message.error_count
|
if validation.status == hd_fields.ValidationResult.Success:
|
||||||
resp_message['details']['messageList'] = [
|
resp_message = validation.to_dict()
|
||||||
m.to_dict() for m in message.message_list
|
|
||||||
]
|
|
||||||
|
|
||||||
if message.error_count == 0:
|
|
||||||
resp_message['status'] = 'Success'
|
|
||||||
resp_message['message'] = 'Drydock Validations succeeded'
|
|
||||||
resp_message['code'] = 200
|
resp_message['code'] = 200
|
||||||
resp.status = falcon.HTTP_200
|
resp.status = falcon.HTTP_200
|
||||||
resp.body = json.dumps(resp_message)
|
resp.body = json.dumps(resp_message)
|
||||||
else:
|
else:
|
||||||
resp_message['status'] = 'Failure'
|
resp_message = validation.to_dict()
|
||||||
resp_message['message'] = 'Drydock Validations failed'
|
|
||||||
resp_message['code'] = 400
|
resp_message['code'] = 400
|
||||||
resp.status = falcon.HTTP_400
|
resp.status = falcon.HTTP_400
|
||||||
resp.body = json.dumps(resp_message)
|
resp.body = json.dumps(resp_message)
|
||||||
|
|
||||||
except errors.InvalidFormat as e:
|
except errors.InvalidFormat as e:
|
||||||
err_message = str(e)
|
err_message = str(e)
|
||||||
resp.status = falcon.HTTP_400
|
|
||||||
self.error(req.context, err_message)
|
self.error(req.context, err_message)
|
||||||
self.return_error(resp, falcon.HTTP_400, 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
|
pass
|
||||||
|
|
||||||
|
|
||||||
class UnsupportedDocumentType(DesignError):
|
class UnsupportedDocumentType(DesignError):
|
||||||
"""
|
"""
|
||||||
**Message:** *Site definition document in an unknown format*.
|
**Message:** *Site definition document in an unknown format*.
|
||||||
|
|
|
@ -13,6 +13,7 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""Models for representing health check status."""
|
"""Models for representing health check status."""
|
||||||
|
|
||||||
|
|
||||||
class HealthCheck(object):
|
class HealthCheck(object):
|
||||||
"""Specialized status for health check status."""
|
"""Specialized status for health check status."""
|
||||||
|
|
||||||
|
|
|
@ -60,7 +60,13 @@ class Validation(TaskStatus):
|
||||||
class ValidationMessage(TaskStatusMessage):
|
class ValidationMessage(TaskStatusMessage):
|
||||||
"""Message describing details of a validation."""
|
"""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.name = name
|
||||||
self.message = msg
|
self.message = msg
|
||||||
self.error = error
|
self.error = error
|
||||||
|
@ -100,7 +106,8 @@ class DocumentReference(base.DrydockObject):
|
||||||
super().__init__(**kwargs)
|
super().__init__(**kwargs)
|
||||||
if (self.doc_type == hd_fields.DocumentType.Deckhand):
|
if (self.doc_type == hd_fields.DocumentType.Deckhand):
|
||||||
if not all([self.doc_schema, self.doc_name]):
|
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:
|
else:
|
||||||
raise errors.UnsupportedDocumentType(
|
raise errors.UnsupportedDocumentType(
|
||||||
"Document type %s not supported." % self.doc_type)
|
"Document type %s not supported." % self.doc_type)
|
||||||
|
|
|
@ -285,7 +285,7 @@ class Orchestrator(object):
|
||||||
val = Validator(self)
|
val = Validator(self)
|
||||||
try:
|
try:
|
||||||
status, site_design = self.get_described_site(design_ref)
|
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_model_inheritance(site_design)
|
||||||
self.compute_bootaction_targets(site_design)
|
self.compute_bootaction_targets(site_design)
|
||||||
self.render_route_domains(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
|
import drydock_provisioner.error as errors
|
||||||
from drydock_provisioner.orchestrator.util import SimpleBytes
|
from drydock_provisioner.orchestrator.util import SimpleBytes
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class BootStorageRational(Validators):
|
class BootStorageRational(Validators):
|
||||||
def __init__(self):
|
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
|
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')
|
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:
|
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
|
root_set = False
|
||||||
|
|
||||||
for storage_device in storage_devices_list:
|
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:
|
for host_partition in partitions_list:
|
||||||
if host_partition.get('name') == 'root':
|
if host_partition.name == 'root':
|
||||||
size = host_partition.get('size')
|
size = host_partition.size
|
||||||
try:
|
try:
|
||||||
cal_size = SimpleBytes.calculate_bytes(size)
|
cal_size = SimpleBytes.calculate_bytes(size)
|
||||||
root_set = True
|
root_set = True
|
||||||
# check if size < 20GB
|
# check if size < 20GB
|
||||||
if cal_size < 20 * BYTES_IN_GB:
|
if cal_size < 20 * BYTES_IN_GB:
|
||||||
msg = (
|
msg = (
|
||||||
'Boot Storage Error: Root volume must be > 20GB on BaremetalNode '
|
'Root volume must be > 20GB on BaremetalNode '
|
||||||
'%s' % baremetal_node.get('name'))
|
'%s' % baremetal_node.name)
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [baremetal_node.doc_ref],
|
||||||
msg=msg,
|
"Configure a larger root volume")
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
except errors.InvalidSizeFormat as e:
|
except errors.InvalidSizeFormat as e:
|
||||||
msg = (
|
msg = (
|
||||||
'Boot Storage Error: Root volume has an invalid size format on BaremetalNode'
|
'Root volume has an invalid size format on BaremetalNode'
|
||||||
'%s.' % baremetal_node.get('name'))
|
'%s.' % baremetal_node.name)
|
||||||
message_list.append(
|
self.report_error(msg, [
|
||||||
TaskStatusMessage(
|
baremetal_node.doc_ref
|
||||||
msg=msg,
|
], "Use a valid root volume storage specification."
|
||||||
error=True,
|
)
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
# check make sure root has been defined and boot volume > 1GB
|
# check make sure root has been defined and boot volume > 1GB
|
||||||
if root_set and host_partition.get('name') == 'boot':
|
if root_set and host_partition.name == 'boot':
|
||||||
size = host_partition.get('size')
|
size = host_partition.size
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cal_size = SimpleBytes.calculate_bytes(size)
|
cal_size = SimpleBytes.calculate_bytes(size)
|
||||||
# check if size < 1GB
|
# check if size < 1GB
|
||||||
if cal_size < BYTES_IN_GB:
|
if cal_size < BYTES_IN_GB:
|
||||||
msg = (
|
msg = (
|
||||||
'Boot Storage Error: Boot volume must be > 1GB on BaremetalNode '
|
'Boot volume must be > 1GB on BaremetalNode '
|
||||||
'%s' % baremetal_node.get('name'))
|
'%s' % baremetal_node.name)
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [baremetal_node.doc_ref],
|
||||||
msg=msg,
|
"Configure a larger boot volume.")
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
except errors.InvalidSizeFormat as e:
|
except errors.InvalidSizeFormat as e:
|
||||||
msg = (
|
msg = (
|
||||||
'Boot Storage Error: Boot volume has an invalid size format on BaremetalNode '
|
'Boot volume has an invalid size format on BaremetalNode '
|
||||||
'%s.' % baremetal_node.get('name'))
|
'%s.' % baremetal_node.name)
|
||||||
message_list.append(
|
self.report_error(msg, [
|
||||||
TaskStatusMessage(
|
baremetal_node.doc_ref
|
||||||
msg=msg,
|
], "Use a valid boot volume storage specification."
|
||||||
error=True,
|
)
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
# This must be set
|
# This must be set
|
||||||
if not root_set:
|
if not root_set:
|
||||||
msg = (
|
msg = (
|
||||||
'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode '
|
'Root volume has to be set and must be > 20GB on BaremetalNode '
|
||||||
'%s' % baremetal_node.get('name'))
|
'%s' % baremetal_node.name)
|
||||||
message_list.append(
|
self.report_error(msg, [
|
||||||
TaskStatusMessage(
|
baremetal_node.doc_ref
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
], "All nodes require a defined root volume at least 20GB in size."
|
||||||
|
)
|
||||||
|
|
||||||
if not message_list:
|
return
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg='Boot Storage', error=False, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
return Validators.report_results(self, message_list)
|
|
||||||
|
|
|
@ -15,34 +15,31 @@ from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from netaddr import IPNetwork, IPAddress
|
from netaddr import IPNetwork, IPAddress
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class IpLocalityCheck(Validators):
|
class IpLocalityCheck(Validators):
|
||||||
def __init__(self):
|
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
|
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.
|
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
|
network_dict = {} # Dictionary Format - network name: cidr
|
||||||
message_list = []
|
|
||||||
|
|
||||||
site_design = site_design.obj_to_simple()
|
baremetal_nodes_list = site_design.baremetal_nodes or []
|
||||||
baremetal_nodes_list = site_design.get('baremetal_nodes', [])
|
network_list = site_design.networks or []
|
||||||
network_list = site_design.get('networks', [])
|
|
||||||
|
|
||||||
if not network_list:
|
if not network_list:
|
||||||
msg = 'No networks found.'
|
msg = 'No networks found.'
|
||||||
message_list.append(
|
self.report_warn(
|
||||||
TaskStatusMessage(
|
msg, [],
|
||||||
msg=msg, error=False, ctx_type='NA', ctx='NA'))
|
'Site design likely incomplete without defined networks')
|
||||||
else:
|
else:
|
||||||
for net in network_list:
|
for net in network_list:
|
||||||
name = net.get('name')
|
name = net.name
|
||||||
cidr = net.get('cidr')
|
cidr = net.cidr
|
||||||
routes = net.get('routes', [])
|
routes = net.routes or []
|
||||||
|
|
||||||
cidr_range = IPNetwork(cidr)
|
cidr_range = IPNetwork(cidr)
|
||||||
network_dict[name] = cidr_range
|
network_dict[name] = cidr_range
|
||||||
|
@ -53,68 +50,52 @@ class IpLocalityCheck(Validators):
|
||||||
|
|
||||||
if not gateway:
|
if not gateway:
|
||||||
msg = 'No gateway found for route %s.' % routes
|
msg = 'No gateway found for route %s.' % routes
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [net.doc_ref],
|
||||||
msg=msg,
|
diagnostic=
|
||||||
error=True,
|
"Define a network-local gateway for the route."
|
||||||
ctx_type='NA',
|
)
|
||||||
ctx='NA'))
|
|
||||||
else:
|
else:
|
||||||
ip = IPAddress(gateway)
|
ip = IPAddress(gateway)
|
||||||
if ip not in cidr_range:
|
if ip not in cidr_range:
|
||||||
msg = (
|
msg = (
|
||||||
'IP Locality Error: The gateway IP Address %s '
|
'The gateway IP Address %s is not within the defined CIDR: %s of %s.'
|
||||||
'is not within the defined CIDR: %s of %s.'
|
|
||||||
% (gateway, cidr, name))
|
% (gateway, cidr, name))
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [net.doc_ref],
|
||||||
msg=msg,
|
"Route gateways must reside on the local network. Check "
|
||||||
error=True,
|
"gateway IP and CIDR netmask.")
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
if not baremetal_nodes_list:
|
if not baremetal_nodes_list:
|
||||||
msg = 'No baremetal_nodes found.'
|
msg = 'No baremetal_nodes found.'
|
||||||
message_list.append(
|
self.report_warn(
|
||||||
TaskStatusMessage(
|
msg, [],
|
||||||
msg=msg, error=False, ctx_type='NA', ctx='NA'))
|
"site design likely incomplete without defined networks.")
|
||||||
else:
|
else:
|
||||||
for node in baremetal_nodes_list:
|
for node in baremetal_nodes_list:
|
||||||
addressing_list = node.get('addressing', [])
|
addressing_list = node.addressing or []
|
||||||
|
|
||||||
for ip_address in addressing_list:
|
for ip_address in addressing_list:
|
||||||
ip_address_network_name = ip_address.get('network')
|
ip_address_network_name = ip_address.network
|
||||||
address = ip_address.get('address')
|
address = ip_address.address
|
||||||
ip_type = ip_address.get('type')
|
ip_type = ip_address.type
|
||||||
|
|
||||||
if ip_type is not 'dhcp':
|
if ip_type is not 'dhcp':
|
||||||
if ip_address_network_name not in network_dict:
|
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)
|
% (ip_address_network_name)
|
||||||
message_list.append(
|
self.report_error(msg, [
|
||||||
TaskStatusMessage(
|
node.doc_ref
|
||||||
msg=msg,
|
], "Define network or correct address definition.")
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
else:
|
else:
|
||||||
if IPAddress(address) not in IPNetwork(
|
if IPAddress(address) not in IPNetwork(
|
||||||
network_dict[ip_address_network_name]):
|
network_dict[ip_address_network_name]):
|
||||||
msg = (
|
msg = (
|
||||||
'IP Locality Error: The IP Address %s '
|
'The IP Address %s is not within the defined CIDR: %s of %s .'
|
||||||
'is not within the defined CIDR: %s of %s .'
|
|
||||||
% (address,
|
% (address,
|
||||||
network_dict[ip_address_network_name],
|
network_dict[ip_address_network_name],
|
||||||
ip_address_network_name))
|
ip_address_network_name))
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [node.doc_ref],
|
||||||
msg=msg,
|
"Define a valid address for this network.")
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
if not message_list:
|
|
||||||
msg = 'IP Locality Success'
|
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=False, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
return Validators.report_results(self, message_list)
|
return
|
||||||
|
|
|
@ -13,66 +13,63 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class MtuRational(Validators):
|
class MtuRational(Validators):
|
||||||
def __init__(self):
|
MIN_MTU_SIZE = 1280
|
||||||
super().__init__('MTU Rational', 1003)
|
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
|
Ensure that the MTU for each network is equal or less than the MTU defined
|
||||||
for the parent NetworkLink for that network.
|
for the parent NetworkLink for that network.
|
||||||
|
|
||||||
Ensure that each defined MTU is a rational size, say > 1400 and < 64000
|
Ensure that each defined MTU is a rational size, say > 1400 and < 64000
|
||||||
"""
|
"""
|
||||||
message_list = []
|
network_links = site_design.network_links or []
|
||||||
site_design = site_design.obj_to_simple()
|
networks = site_design.networks or []
|
||||||
|
|
||||||
network_links = site_design.get('network_links', [])
|
|
||||||
networks = site_design.get('networks', [])
|
|
||||||
|
|
||||||
parent_mtu_check = {}
|
parent_mtu_check = {}
|
||||||
|
|
||||||
for network_link in network_links:
|
for network_link in network_links:
|
||||||
mtu = network_link.get('mtu')
|
mtu = network_link.mtu
|
||||||
# check mtu > 1400 and < 64000
|
if mtu and (mtu < MtuRational.MIN_MTU_SIZE
|
||||||
if mtu and (mtu < 1400 or mtu > 64000):
|
or mtu > MtuRational.MAX_MTU_SIZE):
|
||||||
msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network Link %s.' % network_link.get(
|
msg = ("MTU must be between %d and %d, value is %d" %
|
||||||
'name')
|
(MtuRational.MIN_MTU_SIZE, MtuRational.MAX_MTU_SIZE,
|
||||||
message_list.append(
|
mtu))
|
||||||
TaskStatusMessage(
|
self.report_error(
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
msg, [network_link.doc_ref],
|
||||||
|
"Define a valid MTU. Standard is 1500, Jumbo is 9100.")
|
||||||
|
|
||||||
# add assigned network to dict with parent mtu
|
# 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
|
parent_mtu_check[assigned_network] = mtu
|
||||||
|
|
||||||
for network in networks:
|
for network in networks:
|
||||||
network_mtu = network.get('mtu')
|
network_mtu = network.mtu
|
||||||
|
|
||||||
# check mtu > 1400 and < 64000
|
if network_mtu and (network_mtu < MtuRational.MIN_MTU_SIZE
|
||||||
if network_mtu and (network_mtu < 1400 or network_mtu > 64000):
|
or network_mtu > MtuRational.MAX_MTU_SIZE):
|
||||||
msg = 'Mtu Error: Mtu must be between 1400 and 64000; on Network %s.' % network.get(
|
msg = ("MTU must be between %d and %d, value is %d" %
|
||||||
'name')
|
(MtuRational.MIN_MTU_SIZE, MtuRational.MAX_MTU_SIZE,
|
||||||
message_list.append(
|
mtu))
|
||||||
TaskStatusMessage(
|
self.report_error(
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
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)
|
parent_mtu = parent_mtu_check.get(name)
|
||||||
if network_mtu and parent_mtu:
|
if network_mtu and parent_mtu:
|
||||||
# check to make sure mtu for network is <= parent network link
|
# check to make sure mtu for network is <= parent network link
|
||||||
if network_mtu > parent_mtu:
|
if network_mtu > parent_mtu:
|
||||||
msg = 'Mtu Error: Mtu must be <= the parent Network Link; for Network %s' % (
|
msg = 'MTU must be <= the parent Network Link; for Network %s' % (
|
||||||
network.get('name'))
|
network.name)
|
||||||
message_list.append(
|
self.report_error(msg, [
|
||||||
TaskStatusMessage(
|
network.doc_ref
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
], "Define a MTU less than or equal to that of the carrying network link."
|
||||||
|
)
|
||||||
|
|
||||||
if not message_list:
|
return
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg='Mtu', error=False, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
return Validators.report_results(self, message_list)
|
|
||||||
|
|
|
@ -14,57 +14,49 @@
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
import drydock_provisioner.objects.fields as hd_fields
|
import drydock_provisioner.objects.fields as hd_fields
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class NetworkTrunkingRational(Validators):
|
class NetworkTrunkingRational(Validators):
|
||||||
def __init__(self):
|
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
|
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.
|
enabled. It also makes sure that if trunking mode is disabled then a default network is defined.
|
||||||
"""
|
"""
|
||||||
message_list = []
|
network_link_list = site_design.network_links or []
|
||||||
site_design = site_design.obj_to_simple()
|
|
||||||
|
|
||||||
network_link_list = site_design.get('network_links', [])
|
|
||||||
|
|
||||||
for network_link in network_link_list:
|
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 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):
|
hd_fields.NetworkLinkTrunkingMode.Disabled):
|
||||||
|
msg = ('If there is more than 1 allowed network,'
|
||||||
msg = (
|
'trunking mode must be enabled')
|
||||||
'Rational Network Trunking Error: If there is more than 1 allowed network,'
|
self.report_error(msg, [
|
||||||
'trunking mode must be enabled; on NetworkLink %s' %
|
network_link.doc_ref
|
||||||
network_link.get('name'))
|
], "Reduce the allowed network list to 1 or enable trunking on the link."
|
||||||
|
)
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
# trunking mode is disabled, default_network must be defined
|
# trunking mode is disabled, default_network must be defined
|
||||||
if (network_link.get(
|
if (network_link.trunk_mode ==
|
||||||
'trunk_mode') == hd_fields.NetworkLinkTrunkingMode.Disabled
|
hd_fields.NetworkLinkTrunkingMode.Disabled
|
||||||
and network_link.get('native_network') is None):
|
and network_link.native_network is None):
|
||||||
|
|
||||||
msg = (
|
msg = 'Trunking mode is disabled, a trunking default_network must be defined'
|
||||||
'Rational Network Trunking Error: Trunking mode is disabled, a trunking'
|
self.report_error(
|
||||||
'default_network must be defined; on NetworkLink %s' %
|
msg, [network_link.doc_ref],
|
||||||
network_link.get('name'))
|
"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(
|
return
|
||||||
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)
|
|
||||||
|
|
|
@ -13,51 +13,40 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class NoDuplicateIpsCheck(Validators):
|
class NoDuplicateIpsCheck(Validators):
|
||||||
def __init__(self):
|
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
|
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
|
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.
|
checked against in the future.
|
||||||
"""
|
"""
|
||||||
found_ips = {} # Dictionary Format - IP address: BaremetalNode name
|
found_ips = {} # Dictionary Format - IP address: BaremetalNode name
|
||||||
message_list = []
|
|
||||||
|
|
||||||
site_design = site_design.obj_to_simple()
|
baremetal_nodes_list = site_design.baremetal_nodes or []
|
||||||
baremetal_nodes_list = site_design.get('baremetal_nodes', [])
|
|
||||||
|
|
||||||
if not baremetal_nodes_list:
|
if not baremetal_nodes_list:
|
||||||
msg = 'No BaremetalNodes Found.'
|
msg = 'No BaremetalNodes Found.'
|
||||||
message_list.append(
|
self.report_warn(
|
||||||
TaskStatusMessage(
|
msg, [],
|
||||||
msg=msg, error=False, ctx_type='NA', ctx='NA'))
|
"Site design unlikely complete with no defined baremetal nodes."
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
for node in baremetal_nodes_list:
|
for node in baremetal_nodes_list:
|
||||||
addressing_list = node.get('addressing', [])
|
addressing_list = node.addressing or []
|
||||||
|
|
||||||
for ip_address in addressing_list:
|
for ip_address in addressing_list:
|
||||||
address = ip_address.get('address')
|
address = ip_address.address
|
||||||
node_name = node.get('name')
|
|
||||||
|
|
||||||
if address in found_ips and address is not None:
|
if address in found_ips and address is not None:
|
||||||
msg = ('Error! Duplicate IP Address Found: %s '
|
msg = ('Duplicate IP Address Found: %s ' % address)
|
||||||
'is in use by both %s and %s.' %
|
self.report_error(
|
||||||
(address, found_ips[address], node_name))
|
msg, [node.doc_ref, found_ips[address].doc_ref],
|
||||||
message_list.append(
|
"Select unique IP addresses for each node.")
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
|
||||||
elif address is not None:
|
elif address is not None:
|
||||||
found_ips[address] = node_name
|
found_ips[address] = node
|
||||||
|
|
||||||
if not message_list:
|
return
|
||||||
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)
|
|
||||||
|
|
|
@ -13,32 +13,28 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class PlatformSelection(Validators):
|
class PlatformSelection(Validators):
|
||||||
def __init__(self):
|
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.
|
"""Validate that the platform selection for all nodes is valid.
|
||||||
|
|
||||||
Each node specifies an ``image`` and a ``kernel`` to use for
|
Each node specifies an ``image`` and a ``kernel`` to use for
|
||||||
deployment. Check that these are valid for the image repository
|
deployment. Check that these are valid for the image repository
|
||||||
configured in MAAS.
|
configured in MAAS.
|
||||||
"""
|
"""
|
||||||
message_list = list()
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
node_driver = orchestrator.enabled_drivers['node']
|
node_driver = orchestrator.enabled_drivers['node']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
message_list.append(
|
msg = ("Platform Validation: No enabled node driver, image"
|
||||||
TaskStatusMessage(
|
"and kernel selections not validated.")
|
||||||
msg="Platform Validation: No enabled node driver, image"
|
self.report_warn(
|
||||||
"and kernel selections not validated.",
|
msg, [],
|
||||||
error=False,
|
"Cannot validate platform selection without accessing the node provisioner."
|
||||||
ctx_type='NA',
|
)
|
||||||
ctx='NA'))
|
return
|
||||||
return Validators.report_results(self, message_list)
|
|
||||||
|
|
||||||
valid_images = node_driver.get_available_images()
|
valid_images = node_driver.get_available_images()
|
||||||
|
|
||||||
|
@ -51,28 +47,14 @@ class PlatformSelection(Validators):
|
||||||
if n.image in valid_images:
|
if n.image in valid_images:
|
||||||
if n.kernel in valid_kernels[n.image]:
|
if n.kernel in valid_kernels[n.image]:
|
||||||
continue
|
continue
|
||||||
message_list.append(
|
msg = "Platform Validation: invalid kernel %s" % (n.kernel)
|
||||||
TaskStatusMessage(
|
self.report_error(msg, [n.doc_ref],
|
||||||
msg="Platform Validation: invalid kernel %s for node %s."
|
"Select a valid kernel from: %s" % ",".join(
|
||||||
% (n.kernel, n.name),
|
valid_kernels[n.image]))
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
continue
|
continue
|
||||||
message_list.append(
|
msg = "Platform Validation: invalid image %s" % (n.image)
|
||||||
TaskStatusMessage(
|
self.report_error(
|
||||||
msg="Platform Validation: invalid image %s for node %s." %
|
msg, [n.doc_ref],
|
||||||
(n.image, n.name),
|
"Select a valid image from: %s" % ",".join(valid_images))
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
if not message_list:
|
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg="Platform Validation: all nodes have valid "
|
|
||||||
"image and kernel selections.",
|
|
||||||
error=False,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
return Validators.report_results(self, message_list)
|
return
|
||||||
|
|
|
@ -13,13 +13,12 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class RationalNetworkBond(Validators):
|
class RationalNetworkBond(Validators):
|
||||||
def __init__(self):
|
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.
|
This check ensures that each NetworkLink has a rational bonding setup.
|
||||||
If the bonding mode is set to 'disabled' then it ensures that no other options are specified.
|
If the bonding mode 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
|
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.
|
bonding peer rate are both NOT defined.
|
||||||
"""
|
"""
|
||||||
message_list = []
|
network_links = site_design.network_links or []
|
||||||
site_design = site_design.obj_to_simple()
|
|
||||||
|
|
||||||
network_links = site_design.get('network_links', [])
|
|
||||||
|
|
||||||
for network_link in network_links:
|
for network_link in network_links:
|
||||||
bonding_mode = network_link.get('bonding_mode', [])
|
bonding_mode = network_link.bonding_mode
|
||||||
|
|
||||||
if bonding_mode == 'disabled':
|
if bonding_mode == 'disabled':
|
||||||
# check to make sure nothing else is specified
|
# check to make sure nothing else is specified
|
||||||
if any([
|
if any([
|
||||||
network_link.get(x) for x in [
|
getattr(network_link, x) for x in [
|
||||||
'bonding_peer_rate', 'bonding_xmit_hash',
|
'bonding_peer_rate', 'bonding_xmit_hash',
|
||||||
'bonding_mon_rate', 'bonding_up_delay',
|
'bonding_mon_rate', 'bonding_up_delay',
|
||||||
'bonding_down_delay'
|
'bonding_down_delay'
|
||||||
]
|
]
|
||||||
]):
|
]):
|
||||||
|
msg = 'If bonding mode is disabled no other bond option can be specified'
|
||||||
msg = (
|
self.report_error(
|
||||||
'Network Link Bonding Error: If bonding mode is disabled no other bond option can be'
|
msg, [network_link.doc_ref],
|
||||||
'specified; on BaremetalNode %s' %
|
"Enable a bonding mode or remove the bond options.")
|
||||||
network_link.get('name'))
|
|
||||||
|
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
elif bonding_mode == '802.3ad':
|
elif bonding_mode == '802.3ad':
|
||||||
# check if up_delay and down_delay are >= mon_rate
|
# check if up_delay and down_delay are >= mon_rate
|
||||||
mon_rate = network_link.get('bonding_mon_rate')
|
mon_rate = network_link.bonding_mon_rate
|
||||||
if network_link.get('bonding_up_delay') < mon_rate:
|
if network_link.bonding_up_delay < mon_rate:
|
||||||
msg = ('Network Link Bonding Error: Up delay is less '
|
msg = ('Up delay %d is less than mon rate %d' %
|
||||||
'than mon rate on BaremetalNode %s' %
|
(network_link.bonding_up_delay, mon_rate))
|
||||||
(network_link.get('name')))
|
self.report_error(msg, [
|
||||||
|
network_link.doc_ref
|
||||||
|
], "Link up delay must be equal or greater than the mon_rate"
|
||||||
|
)
|
||||||
|
|
||||||
message_list.append(
|
if network_link.bonding_down_delay < mon_rate:
|
||||||
TaskStatusMessage(
|
msg = ('Down delay %d is less than mon rate %d' %
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
(network_link.bonding_down_delay, mon_rate))
|
||||||
|
self.report_error(msg, [
|
||||||
if network_link.get('bonding_down_delay') < mon_rate:
|
network_link.doc_ref
|
||||||
msg = ('Network Link Bonding Error: Down delay is '
|
], "Link down delay must be equal or greater than the mon_rate"
|
||||||
'less than mon rate on BaremetalNode %s' %
|
)
|
||||||
(network_link.get('name')))
|
|
||||||
|
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
elif bonding_mode in ['active-backup', 'balanced-rr']:
|
elif bonding_mode in ['active-backup', 'balanced-rr']:
|
||||||
# make sure hash and peer_rate are NOT defined
|
# make sure hash and peer_rate are NOT defined
|
||||||
if network_link.get('bonding_xmit_hash'):
|
if network_link.get('bonding_xmit_hash'):
|
||||||
msg = (
|
msg = ('Hash cannot be defined if bond mode is %s' %
|
||||||
'Network Link Bonding Error: Hash cannot be defined if bond mode is '
|
(bonding_mode))
|
||||||
'%s, on BaremetalNode %s' % (bonding_mode,
|
self.report_error(
|
||||||
network_link.get('name')))
|
msg, [network_link.doc_ref],
|
||||||
|
"Hash mode is only applicable to LACP (802.3ad)")
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
|
||||||
|
|
||||||
if network_link.get('bonding_peer_rate'):
|
if network_link.get('bonding_peer_rate'):
|
||||||
msg = (
|
msg = ('Peer rate cannot be defined if bond mode is %s' %
|
||||||
'Network Link Bonding Error: Peer rate cannot be defined if bond mode is '
|
(bonding_mode))
|
||||||
'%s, on BaremetalNode %s' % (bonding_mode,
|
self.report_error(
|
||||||
network_link.get('name')))
|
msg, [network_link.doc_ref],
|
||||||
|
"Peer rate is only applicable to LACP (802.3ad)")
|
||||||
|
|
||||||
message_list.append(
|
return
|
||||||
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)
|
|
||||||
|
|
|
@ -13,90 +13,69 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class StoragePartitioning(Validators):
|
class StoragePartitioning(Validators):
|
||||||
def __init__(self):
|
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
|
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.
|
list it ensures that a file system and partition volume group are not defined in the same partition.
|
||||||
"""
|
"""
|
||||||
message_list = []
|
baremetal_nodes = site_design.baremetal_nodes or []
|
||||||
site_design = site_design.obj_to_simple()
|
|
||||||
|
|
||||||
baremetal_nodes = site_design.get('baremetal_nodes', [])
|
|
||||||
|
|
||||||
volume_group_check_list = []
|
volume_group_check_list = []
|
||||||
|
|
||||||
for baremetal_node in baremetal_nodes:
|
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:
|
for storage_device in storage_devices_list:
|
||||||
partitions_list = storage_device.get('partitions')
|
partitions_list = storage_device.partitions or []
|
||||||
volume_group = storage_device.get('volume_group')
|
volume_group = storage_device.volume_group
|
||||||
|
|
||||||
# error if both or neither is defined
|
# error if both or neither is defined
|
||||||
if all([partitions_list, volume_group
|
if all([partitions_list, volume_group
|
||||||
]) or not any([partitions_list, volume_group]):
|
]) or not any([partitions_list, volume_group]):
|
||||||
msg = ('Storage Partitioning Error: Either a volume group '
|
msg = (
|
||||||
'OR partitions must be defined for each storage '
|
'Either a volume group OR partitions must be defined for each storage '
|
||||||
'device; on BaremetalNode '
|
'device.')
|
||||||
'%s' % baremetal_node.get('name'))
|
self.report_error(
|
||||||
message_list.append(
|
msg, [baremetal_node.doc_ref],
|
||||||
TaskStatusMessage(
|
"A storage device must be used for exactly one of a volume group "
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
"physical volume or carved into partitions.")
|
||||||
|
|
||||||
# if there is a volume group add to list
|
# if there is a volume group add to list
|
||||||
if volume_group is not None:
|
if volume_group is not None:
|
||||||
volume_group_check_list.append(volume_group)
|
volume_group_check_list.append(volume_group)
|
||||||
|
|
||||||
if partitions_list is not None:
|
for partition in partitions_list:
|
||||||
for partition in partitions_list:
|
partition_volume_group = partition.volume_group
|
||||||
partition_volume_group = partition.get('volume_group')
|
fstype = partition.fstype
|
||||||
fstype = partition.get('fstype')
|
|
||||||
|
|
||||||
# error if both are defined
|
# error if both are defined
|
||||||
if all([fstype, partition_volume_group]):
|
if all([fstype, partition_volume_group]):
|
||||||
msg = (
|
msg = ('Both a volume group AND file system cannot be '
|
||||||
'Storage Partitioning Error: Both a volume group AND file system cannot be '
|
'defined in a single partition')
|
||||||
'defined in a sigle partition; on BaremetalNode %s'
|
self.report_error(
|
||||||
% baremetal_node.get('name'))
|
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(
|
# if there is a volume group add to list
|
||||||
TaskStatusMessage(
|
if partition_volume_group is not None:
|
||||||
msg=msg,
|
volume_group_check_list.append(volume_group)
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
# if there is a volume group add to list
|
|
||||||
if partition_volume_group is not None:
|
|
||||||
volume_group_check_list.append(volume_group)
|
|
||||||
|
|
||||||
# checks all volume groups are assigned to a partition or storage device
|
# checks all volume groups are assigned to a partition or storage device
|
||||||
# if one exist that wasn't found earlier it is unassigned
|
# 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:
|
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 = (
|
msg = (
|
||||||
'Storage Partitioning Error: A volume group must be assigned to a storage device or '
|
'Volume group %s not assigned any physical volumes' %
|
||||||
'partition; volume group %s on BaremetalNode %s' %
|
(volume_group.name))
|
||||||
(volume_group.get('name'), baremetal_node.get('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(
|
return
|
||||||
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)
|
|
||||||
|
|
|
@ -13,91 +13,70 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class StorageSizing(Validators):
|
class StorageSizing(Validators):
|
||||||
def __init__(self):
|
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
|
Ensures that for a partitioned physical device or logical volumes
|
||||||
in a volume group, if sizing is a percentage then those percentages
|
in a volume group, if sizing is a percentage then those percentages
|
||||||
do not sum > 99% and have no negative values
|
do not sum > 99% and have no negative values
|
||||||
"""
|
"""
|
||||||
message_list = []
|
baremetal_nodes = site_design.baremetal_nodes or []
|
||||||
site_design = site_design.obj_to_simple()
|
|
||||||
|
|
||||||
baremetal_nodes = site_design.get('baremetal_nodes', [])
|
|
||||||
|
|
||||||
for baremetal_node in baremetal_nodes:
|
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:
|
for storage_device in storage_device_list:
|
||||||
partition_list = storage_device.get('partitions', [])
|
partition_list = storage_device.partitions or []
|
||||||
partition_sum = 0
|
partition_sum = 0
|
||||||
for partition in partition_list:
|
for partition in partition_list:
|
||||||
size = partition.get('size')
|
size = partition.size
|
||||||
percent = size.split('%')
|
percent = size.split('%')
|
||||||
if len(percent) == 2:
|
if len(percent) == 2:
|
||||||
if int(percent[0]) < 0:
|
if int(percent[0]) < 0:
|
||||||
msg = (
|
msg = (
|
||||||
'Storage Sizing Error: Storage partition size is < 0 '
|
'Storage partition %s on device %s size is < 0'
|
||||||
'on Baremetal Node %s' %
|
% (partition.name, storage_device.name))
|
||||||
baremetal_node.get('name'))
|
self.report_error(
|
||||||
message_list.append(
|
msg, [baremetal_node.doc_ref],
|
||||||
TaskStatusMessage(
|
"Partition size must be a positive number.")
|
||||||
msg=msg,
|
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
partition_sum += int(percent[0])
|
partition_sum += int(percent[0])
|
||||||
|
|
||||||
if partition_sum > 99:
|
if partition_sum > 99:
|
||||||
msg = (
|
msg = (
|
||||||
'Storage Sizing Error: Storage partition size is greater than '
|
'Cumulative partition sizes on device %s is greater than 99%%.'
|
||||||
'99 on Baremetal Node %s' %
|
% (storage_device.name))
|
||||||
baremetal_node.get('name'))
|
self.report_error(msg, [
|
||||||
message_list.append(
|
baremetal_node.doc_ref
|
||||||
TaskStatusMessage(
|
], "Percentage-based sizes must sum to less than 100%."
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
)
|
||||||
|
|
||||||
volume_groups = baremetal_node.get('volume_groups', [])
|
volume_groups = baremetal_node.volume_groups or []
|
||||||
volume_sum = 0
|
volume_sum = 0
|
||||||
for volume_group in volume_groups:
|
for volume_group in volume_groups:
|
||||||
logical_volume_list = volume_group.get(
|
logical_volume_list = volume_group.logical_volumes or []
|
||||||
'logical_volumes', [])
|
|
||||||
for logical_volume in logical_volume_list:
|
for logical_volume in logical_volume_list:
|
||||||
size = logical_volume.get('size')
|
size = logical_volume.size
|
||||||
percent = size.split('%')
|
percent = size.split('%')
|
||||||
if len(percent) == 2:
|
if len(percent) == 2:
|
||||||
if int(percent[0]) < 0:
|
if int(percent[0]) < 0:
|
||||||
msg = (
|
msg = ('Logical Volume %s size is < 0 ' %
|
||||||
'Storage Sizing Error: Storage volume size is < 0 '
|
(logical_volume.name))
|
||||||
'on Baremetal Node %s' %
|
self.report_error(msg,
|
||||||
baremetal_node.get('name'))
|
[baremetal_node.doc_ref], "")
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg=msg,
|
|
||||||
error=True,
|
|
||||||
ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
volume_sum += int(percent[0])
|
volume_sum += int(percent[0])
|
||||||
|
|
||||||
if volume_sum > 99:
|
if volume_sum > 99:
|
||||||
msg = (
|
msg = ('Cumulative logical volume size is greater '
|
||||||
'Storage Sizing Error: Storage volume size is greater '
|
'than 99% in volume group %s' %
|
||||||
'than 99 on Baremetal Node %s.' %
|
(volume_group.name))
|
||||||
baremetal_node.get('name'))
|
self.report_error(msg, [
|
||||||
message_list.append(
|
baremetal_node.doc_ref
|
||||||
TaskStatusMessage(
|
], "Percentage-based sizes must sum to less than 100%."
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
)
|
||||||
|
|
||||||
if not message_list:
|
return
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg='Storage Sizing', error=False, ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
return Validators.report_results(self, message_list)
|
|
||||||
|
|
|
@ -13,39 +13,37 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
from drydock_provisioner.orchestrator.validations.validators import Validators
|
from drydock_provisioner.orchestrator.validations.validators import Validators
|
||||||
|
|
||||||
from drydock_provisioner.objects.task import TaskStatusMessage
|
|
||||||
|
|
||||||
class UniqueNetworkCheck(Validators):
|
class UniqueNetworkCheck(Validators):
|
||||||
def __init__(self):
|
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
|
Ensures that each network name appears at most once between all NetworkLink
|
||||||
allowed networks
|
allowed networks
|
||||||
"""
|
"""
|
||||||
message_list = []
|
network_link_list = site_design.network_links or []
|
||||||
site_design = site_design.obj_to_simple()
|
link_allowed_nets = {}
|
||||||
network_link_list = site_design.get('network_links', [])
|
|
||||||
compare = {}
|
|
||||||
|
|
||||||
for network_link in network_link_list:
|
for network_link in network_link_list:
|
||||||
allowed_network_list = network_link.get('allowed_networks', [])
|
allowed_network_list = network_link.allowed_networks
|
||||||
compare[network_link.get('name')] = allowed_network_list
|
link_allowed_nets[network_link.name] = allowed_network_list
|
||||||
|
|
||||||
# This checks the allowed networks for each network link against
|
# This checks the allowed networks for each network link against
|
||||||
# the other allowed networks
|
# the other allowed networks
|
||||||
checked_pairs = []
|
checked_pairs = []
|
||||||
for network_link_name in compare:
|
for network_link_name in link_allowed_nets:
|
||||||
allowed_network_list_1 = compare[network_link_name]
|
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
|
if (network_link_name is not network_link_name_2
|
||||||
and sorted([network_link_name, network_link_name_2
|
and sorted([network_link_name, network_link_name_2
|
||||||
]) not in checked_pairs):
|
]) not in checked_pairs):
|
||||||
checked_pairs.append(
|
checked_pairs.append(
|
||||||
sorted([network_link_name, network_link_name_2]))
|
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
|
# creates a list of duplicated allowed networks
|
||||||
duplicated_names = [
|
duplicated_names = [
|
||||||
i for i in allowed_network_list_1
|
i for i in allowed_network_list_1
|
||||||
|
@ -54,17 +52,36 @@ class UniqueNetworkCheck(Validators):
|
||||||
|
|
||||||
for name in duplicated_names:
|
for name in duplicated_names:
|
||||||
msg = (
|
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,
|
'%s' % (name, network_link_name,
|
||||||
network_link_name_2))
|
network_link_name_2))
|
||||||
message_list.append(
|
self.report_error(
|
||||||
TaskStatusMessage(
|
msg, [],
|
||||||
msg=msg, error=True, ctx_type='NA', ctx='NA'))
|
"Each network is only allowed to cross a single network link."
|
||||||
|
)
|
||||||
|
|
||||||
if not message_list:
|
node_list = site_design.baremetal_nodes or []
|
||||||
message_list.append(
|
|
||||||
TaskStatusMessage(
|
|
||||||
msg='Unique Network', error=False, ctx_type='NA',
|
|
||||||
ctx='NA'))
|
|
||||||
|
|
||||||
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
|
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.boot_storage_rational import BootStorageRational
|
||||||
from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck
|
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.storage_sizing import StorageSizing
|
||||||
from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck
|
from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck
|
||||||
|
|
||||||
|
|
||||||
class Validator():
|
class Validator():
|
||||||
def __init__(self, orchestrator):
|
def __init__(self, orchestrator):
|
||||||
"""Create a validator with a reference to the orchestrator.
|
"""Create a validator with a reference to the orchestrator.
|
||||||
|
@ -36,7 +37,10 @@ class Validator():
|
||||||
"""
|
"""
|
||||||
self.orchestrator = orchestrator
|
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.
|
"""Validate the design in site_design passes all validation rules.
|
||||||
|
|
||||||
Apply all validation rules to the design in site_design. If result_status is
|
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
|
:param result_status: instance of objects.TaskStatus
|
||||||
"""
|
"""
|
||||||
if result_status is None:
|
if result_status is None:
|
||||||
result_status = TaskStatus()
|
result_status = Validation()
|
||||||
|
|
||||||
validation_error = False
|
validation_error = False
|
||||||
message_lists = []
|
|
||||||
for rule in rule_set:
|
for rule in rule_set:
|
||||||
results, message_list = rule.execute(site_design=site_design, orchestrator=self.orchestrator)
|
message_list = rule.execute(
|
||||||
for item in message_list:
|
site_design=site_design, orchestrator=self.orchestrator)
|
||||||
message_lists.append(item)
|
result_status.message_list.extend(message_list)
|
||||||
result_status.message_list.extend(results)
|
error_msg = [m for m in message_list if m.error]
|
||||||
error_msg = [m for m in results if m.error]
|
|
||||||
result_status.error_count = result_status.error_count + len(
|
result_status.error_count = result_status.error_count + len(
|
||||||
error_msg)
|
error_msg)
|
||||||
if len(error_msg) > 0:
|
if len(error_msg) > 0:
|
||||||
|
@ -64,29 +66,12 @@ class Validator():
|
||||||
|
|
||||||
if validation_error:
|
if validation_error:
|
||||||
result_status.set_status(hd_fields.ValidationResult.Failure)
|
result_status.set_status(hd_fields.ValidationResult.Failure)
|
||||||
|
result_status.message = "Site design failed validation."
|
||||||
|
result_status.reason = "See detail messages."
|
||||||
else:
|
else:
|
||||||
result_status.set_status(hd_fields.ValidationResult.Success)
|
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
|
return result_status
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -13,28 +13,58 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
"""Business Logic Validation"""
|
"""Business Logic Validation"""
|
||||||
|
|
||||||
|
from drydock_provisioner import objects
|
||||||
|
from drydock_provisioner.objects import fields as hd_fields
|
||||||
|
|
||||||
|
|
||||||
class Validators:
|
class Validators:
|
||||||
def __init__(self, name, code):
|
def __init__(self, long_name, name):
|
||||||
self.name = name
|
self.name = name
|
||||||
self.code = code
|
self.long_name = long_name
|
||||||
|
self.reset_message_list()
|
||||||
|
|
||||||
def report_results(self, results):
|
def report_msg(self, msg, docs, diagnostic, error, level):
|
||||||
# https://github.com/att-comdev/ucp-integration/blob/master/docs/source/api-conventions.rst#output-structure
|
"""Add a validation message to the result list.
|
||||||
message_list = []
|
|
||||||
for result in results:
|
|
||||||
rd = result.to_dict()
|
|
||||||
if isinstance(rd, dict) and rd['error']:
|
|
||||||
item = {
|
|
||||||
"message": rd['message'],
|
|
||||||
"error": True,
|
|
||||||
"name": self.name,
|
|
||||||
"documents": [],
|
|
||||||
"level": "Error",
|
|
||||||
"diagnostic": "Context Type = %s, Context = %s" % (rd['context_type'], rd['context']),
|
|
||||||
"kind": "ValidationMessage"
|
|
||||||
}
|
|
||||||
message_list.append(item)
|
|
||||||
return results, message_list
|
|
||||||
|
|
||||||
def execute(site_design, orchestrator=None):
|
:param msg: String - msg, will be prepended with validator long name
|
||||||
pass
|
: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',
|
'path': '/api/v1.0/designs/{design_id}/parts',
|
||||||
'method': 'POST'
|
'method': 'POST'
|
||||||
}]),
|
}]),
|
||||||
policy.DocumentedRuleDefault(
|
policy.DocumentedRuleDefault('physical_provisioner:health_data',
|
||||||
'physical_provisioner:health_data', 'role:admin',
|
'role:admin', 'et health status',
|
||||||
'et health status',
|
[{
|
||||||
[{
|
'path': '/api/v1.0/health/extended',
|
||||||
'path': '/api/v1.0/health/extended',
|
'method': 'GET'
|
||||||
'method': 'GET'
|
}])
|
||||||
}])
|
|
||||||
]
|
]
|
||||||
|
|
||||||
# Validate Design Policy
|
# Validate Design Policy
|
||||||
|
|
|
@ -684,7 +684,5 @@ class DrydockState(object):
|
||||||
return None
|
return None
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
self.logger.error(str(ex))
|
self.logger.error(str(ex))
|
||||||
self.logger.error(
|
self.logger.error("Error querying for now()", exc_info=True)
|
||||||
"Error querying for now()",
|
|
||||||
exc_info=True)
|
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -19,7 +19,8 @@ import falcon
|
||||||
|
|
||||||
|
|
||||||
def test_get_health(mocker, deckhand_orchestrator, drydock_state):
|
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
|
# Configure mocked request and response
|
||||||
req = mocker.MagicMock(spec=falcon.Request)
|
req = mocker.MagicMock(spec=falcon.Request)
|
||||||
|
|
|
@ -20,13 +20,13 @@ import drydock_provisioner.objects.fields as hd_fields
|
||||||
|
|
||||||
|
|
||||||
class TestClass(object):
|
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):
|
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)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
orchestrator = orch.Orchestrator(
|
orchestrator = orch.Orchestrator(
|
||||||
state_manager=blank_state, ingester=yaml_ingester)
|
state_manager=blank_state, ingester=deckhand_ingester)
|
||||||
orch_task = orchestrator.create_task(
|
orch_task = orchestrator.create_task(
|
||||||
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
|
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
|
||||||
orch_task.set_status(hd_fields.TaskStatus.Queued)
|
orch_task.set_status(hd_fields.TaskStatus.Queued)
|
||||||
|
@ -45,13 +45,13 @@ class TestClass(object):
|
||||||
orchestrator.stop_orchestrator()
|
orchestrator.stop_orchestrator()
|
||||||
orch_thread.join(10)
|
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):
|
blank_state):
|
||||||
input_file = input_files.join("fullsite.yaml")
|
input_file = input_files.join("deckhand_fullsite.yaml")
|
||||||
design_ref = "file://%s" % str(input_file)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
orchestrator = orch.Orchestrator(
|
orchestrator = orch.Orchestrator(
|
||||||
state_manager=blank_state, ingester=yaml_ingester)
|
state_manager=blank_state, ingester=deckhand_ingester)
|
||||||
orch_task = orchestrator.create_task(
|
orch_task = orchestrator.create_task(
|
||||||
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
|
action=hd_fields.OrchestratorAction.Noop, design_ref=design_ref)
|
||||||
|
|
||||||
|
|
|
@ -16,12 +16,15 @@ from falcon import testing
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
import json
|
import json
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner import policy
|
from drydock_provisioner import policy
|
||||||
from drydock_provisioner.control.api import start_api
|
from drydock_provisioner.control.api import start_api
|
||||||
|
|
||||||
import falcon
|
import falcon
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestValidationApi(object):
|
class TestValidationApi(object):
|
||||||
def test_post_validation_resp(self, input_files, falcontest, drydock_state,
|
def test_post_validation_resp(self, input_files, falcontest, drydock_state,
|
||||||
|
@ -46,6 +49,7 @@ class TestValidationApi(object):
|
||||||
result = falcontest.simulate_post(
|
result = falcontest.simulate_post(
|
||||||
url, headers=hdr, body=json.dumps(body))
|
url, headers=hdr, body=json.dumps(body))
|
||||||
|
|
||||||
|
LOG.debug(result.text)
|
||||||
assert result.status == falcon.HTTP_200
|
assert result.status == falcon.HTTP_200
|
||||||
|
|
||||||
def test_href_error(self, input_files, falcontest):
|
def test_href_error(self, input_files, falcontest):
|
||||||
|
@ -65,6 +69,7 @@ class TestValidationApi(object):
|
||||||
result = falcontest.simulate_post(
|
result = falcontest.simulate_post(
|
||||||
url, headers=hdr, body=json.dumps(body))
|
url, headers=hdr, body=json.dumps(body))
|
||||||
|
|
||||||
|
LOG.debug(result.text)
|
||||||
assert result.status == falcon.HTTP_400
|
assert result.status == falcon.HTTP_400
|
||||||
|
|
||||||
def test_json_data_error(self, input_files, falcontest):
|
def test_json_data_error(self, input_files, falcontest):
|
||||||
|
@ -80,6 +85,7 @@ class TestValidationApi(object):
|
||||||
result = falcontest.simulate_post(
|
result = falcontest.simulate_post(
|
||||||
url, headers=hdr, body=json.dumps(body))
|
url, headers=hdr, body=json.dumps(body))
|
||||||
|
|
||||||
|
LOG.debug(result.text)
|
||||||
assert result.status == falcon.HTTP_400
|
assert result.status == falcon.HTTP_400
|
||||||
|
|
||||||
def test_invalid_post_resp(self, input_files, falcontest, drydock_state,
|
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.host_profiles) == 2
|
||||||
assert len(design_data.baremetal_nodes) == 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."""
|
"""Test that each processed document has a doc_ref."""
|
||||||
input_file = input_files.join('deckhand_fullsite.yaml')
|
input_file = input_files.join('deckhand_fullsite.yaml')
|
||||||
|
|
||||||
|
|
|
@ -33,7 +33,4 @@ class TestDesignValidator(object):
|
||||||
val = Validator(orch)
|
val = Validator(orch)
|
||||||
response = val.validate_design(site_design)
|
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
|
assert response.status == hd_fields.ValidationResult.Success
|
||||||
|
|
|
@ -14,15 +14,17 @@
|
||||||
"""Test Validation Rule Rational Boot Storage"""
|
"""Test Validation Rule Rational Boot Storage"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational
|
from drydock_provisioner.orchestrator.validations.boot_storage_rational import BootStorageRational
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestRationalBootStorage(object):
|
class TestRationalBootStorage(object):
|
||||||
def test_boot_storage_rational(self, deckhand_ingester, drydock_state,
|
def test_boot_storage_rational(self, deckhand_ingester, drydock_state,
|
||||||
input_files, mock_get_build_data):
|
input_files, mock_get_build_data):
|
||||||
|
|
||||||
input_file = input_files.join("validation.yaml")
|
input_file = input_files.join("validation.yaml")
|
||||||
design_ref = "file://%s" % str(input_file)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
|
@ -32,16 +34,14 @@ class TestRationalBootStorage(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = BootStorageRational()
|
validator = BootStorageRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'Boot Storage'
|
|
||||||
assert msg.get('error') is False
|
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,
|
def test_invalid_boot_storage_small(self, deckhand_ingester, drydock_state,
|
||||||
input_files, mock_get_build_data):
|
input_files, mock_get_build_data):
|
||||||
|
|
||||||
input_file = input_files.join("invalid_boot_storage_small.yaml")
|
input_file = input_files.join("invalid_boot_storage_small.yaml")
|
||||||
design_ref = "file://%s" % str(input_file)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
|
@ -51,21 +51,21 @@ class TestRationalBootStorage(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = BootStorageRational()
|
validator = BootStorageRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('.+ volume must be > .+GB')
|
||||||
'Boot Storage Error: .+ volume must be > .+GB on BaremetalNode .+')
|
|
||||||
|
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
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 msg.get('error')
|
||||||
|
|
||||||
assert len(results) == 4
|
assert len(message_list) == 4
|
||||||
|
|
||||||
def test_invalid_boot_storage_root_not_set(self, deckhand_ingester,
|
def test_invalid_boot_storage_root_not_set(self, deckhand_ingester,
|
||||||
drydock_state, input_files):
|
drydock_state, input_files):
|
||||||
|
|
||||||
input_file = input_files.join("invalid_validation.yaml")
|
input_file = input_files.join("invalid_validation.yaml")
|
||||||
design_ref = "file://%s" % str(input_file)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
|
@ -75,15 +75,15 @@ class TestRationalBootStorage(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = BootStorageRational()
|
validator = BootStorageRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('Root volume has to be set and must be > 20GB')
|
||||||
'Boot Storage Error: Root volume has to be set and must be > 20GB on BaremetalNode .+'
|
|
||||||
)
|
|
||||||
|
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
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 msg.get('error')
|
||||||
|
|
||||||
assert len(results) == 2
|
assert len(message_list) == 2
|
||||||
|
|
|
@ -13,10 +13,13 @@
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck
|
from drydock_provisioner.orchestrator.validations.ip_locality_check import IpLocalityCheck
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestIPLocality(object):
|
class TestIPLocality(object):
|
||||||
def test_ip_locality(self, input_files, drydock_state, deckhand_ingester):
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = IpLocalityCheck()
|
validator = IpLocalityCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'IP Locality Success'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_ip_locality_no_networks(self, input_files, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = IpLocalityCheck()
|
validator = IpLocalityCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
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
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_ip_locality_no_gateway(self, input_files, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = IpLocalityCheck()
|
validator = IpLocalityCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert 'No gateway found' in msg.get('message')
|
assert 'No gateway found' in msg.get('message')
|
||||||
assert msg.get('error') is True
|
assert msg.get('error') is True
|
||||||
|
@ -80,10 +82,10 @@ class TestIPLocality(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = IpLocalityCheck()
|
validator = IpLocalityCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
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
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_invalid_ip_locality_invalid_network(
|
def test_invalid_ip_locality_invalid_network(
|
||||||
|
@ -97,20 +99,24 @@ class TestIPLocality(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = IpLocalityCheck()
|
validator = IpLocalityCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
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(
|
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
|
assert len(message_list) == 3
|
||||||
for msg in results:
|
|
||||||
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
|
LOG.debug(msg)
|
||||||
|
assert len(msg.get('documents')) > 0
|
||||||
assert msg.get('error')
|
assert msg.get('error')
|
||||||
assert (regex.match(msg.get('message')) is not None
|
assert any([
|
||||||
or regex_1.match(msg.get('message')) is not None
|
regex.search(msg.get('message')),
|
||||||
or regex_2.match(msg.get('message')) is not None)
|
regex_1.search(msg.get('message')),
|
||||||
|
regex_2.search(msg.get('message'))
|
||||||
|
])
|
||||||
|
|
|
@ -14,14 +14,16 @@
|
||||||
"""Test Validation Rule Unique Network"""
|
"""Test Validation Rule Unique Network"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.mtu_rational import MtuRational
|
from drydock_provisioner.orchestrator.validations.mtu_rational import MtuRational
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestMtu(object):
|
class TestMtu(object):
|
||||||
def test_mtu(self, mocker, deckhand_ingester, drydock_state, input_files):
|
def test_mtu(self, mocker, deckhand_ingester, drydock_state, input_files):
|
||||||
|
|
||||||
input_file = input_files.join("validation.yaml")
|
input_file = input_files.join("validation.yaml")
|
||||||
design_ref = "file://%s" % str(input_file)
|
design_ref = "file://%s" % str(input_file)
|
||||||
|
|
||||||
|
@ -31,12 +33,12 @@ class TestMtu(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = MtuRational()
|
validator = MtuRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'Mtu'
|
assert 'MTU' in msg.get('message')
|
||||||
assert msg.get('error') is False
|
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,
|
def test_invalid_mtu(self, mocker, deckhand_ingester, drydock_state,
|
||||||
input_files):
|
input_files):
|
||||||
|
@ -50,19 +52,19 @@ class TestMtu(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = MtuRational()
|
validator = MtuRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('MTU must be between \d+ and \d+')
|
||||||
'Mtu Error: Mtu must be between 1400 and 64000; on Network .+')
|
regex_1 = re.compile('MTU must be <= the parent Network Link')
|
||||||
regex_1 = re.compile(
|
|
||||||
'Mtu Error: Mtu must be <= the parent Network Link; for Network .+'
|
|
||||||
)
|
|
||||||
|
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
|
LOG.debug(msg)
|
||||||
assert msg.get('error')
|
assert msg.get('error')
|
||||||
assert regex.match(
|
assert len(msg.get('documents')) > 0
|
||||||
msg.get('message')) is not None or regex_1.match(
|
assert any([
|
||||||
msg.get('message')) is not None
|
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"""
|
"""Test Validation Rule Rational Network Bond"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.rational_network_bond import RationalNetworkBond
|
from drydock_provisioner.orchestrator.validations.rational_network_bond import RationalNetworkBond
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestRationalNetworkLinkBond(object):
|
class TestRationalNetworkLinkBond(object):
|
||||||
def test_rational_network_bond(self, mocker, deckhand_ingester,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = RationalNetworkBond()
|
validator = RationalNetworkBond()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'Network Link Bonding'
|
|
||||||
assert msg.get('error') is False
|
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,
|
def test_invalid_rational_network_bond(self, mocker, deckhand_ingester,
|
||||||
drydock_state, input_files):
|
drydock_state, input_files):
|
||||||
|
@ -50,20 +53,19 @@ class TestRationalNetworkLinkBond(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = RationalNetworkBond()
|
validator = RationalNetworkBond()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('Down delay \S+ is less than mon rate \S+')
|
||||||
'Network Link Bonding Error: Down delay is less than mon rate on BaremetalNode .+'
|
regex_1 = re.compile('Up delay \S+ is less than mon rate \S+')
|
||||||
)
|
|
||||||
regex_1 = re.compile(
|
|
||||||
'Network Link Bonding Error: Up delay is less than mon rate on BaremetalNode .+'
|
|
||||||
)
|
|
||||||
|
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
assert msg.get('error') is True
|
LOG.debug(msg)
|
||||||
assert regex.match(
|
assert msg.get('error')
|
||||||
msg.get('message')) is not None or regex_1.match(
|
assert len(msg.get('documents')) > 0
|
||||||
msg.get('message')) is not None
|
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"""
|
"""Test Validation Rule Rational Network Trunking"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.network_trunking_rational import NetworkTrunkingRational
|
from drydock_provisioner.orchestrator.validations.network_trunking_rational import NetworkTrunkingRational
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestRationalNetworkTrunking(object):
|
class TestRationalNetworkTrunking(object):
|
||||||
def test_rational_network_trunking(self, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NetworkTrunkingRational()
|
validator = NetworkTrunkingRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'Rational Network Trunking'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_invalid_rational_network_trunking(self, deckhand_ingester,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NetworkTrunkingRational()
|
validator = NetworkTrunkingRational()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile(
|
||||||
'Rational Network Trunking Error: Trunking mode is disabled, a trunking'
|
'Trunking mode is disabled, a trunking default_network must be defined'
|
||||||
'default_network must be defined; on NetworkLink .+')
|
)
|
||||||
|
|
||||||
regex_1 = re.compile(
|
regex_1 = re.compile(
|
||||||
'Rational Network Trunking Error: If there is more than 1 allowed network,'
|
'If there is more than 1 allowed network,trunking mode must be enabled'
|
||||||
'trunking mode must be enabled; on NetworkLink .+')
|
)
|
||||||
|
|
||||||
for msg in results:
|
regex_2 = re.compile('native network has a defined VLAN tag')
|
||||||
|
|
||||||
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
|
LOG.debug(msg)
|
||||||
assert msg.get('error')
|
assert msg.get('error')
|
||||||
assert regex.match(
|
assert len(msg.get('documents')) > 0
|
||||||
msg.get('message')) is not None or regex_1.match(
|
assert any([
|
||||||
msg.get('message')) is not None
|
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.
|
# limitations under the License.
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.validations.no_duplicate_ips_check import NoDuplicateIpsCheck
|
from drydock_provisioner.orchestrator.validations.no_duplicate_ips_check import NoDuplicateIpsCheck
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestDuplicateIPs(object):
|
class TestDuplicateIPs(object):
|
||||||
def test_no_duplicate_IPs(self, input_files, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NoDuplicateIpsCheck()
|
validator = NoDuplicateIpsCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'No Duplicate IP Addresses.'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_no_duplicate_IPs_no_baremetal_node(
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NoDuplicateIpsCheck()
|
validator = NoDuplicateIpsCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
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
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_no_duplicate_IPs_no_addressing(self, input_files, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NoDuplicateIpsCheck()
|
validator = NoDuplicateIpsCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
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
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_invalid_no_duplicate_IPs(self, input_files, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = NoDuplicateIpsCheck()
|
validator = NoDuplicateIpsCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('Duplicate IP Address Found: [0-9.]+')
|
||||||
'Error! Duplicate IP Address Found: .+ is in use by both .+ and .+.'
|
|
||||||
)
|
for msg in message_list:
|
||||||
for msg in results:
|
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
|
LOG.debug(msg)
|
||||||
|
assert len(msg.get('documents')) > 0
|
||||||
assert msg.get('error') is True
|
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"""
|
"""Test Validation Rule Storage Partitioning"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.storage_partititioning import StoragePartitioning
|
from drydock_provisioner.orchestrator.validations.storage_partititioning import StoragePartitioning
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestRationalNetworkTrunking(object):
|
class TestRationalNetworkTrunking(object):
|
||||||
def test_storage_partitioning(self, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = StoragePartitioning()
|
validator = StoragePartitioning()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(message_list) == 1
|
||||||
assert msg.get('message') == 'Storage Partitioning'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_storage_partitioning_unassigned_partition(
|
def test_storage_partitioning_unassigned_partition(
|
||||||
|
@ -50,11 +52,10 @@ class TestRationalNetworkTrunking(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = StoragePartitioning()
|
validator = StoragePartitioning()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(message_list) == 1
|
||||||
assert msg.get('message') == 'Storage Partitioning'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_invalid_storage_partitioning(self, deckhand_ingester,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = StoragePartitioning()
|
validator = StoragePartitioning()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile('Volume group .+ not assigned any physical volumes')
|
||||||
'Storage Partitioning Error: A volume group must be assigned to a storage device or '
|
|
||||||
'partition; volume group .+ on BaremetalNode .+')
|
|
||||||
|
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
|
LOG.debug(msg)
|
||||||
|
assert len(msg.get('documents')) > 0
|
||||||
assert msg.get('error')
|
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"""
|
"""Test Validation Rule Rational Network Trunking"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing
|
from drydock_provisioner.orchestrator.validations.storage_sizing import StorageSizing
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestStorageSizing(object):
|
class TestStorageSizing(object):
|
||||||
def test_storage_sizing(self, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = StorageSizing()
|
validator = StorageSizing()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert len(results) == 1
|
assert len(message_list) == 1
|
||||||
assert msg.get('message') == 'Storage Sizing'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
|
|
||||||
def test_invalid_storage_sizing(self, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = StorageSizing()
|
validator = StorageSizing()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
regex = re.compile(
|
||||||
'Storage Sizing Error: Storage .+ size is < 0 on Baremetal Node .+'
|
'(Storage partition)|(Logical Volume) .+ size is < 0')
|
||||||
)
|
regex_1 = re.compile('greater than 99%')
|
||||||
regex_1 = re.compile(
|
|
||||||
'Storage Sizing Error: Storage .+ size is greater than 99 on Baremetal Node .+'
|
|
||||||
)
|
|
||||||
|
|
||||||
assert len(results) == 6
|
assert len(message_list) == 6
|
||||||
for msg in results:
|
for msg in message_list:
|
||||||
msg = msg.to_dict()
|
msg = msg.to_dict()
|
||||||
assert regex.match(
|
LOG.debug(msg)
|
||||||
msg.get('message')) is not None or regex_1.match(
|
assert regex.search(
|
||||||
|
msg.get('message')) is not None or regex_1.search(
|
||||||
msg.get('message')) is not None
|
msg.get('message')) is not None
|
||||||
assert msg.get('error') is True
|
assert msg.get('error') is True
|
||||||
|
|
|
@ -14,10 +14,13 @@
|
||||||
"""Test Validation Rule Unique Network"""
|
"""Test Validation Rule Unique Network"""
|
||||||
|
|
||||||
import re
|
import re
|
||||||
|
import logging
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck
|
from drydock_provisioner.orchestrator.validations.unique_network_check import UniqueNetworkCheck
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestUniqueNetwork(object):
|
class TestUniqueNetwork(object):
|
||||||
def test_unique_network(self, mocker, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = UniqueNetworkCheck()
|
validator = UniqueNetworkCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
msg = results[0].to_dict()
|
msg = message_list[0].to_dict()
|
||||||
|
|
||||||
assert msg.get('message') == 'Unique Network'
|
|
||||||
assert msg.get('error') is False
|
assert msg.get('error') is False
|
||||||
assert len(results) == 1
|
assert len(message_list) == 1
|
||||||
|
|
||||||
def test_invalid_unique_network(self, mocker, deckhand_ingester,
|
def test_invalid_unique_network(self, mocker, deckhand_ingester,
|
||||||
drydock_state, input_files):
|
drydock_state, input_files):
|
||||||
|
@ -51,15 +53,22 @@ class TestUniqueNetwork(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = UniqueNetworkCheck()
|
validator = UniqueNetworkCheck()
|
||||||
results, message_list = validator.execute(site_design)
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
|
|
||||||
regex = re.compile(
|
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:
|
assert len(message_list) >= 2
|
||||||
msg = msg.to_dict()
|
|
||||||
assert msg.get('error')
|
|
||||||
assert regex.match(msg.get('message')) is not None
|
|
||||||
|
|
||||||
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.
|
# limitations under the License.
|
||||||
"""Test Validation Rule Rational Boot Storage"""
|
"""Test Validation Rule Rational Boot Storage"""
|
||||||
|
|
||||||
|
import logging
|
||||||
|
|
||||||
import drydock_provisioner.config as config
|
import drydock_provisioner.config as config
|
||||||
|
|
||||||
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
from drydock_provisioner.orchestrator.orchestrator import Orchestrator
|
||||||
from drydock_provisioner.orchestrator.validations.platform_selection import PlatformSelection
|
from drydock_provisioner.orchestrator.validations.platform_selection import PlatformSelection
|
||||||
|
|
||||||
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class TestValidPlatform(object):
|
class TestValidPlatform(object):
|
||||||
def test_valid_platform(self, mocker, deckhand_ingester, drydock_state,
|
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)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = PlatformSelection()
|
validator = PlatformSelection()
|
||||||
results, message_list = validator.execute(
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
site_design, orchestrator=orch)
|
|
||||||
for r in results:
|
|
||||||
print(r.to_dict())
|
|
||||||
|
|
||||||
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 msg.get('error') is False
|
||||||
assert len(results) == 1
|
assert len(message_list) == 1
|
||||||
|
|
||||||
def test_invalid_platform(self, mocker, deckhand_ingester, drydock_state,
|
def test_invalid_platform(self, mocker, deckhand_ingester, drydock_state,
|
||||||
input_files):
|
input_files):
|
||||||
|
@ -75,13 +78,13 @@ class TestValidPlatform(object):
|
||||||
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
status, site_design = Orchestrator.get_effective_site(orch, design_ref)
|
||||||
|
|
||||||
validator = PlatformSelection()
|
validator = PlatformSelection()
|
||||||
results, message_list = validator.execute(
|
message_list = validator.execute(site_design, orchestrator=orch)
|
||||||
site_design, orchestrator=orch)
|
|
||||||
|
|
||||||
for r in results:
|
for r in message_list:
|
||||||
print(r.to_dict())
|
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 'invalid kernel lts' in msg.get('message')
|
||||||
assert msg.get('error')
|
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
|
default_network: mgmt
|
||||||
allowed_networks:
|
allowed_networks:
|
||||||
- public
|
- public
|
||||||
|
- private
|
||||||
- mgmt
|
- mgmt
|
||||||
---
|
---
|
||||||
schema: 'drydock/Rack/v1'
|
schema: 'drydock/Rack/v1'
|
||||||
|
|
|
@ -308,6 +308,10 @@ data:
|
||||||
networks:
|
networks:
|
||||||
- mgmt
|
- mgmt
|
||||||
- private
|
- private
|
||||||
|
############
|
||||||
|
### This fails here
|
||||||
|
############
|
||||||
|
- foo
|
||||||
metadata:
|
metadata:
|
||||||
tags:
|
tags:
|
||||||
- 'test'
|
- 'test'
|
||||||
|
|
|
@ -93,6 +93,7 @@ data:
|
||||||
default_network: mgmt
|
default_network: mgmt
|
||||||
allowed_networks:
|
allowed_networks:
|
||||||
- public
|
- public
|
||||||
|
- private
|
||||||
- mgmt
|
- mgmt
|
||||||
---
|
---
|
||||||
schema: 'drydock/Rack/v1'
|
schema: 'drydock/Rack/v1'
|
||||||
|
|
2
tox.ini
2
tox.ini
|
@ -69,7 +69,7 @@ commands = flake8 \
|
||||||
commands = bandit -r drydock_provisioner -n 5
|
commands = bandit -r drydock_provisioner -n 5
|
||||||
|
|
||||||
[flake8]
|
[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/
|
exclude= venv,.venv,.git,.idea,.tox,*.egg-info,*.eggs,bin,dist,./build/,alembic/
|
||||||
max-line-length=119
|
max-line-length=119
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue