Merge "Add node attributes validator"

This commit is contained in:
Jenkins 2016-03-09 11:45:45 +00:00 committed by Gerrit Code Review
commit 4f04cbade3
10 changed files with 561 additions and 517 deletions

View File

@ -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 DeploymentTasksHandler
from nailgun.api.v1.handlers.base import SingleHandler 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 ClusterChangesValidator
from nailgun.api.v1.validators.cluster import ClusterStopDeploymentValidator from nailgun.api.v1.validators.cluster import ClusterStopDeploymentValidator
from nailgun.api.v1.validators.cluster import ClusterValidator from nailgun.api.v1.validators.cluster import ClusterValidator
@ -121,7 +121,7 @@ class ClusterAttributesHandler(BaseHandler):
"editable", "editable",
) )
validator = AttributesValidator validator = ClusterAttributesValidator
@content @content
def GET(self, cluster_id): def GET(self, cluster_id):

View File

@ -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 content
from nailgun.api.v1.handlers.base import SingleHandler from nailgun.api.v1.handlers.base import SingleHandler
from nailgun.api.v1.validators.network import NetAssignmentValidator 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 import consts
from nailgun.errors import errors from nailgun.errors import errors
@ -48,7 +48,7 @@ from nailgun import notifier
class NodeHandler(SingleHandler): class NodeHandler(SingleHandler):
single = objects.Node single = objects.Node
validator = NodeValidator validator = node_validators.NodeValidator
@content @content
def DELETE(self, obj_id): def DELETE(self, obj_id):
@ -76,7 +76,7 @@ class NodeHandler(SingleHandler):
class NodeCollectionHandler(CollectionHandler): class NodeCollectionHandler(CollectionHandler):
"""Node collection handler""" """Node collection handler"""
validator = NodeValidator validator = node_validators.NodeValidator
collection = objects.NodeCollection collection = objects.NodeCollection
@content @content
@ -159,7 +159,7 @@ class NodeCollectionHandler(CollectionHandler):
class NodeAgentHandler(BaseHandler): class NodeAgentHandler(BaseHandler):
collection = objects.NodeCollection collection = objects.NodeCollection
validator = NodeValidator validator = node_validators.NodeValidator
@content @content
def PUT(self): def PUT(self):
@ -328,7 +328,7 @@ class NodesAllocationStatsHandler(BaseHandler):
class NodeAttributesHandler(BaseHandler): class NodeAttributesHandler(BaseHandler):
"""Node attributes handler""" """Node attributes handler"""
# TODO(asvechnikov): Add validator validator = node_validators.NodeAttributesValidator
@content @content
def GET(self, node_id): def GET(self, node_id):

View File

@ -13,14 +13,17 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy
import jsonschema import jsonschema
from jsonschema.exceptions import ValidationError from jsonschema import exceptions
from oslo_serialization import jsonutils
import six import six
from oslo_serialization import jsonutils from nailgun.api.v1.validators.json_schema import base_types
from nailgun.errors import errors from nailgun.errors import errors
from nailgun import objects from nailgun import objects
from nailgun.utils import restrictions
class BasicValidator(object): class BasicValidator(object):
@ -60,7 +63,7 @@ class BasicValidator(object):
try: try:
jsonschema.validate(json_req, use_schema) jsonschema.validate(json_req, use_schema)
except ValidationError as exc: except exceptions.ValidationError as exc:
if len(exc.path) > 0: if len(exc.path) > 0:
raise errors.InvalidData( raise errors.InvalidData(
# NOTE(ikutukov): here was a exc.path.pop(). It was buggy # NOTE(ikutukov): here was a exc.path.pop(). It was buggy
@ -121,3 +124,80 @@ class BaseDefferedTaskValidator(BasicValidator):
@classmethod @classmethod
def validate(cls, cluster): def validate(cls, cluster):
pass 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))

View File

