fb8a67740b
Cinder team decided to remove Cinder V1 (which is ok since they already have V3 API) but our logic of required_service validator was not ready for such step. This patch includes the next changes of the validaotr: * Check for nova-network before initializing clients. This is a simple check which does not require initialized clients. Since the keystone token is fetched while initializing clients, it can be time wasting. Also, it is time to fail on this check. * In case of NotFound error while direct search by service type, use the default one NOTE: actually, the default service type should be the same as what we have in consts file... Change-Id: I2018a9b9323cce05ee226fefdc3fe50c356aed9a
1026 lines
41 KiB
Python
1026 lines
41 KiB
Python
# Copyright 2017: Mirantis Inc.
|
|
# All Rights Reserved.
|
|
#
|
|
# 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 copy
|
|
import ddt
|
|
import mock
|
|
|
|
from glanceclient import exc as glance_exc
|
|
from novaclient import exceptions as nova_exc
|
|
from rally import exceptions
|
|
|
|
from rally_openstack import consts
|
|
from rally_openstack import validators
|
|
from tests.unit import test
|
|
|
|
|
|
PATH = "rally_openstack.validators"
|
|
|
|
|
|
context = {
|
|
"admin": mock.MagicMock(),
|
|
"users": [mock.MagicMock()],
|
|
}
|
|
|
|
config = dict(args={"image": {"id": "fake_id",
|
|
"min_ram": 10,
|
|
"size": 1024 ** 3,
|
|
"min_disk": 10.0 * (1024 ** 3),
|
|
"image_name": "foo_image"},
|
|
"flavor": {"id": "fake_flavor_id",
|
|
"name": "test"},
|
|
"foo_image": {"id": "fake_image_id"}
|
|
},
|
|
context={"images": {"image_name": "foo_image"},
|
|
"api_versions@openstack": mock.MagicMock()}
|
|
)
|
|
|
|
|
|
@mock.patch("rally_openstack.contexts.keystone.roles.RoleGenerator")
|
|
def test_with_roles_ctx(mock_role_generator):
|
|
|
|
@validators.with_roles_ctx()
|
|
def func(config, context):
|
|
pass
|
|
|
|
config = {"contexts": {}}
|
|
context = {"admin": {"credential": mock.MagicMock()},
|
|
"task": mock.MagicMock()}
|
|
func(config, context)
|
|
mock_role_generator().setup.assert_not_called()
|
|
|
|
config = {"contexts": {"roles": "admin"}}
|
|
func(config, context)
|
|
mock_role_generator().setup.assert_called_once_with()
|
|
|
|
|
|
class RequiredOpenStackValidatorTestCase(test.TestCase):
|
|
def validate(self):
|
|
validator = validators.RequiredOpenStackValidator(admin=True)
|
|
validator.validate(
|
|
{"platforms": {"openstack": {"admin": "foo"}}}, {}, None, None)
|
|
|
|
validator = validators.RequiredOpenStackValidator(users=True)
|
|
validator.validate(
|
|
{"platforms": {"openstack": {"admin": "foo"}}}, {}, None, None)
|
|
|
|
validator = validators.RequiredOpenStackValidator(users=True)
|
|
validator.validate(
|
|
{"platforms": {"openstack": {"users": ["foo"]}}}, {}, None, None)
|
|
|
|
def test_validate_failed(self):
|
|
# case #1: wrong configuration of validator
|
|
validator = validators.RequiredOpenStackValidator()
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, {}, {}, None, None)
|
|
self.assertEqual(
|
|
"You should specify admin=True or users=True or both.",
|
|
e.message)
|
|
|
|
# case #2: admin is not present
|
|
validator = validators.RequiredOpenStackValidator(admin=True)
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate,
|
|
{"platforms": {"openstack": {}}}, {}, None, None)
|
|
self.assertEqual("No admin credentials for openstack",
|
|
e.message)
|
|
|
|
# case #3: users are not present
|
|
validator = validators.RequiredOpenStackValidator(users=True)
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate,
|
|
{"platforms": {"openstack": {}}}, {}, None, None)
|
|
self.assertEqual("No user credentials for openstack",
|
|
e.message)
|
|
|
|
|
|
@ddt.ddt
|
|
class ImageExistsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ImageExistsValidatorTestCase, self).setUp()
|
|
self.validator = validators.ImageExistsValidator("image", True)
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
@ddt.unpack
|
|
@ddt.data(
|
|
{"param_name": "fake_param", "nullable": True, "err_msg": None},
|
|
{"param_name": "fake_param", "nullable": False,
|
|
"err_msg": "Parameter fake_param is not specified."},
|
|
{"param_name": "image", "nullable": True, "err_msg": None},
|
|
)
|
|
def test_validator(self, param_name, nullable, err_msg, ex=False):
|
|
validator = validators.ImageExistsValidator(param_name,
|
|
nullable)
|
|
|
|
clients = self.context["users"][0].clients.return_value
|
|
|
|
clients.glance().images.get = mock.Mock()
|
|
if ex:
|
|
clients.glance().images.get.side_effect = ex
|
|
|
|
if err_msg:
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual(err_msg, e.message)
|
|
else:
|
|
result = validator.validate(self.config, self.context, None,
|
|
None)
|
|
self.assertIsNone(result)
|
|
|
|
def test_validator_image_from_context(self):
|
|
config = {
|
|
"args": {"image": {"regex": r"^foo$"}},
|
|
"contexts": {"images": {"image_name": "foo"}}}
|
|
|
|
self.validator.validate(self.context, config, None, None)
|
|
|
|
@mock.patch("%s.openstack_types.GlanceImage" % PATH)
|
|
def test_validator_image_not_in_context(self, mock_glance_image):
|
|
mock_glance_image.return_value.pre_process.return_value = "image_id"
|
|
config = {
|
|
"args": {"image": "fake_image"},
|
|
"contexts": {
|
|
"images": {"fake_image_name": "foo"}}}
|
|
|
|
clients = self.context[
|
|
"users"][0]["credential"].clients.return_value
|
|
clients.glance().images.get = mock.Mock()
|
|
|
|
result = self.validator.validate(self.context, config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
mock_glance_image.assert_called_once_with(
|
|
context={"admin": {
|
|
"credential": self.context["users"][0]["credential"]}})
|
|
mock_glance_image.return_value.pre_process.assert_called_once_with(
|
|
config["args"]["image"], config={})
|
|
clients.glance().images.get.assert_called_with("image_id")
|
|
|
|
exs = [exceptions.InvalidScenarioArgument(),
|
|
glance_exc.HTTPNotFound()]
|
|
for ex in exs:
|
|
clients.glance().images.get.side_effect = ex
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, self.context, config, None, None)
|
|
|
|
self.assertEqual("Image 'fake_image' not found", e.message)
|
|
|
|
|
|
@ddt.ddt
|
|
class ExternalNetworkExistsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ExternalNetworkExistsValidatorTestCase, self).setUp()
|
|
self.validator = validators.ExternalNetworkExistsValidator("net")
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
@ddt.unpack
|
|
@ddt.data(
|
|
{"foo_conf": {}},
|
|
{"foo_conf": {"args": {"net": "custom"}}},
|
|
{"foo_conf": {"args": {"net": "non_exist"}},
|
|
"err_msg": "External (floating) network with name non_exist"
|
|
" not found by user {}. Available networks:"
|
|
" [{}, {}]"},
|
|
{"foo_conf": {"args": {"net": "custom"}},
|
|
"net1_name": {"name": {"net": "public"}},
|
|
"net2_name": {"name": {"net": "custom"}},
|
|
"err_msg": "External (floating) network with name custom"
|
|
" not found by user {}. Available networks:"
|
|
" [{}, {}]"}
|
|
)
|
|
def test_validator(self, foo_conf, net1_name="public", net2_name="custom",
|
|
err_msg=""):
|
|
|
|
user = self.context["users"][0]
|
|
|
|
net1 = {"name": net1_name, "router:external": True}
|
|
net2 = {"name": net2_name, "router:external": True}
|
|
|
|
user["credential"].clients().neutron().list_networks.return_value = {
|
|
"networks": [net1, net2]}
|
|
if err_msg:
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, self.context, foo_conf,
|
|
None, None)
|
|
self.assertEqual(
|
|
err_msg.format(user["credential"].username, net1, net2),
|
|
e.message)
|
|
else:
|
|
result = self.validator.validate(self.context, foo_conf,
|
|
None, None)
|
|
self.assertIsNone(result, "Unexpected result '%s'" % result)
|
|
|
|
|
|
@ddt.ddt
|
|
class RequiredNeutronExtensionsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(RequiredNeutronExtensionsValidatorTestCase, self).setUp()
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
def test_validator(self):
|
|
validator = validators.RequiredNeutronExtensionsValidator(
|
|
"existing_extension")
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.neutron().list_extensions.return_value = {
|
|
"extensions": [{"alias": "existing_extension"}]}
|
|
|
|
validator.validate(self.context, {}, None, None)
|
|
|
|
def test_validator_failed(self):
|
|
err_msg = "Neutron extension absent_extension is not configured"
|
|
validator = validators.RequiredNeutronExtensionsValidator(
|
|
"absent_extension")
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.neutron().list_extensions.return_value = {
|
|
"extensions": [{"alias": "existing_extension"}]}
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None)
|
|
self.assertEqual(err_msg, e.message)
|
|
|
|
|
|
class FlavorExistsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(FlavorExistsValidatorTestCase, self).setUp()
|
|
self.validator = validators.FlavorExistsValidator(
|
|
param_name="foo_flavor")
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
def test__get_validated_flavor_wrong_value_in_config(self):
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_validated_flavor, self.config,
|
|
mock.MagicMock(), "foo_flavor")
|
|
self.assertEqual("Parameter foo_flavor is not specified.",
|
|
e.message)
|
|
|
|
@mock.patch("%s.openstack_types.Flavor" % PATH)
|
|
def test__get_validated_flavor(self, mock_flavor):
|
|
mock_flavor.return_value.pre_process.return_value = "flavor_id"
|
|
|
|
clients = mock.Mock()
|
|
clients.nova().flavors.get.return_value = "flavor"
|
|
|
|
result = self.validator._get_validated_flavor(self.config,
|
|
clients,
|
|
"flavor")
|
|
self.assertEqual("flavor", result)
|
|
|
|
mock_flavor.assert_called_once_with(
|
|
context={"admin": {"credential": clients.credential}}
|
|
)
|
|
mock_flavor_obj = mock_flavor.return_value
|
|
mock_flavor_obj.pre_process.assert_called_once_with(
|
|
self.config["args"]["flavor"], config={})
|
|
clients.nova().flavors.get.assert_called_once_with(flavor="flavor_id")
|
|
mock_flavor_obj.pre_process.reset_mock()
|
|
|
|
clients.side_effect = exceptions.InvalidScenarioArgument("")
|
|
result = self.validator._get_validated_flavor(
|
|
self.config, clients, "flavor")
|
|
self.assertEqual("flavor", result)
|
|
mock_flavor_obj.pre_process.assert_called_once_with(
|
|
self.config["args"]["flavor"], config={})
|
|
clients.nova().flavors.get.assert_called_with(flavor="flavor_id")
|
|
|
|
@mock.patch("%s.openstack_types.Flavor" % PATH)
|
|
def test__get_validated_flavor_not_found(self, mock_flavor):
|
|
mock_flavor.return_value.pre_process.return_value = "flavor_id"
|
|
|
|
clients = mock.MagicMock()
|
|
clients.nova().flavors.get.side_effect = nova_exc.NotFound("")
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_validated_flavor,
|
|
self.config, clients, "flavor")
|
|
self.assertEqual("Flavor '%s' not found" %
|
|
self.config["args"]["flavor"],
|
|
e.message)
|
|
mock_flavor_obj = mock_flavor.return_value
|
|
mock_flavor_obj.pre_process.assert_called_once_with(
|
|
self.config["args"]["flavor"], config={})
|
|
|
|
@mock.patch("%s.types.obj_from_name" % PATH)
|
|
@mock.patch("%s.flavors_ctx.FlavorConfig" % PATH)
|
|
def test__get_flavor_from_context(self, mock_flavor_config,
|
|
mock_obj_from_name):
|
|
config = {
|
|
"contexts": {"images": {"fake_parameter_name": "foo_image"}}}
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_flavor_from_context,
|
|
config, "foo_flavor")
|
|
self.assertEqual("No flavors context", e.message)
|
|
|
|
config = {"contexts": {"images": {"fake_parameter_name": "foo_image"},
|
|
"flavors": [{"flavor1": "fake_flavor1"}]}}
|
|
result = self.validator._get_flavor_from_context(config, "foo_flavor")
|
|
self.assertEqual("<context flavor: %s>" % result.name, result.id)
|
|
|
|
def test_validate(self):
|
|
expected_e = validators.validation.ValidationError("fpp")
|
|
self.validator._get_validated_flavor = mock.Mock(
|
|
side_effect=expected_e)
|
|
|
|
config = {}
|
|
ctx = mock.MagicMock()
|
|
actual_e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, ctx, config, None, None)
|
|
self.assertEqual(expected_e, actual_e)
|
|
self.validator._get_validated_flavor.assert_called_once_with(
|
|
config=config,
|
|
clients=ctx["users"][0]["credential"].clients(),
|
|
param_name=self.validator.param_name)
|
|
|
|
|
|
@ddt.ddt
|
|
class ImageValidOnFlavorValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ImageValidOnFlavorValidatorTestCase, self).setUp()
|
|
self.validator = validators.ImageValidOnFlavorValidator("foo_flavor",
|
|
"image")
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
@ddt.data(
|
|
{"validate_disk": True, "flavor_disk": True},
|
|
{"validate_disk": False, "flavor_disk": True},
|
|
{"validate_disk": False, "flavor_disk": False}
|
|
)
|
|
@ddt.unpack
|
|
def test_validate(self, validate_disk, flavor_disk):
|
|
validator = validators.ImageValidOnFlavorValidator(
|
|
flavor_param="foo_flavor",
|
|
image_param="image",
|
|
fail_on_404_image=False,
|
|
validate_disk=validate_disk)
|
|
|
|
min_ram = 2048
|
|
disk = 10
|
|
fake_image = {"min_ram": min_ram,
|
|
"size": disk * (1024 ** 3),
|
|
"min_disk": disk}
|
|
fake_flavor = mock.Mock(disk=None, ram=min_ram * 2)
|
|
if flavor_disk:
|
|
fake_flavor.disk = disk * 2
|
|
|
|
validator._get_validated_flavor = mock.Mock(
|
|
return_value=fake_flavor)
|
|
|
|
# case 1: no image, but it is ok, since fail_on_404_image is False
|
|
validator._get_validated_image = mock.Mock(
|
|
side_effect=validators.validation.ValidationError("!!!"))
|
|
validator.validate(self.context, {}, None, None)
|
|
|
|
# case 2: there is an image
|
|
validator._get_validated_image = mock.Mock(
|
|
return_value=fake_image)
|
|
validator.validate(self.context, {}, None, None)
|
|
|
|
# case 3: check caching of the flavor
|
|
self.context["users"].append(self.context["users"][0])
|
|
validator._get_validated_image.reset_mock()
|
|
validator._get_validated_flavor.reset_mock()
|
|
|
|
validator.validate(self.context, {}, None, None)
|
|
|
|
self.assertEqual(1, validator._get_validated_flavor.call_count)
|
|
self.assertEqual(2, validator._get_validated_image.call_count)
|
|
|
|
def test_validate_failed(self):
|
|
validator = validators.ImageValidOnFlavorValidator(
|
|
flavor_param="foo_flavor",
|
|
image_param="image",
|
|
fail_on_404_image=True,
|
|
validate_disk=True)
|
|
|
|
min_ram = 2048
|
|
disk = 10
|
|
fake_flavor = mock.Mock(disk=disk, ram=min_ram)
|
|
fake_flavor.id = "flavor_id"
|
|
|
|
validator._get_validated_flavor = mock.Mock(
|
|
return_value=fake_flavor)
|
|
|
|
# case 1: there is no image and fail_on_404_image flag is True
|
|
expected_e = validators.validation.ValidationError("!!!")
|
|
validator._get_validated_image = mock.Mock(
|
|
side_effect=expected_e)
|
|
actual_e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
self.assertEqual(expected_e, actual_e)
|
|
|
|
# case 2: there is no right flavor
|
|
expected_e = KeyError("Ooops")
|
|
validator._get_validated_flavor.side_effect = expected_e
|
|
actual_e = self.assertRaises(
|
|
KeyError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
self.assertEqual(expected_e, actual_e)
|
|
|
|
# case 3: ram of a flavor is less than min_ram of an image
|
|
validator._get_validated_flavor = mock.Mock(
|
|
return_value=fake_flavor)
|
|
|
|
fake_image = {"min_ram": min_ram * 2, "id": "image_id"}
|
|
validator._get_validated_image = mock.Mock(
|
|
return_value=fake_image)
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
self.assertEqual(
|
|
"The memory size for flavor 'flavor_id' is too small for "
|
|
"requested image 'image_id'.", e.message)
|
|
|
|
# case 4: disk of a flavor is less than size of an image
|
|
fake_image = {"min_ram": min_ram / 2.0,
|
|
"size": disk * (1024 ** 3) * 3,
|
|
"id": "image_id"}
|
|
validator._get_validated_image = mock.Mock(
|
|
return_value=fake_image)
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
self.assertEqual(
|
|
"The disk size for flavor 'flavor_id' is too small for "
|
|
"requested image 'image_id'.", e.message)
|
|
|
|
# case 5: disk of a flavor is less than size of an image
|
|
fake_image = {"min_ram": min_ram,
|
|
"size": disk * (1024 ** 3),
|
|
"min_disk": disk * 2,
|
|
"id": "image_id"}
|
|
validator._get_validated_image = mock.Mock(
|
|
return_value=fake_image)
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
self.assertEqual(
|
|
"The minimal disk size for flavor 'flavor_id' is too small for "
|
|
"requested image 'image_id'.", e.message)
|
|
|
|
# case 6: _get_validated_image raises an unexpected error,
|
|
# fail_on_404_image=False should not work in this case
|
|
expected_e = KeyError("Foo!")
|
|
validator = validators.ImageValidOnFlavorValidator(
|
|
flavor_param="foo_flavor",
|
|
image_param="image",
|
|
fail_on_404_image=False,
|
|
validate_disk=True)
|
|
validator._get_validated_image = mock.Mock(
|
|
side_effect=expected_e)
|
|
validator._get_validated_flavor = mock.Mock()
|
|
|
|
actual_e = self.assertRaises(
|
|
KeyError,
|
|
validator.validate, self.context, {}, None, None
|
|
)
|
|
|
|
self.assertEqual(expected_e, actual_e)
|
|
|
|
@mock.patch("%s.openstack_types.GlanceImage" % PATH)
|
|
def test__get_validated_image(self, mock_glance_image):
|
|
mock_glance_image.return_value.pre_process.return_value = "image_id"
|
|
image = {
|
|
"size": 0,
|
|
"min_ram": 0,
|
|
"min_disk": 0
|
|
}
|
|
# Get image name from context
|
|
result = self.validator._get_validated_image({
|
|
"args": {
|
|
"image": {"regex": r"^foo$"}},
|
|
"contexts": {
|
|
"images": {"image_name": "foo"}}},
|
|
mock.Mock(), "image")
|
|
self.assertEqual(image, result)
|
|
|
|
clients = mock.Mock()
|
|
clients.glance().images.get().to_dict.return_value = {
|
|
"image": "image_id"}
|
|
image["image"] = "image_id"
|
|
|
|
result = self.validator._get_validated_image(self.config,
|
|
clients,
|
|
"image")
|
|
self.assertEqual(image, result)
|
|
mock_glance_image.assert_called_once_with(
|
|
context={"admin": {"credential": clients.credential}})
|
|
mock_glance_image.return_value.pre_process.assert_called_once_with(
|
|
config["args"]["image"], config={})
|
|
clients.glance().images.get.assert_called_with("image_id")
|
|
|
|
@mock.patch("%s.openstack_types.GlanceImage" % PATH)
|
|
def test__get_validated_image_incorrect_param(self, mock_glance_image):
|
|
mock_glance_image.return_value.pre_process.return_value = "image_id"
|
|
# Wrong 'param_name'
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_validated_image, self.config,
|
|
mock.Mock(), "fake_param")
|
|
self.assertEqual("Parameter fake_param is not specified.",
|
|
e.message)
|
|
|
|
# 'image_name' is not in 'image_context'
|
|
image = {"id": "image_id", "size": 1024,
|
|
"min_ram": 256, "min_disk": 512}
|
|
|
|
clients = mock.Mock()
|
|
clients.glance().images.get().to_dict.return_value = image
|
|
config = {"args": {"image": "foo_image",
|
|
"context": {"images": {
|
|
"fake_parameter_name": "foo_image"}
|
|
}}
|
|
}
|
|
result = self.validator._get_validated_image(config, clients, "image")
|
|
self.assertEqual(image, result)
|
|
|
|
mock_glance_image.assert_called_once_with(
|
|
context={"admin": {"credential": clients.credential}})
|
|
mock_glance_image.return_value.pre_process.assert_called_once_with(
|
|
config["args"]["image"], config={})
|
|
clients.glance().images.get.assert_called_with("image_id")
|
|
|
|
@mock.patch("%s.openstack_types.GlanceImage" % PATH)
|
|
def test__get_validated_image_exceptions(self, mock_glance_image):
|
|
mock_glance_image.return_value.pre_process.return_value = "image_id"
|
|
clients = mock.Mock()
|
|
clients.glance().images.get.side_effect = glance_exc.HTTPNotFound("")
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_validated_image,
|
|
config, clients, "image")
|
|
self.assertEqual("Image '%s' not found" % config["args"]["image"],
|
|
e.message)
|
|
|
|
mock_glance_image.assert_called_once_with(
|
|
context={"admin": {"credential": clients.credential}})
|
|
mock_glance_image.return_value.pre_process.assert_called_once_with(
|
|
config["args"]["image"], config={})
|
|
clients.glance().images.get.assert_called_with("image_id")
|
|
mock_glance_image.return_value.pre_process.reset_mock()
|
|
|
|
clients.side_effect = exceptions.InvalidScenarioArgument("")
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator._get_validated_image, config, clients, "image")
|
|
self.assertEqual("Image '%s' not found" % config["args"]["image"],
|
|
e.message)
|
|
mock_glance_image.return_value.pre_process.assert_called_once_with(
|
|
config["args"]["image"], config={})
|
|
clients.glance().images.get.assert_called_with("image_id")
|
|
|
|
|
|
class RequiredClientsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(RequiredClientsValidatorTestCase, self).setUp()
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
def test_validate(self):
|
|
validator = validators.RequiredClientsValidator(components=["keystone",
|
|
"nova"])
|
|
clients = self.context["users"][0]["credential"].clients.return_value
|
|
|
|
result = validator.validate(self.context, self.config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
clients.nova.side_effect = ImportError
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual("Client for nova is not installed. To install it "
|
|
"run `pip install python-novaclient`", e.message)
|
|
|
|
def test_validate_with_admin(self):
|
|
validator = validators.RequiredClientsValidator(components=["keystone",
|
|
"nova"],
|
|
admin=True)
|
|
clients = self.context["admin"]["credential"].clients.return_value
|
|
result = validator.validate(self.context, self.config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
clients.keystone.side_effect = ImportError
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual("Client for keystone is not installed. To install it "
|
|
"run `pip install python-keystoneclient`", e.message)
|
|
|
|
|
|
class RequiredServicesValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(RequiredServicesValidatorTestCase, self).setUp()
|
|
self.validator = validators.RequiredServicesValidator([
|
|
consts.Service.KEYSTONE,
|
|
consts.Service.NOVA])
|
|
self.config = config
|
|
self.context = context
|
|
|
|
def test_validator(self):
|
|
|
|
self.config["context"]["api_versions@openstack"].get = mock.Mock(
|
|
return_value={consts.Service.KEYSTONE: "service_type"})
|
|
|
|
clients = self.context["admin"].get("credential").clients()
|
|
|
|
clients.services().values.return_value = [
|
|
consts.Service.KEYSTONE, consts.Service.NOVA,
|
|
consts.Service.NOVA_NET]
|
|
fake_service = mock.Mock(binary="nova-network", status="enabled")
|
|
clients.nova.services.list.return_value = [fake_service]
|
|
result = self.validator.validate(self.context, self.config,
|
|
None, None)
|
|
self.assertIsNone(result)
|
|
|
|
fake_service = mock.Mock(binary="keystone", status="enabled")
|
|
clients.nova.services.list.return_value = [fake_service]
|
|
result = self.validator.validate(self.context, self.config,
|
|
None, None)
|
|
self.assertIsNone(result)
|
|
|
|
fake_service = mock.Mock(binary="nova-network", status="disabled")
|
|
clients.nova.services.list.return_value = [fake_service]
|
|
result = self.validator.validate(self.context, self.config,
|
|
None, None)
|
|
self.assertIsNone(result)
|
|
|
|
def test_validator_wrong_service(self):
|
|
|
|
self.config["context"]["api_versions@openstack"].get = mock.Mock(
|
|
return_value={consts.Service.KEYSTONE: "service_type",
|
|
consts.Service.NOVA: "service_name"})
|
|
|
|
clients = self.context["admin"].get("credential").clients()
|
|
clients.services().values.return_value = [
|
|
consts.Service.KEYSTONE, consts.Service.NOVA]
|
|
|
|
validator = validators.RequiredServicesValidator([
|
|
consts.Service.KEYSTONE,
|
|
consts.Service.NOVA, "lol"])
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, {}, None, None)
|
|
expected_msg = ("'{0}' service is not available. Hint: If '{0}'"
|
|
" service has non-default service_type, try to setup"
|
|
" it via 'api_versions@openstack' context."
|
|
).format("lol")
|
|
self.assertEqual(expected_msg, e.message)
|
|
|
|
|
|
@ddt.ddt
|
|
class ValidateHeatTemplateValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(ValidateHeatTemplateValidatorTestCase, self).setUp()
|
|
self.validator = validators.ValidateHeatTemplateValidator(
|
|
"template_path1", "template_path2")
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
@ddt.data(
|
|
{"exception_msg": "Heat template validation failed on fake_path1. "
|
|
"Original error message: fake_msg."},
|
|
{"exception_msg": None}
|
|
)
|
|
@ddt.unpack
|
|
@mock.patch("%s.os.path.exists" % PATH,
|
|
return_value=True)
|
|
@mock.patch("rally_openstack.validators.open",
|
|
side_effect=mock.mock_open(), create=True)
|
|
def test_validate(self, mock_open, mock_exists, exception_msg):
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
mock_open().__enter__().read.side_effect = ["fake_template1",
|
|
"fake_template2"]
|
|
heat_validator = mock.MagicMock()
|
|
if exception_msg:
|
|
heat_validator.side_effect = Exception("fake_msg")
|
|
clients.heat().stacks.validate = heat_validator
|
|
context = {"args": {"template_path1": "fake_path1",
|
|
"template_path2": "fake_path2"}}
|
|
if not exception_msg:
|
|
result = self.validator.validate(self.context, context, None, None)
|
|
|
|
heat_validator.assert_has_calls([
|
|
mock.call(template="fake_template1"),
|
|
mock.call(template="fake_template2")
|
|
])
|
|
mock_open.assert_has_calls([
|
|
mock.call("fake_path1", "r"),
|
|
mock.call("fake_path2", "r")
|
|
], any_order=True)
|
|
self.assertIsNone(result)
|
|
else:
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, self.context, context, None, None)
|
|
heat_validator.assert_called_once_with(
|
|
template="fake_template1")
|
|
self.assertEqual(
|
|
"Heat template validation failed on fake_path1."
|
|
" Original error message: fake_msg.", e.message)
|
|
|
|
def test_validate_missed_params(self):
|
|
validator = validators.ValidateHeatTemplateValidator(
|
|
params="fake_param")
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
|
|
expected_msg = ("Path to heat template is not specified. Its needed "
|
|
"for heat template validation. Please check the "
|
|
"content of `fake_param` scenario argument.")
|
|
self.assertEqual(expected_msg, e.message)
|
|
|
|
@mock.patch("%s.os.path.exists" % PATH,
|
|
return_value=False)
|
|
def test_validate_file_not_found(self, mock_exists):
|
|
config = {"args": {"template_path1": "fake_path1",
|
|
"template_path2": "fake_path2"}}
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, self.context, config, None, None)
|
|
expected_msg = "No file found by the given path fake_path1"
|
|
self.assertEqual(expected_msg, e.message)
|
|
|
|
|
|
class RequiredCinderServicesValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(RequiredCinderServicesValidatorTestCase, self).setUp()
|
|
self.context = copy.deepcopy(context)
|
|
self.config = copy.deepcopy(config)
|
|
|
|
def test_validate(self):
|
|
validator = validators.RequiredCinderServicesValidator(
|
|
"cinder_service")
|
|
|
|
fake_service = mock.Mock(binary="cinder_service", state="up")
|
|
clients = self.context["admin"]["credential"].clients()
|
|
clients.cinder().services.list.return_value = [fake_service]
|
|
result = validator.validate(self.context, self.config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
fake_service.state = "down"
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual("cinder_service service is not available",
|
|
e.message)
|
|
|
|
|
|
@ddt.ddt
|
|
class RequiredAPIVersionsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(RequiredAPIVersionsValidatorTestCase, self).setUp()
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
def _get_keystone_v2_mock_client(self):
|
|
keystone = mock.Mock()
|
|
del keystone.projects
|
|
keystone.tenants = mock.Mock()
|
|
return keystone
|
|
|
|
def _get_keystone_v3_mock_client(self):
|
|
keystone = mock.Mock()
|
|
del keystone.tenants
|
|
keystone.projects = mock.Mock()
|
|
return keystone
|
|
|
|
def test_validate(self):
|
|
validator = validators.RequiredAPIVersionsValidator("keystone",
|
|
[2.0, 3])
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.keystone.return_value = self._get_keystone_v3_mock_client()
|
|
validator.validate(self.context, self.config, None, None)
|
|
|
|
clients.keystone.return_value = self._get_keystone_v2_mock_client()
|
|
validator.validate(self.context, self.config, None, None)
|
|
|
|
def test_validate_with_keystone_v2(self):
|
|
validator = validators.RequiredAPIVersionsValidator("keystone",
|
|
[2.0])
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
clients.keystone.return_value = self._get_keystone_v2_mock_client()
|
|
validator.validate(self.context, self.config, None, None)
|
|
|
|
clients.keystone.return_value = self._get_keystone_v3_mock_client()
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual("Task was designed to be used with keystone V2.0, "
|
|
"but V3 is selected.", e.message)
|
|
|
|
def test_validate_with_keystone_v3(self):
|
|
validator = validators.RequiredAPIVersionsValidator("keystone",
|
|
[3])
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
clients.keystone.return_value = self._get_keystone_v3_mock_client()
|
|
validator.validate(self.context, self.config, None, None)
|
|
|
|
clients.keystone.return_value = self._get_keystone_v2_mock_client()
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual("Task was designed to be used with keystone V3, "
|
|
"but V2.0 is selected.", e.message)
|
|
|
|
@ddt.unpack
|
|
@ddt.data(
|
|
{"nova": 2, "versions": [2], "err_msg": None},
|
|
{"nova": 3, "versions": [2],
|
|
"err_msg": "Task was designed to be used with nova V2, "
|
|
"but V3 is selected."},
|
|
{"nova": None, "versions": [2],
|
|
"err_msg": "Unable to determine the API version."},
|
|
{"nova": 2, "versions": [2, 3], "err_msg": None},
|
|
{"nova": 4, "versions": [2, 3],
|
|
"err_msg": "Task was designed to be used with nova V2, 3, "
|
|
"but V4 is selected."}
|
|
)
|
|
def test_validate_nova(self, nova, versions, err_msg):
|
|
validator = validators.RequiredAPIVersionsValidator("nova",
|
|
versions)
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.nova.choose_version.return_value = nova
|
|
config = {"contexts": {"api_versions@openstack": {}}}
|
|
|
|
if err_msg:
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, config, None, None)
|
|
self.assertEqual(err_msg, e.message)
|
|
else:
|
|
result = validator.validate(self.context, config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
@ddt.unpack
|
|
@ddt.data({"version": 2, "err_msg": None},
|
|
{"version": 3, "err_msg": "Task was designed to be used with "
|
|
"nova V3, but V2 is selected."})
|
|
def test_validate_context(self, version, err_msg):
|
|
validator = validators.RequiredAPIVersionsValidator("nova",
|
|
[version])
|
|
|
|
config = {
|
|
"contexts": {"api_versions@openstack": {"nova": {"version": 2}}}}
|
|
|
|
if err_msg:
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, config, None, None)
|
|
self.assertEqual(err_msg, e.message)
|
|
else:
|
|
result = validator.validate(self.context, config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
|
|
class VolumeTypeExistsValidatorTestCase(test.TestCase):
|
|
|
|
def setUp(self):
|
|
super(VolumeTypeExistsValidatorTestCase, self).setUp()
|
|
self.validator = validators.VolumeTypeExistsValidator("volume_type",
|
|
True)
|
|
self.config = copy.deepcopy(config)
|
|
self.context = copy.deepcopy(context)
|
|
|
|
def test_validator_without_ctx(self):
|
|
validator = validators.VolumeTypeExistsValidator("fake_param",
|
|
nullable=True)
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.cinder().volume_types.list.return_value = [mock.MagicMock()]
|
|
|
|
result = validator.validate(self.context, self.config, None, None)
|
|
self.assertIsNone(result, "Unexpected result")
|
|
|
|
def test_validator_without_ctx_failed(self):
|
|
validator = validators.VolumeTypeExistsValidator("fake_param",
|
|
nullable=False)
|
|
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
|
|
clients.cinder().volume_types.list.return_value = [mock.MagicMock()]
|
|
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
validator.validate, self.context, self.config, None, None)
|
|
self.assertEqual(
|
|
"The parameter 'fake_param' is required and should not be empty.",
|
|
e.message)
|
|
|
|
def test_validate_with_ctx(self):
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
clients.cinder().volume_types.list.return_value = []
|
|
ctx = {"args": {"volume_type": "fake_type"},
|
|
"contexts": {"volume_types": ["fake_type"]}}
|
|
result = self.validator.validate(self.context, ctx, None, None)
|
|
|
|
self.assertIsNone(result)
|
|
|
|
def test_validate_with_ctx_failed(self):
|
|
clients = self.context["users"][0]["credential"].clients()
|
|
clients.cinder().volume_types.list.return_value = []
|
|
config = {"args": {"volume_type": "fake_type"},
|
|
"contexts": {"volume_types": ["fake_type_2"]}}
|
|
e = self.assertRaises(
|
|
validators.validation.ValidationError,
|
|
self.validator.validate, self.context, config, None, None)
|
|
|
|
err_msg = ("Specified volume type fake_type not found for user {}. "
|
|
"List of available types: ['fake_type_2']")
|
|
fake_user = self.context["users"][0]
|
|
self.assertEqual(err_msg.format(fake_user), e.message)
|
|
|
|
|
|
@ddt.ddt
|
|
class WorkbookContainsWorkflowValidatorTestCase(test.TestCase):
|
|
|
|
@mock.patch("rally.common.yamlutils.safe_load")
|
|
@mock.patch("%s.os.access" % PATH)
|
|
@mock.patch("%s.open" % PATH)
|
|
def test_validator(self, mock_open, mock_access, mock_safe_load):
|
|
mock_safe_load.return_value = {
|
|
"version": "2.0",
|
|
"name": "wb",
|
|
"workflows": {
|
|
"wf1": {
|
|
"type": "direct",
|
|
"tasks": {
|
|
"t1": {
|
|
"action": "std.noop"
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
validator = validators.WorkbookContainsWorkflowValidator(
|
|
workbook_param="definition", workflow_param="workflow_name")
|
|
|
|
config = {
|
|
"args": {
|
|
"definition": "fake_path1",
|
|
"workflow_name": "wf1"
|
|
}
|
|
}
|
|
|
|
result = validator.validate(None, config, None, None)
|
|
self.assertIsNone(result)
|
|
|
|
self.assertEqual(1, mock_open.called)
|
|
self.assertEqual(1, mock_access.called)
|
|
self.assertEqual(1, mock_safe_load.called)
|