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 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
@ -121,7 +121,7 @@ class ClusterAttributesHandler(BaseHandler):
"editable",
)
validator = AttributesValidator
validator = ClusterAttributesValidator
@content
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 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):

View File

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

View File

@ -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
@ -219,7 +215,7 @@ class ClusterValidator(BasicValidator):
)
class AttributesValidator(BasicValidator):
class ClusterAttributesValidator(base.BasicAttributesValidator):
@classmethod
def validate(cls, data, cluster=None, force=False):
@ -240,15 +236,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'] != \
@ -262,72 +258,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.
@ -391,7 +321,7 @@ class AttributesValidator(BasicValidator):
)
class ClusterChangesValidator(BaseDefferedTaskValidator):
class ClusterChangesValidator(base.BaseDefferedTaskValidator):
@classmethod
def validate(cls, cluster):
@ -399,7 +329,7 @@ class ClusterChangesValidator(BaseDefferedTaskValidator):
ProvisionSelectedNodesValidator.validate_provision(None, cluster)
class ClusterStopDeploymentValidator(BaseDefferedTaskValidator):
class ClusterStopDeploymentValidator(base.BaseDefferedTaskValidator):
@classmethod
def validate(cls, cluster):
@ -412,7 +342,7 @@ class ClusterStopDeploymentValidator(BaseDefferedTaskValidator):
raise errors.CannotBeStopped()
class VmwareAttributesValidator(BasicValidator):
class VmwareAttributesValidator(base.BasicValidator):
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"]
}
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",

View File

@ -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):
@ -437,3 +435,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)

View File

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

View File

@ -431,11 +431,13 @@ class TestHandlers(BaseIntegrationTest):
'group1': {
'metadata': {},
'comp1': {
'value': 42
'type': 'text',
'value': '42'
}
},
'group2': {
'comp2': {
'type': 'text',
'value': 'value1'
}
}
@ -444,7 +446,8 @@ class TestHandlers(BaseIntegrationTest):
update_attributes = {
'group1': {
'comp1': {
'value': 41
'type': 'text',
'value': '41'
}
}
}
@ -453,6 +456,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)

View File

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