@ -13,28 +13,24 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
import copy from distutils import version
from distutils.version import StrictVersion
from itertools import groupby from itertools import groupby
import six import six
import sqlalchemy as sa import sqlalchemy as sa
from nailgun.api.v1.validators.base import BaseDefferedTaskValidator from nailgun.api.v1.validators import base
from nailgun.api.v1.validators.base import BasicValidator
from nailgun.api.v1.validators.json_schema import cluster as cluster_schema from nailgun.api.v1.validators.json_schema import cluster as cluster_schema
from nailgun.api.v1.validators.node import ProvisionSelectedNodesValidator from nailgun.api.v1.validators.node import ProvisionSelectedNodesValidator
from nailgun import consts from nailgun import consts
from nailgun.db import db from nailgun.db import db
from nailgun.db.sqlalchemy.models import Node from nailgun.db.sqlalchemy.models import Node
from nailgun.errors import errors from nailgun.errors import errors
from nailgun import objects from nailgun import objects
from nailgun.plugins.manager import PluginManager from nailgun.plugins.manager import PluginManager
from nailgun.utils import restrictions
class ClusterValidator(BasicValidator): class ClusterValidator(base.BasicValidator):
single_schema = cluster_schema.single_schema single_schema = cluster_schema.single_schema
collection_schema = cluster_schema.collection_schema collection_schema = cluster_schema.collection_schema
@ -219,7 +215,7 @@ class ClusterValidator(BasicValidator):
) )
class AttributesValidator(BasicValidator): class ClusterAttributesValidator(base.BasicAttributesValidator):
@classmethod @classmethod
def validate(cls, data, cluster=None, force=False): def validate(cls, data, cluster=None, force=False):
@ -240,15 +236,15 @@ class AttributesValidator(BasicValidator):
attrs = objects.Cluster.get_updated_editable_attributes(cluster, d) attrs = objects.Cluster.get_updated_editable_attributes(cluster, d)
cls.validate_provision(cluster, attrs) cls.validate_provision(cluster, attrs)
cls.validate_allowed_attributes(cluster, d, force) cls.validate_allowed_attributes(cluster, d, force)
cls.validate_editable_attributes(attrs) cls.validate_attributes(attrs.get('editable', {}))
return d return d
@classmethod @classmethod
def validate_provision(cls, cluster, attrs): def validate_provision(cls, cluster, attrs):
# NOTE(agordeev): disable classic provisioning for 7.0 or higher # NOTE(agordeev): disable classic provisioning for 7.0 or higher
if StrictVersion(cluster.release.environment_version) >= \ if version.StrictVersion(cluster.release.environment_version) >= \
StrictVersion(consts.FUEL_IMAGE_BASED_ONLY): version.StrictVersion(consts.FUEL_IMAGE_BASED_ONLY):
provision_data = attrs['editable'].get('provision') provision_data = attrs['editable'].get('provision')
if provision_data: if provision_data:
if provision_data['method']['value'] != \ if provision_data['method']['value'] != \
@ -262,72 +258,6 @@ class AttributesValidator(BasicValidator):
u"Provisioning method is not set. Unable to continue", u"Provisioning method is not set. Unable to continue",
log_message=True) 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 @classmethod
def validate_allowed_attributes(cls, cluster, data, force): def validate_allowed_attributes(cls, cluster, data, force):
"""Validates if attributes are hot pluggable or not. """Validates if attributes are hot pluggable or not.
@ -391,7 +321,7 @@ class AttributesValidator(BasicValidator):
) )
class ClusterChangesValidator(BaseDefferedTaskValidator): class ClusterChangesValidator(base.BaseDefferedTaskValidator):
@classmethod @classmethod
def validate(cls, cluster): def validate(cls, cluster):
@ -399,7 +329,7 @@ class ClusterChangesValidator(BaseDefferedTaskValidator):
ProvisionSelectedNodesValidator.validate_provision(None, cluster) ProvisionSelectedNodesValidator.validate_provision(None, cluster)
class ClusterStopDeploymentValidator(BaseDefferedTaskValidator): class ClusterStopDeploymentValidator(base.BaseDefferedTaskValidator):
@classmethod @classmethod
def validate(cls, cluster): def validate(cls, cluster):
@ -412,7 +342,7 @@ class ClusterStopDeploymentValidator(BaseDefferedTaskValidator):
raise errors.CannotBeStopped() raise errors.CannotBeStopped()
class VmwareAttributesValidator(BasicValidator): class VmwareAttributesValidator(base.BasicValidator):
single_schema = cluster_schema.vmware_attributes_schema single_schema = cluster_schema.vmware_attributes_schema

View File

@ -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
}

View File

