sahara/sahara/tests/unit/service/validation/utils.py

447 lines
15 KiB
Python

# Copyright (c) 2013 Mirantis Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import ast
import re
import mock
import novaclient.exceptions as nova_ex
import six
from sahara.conductor import resource as r
from sahara.plugins.fake import plugin
import sahara.service.validation as v
from sahara.tests.unit import base
from sahara.tests.unit import testutils as tu
from sahara.utils import cluster as c_u
m = {}
_types_checks = {
"string": [1, (), {}, True],
"integer": ["a", (), {}, True],
"uuid": ["z550e8400-e29b-41d4-a716-446655440000", 1, "a", (), {}, True],
"array": [{}, 'a', 1, True],
"boolean": [1, 'a', (), {}]
}
def _update_data(data, update):
data.update(update)
return data
def _get_plugins():
fake = plugin.FakePluginProvider
fake.name = 'fake'
return [fake]
def _get_plugin(name):
if name == 'fake':
fake = plugin.FakePluginProvider
fake.name = 'fake'
return fake
return None
def _get_keypair(name):
if name != "test_keypair":
raise nova_ex.NotFound("")
def _get_network(**kwargs):
if 'id' in kwargs and (
kwargs['id'] != "d9a3bebc-f788-4b81-9a93-aa048022c1ca"):
raise nova_ex.NotFound("")
return 'OK'
def _get_fl_ip_pool_list():
return [FakeNetwork("d9a3bebc-f788-4b81-9a93-aa048022c1ca")]
def _get_availability_zone_list(detailed=True):
return [FakeAvailabilityZone('nova')]
def _get_heat_stack_list(**kwargs):
if (kwargs.get('filters') and
kwargs.get('filters').get('name') == 'test-heat'):
return [FakeStack('test-heat')]
return []
class FakeStack(object):
def __init__(self, name):
self.stack_name = name
class FakeNetwork(object):
def __init__(self, name):
self.name = name
class FakeAvailabilityZone(object):
def __init__(self, name):
self.zoneName = name
class FakeFlavor(object):
def __init__(self, id):
self.id = id
class FakeSecurityGroup(object):
def __init__(self, id, name):
self.id = id
self.name = name
def _get_flavors_list():
return [FakeFlavor("42")]
def _get_security_groups_list():
return [FakeSecurityGroup("1", "default"),
FakeSecurityGroup("2", "group1"),
FakeSecurityGroup("3", "group2")]
def start_patch(patch_templates=True):
get_clusters_p = mock.patch("sahara.service.api.v10.get_clusters")
get_cluster_p = mock.patch("sahara.service.api.v10.get_cluster")
if patch_templates:
get_ng_templates_p = mock.patch(
"sahara.service.api.v10.get_node_group_templates")
get_ng_template_p = mock.patch(
"sahara.service.api.v10.get_node_group_template")
if patch_templates:
get_cl_templates_p = mock.patch(
"sahara.service.api.v10.get_cluster_templates")
get_cl_template_p = mock.patch(
"sahara.service.api.v10.get_cluster_template")
nova_p = mock.patch("sahara.utils.openstack.nova.client")
heat_p = mock.patch("sahara.utils.openstack.heat.client")
image_manager_p = mock.patch(
"sahara.utils.openstack.images.SaharaImageManager")
cinder_p = mock.patch("sahara.utils.openstack.cinder.client")
cinder_exists_p = mock.patch(
"sahara.utils.openstack.cinder.check_cinder_exists")
get_image_p = mock.patch("sahara.service.api.v10.get_image")
get_image = get_image_p.start()
get_clusters = get_clusters_p.start()
get_cluster = get_cluster_p.start()
if patch_templates:
get_ng_templates = get_ng_templates_p.start()
get_ng_template = get_ng_template_p.start()
if patch_templates:
get_cl_templates = get_cl_templates_p.start()
get_cl_template_p.start()
nova = nova_p.start()
if patch_templates:
get_cl_templates.return_value = []
nova().flavors.list.side_effect = _get_flavors_list
nova().security_groups.list.side_effect = _get_security_groups_list
nova().keypairs.get.side_effect = _get_keypair
nova().networks.find.side_effect = _get_network
nova().networks.find.__name__ = 'find'
nova().floating_ip_pools.list.side_effect = _get_fl_ip_pool_list
nova().availability_zones.list.side_effect = _get_availability_zone_list
heat = heat_p.start()
heat().stacks.list.side_effect = _get_heat_stack_list
image_manager = image_manager_p.start()
cinder = cinder_p.start()
cinder().availability_zones.list.side_effect = _get_availability_zone_list
cinder_exists = cinder_exists_p.start()
cinder_exists.return_value = True
class Image(object):
def __init__(self, name='test'):
self.name = name
@property
def id(self):
if self.name == 'test':
return '550e8400-e29b-41d4-a716-446655440000'
else:
return '813fe450-40d2-4acc-ade5-ea753a1bd5bc'
@property
def tags(self):
if self.name == 'test':
return ['fake', '0.1']
else:
return ['fake', 'wrong_tag']
def _get_image(id):
if id == '550e8400-e29b-41d4-a716-446655440000':
return Image()
else:
return Image('wrong_test')
get_image.side_effect = _get_image
image_manager().list_registered.return_value = [Image(),
Image(name='wrong_name')]
ng_dict = tu.make_ng_dict('ng', '42', ['namenode'], 1)
cluster = tu.create_cluster('test', 't', 'fake', '0.1', [ng_dict],
id=1, status=c_u.CLUSTER_STATUS_ACTIVE)
# stub clusters list
get_clusters.return_value = [cluster]
get_cluster.return_value = cluster
# stub node templates
if patch_templates:
ngt_dict = {'name': 'test', 'tenant_id': 't', 'flavor_id': '42',
'plugin_name': 'fake', 'hadoop_version': '0.1',
'id': '550e8400-e29b-41d4-a716-446655440000',
'node_processes': ['namenode']}
get_ng_templates.return_value = [r.NodeGroupTemplateResource(ngt_dict)]
ct_dict = {'name': 'test', 'tenant_id': 't',
'plugin_name': 'fake', 'hadoop_version': '0.1'}
get_cl_templates.return_value = [r.ClusterTemplateResource(ct_dict)]
def _get_ng_template(id):
for template in get_ng_templates():
if template.id == id:
return template
return None
if patch_templates:
get_ng_template.side_effect = _get_ng_template
# request data to validate
patchers = [get_clusters_p, get_cluster_p,
nova_p, get_image_p, heat_p, image_manager_p, cinder_p,
cinder_exists_p]
if patch_templates:
patchers.extend([get_ng_template_p, get_ng_templates_p,
get_cl_template_p, get_cl_templates_p])
return patchers
def stop_patch(patchers):
for patcher in reversed(patchers):
patcher.stop()
class ValidationTestCase(base.SaharaTestCase):
def setUp(self):
super(ValidationTestCase, self).setUp()
self._create_object_fun = None
self.scheme = None
self.override_config('plugins', ['fake'])
def tearDown(self):
self._create_object_fun = None
super(ValidationTestCase, self).tearDown()
def _assert_calls(self, mock, call_info):
if not call_info:
self.assertEqual(0, mock.call_count, "Unexpected call to %s: %s"
% (mock.name, str(mock.call_args)))
else:
self.assertEqual(call_info[0], mock.call_count)
self.assertEqual(call_info[1], mock.call_args[0][0].code)
possible_messages = ([call_info[2]] if isinstance(
call_info[2], six.string_types) else call_info[2])
match = False
check = mock.call_args[0][0].message
if check.find('Error ID:') != -1:
check = check.split('\n')[0]
for message in possible_messages:
if self._check_match(message, check):
match = True
break
if not match:
self.assertIn(check, possible_messages)
def _check_match(self, expected, actual):
d1, r1 = self._extract_printed_dict(expected)
d2, r2 = self._extract_printed_dict(actual)
# Note(slukjanov): regex needed because of different
# versions of jsonschema generate different
# messages.
return (r1 == r2 or re.match(r1, r2)) and (d1 == d2)
def _extract_printed_dict(self, s):
start = s.find('{')
if start == -1:
return None, s
end = s.rfind('}')
if end == -1:
return None, s
return ast.literal_eval(s[start:end+1]), s[0:start+1] + s[end]
@mock.patch("sahara.utils.api.request_data")
@mock.patch("sahara.utils.api.bad_request")
def _assert_create_object_validation(
self, bad_req=None, request_data=None,
data=None, bad_req_i=None):
request_data.return_value = data
# mock function that should be validated
patchers = start_patch()
m_func = mock.Mock()
m_func.__name__ = "m_func"
v.validate(self.scheme, self._create_object_fun)(m_func)(data=data)
self.assertEqual(1, request_data.call_count)
self._assert_calls(bad_req, bad_req_i)
stop_patch(patchers)
def _assert_valid_name_hostname_validation(self, data):
data.update({'name': None})
self._assert_create_object_validation(
data=data,
bad_req_i=(1, "VALIDATION_ERROR",
u"name: None is not of type 'string'")
)
data.update({'name': ""})
self._assert_create_object_validation(
data=data,
bad_req_i=(1, "VALIDATION_ERROR",
u"name: '' is too short")
)
data.update({'name': ('a' * 51)})
self._assert_create_object_validation(
data=data,
bad_req_i=(1, "VALIDATION_ERROR",
u"name: '%s' is too long" % ('a' * 51))
)
data.update({'name': 'a-!'})
self._assert_create_object_validation(
data=data,
bad_req_i=(1, "VALIDATION_ERROR",
u"name: 'a-!' is not a 'valid_name_hostname'")
)
def _prop_types_str(self, prop_types):
return ", ".join(["'%s'" % prop for prop in prop_types])
def _assert_types(self, default_data):
for p_name in self.scheme['properties']:
prop = self.scheme['properties'][p_name]
prop_types = prop["type"]
if type(prop_types) is not list:
prop_types = [prop_types]
for prop_type in prop_types:
if prop_type in _types_checks:
for type_ex in _types_checks[prop_type]:
data = default_data.copy()
value = type_ex
value_str = str(value)
if isinstance(value, str):
value_str = "'%s'" % value_str
data.update({p_name: value})
message = ("%s: %s is not of type %s" %
(p_name, value_str,
self._prop_types_str(prop_types)))
if "enum" in prop:
message = [message, "%s: %s is not one of %s" %
(p_name, value_str,
prop["enum"])]
self._assert_create_object_validation(
data=data,
bad_req_i=(1, 'VALIDATION_ERROR', message)
)
def _assert_cluster_configs_validation(self, require_image_id=False):
data = {
'name': 'test-cluster',
'plugin_name': 'fake',
'hadoop_version': '0.1',
'cluster_configs': {
'HDFS': {
u'mapreduce.task.tmp.dir': '/temp/'
}
},
'default_image_id': '550e8400-e29b-41d4-a716-446655440000'
}
if require_image_id:
data_without_image = data.copy()
data_without_image.pop('default_image_id')
self._assert_create_object_validation(
data=data_without_image,
bad_req_i=(1, 'NOT_FOUND',
"'default_image_id' field is not found")
)
self._assert_create_object_validation(
data=_update_data(data.copy(), {
'cluster_configs': {
'wrong_target': {
u'mapreduce.task.tmp.dir': '/temp/'
}
}}),
bad_req_i=(1, 'INVALID_REFERENCE',
"Plugin doesn't contain applicable "
"target 'wrong_target'")
)
self._assert_create_object_validation(
data=_update_data(data.copy(), {
'cluster_configs': {
'general': {
u's': '/temp/'
}
}
}),
bad_req_i=(1, 'INVALID_REFERENCE',
"Plugin's applicable target 'general' doesn't "
"contain config with name 's'")
)
def _assert_cluster_default_image_tags_validation(self):
data = {
'name': 'test-cluster',
'plugin_name': 'fake',
'hadoop_version': '0.1',
'default_image_id': '550e8400-e29b-41d4-a716-446655440000',
'domain_name': 'domain.org.'
}
self._assert_create_object_validation(data=data)
data = {
'name': 'test-cluster',
'plugin_name': 'fake',
'hadoop_version': '0.1',
'default_image_id': '813fe450-40d2-4acc-ade5-ea753a1bd5bc'
}
self._assert_create_object_validation(
data=data,
bad_req_i=(1, 'INVALID_REFERENCE',
"Requested image "
"'813fe450-40d2-4acc-ade5-ea753a1bd5bc' "
"doesn't contain required tags: "
"['0.1']"))
def assert_protected_resource_exception(self, ex):
self.assertIn("marked as protected", six.text_type(ex))
def assert_created_in_another_tenant_exception(self, ex):
self.assertIn("wasn't created in this tenant", six.text_type(ex))