Add node attributes validator
Cluster attributes validator is also refactored Change-Id: Iedf640e0cabb38ba082156dd5f68248996926750 Implements: blueprint support-numa-cpu-pinning
This commit is contained in:
parent
955b3b6c15
commit
ee15664ba0
|
@ -28,7 +28,7 @@ from nailgun.api.v1.handlers.base import DeferredTaskHandler
|
|||
from nailgun.api.v1.handlers.base import DeploymentTasksHandler
|
||||
from nailgun.api.v1.handlers.base import SingleHandler
|
||||
|
||||
from nailgun.api.v1.validators.cluster import AttributesValidator
|
||||
from nailgun.api.v1.validators.cluster import ClusterAttributesValidator
|
||||
from nailgun.api.v1.validators.cluster import ClusterChangesValidator
|
||||
from nailgun.api.v1.validators.cluster import ClusterStopDeploymentValidator
|
||||
from nailgun.api.v1.validators.cluster import ClusterValidator
|
||||
|
@ -120,7 +120,7 @@ class ClusterAttributesHandler(BaseHandler):
|
|||
"editable",
|
||||
)
|
||||
|
||||
validator = AttributesValidator
|
||||
validator = ClusterAttributesValidator
|
||||
|
||||
@content
|
||||
def GET(self, cluster_id):
|
||||
|
|
|
@ -27,7 +27,7 @@ from nailgun.api.v1.handlers.base import CollectionHandler
|
|||
from nailgun.api.v1.handlers.base import content
|
||||
from nailgun.api.v1.handlers.base import SingleHandler
|
||||
from nailgun.api.v1.validators.network import NetAssignmentValidator
|
||||
from nailgun.api.v1.validators.node import NodeValidator
|
||||
from nailgun.api.v1.validators import node as node_validators
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.errors import errors
|
||||
|
@ -48,7 +48,7 @@ from nailgun import notifier
|
|||
|
||||
class NodeHandler(SingleHandler):
|
||||
single = objects.Node
|
||||
validator = NodeValidator
|
||||
validator = node_validators.NodeValidator
|
||||
|
||||
@content
|
||||
def DELETE(self, obj_id):
|
||||
|
@ -76,7 +76,7 @@ class NodeHandler(SingleHandler):
|
|||
class NodeCollectionHandler(CollectionHandler):
|
||||
"""Node collection handler"""
|
||||
|
||||
validator = NodeValidator
|
||||
validator = node_validators.NodeValidator
|
||||
collection = objects.NodeCollection
|
||||
|
||||
@content
|
||||
|
@ -159,7 +159,7 @@ class NodeCollectionHandler(CollectionHandler):
|
|||
class NodeAgentHandler(BaseHandler):
|
||||
|
||||
collection = objects.NodeCollection
|
||||
validator = NodeValidator
|
||||
validator = node_validators.NodeValidator
|
||||
|
||||
@content
|
||||
def PUT(self):
|
||||
|
@ -328,7 +328,7 @@ class NodesAllocationStatsHandler(BaseHandler):
|
|||
class NodeAttributesHandler(BaseHandler):
|
||||
"""Node attributes handler"""
|
||||
|
||||
# TODO(asvechnikov): Add validator
|
||||
validator = node_validators.NodeAttributesValidator
|
||||
|
||||
@content
|
||||
def GET(self, node_id):
|
||||
|
|
|
@ -13,14 +13,17 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
|
||||
import jsonschema
|
||||
from jsonschema.exceptions import ValidationError
|
||||
from jsonschema import exceptions
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nailgun.api.v1.validators.json_schema import base_types
|
||||
from nailgun.errors import errors
|
||||
from nailgun import objects
|
||||
from nailgun.utils import restrictions
|
||||
|
||||
|
||||
class BasicValidator(object):
|
||||
|
@ -60,7 +63,7 @@ class BasicValidator(object):
|
|||
|
||||
try:
|
||||
jsonschema.validate(json_req, use_schema)
|
||||
except ValidationError as exc:
|
||||
except exceptions.ValidationError as exc:
|
||||
if len(exc.path) > 0:
|
||||
raise errors.InvalidData(
|
||||
# NOTE(ikutukov): here was a exc.path.pop(). It was buggy
|
||||
|
@ -121,3 +124,80 @@ class BaseDefferedTaskValidator(BasicValidator):
|
|||
@classmethod
|
||||
def validate(cls, cluster):
|
||||
pass
|
||||
|
||||
|
||||
class BasicAttributesValidator(BasicValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data):
|
||||
attrs = cls.validate_json(data)
|
||||
|
||||
cls.validate_attributes(attrs)
|
||||
|
||||
return attrs
|
||||
|
||||
@classmethod
|
||||
def validate_attributes(cls, data):
|
||||
"""Validate attributes."""
|
||||
for attrs in six.itervalues(data):
|
||||
if not isinstance(attrs, dict):
|
||||
continue
|
||||
for attr_name, attr in six.iteritems(attrs):
|
||||
cls.validate_attribute(attr_name, attr)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def validate_attribute(cls, attr_name, attr):
|
||||
"""Validates a single attribute from settings.yaml.
|
||||
|
||||
Dict is of this form::
|
||||
|
||||
description: <description>
|
||||
label: <label>
|
||||
restrictions:
|
||||
- <restriction>
|
||||
- <restriction>
|
||||
- ...
|
||||
type: <type>
|
||||
value: <value>
|
||||
weight: <weight>
|
||||
regex:
|
||||
error: <error message>
|
||||
source: <regexp source>
|
||||
|
||||
We validate that 'value' corresponds to 'type' according to
|
||||
attribute_type_schemas mapping in json_schema/cluster.py.
|
||||
If regex is present, we additionally check that the provided string
|
||||
value matches the regexp.
|
||||
|
||||
:param attr_name: Name of the attribute being checked
|
||||
:param attr: attribute value
|
||||
:return: attribute or raise InvalidData exception
|
||||
"""
|
||||
|
||||
if not isinstance(attr, dict):
|
||||
return attr
|
||||
|
||||
if 'type' not in attr and 'value' not in attr:
|
||||
return attr
|
||||
|
||||
schema = copy.deepcopy(base_types.ATTRIBUTE_SCHEMA)
|
||||
type_ = attr.get('type')
|
||||
if type_:
|
||||
value_schema = base_types.ATTRIBUTE_TYPE_SCHEMAS.get(type_)
|
||||
if value_schema:
|
||||
schema['properties'].update(value_schema)
|
||||
|
||||
try:
|
||||
cls.validate_schema(attr, schema)
|
||||
except errors.InvalidData as e:
|
||||
raise errors.InvalidData('[{0}] {1}'.format(attr_name, e.message))
|
||||
|
||||
# Validate regexp only if some value is present
|
||||
# Otherwise regexp might be invalid
|
||||
if attr['value']:
|
||||
regex_err = restrictions.AttributesRestriction.validate_regex(attr)
|
||||
if regex_err is not None:
|
||||
raise errors.InvalidData(
|
||||
'[{0}] {1}'.format(attr_name, regex_err))
|
||||
|
|
|
@ -13,28 +13,24 @@
|
|||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import copy
|
||||
from distutils.version import StrictVersion
|
||||
from distutils import version
|
||||
from itertools import groupby
|
||||
|
||||
import six
|
||||
import sqlalchemy as sa
|
||||
|
||||
from nailgun.api.v1.validators.base import BaseDefferedTaskValidator
|
||||
from nailgun.api.v1.validators.base import BasicValidator
|
||||
from nailgun.api.v1.validators import base
|
||||
from nailgun.api.v1.validators.json_schema import cluster as cluster_schema
|
||||
from nailgun.api.v1.validators.node import ProvisionSelectedNodesValidator
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun.db import db
|
||||
from nailgun.db.sqlalchemy.models import Node
|
||||
from nailgun.errors import errors
|
||||
from nailgun import objects
|
||||
from nailgun.plugins.manager import PluginManager
|
||||
from nailgun.utils import restrictions
|
||||
|
||||
|
||||
class ClusterValidator(BasicValidator):
|
||||
class ClusterValidator(base.BasicValidator):
|
||||
|
||||
single_schema = cluster_schema.single_schema
|
||||
collection_schema = cluster_schema.collection_schema
|
||||
|
@ -255,7 +251,7 @@ class ClusterValidator(BasicValidator):
|
|||
)
|
||||
|
||||
|
||||
class AttributesValidator(BasicValidator):
|
||||
class ClusterAttributesValidator(base.BasicAttributesValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data, cluster=None, force=False):
|
||||
|
@ -276,15 +272,15 @@ class AttributesValidator(BasicValidator):
|
|||
attrs = objects.Cluster.get_updated_editable_attributes(cluster, d)
|
||||
cls.validate_provision(cluster, attrs)
|
||||
cls.validate_allowed_attributes(cluster, d, force)
|
||||
cls.validate_editable_attributes(attrs)
|
||||
cls.validate_attributes(attrs.get('editable', {}))
|
||||
|
||||
return d
|
||||
|
||||
@classmethod
|
||||
def validate_provision(cls, cluster, attrs):
|
||||
# NOTE(agordeev): disable classic provisioning for 7.0 or higher
|
||||
if StrictVersion(cluster.release.environment_version) >= \
|
||||
StrictVersion(consts.FUEL_IMAGE_BASED_ONLY):
|
||||
if version.StrictVersion(cluster.release.environment_version) >= \
|
||||
version.StrictVersion(consts.FUEL_IMAGE_BASED_ONLY):
|
||||
provision_data = attrs['editable'].get('provision')
|
||||
if provision_data:
|
||||
if provision_data['method']['value'] != \
|
||||
|
@ -298,72 +294,6 @@ class AttributesValidator(BasicValidator):
|
|||
u"Provisioning method is not set. Unable to continue",
|
||||
log_message=True)
|
||||
|
||||
@classmethod
|
||||
def validate_editable_attributes(cls, data):
|
||||
"""Validate 'editable' attributes."""
|
||||
for attrs in data.get('editable', {}).values():
|
||||
if not isinstance(attrs, dict):
|
||||
continue
|
||||
for attr_name, attr in six.iteritems(attrs):
|
||||
cls.validate_attribute(attr_name, attr)
|
||||
|
||||
return data
|
||||
|
||||
@classmethod
|
||||
def validate_attribute(cls, attr_name, attr):
|
||||
"""Validates a single attribute from settings.yaml.
|
||||
|
||||
Dict is of this form:
|
||||
|
||||
description: <description>
|
||||
label: <label>
|
||||
restrictions:
|
||||
- <restriction>
|
||||
- <restriction>
|
||||
- ...
|
||||
type: <type>
|
||||
value: <value>
|
||||
weight: <weight>
|
||||
regex:
|
||||
error: <error message>
|
||||
source: <regexp source>
|
||||
|
||||
We validate that 'value' corresponds to 'type' according to
|
||||
attribute_type_schemas mapping in json_schema/cluster.py.
|
||||
If regex is present, we additionally check that the provided string
|
||||
value matches the regexp.
|
||||
|
||||
:param attr_name: Name of the attribute being checked
|
||||
:param attr: attribute value
|
||||
:return: attribute or raise InvalidData exception
|
||||
"""
|
||||
|
||||
if not isinstance(attr, dict):
|
||||
return attr
|
||||
|
||||
if 'type' not in attr and 'value' not in attr:
|
||||
return attr
|
||||
|
||||
schema = copy.deepcopy(cluster_schema.attribute_schema)
|
||||
type_ = attr.get('type')
|
||||
if type_:
|
||||
value_schema = cluster_schema.attribute_type_schemas.get(type_)
|
||||
if value_schema:
|
||||
schema['properties'].update(value_schema)
|
||||
|
||||
try:
|
||||
cls.validate_schema(attr, schema)
|
||||
except errors.InvalidData as e:
|
||||
raise errors.InvalidData('[{0}] {1}'.format(attr_name, e.message))
|
||||
|
||||
# Validate regexp only if some value is present
|
||||
# Otherwise regexp might be invalid
|
||||
if attr['value']:
|
||||
regex_err = restrictions.AttributesRestriction.validate_regex(attr)
|
||||
if regex_err is not None:
|
||||
raise errors.InvalidData(
|
||||
'[{0}] {1}'.format(attr_name, regex_err))
|
||||
|
||||
@classmethod
|
||||
def validate_allowed_attributes(cls, cluster, data, force):
|
||||
"""Validates if attributes are hot pluggable or not.
|
||||
|
@ -427,7 +357,7 @@ class AttributesValidator(BasicValidator):
|
|||
)
|
||||
|
||||
|
||||
class ClusterChangesValidator(BaseDefferedTaskValidator):
|
||||
class ClusterChangesValidator(base.BaseDefferedTaskValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, cluster):
|
||||
|
@ -435,7 +365,7 @@ class ClusterChangesValidator(BaseDefferedTaskValidator):
|
|||
ProvisionSelectedNodesValidator.validate_provision(None, cluster)
|
||||
|
||||
|
||||
class ClusterStopDeploymentValidator(BaseDefferedTaskValidator):
|
||||
class ClusterStopDeploymentValidator(base.BaseDefferedTaskValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, cluster):
|
||||
|
@ -448,7 +378,7 @@ class ClusterStopDeploymentValidator(BaseDefferedTaskValidator):
|
|||
raise errors.CannotBeStopped()
|
||||
|
||||
|
||||
class VmwareAttributesValidator(BasicValidator):
|
||||
class VmwareAttributesValidator(base.BasicValidator):
|
||||
|
||||
single_schema = cluster_schema.vmware_attributes_schema
|
||||
|
||||
|
|
|
@ -217,3 +217,107 @@ UI_SETTINGS = {
|
|||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
ATTRIBUTE_SCHEMA = {
|
||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||
'title': 'Schema for single editable attribute',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {
|
||||
'enum': [
|
||||
'checkbox',
|
||||
'custom_repo_configuration',
|
||||
'hidden',
|
||||
'password',
|
||||
'radio',
|
||||
'select',
|
||||
'text',
|
||||
'textarea',
|
||||
'file',
|
||||
'text_list',
|
||||
'textarea_list',
|
||||
'custom_hugepages'
|
||||
]
|
||||
},
|
||||
# 'value': None, # custom validation depending on type
|
||||
'restrictions': RESTRICTIONS,
|
||||
'weight': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
},
|
||||
},
|
||||
'required': ['type', 'value'],
|
||||
}
|
||||
|
||||
# Schema with allowed values for 'radio' and 'select' attribute types
|
||||
ALLOWED_VALUES_SCHEMA = {
|
||||
'value': {
|
||||
'type': 'string',
|
||||
},
|
||||
'values': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': [
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'data': {'type': 'string'},
|
||||
'label': {'type': 'string'},
|
||||
'description': {'type': 'string'},
|
||||
'restrictions': RESTRICTIONS,
|
||||
},
|
||||
'required': ['data', 'label'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# Schema with a structure of multiple text fields setting value
|
||||
MULTIPLE_TEXT_FIELDS_SCHEMA = {
|
||||
'value': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'min': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
},
|
||||
'max': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
}
|
||||
}
|
||||
|
||||
# Additional properties definitions for 'attirbute_schema'
|
||||
# depending on 'type' property
|
||||
ATTRIBUTE_TYPE_SCHEMAS = {
|
||||
'checkbox': {'value': {'type': 'boolean'}},
|
||||
'custom_repo_configuration': {
|
||||
'value': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': [
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'},
|
||||
'priority': {'type': ['integer', 'null']},
|
||||
'section': {'type': 'string'},
|
||||
'suite': {'type': 'string'},
|
||||
'type': {'type': 'string'},
|
||||
'uri': {'type': 'string'},
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
'password': {'value': {'type': 'string'}},
|
||||
'radio': ALLOWED_VALUES_SCHEMA,
|
||||
'select': ALLOWED_VALUES_SCHEMA,
|
||||
'text': {'value': {'type': 'string'}},
|
||||
'textarea': {'value': {'type': 'string'}},
|
||||
'text_list': MULTIPLE_TEXT_FIELDS_SCHEMA,
|
||||
'textarea_list': MULTIPLE_TEXT_FIELDS_SCHEMA
|
||||
}
|
||||
|
|
|
@ -65,108 +65,6 @@ collection_schema = {
|
|||
"items": single_schema["properties"]
|
||||
}
|
||||
|
||||
attribute_schema = {
|
||||
'$schema': 'http://json-schema.org/draft-04/schema#',
|
||||
'title': 'Schema for single editable attribute',
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'type': {
|
||||
'enum': [
|
||||
'checkbox',
|
||||
'custom_repo_configuration',
|
||||
'hidden',
|
||||
'password',
|
||||
'radio',
|
||||
'select',
|
||||
'text',
|
||||
'textarea',
|
||||
'file',
|
||||
'text_list',
|
||||
'textarea_list'
|
||||
]
|
||||
},
|
||||
# 'value': None, # custom validation depending on type
|
||||
'restrictions': base_types.RESTRICTIONS,
|
||||
'weight': {
|
||||
'type': 'integer',
|
||||
'minimum': 0,
|
||||
},
|
||||
},
|
||||
'required': ['type', 'value'],
|
||||
}
|
||||
|
||||
# Schema with allowed values for 'radio' and 'select' attribute types
|
||||
allowed_values_schema = {
|
||||
'value': {
|
||||
'type': 'string',
|
||||
},
|
||||
'values': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': [
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'data': {'type': 'string'},
|
||||
'label': {'type': 'string'},
|
||||
'description': {'type': 'string'},
|
||||
'restrictions': base_types.RESTRICTIONS,
|
||||
},
|
||||
'required': ['data', 'label'],
|
||||
},
|
||||
],
|
||||
},
|
||||
}
|
||||
|
||||
# Schema with a structure of multiple text fields setting value
|
||||
multiple_text_fields_schema = {
|
||||
'value': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': {'type': 'string'},
|
||||
},
|
||||
'min': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
},
|
||||
'max': {
|
||||
'type': 'integer',
|
||||
'minimum': 1,
|
||||
}
|
||||
}
|
||||
|
||||
# Additional properties definitions for 'attirbute_schema'
|
||||
# depending on 'type' property
|
||||
attribute_type_schemas = {
|
||||
'checkbox': {'value': {'type': 'boolean'}},
|
||||
'custom_repo_configuration': {
|
||||
'value': {
|
||||
'type': 'array',
|
||||
'minItems': 1,
|
||||
'items': [
|
||||
{
|
||||
'type': 'object',
|
||||
'properties': {
|
||||
'name': {'type': 'string'},
|
||||
'priority': {'type': ['integer', 'null']},
|
||||
'section': {'type': 'string'},
|
||||
'suite': {'type': 'string'},
|
||||
'type': {'type': 'string'},
|
||||
'uri': {'type': 'string'},
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
},
|
||||
'password': {'value': {'type': 'string'}},
|
||||
'radio': allowed_values_schema,
|
||||
'select': allowed_values_schema,
|
||||
'text': {'value': {'type': 'string'}},
|
||||
'textarea': {'value': {'type': 'string'}},
|
||||
'text_list': multiple_text_fields_schema,
|
||||
'textarea_list': multiple_text_fields_schema
|
||||
}
|
||||
|
||||
vmware_attributes_schema = {
|
||||
"$schema": "http://json-schema.org/draft-04/schema#",
|
||||
"title": "Vmware attributes",
|
||||
|
|
|
@ -15,21 +15,19 @@
|
|||
|
||||
import six
|
||||
|
||||
from nailgun.api.v1.validators.base import BasicValidator
|
||||
from nailgun.api.v1.validators import base
|
||||
from nailgun.api.v1.validators.graph import TaskDeploymentValidator
|
||||
from nailgun.api.v1.validators.json_schema import base_types
|
||||
from nailgun.api.v1.validators.json_schema import node_schema
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun import objects
|
||||
|
||||
from nailgun.db import db
|
||||
from nailgun.db.sqlalchemy.models import Node
|
||||
from nailgun.db.sqlalchemy.models import NodeNICInterface
|
||||
from nailgun.errors import errors
|
||||
from nailgun import objects
|
||||
|
||||
|
||||
class MetaInterfacesValidator(BasicValidator):
|
||||
class MetaInterfacesValidator(base.BasicValidator):
|
||||
@classmethod
|
||||
def _validate_data(cls, interfaces):
|
||||
if not isinstance(interfaces, list):
|
||||
|
@ -67,7 +65,7 @@ class MetaInterfacesValidator(BasicValidator):
|
|||
return interfaces
|
||||
|
||||
|
||||
class MetaValidator(BasicValidator):
|
||||
class MetaValidator(base.BasicValidator):
|
||||
@classmethod
|
||||
def _validate_data(cls, meta):
|
||||
if not isinstance(meta, dict):
|
||||
|
@ -101,7 +99,7 @@ class MetaValidator(BasicValidator):
|
|||
return meta
|
||||
|
||||
|
||||
class NodeValidator(BasicValidator):
|
||||
class NodeValidator(base.BasicValidator):
|
||||
|
||||
single_schema = node_schema.single_schema
|
||||
|
||||
|
@ -315,7 +313,7 @@ class NodeValidator(BasicValidator):
|
|||
return d
|
||||
|
||||
|
||||
class NodesFilterValidator(BasicValidator):
|
||||
class NodesFilterValidator(base.BasicValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, nodes):
|
||||
|
@ -420,3 +418,10 @@ class NodeDeploymentValidator(TaskDeploymentValidator,
|
|||
raise errors.InvalidData('Tasks list must be specified.')
|
||||
|
||||
return data
|
||||
|
||||
|
||||
class NodeAttributesValidator(base.BasicAttributesValidator):
|
||||
|
||||
@classmethod
|
||||
def validate(cls, data, node=None):
|
||||
return super(NodeAttributesValidator, cls).validate(data)
|
||||
|
|
|
@ -15,20 +15,18 @@
|
|||
# under the License.
|
||||
|
||||
from mock import patch
|
||||
from oslo_serialization import jsonutils
|
||||
import six
|
||||
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from nailgun import consts
|
||||
from nailgun import objects
|
||||
|
||||
from nailgun.db.sqlalchemy.models import Release
|
||||
from nailgun import objects
|
||||
from nailgun.settings import settings
|
||||
from nailgun.test.base import BaseIntegrationTest
|
||||
from nailgun.utils import reverse
|
||||
|
||||
|
||||
class TestAttributes(BaseIntegrationTest):
|
||||
class TestClusterAttributes(BaseIntegrationTest):
|
||||
|
||||
def test_attributes_creation(self):
|
||||
cluster = self.env.create_cluster(api=True)
|
||||
|
|
|
@ -432,11 +432,13 @@ class TestHandlers(BaseIntegrationTest):
|
|||
'group1': {
|
||||
'metadata': {},
|
||||
'comp1': {
|
||||
'value': 42
|
||||
'type': 'text',
|
||||
'value': '42'
|
||||
}
|
||||
},
|
||||
'group2': {
|
||||
'comp2': {
|
||||
'type': 'text',
|
||||
'value': 'value1'
|
||||
}
|
||||
}
|
||||
|
@ -445,7 +447,8 @@ class TestHandlers(BaseIntegrationTest):
|
|||
update_attributes = {
|
||||
'group1': {
|
||||
'comp1': {
|
||||
'value': 41
|
||||
'type': 'text',
|
||||
'value': '41'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -454,6 +457,6 @@ class TestHandlers(BaseIntegrationTest):
|
|||
jsonutils.dumps(update_attributes),
|
||||
headers=self.default_headers)
|
||||
|
||||
fake_attributes['group1']['comp1']['value'] = 41
|
||||
fake_attributes['group1']['comp1']['value'] = '41'
|
||||
self.assertEqual(200, resp.status_code)
|
||||
self.assertEqual(fake_attributes, resp.json_body)
|
||||
|
|
|
@ -12,353 +12,379 @@
|
|||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
from mock import Mock
|
||||
from mock import patch
|
||||
|
||||
import json
|
||||
|
||||
import mock
|
||||
import yaml
|
||||
|
||||
from nailgun.api.v1.validators.cluster import AttributesValidator
|
||||
from nailgun.api.v1.validators import base
|
||||
from nailgun.api.v1.validators import cluster
|
||||
from nailgun.errors import errors
|
||||
from nailgun.test.base import BaseTestCase
|
||||
from nailgun.test import base as base_test
|
||||
|
||||
|
||||
class TestAttributesValidator(BaseTestCase):
|
||||
class TestClusterAttributesValidator(base_test.BaseTestCase):
|
||||
def test_generated_attributes_validation(self):
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
'{"generated": {"name": "test"}}')
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
'{"generated": {"name": "test"}}')
|
||||
|
||||
def test_editable_attributes_validation(self):
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
'{"editable": "name"}')
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
'{"editable": "name"}')
|
||||
|
||||
def test_missing_type(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
value: 'x'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_missing_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_invalid_regexp(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: '212a'
|
||||
regex:
|
||||
error: Invalid
|
||||
source: ^\d+$
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_checkbox_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
value: true
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
value: 'x'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_custom_repo_configuration_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
repos:
|
||||
description: desc
|
||||
type: custom_repo_configuration
|
||||
value:
|
||||
- name: ubuntu
|
||||
priority: null
|
||||
section: main universe multiverse
|
||||
suite: trusty
|
||||
type: deb
|
||||
uri: http://archive.ubuntu.com/ubuntu/
|
||||
- name: ubuntu-updates
|
||||
priority: null
|
||||
section: main universe multiverse
|
||||
suite: trusty-updates
|
||||
type: deb
|
||||
uri: http://archive.ubuntu.com/ubuntu/
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_password_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: password
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: password
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_radio_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
syslog_transport:
|
||||
label: Syslog transport protocol
|
||||
type: radio
|
||||
value: tcp
|
||||
values:
|
||||
- data: udp
|
||||
description: ''
|
||||
label: UDP
|
||||
- data: tcp
|
||||
description: ''
|
||||
label: TCP
|
||||
- data: missing-description
|
||||
label: Missing Description
|
||||
weight: 3
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_select_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
common:
|
||||
libvirt_type:
|
||||
label: Hypervisor type
|
||||
type: select
|
||||
value: qemu
|
||||
values:
|
||||
- data: kvm
|
||||
label: KVM
|
||||
description: KVM description
|
||||
- data: qemu
|
||||
label: QEMU
|
||||
description: QEMU description
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_text_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_textarea_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: textarea
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: textarea
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_text_list_value(self):
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text_list
|
||||
value: ['2']
|
||||
weight: 80
|
||||
'''
|
||||
# check that text_list value is a list
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
editable:
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text_list
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
@patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
@mock.patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
def test_invalid_provisioning_method(self, mock_cluster_attrs):
|
||||
attrs = {'editable': {'provision': {'method':
|
||||
{'value': 'not_image', 'type': 'text'}}}}
|
||||
mock_cluster_attrs.return_value = attrs
|
||||
cluster_mock = Mock(release=Mock(environment_version='7.0'))
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
cluster_mock = mock.Mock(release=mock.Mock(environment_version='7.0'))
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
|
||||
@patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
@mock.patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
def test_provision_method_missing(self, mock_cluster_attrs):
|
||||
attrs = {'editable': {'method':
|
||||
{'value': 'not_image', 'type': 'text'}}}
|
||||
mock_cluster_attrs.return_value = attrs
|
||||
cluster_mock = Mock(release=Mock(environment_version='7.0'))
|
||||
self.assertRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
cluster_mock = mock.Mock(release=mock.Mock(environment_version='7.0'))
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
|
||||
@patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
@mock.patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
def test_provision_method_passed(self, mock_cluster_attrs):
|
||||
attrs = {'editable': {'provision': {'method':
|
||||
{'value': 'image', 'type': 'text'}}}}
|
||||
mock_cluster_attrs.return_value = attrs
|
||||
cluster_mock = Mock(
|
||||
is_locked=False, release=Mock(environment_version='7.0')
|
||||
cluster_mock = mock.Mock(
|
||||
is_locked=False, release=mock.Mock(environment_version='7.0')
|
||||
)
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
|
||||
@patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
@mock.patch('nailgun.objects.Cluster.get_updated_editable_attributes')
|
||||
def test_provision_method_passed_old(self, mock_cluster_attrs):
|
||||
attrs = {'editable': {'provision': {'method':
|
||||
{'value': 'image', 'type': 'text'}}}}
|
||||
mock_cluster_attrs.return_value = attrs
|
||||
cluster_mock = Mock(
|
||||
is_locked=False, release=Mock(environment_version='6.0')
|
||||
cluster_mock = mock.Mock(
|
||||
is_locked=False, release=mock.Mock(environment_version='6.0')
|
||||
)
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
json.dumps(attrs), cluster_mock)
|
||||
|
||||
def test_valid_attributes(self):
|
||||
valid_attibutes = [
|
||||
'{"editable": {"name": "test"}}',
|
||||
'{"group": {"name": "test"}}',
|
||||
'{"name": "test"}',
|
||||
]
|
||||
|
||||
for attributes in valid_attibutes:
|
||||
self.assertNotRaises(errors.InvalidData,
|
||||
AttributesValidator.validate,
|
||||
attributes)
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
AttributesValidator.validate_editable_attributes,
|
||||
cluster.ClusterAttributesValidator.validate,
|
||||
attributes)
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
cluster.ClusterAttributesValidator.validate_attributes,
|
||||
yaml.load(attributes))
|
||||
|
||||
|
||||
class TestBasicAttributesValidator(base_test.BaseTestCase):
|
||||
def test_missing_type(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
value: 'x'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_missing_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_invalid_regexp(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: '212a'
|
||||
regex:
|
||||
error: Invalid
|
||||
source: ^\d+$
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_checkbox_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
value: true
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: checkbox
|
||||
value: 'x'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_custom_repo_configuration_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
repos:
|
||||
description: desc
|
||||
type: custom_repo_configuration
|
||||
value:
|
||||
- name: ubuntu
|
||||
priority: null
|
||||
section: main universe multiverse
|
||||
suite: trusty
|
||||
type: deb
|
||||
uri: http://archive.ubuntu.com/ubuntu/
|
||||
- name: ubuntu-updates
|
||||
priority: null
|
||||
section: main universe multiverse
|
||||
suite: trusty-updates
|
||||
type: deb
|
||||
uri: http://archive.ubuntu.com/ubuntu/
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_password_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: password
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: password
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_radio_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
syslog_transport:
|
||||
label: Syslog transport protocol
|
||||
type: radio
|
||||
value: tcp
|
||||
values:
|
||||
- data: udp
|
||||
description: ''
|
||||
label: UDP
|
||||
- data: tcp
|
||||
description: ''
|
||||
label: TCP
|
||||
- data: missing-description
|
||||
label: Missing Description
|
||||
weight: 3
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_select_value(self):
|
||||
attrs = '''
|
||||
common:
|
||||
libvirt_type:
|
||||
label: Hypervisor type
|
||||
type: select
|
||||
value: qemu
|
||||
values:
|
||||
- data: kvm
|
||||
label: KVM
|
||||
description: KVM description
|
||||
- data: qemu
|
||||
label: QEMU
|
||||
description: QEMU description
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_text_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_textarea_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: textarea
|
||||
value: '2'
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: textarea
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_text_list_value(self):
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text_list
|
||||
value: ['2']
|
||||
weight: 80
|
||||
'''
|
||||
# check that text_list value is a list
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
attrs = '''
|
||||
storage:
|
||||
osd_pool_size:
|
||||
description: desc
|
||||
label: OSD Pool Size
|
||||
type: text_list
|
||||
value: 2
|
||||
weight: 80
|
||||
'''
|
||||
|
||||
self.assertRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attrs))
|
||||
|
||||
def test_valid_attributes(self):
|
||||
valid_attibutes = [
|
||||
'{"group": {"name": "test"}}',
|
||||
'{"name": "test"}',
|
||||
]
|
||||
|
||||
for attributes in valid_attibutes:
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate,
|
||||
attributes)
|
||||
self.assertNotRaises(
|
||||
errors.InvalidData,
|
||||
base.BasicAttributesValidator.validate_attributes,
|
||||
yaml.load(attributes))
|
||||
|
|
Loading…
Reference in New Issue