@ -64,108 +64,6 @@ collection_schema = {
"items": single_schema["properties"] "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 = { vmware_attributes_schema = {
"$schema": "http://json-schema.org/draft-04/schema#", "$schema": "http://json-schema.org/draft-04/schema#",
"title": "Vmware attributes", "title": "Vmware attributes",

View File

@ -15,21 +15,19 @@
import six 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.graph import TaskDeploymentValidator
from nailgun.api.v1.validators.json_schema import base_types from nailgun.api.v1.validators.json_schema import base_types
from nailgun.api.v1.validators.json_schema import node_schema from nailgun.api.v1.validators.json_schema import node_schema
from nailgun import consts from nailgun import consts
from nailgun import objects
from nailgun.db import db from nailgun.db import db
from nailgun.db.sqlalchemy.models import Node from nailgun.db.sqlalchemy.models import Node
from nailgun.db.sqlalchemy.models import NodeNICInterface from nailgun.db.sqlalchemy.models import NodeNICInterface
from nailgun.errors import errors from nailgun.errors import errors
from nailgun import objects
class MetaInterfacesValidator(BasicValidator): class MetaInterfacesValidator(base.BasicValidator):
@classmethod @classmethod
def _validate_data(cls, interfaces): def _validate_data(cls, interfaces):
if not isinstance(interfaces, list): if not isinstance(interfaces, list):
@ -67,7 +65,7 @@ class MetaInterfacesValidator(BasicValidator):
return interfaces return interfaces
class MetaValidator(BasicValidator): class MetaValidator(base.BasicValidator):
@classmethod @classmethod
def _validate_data(cls, meta): def _validate_data(cls, meta):
if not isinstance(meta, dict): if not isinstance(meta, dict):
@ -101,7 +99,7 @@ class MetaValidator(BasicValidator):
return meta return meta
class NodeValidator(BasicValidator): class NodeValidator(base.BasicValidator):
single_schema = node_schema.single_schema single_schema = node_schema.single_schema
@ -315,7 +313,7 @@ class NodeValidator(BasicValidator):
return d return d
class NodesFilterValidator(BasicValidator): class NodesFilterValidator(base.BasicValidator):
@classmethod @classmethod
def validate(cls, nodes): def validate(cls, nodes):
@ -437,3 +435,10 @@ class NodeDeploymentValidator(TaskDeploymentValidator,
raise errors.InvalidData('Tasks list must be specified.') raise errors.InvalidData('Tasks list must be specified.')
return data return data
class NodeAttributesValidator(base.BasicAttributesValidator):
@classmethod
def validate(cls, data, node=None):
return super(NodeAttributesValidator, cls).validate(data)

View File

@ -15,20 +15,18 @@
# under the License. # under the License.
from mock import patch from mock import patch
from oslo_serialization import jsonutils
import six import six
from oslo_serialization import jsonutils
from nailgun import consts from nailgun import consts
from nailgun import objects
from nailgun.db.sqlalchemy.models import Release from nailgun.db.sqlalchemy.models import Release
from nailgun import objects
from nailgun.settings import settings from nailgun.settings import settings
from nailgun.test.base import BaseIntegrationTest from nailgun.test.base import BaseIntegrationTest
from nailgun.utils import reverse from nailgun.utils import reverse
class TestAttributes(BaseIntegrationTest): class TestClusterAttributes(BaseIntegrationTest):
def test_attributes_creation(self): def test_attributes_creation(self):
cluster = self.env.create_cluster(api=True) cluster = self.env.create_cluster(api=True)

View File

@ -431,11 +431,13 @@ class TestHandlers(BaseIntegrationTest):
'group1': { 'group1': {
'metadata': {}, 'metadata': {},
'comp1': { 'comp1': {
'value': 42 'type': 'text',
'value': '42'
} }
}, },
'group2': { 'group2': {
'comp2': { 'comp2': {
'type': 'text',
'value': 'value1' 'value': 'value1'
} }
} }
@ -444,7 +446,8 @@ class TestHandlers(BaseIntegrationTest):
update_attributes = { update_attributes = {
'group1': { 'group1': {
'comp1': { 'comp1': {
'value': 41 'type': 'text',
'value': '41'
} }
} }
} }
@ -453,6 +456,6 @@ class TestHandlers(BaseIntegrationTest):
jsonutils.dumps(update_attributes), jsonutils.dumps(update_attributes),
headers=self.default_headers) headers=self.default_headers)
fake_attributes['group1']['comp1']['value'] = 41 fake_attributes['group1']['comp1']['value'] = '41'
self.assertEqual(200, resp.status_code) self.assertEqual(200, resp.status_code)
self.assertEqual(fake_attributes, resp.json_body) self.assertEqual(fake_attributes, resp.json_body)

View File

@ -12,31 +12,99 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from mock import Mock
from mock import patch
import json import json
import mock
import yaml 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.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): def test_generated_attributes_validation(self):
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate, errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
'{"generated": {"name": "test"}}') '{"generated": {"name": "test"}}')
def test_editable_attributes_validation(self): def test_editable_attributes_validation(self):
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate, errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
'{"editable": "name"}') '{"editable": "name"}')
@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.Mock(release=mock.Mock(environment_version='7.0'))
self.assertRaises(
errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
json.dumps(attrs), cluster_mock)
@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.Mock(release=mock.Mock(environment_version='7.0'))
self.assertRaises(
errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
json.dumps(attrs), cluster_mock)
@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.Mock(
is_locked=False, release=mock.Mock(environment_version='7.0')
)
self.assertNotRaises(
errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
json.dumps(attrs), cluster_mock)
@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.Mock(
is_locked=False, release=mock.Mock(environment_version='6.0')
)
self.assertNotRaises(
errors.InvalidData,
cluster.ClusterAttributesValidator.validate,
json.dumps(attrs), cluster_mock)
def test_valid_attributes(self):
valid_attibutes = [
'{"group": {"name": "test"}}',
'{"name": "test"}',
]
for attributes in valid_attibutes:
self.assertNotRaises(
errors.InvalidData,
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): def test_missing_type(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -45,13 +113,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_missing_value(self): def test_missing_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -60,13 +128,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_invalid_regexp(self): def test_invalid_regexp(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -79,13 +147,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_checkbox_value(self): def test_checkbox_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -95,11 +163,11 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -109,13 +177,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_custom_repo_configuration_value(self): def test_custom_repo_configuration_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
repos: repos:
description: desc description: desc
@ -135,13 +203,13 @@ class TestAttributesValidator(BaseTestCase):
uri: http://archive.ubuntu.com/ubuntu/ uri: http://archive.ubuntu.com/ubuntu/
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_password_value(self): def test_password_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -151,11 +219,11 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -165,13 +233,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_radio_value(self): def test_radio_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
syslog_transport: syslog_transport:
label: Syslog transport protocol label: Syslog transport protocol
@ -189,13 +257,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 3 weight: 3
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_select_value(self): def test_select_value(self):
attrs = ''' attrs = '''
editable:
common: common:
libvirt_type: libvirt_type:
label: Hypervisor type label: Hypervisor type
@ -210,13 +278,13 @@ class TestAttributesValidator(BaseTestCase):
description: QEMU description description: QEMU description
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_text_value(self): def test_text_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -226,11 +294,11 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -240,13 +308,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_textarea_value(self): def test_textarea_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -256,11 +324,11 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -270,13 +338,13 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
def test_text_list_value(self): def test_text_list_value(self):
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -286,11 +354,11 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
# check that text_list value is a list # check that text_list value is a list
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
attrs = ''' attrs = '''
editable:
storage: storage:
osd_pool_size: osd_pool_size:
description: desc description: desc
@ -300,65 +368,23 @@ class TestAttributesValidator(BaseTestCase):
weight: 80 weight: 80
''' '''
self.assertRaises(errors.InvalidData, self.assertRaises(
AttributesValidator.validate_editable_attributes, errors.InvalidData,
base.BasicAttributesValidator.validate_attributes,
yaml.load(attrs)) yaml.load(attrs))
@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)
@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)
@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')
)
self.assertNotRaises(errors.InvalidData,
AttributesValidator.validate,
json.dumps(attrs), cluster_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')
)
self.assertNotRaises(errors.InvalidData,
AttributesValidator.validate,
json.dumps(attrs), cluster_mock)
def test_valid_attributes(self): def test_valid_attributes(self):
valid_attibutes = [ valid_attibutes = [
'{"editable": {"name": "test"}}', '{"group": {"name": "test"}}',
'{"name": "test"}', '{"name": "test"}',
] ]
for attributes in valid_attibutes: for attributes in valid_attibutes:
self.assertNotRaises(errors.InvalidData, self.assertNotRaises(
AttributesValidator.validate, errors.InvalidData,
base.BasicAttributesValidator.validate,
attributes) attributes)
self.assertNotRaises( self.assertNotRaises(
errors.InvalidData, errors.InvalidData,
AttributesValidator.validate_editable_attributes, base.BasicAttributesValidator.validate_attributes,
yaml.load(attributes)) yaml.load(attributes))