Move old validators to the new style

* Move valid_command validator under the new plugin base and put it
  under VMTask module, since it is hardcoded for only one type of
  scenarios
* Move flavor_exists to openstack plugins
* Move workbook_contains_workflow to openstack plugins and rename
  'workbook' argument to unified 'param_name'
* Deprecate validate_share_proto in favor of enum validator
* Deprecate an old mechanism for validators

Co-Authored-By: Andrey Kurilin <andr.kurilin@gmail.com>

Change-Id: Ic7b5687d5d83b7f032dd61a9ccc99c6236d63157
This commit is contained in:
Roman Vasyets 2017-08-08 01:42:34 +03:00 committed by Andrey Kurilin
parent 71179ed012
commit 59db62a684
10 changed files with 446 additions and 129 deletions

View File

@ -200,7 +200,7 @@ class CreateAndDeleteImage(GlanceBasic):
values=["ami", "ari", "aki", "vhd", "vmdk", "raw",
"qcow2", "vdi", "iso"])
@validation.add("restricted_parameters", param_names=["image_name", "name"])
@validation.flavor_exists("flavor")
@validation.add("flavor_exists", param_name="flavor")
@validation.add("required_services", services=[consts.Service.GLANCE,
consts.Service.NOVA])
@validation.add("required_platform", platform="openstack", users=True)

View File

@ -24,7 +24,9 @@ from rally.task import validation
"""Scenarios for Manila shares."""
@validation.validate_share_proto()
@validation.add("enum", param_name="share_proto",
values=["NFS", "CIFS", "GLUSTERFS", "HDFS", "CEPHFS"],
case_insensitive=True, missed=False)
@validation.add("required_services", services=[consts.Service.MANILA])
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["manila"]},
@ -70,8 +72,8 @@ class ListShares(utils.ManilaScenario):
self._list_shares(detailed=detailed, search_opts=search_opts)
@validation.add("enum", param_name="share_proto", values=["nfs", "cephfs",
"cifs", "glusterfs", "hdfs"], missed=False,
@validation.add("enum", param_name="share_proto",
values=["NFS", "CIFS", "GLUSTERFS", "HDFS", "CEPHFS"],
case_insensitive=True)
@validation.add("required_services", services=[consts.Service.MANILA])
@validation.add("required_platform", platform="openstack", users=True)
@ -114,8 +116,8 @@ class CreateAndExtendShare(utils.ManilaScenario):
self._extend_share(share, new_size)
@validation.add("enum", param_name="share_proto", values=["nfs", "cephfs",
"cifs", "glusterfs", "hdfs"], missed=False,
@validation.add("enum", param_name="share_proto",
values=["NFS", "CIFS", "GLUSTERFS", "HDFS", "CEPHFS"],
case_insensitive=True)
@validation.add("required_services", services=[consts.Service.MANILA])
@validation.add("required_platform", platform="openstack", users=True)
@ -296,7 +298,9 @@ class AttachSecurityServiceToShareNetwork(utils.ManilaScenario):
self._add_security_service_to_share_network(sn, ss)
@validation.validate_share_proto()
@validation.add("enum", param_name="share_proto",
values=["NFS", "CIFS", "GLUSTERFS", "HDFS", "CEPHFS"],
case_insensitive=True)
@validation.add("required_services", services=[consts.Service.MANILA])
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["manila"]},

View File

@ -51,14 +51,15 @@ class ListExecutions(utils.MistralScenario):
sort_keys=sort_keys, sort_dirs=sort_dirs)
@validation.add("file_exists", param_name="definition")
@types.convert(definition={"type": "file"})
@types.convert(params={"type": "file"})
@types.convert(wf_input={"type": "file"})
@validation.add("file_exists", param_name="definition")
@validation.add("required_platform", platform="openstack", users=True)
@validation.add("required_services",
services=[consts.Service.MISTRAL])
@validation.workbook_contains_workflow("definition", "workflow_name")
@validation.add("workbook_contains_workflow", param_name="definition",
workflow_name="workflow_name")
@scenario.configure(name="MistralExecutions.create_execution_from_workbook",
context={"cleanup": ["mistral"]},
platform="openstack")

View File

@ -39,8 +39,8 @@ class ListWorkbooks(utils.MistralScenario):
self._list_workbooks()
@validation.add("file_exists", param_name="definition")
@types.convert(definition={"type": "file"})
@validation.add("file_exists", param_name="definition")
@validation.add("required_platform", platform="openstack", users=True)
@validation.add("required_services",
services=[consts.Service.MISTRAL])

View File

@ -30,8 +30,8 @@ LOG = logging.getLogger(__name__)
worker_flavor={"type": "nova_flavor"},
neutron_net={"type": "neutron_network"},
floating_ip_pool={"type": "neutron_network"})
@validation.flavor_exists("master_flavor")
@validation.flavor_exists("worker_flavor")
@validation.add("flavor_exists", param_name="master_flavor")
@validation.add("flavor_exists", param_name="worker_flavor")
@validation.add("required_contexts", contexts=["users", "sahara_image"])
@validation.add("number", param_name="workers_count", minval=1,
integer_only=True)
@ -122,8 +122,8 @@ class CreateAndDeleteCluster(utils.SaharaScenario):
@types.convert(flavor={"type": "nova_flavor"},
master_flavor={"type": "nova_flavor"},
worker_flavor={"type": "nova_flavor"})
@validation.flavor_exists("master_flavor")
@validation.flavor_exists("worker_flavor")
@validation.add("flavor_exists", param_name="master_flavor")
@validation.add("flavor_exists", param_name="worker_flavor")
@validation.add("required_services", services=[consts.Service.SAHARA])
@validation.add("required_contexts", contexts=["users", "sahara_image"])
@validation.add("number", param_name="workers_count", minval=1,

View File

@ -23,7 +23,7 @@ from rally.task import validation
@types.convert(flavor={"type": "nova_flavor"})
@validation.flavor_exists("flavor")
@validation.add("flavor_exists", param_name="flavor")
@validation.add("required_services", services=[consts.Service.SAHARA])
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(
@ -69,7 +69,7 @@ class CreateAndListNodeGroupTemplates(utils.SaharaScenario):
@types.convert(flavor={"type": "nova_flavor"})
@validation.flavor_exists("flavor")
@validation.add("flavor_exists", param_name="flavor")
@validation.add("required_services", services=[consts.Service.SAHARA])
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(

View File

@ -14,19 +14,21 @@
# under the License.
import json
import os
import pkgutil
from rally.common import logging
from rally.common import sshutils
from rally.common import validation
from rally import consts
from rally import exceptions
from rally.plugins.common import validators
from rally.plugins.openstack import scenario
from rally.plugins.openstack.scenarios.cinder import utils as cinder_utils
from rally.plugins.openstack.scenarios.vm import utils as vm_utils
from rally.plugins.openstack.services import heat
from rally.task import atomic
from rally.task import types
from rally.task import validation
"""Scenarios that are to be run inside VM instances."""
@ -35,11 +37,91 @@ from rally.task import validation
LOG = logging.getLogger(__name__)
# TODO(andreykurilin): replace by advanced jsonschema(lollipop?!) someday
@validation.configure(name="valid_command", platform="openstack")
class ValidCommandValidator(validation.Validator):
def __init__(self, param_name, required=True):
"""Checks that parameter is a proper command-specifying dictionary.
Ensure that the command dictionary is a proper command-specifying
dictionary described in `vmtasks.VMTasks.boot_runcommand_delete'
docstring.
:param param_name: Name of parameter to validate
:param required: Boolean indicating that the command dictionary is
required
"""
super(ValidCommandValidator, self).__init__()
self.param_name = param_name
self.required = required
def check_command_dict(self, command):
"""Check command-specifying dict `command'
:raises ValueError: on error
"""
if not isinstance(command, dict):
raise ValueError("Command must be a dictionary")
# NOTE(pboldin): Here we check for the values not for presence of the
# keys due to template-driven configuration generation that can leave
# keys defined but values empty.
if command.get("interpreter"):
script_file = command.get("script_file")
if script_file:
if "script_inline" in command:
raise ValueError(
"Exactly one of script_inline or script_file with "
"interpreter is expected: %r" % command)
# User tries to upload a shell? Make sure it is same as interpreter
interpreter = command.get("interpreter")
interpreter = (interpreter[-1]
if isinstance(interpreter, (tuple, list))
else interpreter)
if (command.get("local_path") and
command.get("remote_path") != interpreter):
raise ValueError(
"When uploading an interpreter its path should be as well"
" specified as the `remote_path' string: %r" % command)
elif not command.get("remote_path"):
# No interpreter and no remote command to execute is given
raise ValueError(
"Supplied dict specifies no command to execute, either "
"interpreter or remote_path is required: %r" % command)
unexpected_keys = set(command) - {"script_file", "script_inline",
"interpreter", "remote_path",
"local_path", "command_args"}
if unexpected_keys:
raise ValueError(
"Unexpected command parameters: %s" % ", ".join(
unexpected_keys))
def validate(self, config, credentials, plugin_cls, plugin_cfg):
command = config.get("args", {}).get(self.param_name)
if command is None and not self.required:
return
try:
self.check_command_dict(command)
except ValueError as e:
return self.fail(str(e))
for key in "script_file", "local_path":
if command.get(key):
return validators.ValidatorUtils._file_access_ok(
filename=command[key], mode=os.R_OK,
param_name=self.param_name, required=self.required)
@types.convert(image={"type": "glance_image"},
flavor={"type": "nova_flavor"})
@validation.add("image_valid_on_flavor", flavor_param="flavor",
image_param="image", fail_on_404_image=False)
@validation.valid_command("command")
@validation.add("valid_command", param_name="command")
@validation.add("number", param_name="port", minval=1, maxval=65535,
nullable=True, integer_only=True)
@validation.add("external_network_exists", param_name="floating_network")
@ -398,7 +480,7 @@ EOF
flavor={"type": "nova_flavor"})
@validation.add("image_valid_on_flavor", flavor_param="flavor",
image_param="image")
@validation.valid_command("command")
@validation.add("valid_command", param_name="command")
@validation.add("number", param_name="port", minval=1, maxval=65535,
nullable=True, integer_only=True)
@validation.add("external_network_exists", param_name="floating_network")

View File

@ -23,8 +23,10 @@ from rally.task import types
from rally.common import logging
from rally.common import validation
from rally.common import yamlutils as yaml
from rally import consts
from rally import exceptions
from rally.plugins.common import validators
from rally.plugins.openstack.context.nova import flavors as flavors_ctx
from rally.plugins.openstack import types as openstack_types
@ -151,9 +153,62 @@ class RequiredNeutronExtensionsValidator(validation.Validator):
return self.fail(msg)
@validation.add("required_platform", platform="openstack", users=True)
@validation.configure(name="flavor_exists", platform="openstack")
class FlavorExistsValidator(validation.Validator):
def __init__(self, param_name):
"""Returns validator for flavor
:param param_name: defines which variable should be used
to get flavor id value.
"""
super(FlavorExistsValidator, self).__init__()
self.param_name = param_name
def _get_flavor_from_context(self, config, flavor_value):
if "flavors" not in config.get("context", {}):
raise exceptions.InvalidScenarioArgument("No flavors context")
flavors = [flavors_ctx.FlavorConfig(**f)
for f in config["context"]["flavors"]]
resource = types.obj_from_name(resource_config=flavor_value,
resources=flavors, typename="flavor")
flavor = flavors_ctx.FlavorConfig(**resource)
flavor.id = "<context flavor: %s>" % flavor.name
return flavor
def _get_validated_flavor(self, config, clients, param_name):
flavor_value = config.get("args", {}).get(param_name)
if not flavor_value:
msg = "Parameter %s is not specified." % param_name
return ValidationResult(False, msg), None
try:
flavor_id = openstack_types.Flavor.transform(
clients=clients, resource_config=flavor_value)
flavor = clients.nova().flavors.get(flavor=flavor_id)
return ValidationResult(True), flavor
except (nova_exc.NotFound, exceptions.InvalidScenarioArgument):
try:
return ValidationResult(True), self._get_flavor_from_context(
config, flavor_value)
except exceptions.InvalidScenarioArgument:
pass
message = "Flavor '%s' not found" % flavor_value
return ValidationResult(False, message), None
def validate(self, config, credentials, plugin_cls, plugin_cfg):
# flavors do not depend on user or tenant, so checking for one user
# should be enough
user = credentials["openstack"]["users"][0]
clients = user["credential"].clients()
return self._get_validated_flavor(config, clients, self.param_name)[0]
@validation.add("required_platform", platform="openstack", users=True)
@validation.configure(name="image_valid_on_flavor", platform="openstack")
class ImageValidOnFlavorValidator(validation.Validator):
class ImageValidOnFlavorValidator(FlavorExistsValidator):
def __init__(self, flavor_param, image_param,
fail_on_404_image=True, validate_disk=True):
@ -170,8 +225,7 @@ class ImageValidOnFlavorValidator(validation.Validator):
:param fail_on_404_image: flag what indicate whether to validate image
or not.
"""
super(ImageValidOnFlavorValidator, self).__init__()
self.flavor_name = flavor_param
super(ImageValidOnFlavorValidator, self).__init__(flavor_param)
self.image_name = image_param
self.fail_on_404_image = fail_on_404_image
self.validate_disk = validate_disk
@ -219,36 +273,6 @@ class ImageValidOnFlavorValidator(validation.Validator):
message = ("Image '%s' not found") % image_args
return (ValidationResult(False, message), None)
def _get_flavor_from_context(self, config, flavor_value):
if "flavors" not in config.get("context", {}):
raise exceptions.InvalidScenarioArgument("No flavors context")
flavors = [flavors_ctx.FlavorConfig(**f)
for f in config["context"]["flavors"]]
resource = types.obj_from_name(resource_config=flavor_value,
resources=flavors, typename="flavor")
flavor = flavors_ctx.FlavorConfig(**resource)
flavor.id = "<context flavor: %s>" % flavor.name
return (ValidationResult(True), flavor)
def _get_validated_flavor(self, config, clients, param_name):
flavor_value = config.get("args", {}).get(param_name)
if not flavor_value:
msg = "Parameter %s is not specified." % param_name
return (ValidationResult(False, msg), None)
try:
flavor_id = openstack_types.Flavor.transform(
clients=clients, resource_config=flavor_value)
flavor = clients.nova().flavors.get(flavor=flavor_id)
return (ValidationResult(True), flavor)
except (nova_exc.NotFound, exceptions.InvalidScenarioArgument):
try:
return self._get_flavor_from_context(config, flavor_value)
except exceptions.InvalidScenarioArgument:
pass
message = ("Flavor '%s' not found") % flavor_value
return (ValidationResult(False, message), None)
def validate(self, config, credentials, plugin_cls, plugin_cfg):
flavor = None
@ -257,7 +281,7 @@ class ImageValidOnFlavorValidator(validation.Validator):
if not flavor:
valid_result, flavor = self._get_validated_flavor(
config, clients, self.flavor_name)
config, clients, self.param_name)
if not valid_result.is_valid:
return valid_result
@ -555,3 +579,35 @@ class VolumeTypeExistsValidator(validation.Validator):
else:
msg = ("The parameter '{}' is required and should not be empty.")
return self.fail(msg.format(self.param))
@validation.configure(name="workbook_contains_workflow", platform="openstack")
class WorkbookContainsWorkflowValidator(validation.Validator):
def __init__(self, param_name, workflow_name):
"""Validate that workflow exist in workbook when workflow is passed
:param param_name: parameter containing the workbook definition
:param workflow_name: parameter containing the workflow name
"""
super(WorkbookContainsWorkflowValidator, self).__init__()
self.param_name = param_name
self.workflow_name = workflow_name
def validate(self, config, credentials, plugin_cls, plugin_cfg):
wf_name = config.get("args", {}).get(self.param_name)
if wf_name:
wb_path = config.get("args", {}).get(self.param_name)
wb_path = os.path.expanduser(wb_path)
file_result = validators.ValidatorUtils._file_access_ok(
config.get("args", {}).get(self.param_name),
os.R_OK, self.param_name)
if not file_result.is_valid:
return file_result
with open(wb_path, "r") as wb_def:
wb_def = yaml.safe_load(wb_def)
if wf_name not in wb_def["workflows"]:
self.fail("workflow '{}' not found "
"in the definition '{}'".format(wf_name,
wb_def))

View File

@ -13,9 +13,12 @@
# License for the specific language governing permissions and limitations
# under the License.
import os
import ddt
import mock
from rally.common import validation
from rally import exceptions
from rally.plugins.openstack.scenarios.vm import vmtasks
from tests.unit import test
@ -284,3 +287,123 @@ class VMTasksTestCase(test.ScenarioTestCase):
"description": "Data generated by workload",
"title": "Workload summary"}
scenario.add_output.assert_called_once_with(complete=expected)
@ddt.ddt
class ValidCommandValidatorTestCase(test.TestCase):
def setUp(self):
super(ValidCommandValidatorTestCase, self).setUp()
self.credentials = dict(openstack={"admin": mock.MagicMock(),
"users": [mock.MagicMock()], })
@ddt.data({"raises_message": "Command must be a dictionary"},
{"command": "foo",
"raises_message": "Command must be a dictionary"},
{"command": {"interpreter": "foobar", "script_file": "foo",
"script_inline": "bar"},
"raises_message": "Exactly one of "},
{"command": {"script_file": "foobar"},
"raises_message": "Supplied dict specifies no"},
{"command": {"script_inline": "foobar",
"interpreter": "foo",
"local_path": "bar"},
"raises_message": "When uploading an interpreter its path"},
{"command": {"interpreter": "/bin/bash",
"script_path": "foo"},
"raises_message": ("Unexpected command parameters: "
"script_path")},
{"command": {"script_inline": "foobar",
"interpreter": ["ENV=bar", "/bin/foo"],
"local_path": "bar",
"remote_path": "/bin/foo"}},
{"command": {"script_inline": "foobar", "interpreter": "foo"}})
@ddt.unpack
def test_check_command_dict(self, command=None, raises_message=None):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
if raises_message:
self.assertRaises(
ValueError, validator.check_command_dict, command)
else:
self.assertIsNone(validator.check_command_dict(command))
@mock.patch("rally.plugins.common.validators.ValidatorUtils"
"._file_access_ok")
def test_validate(self, mock__file_access_ok):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
mock__file_access_ok.return_value = None
command = {"script_file": "foobar", "interpreter": "foo"}
result = validator.validate({"args": {"p": command}},
self.credentials, None, None)
self.assertIsNone(result)
mock__file_access_ok.assert_called_once_with(
filename="foobar", mode=os.R_OK, param_name="p",
required=True)
def test_valid_command_not_required(self):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=False)
result = validator.validate({"args": {"p": None}},
self.credentials, None, None)
self.assertIsNone(result)
def test_valid_command_required(self):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
result = validator.validate({"args": {"p": None}},
self.credentials, None, None)
self.assertEqual("Command must be a dictionary", result.msg)
@mock.patch("rally.plugins.common.validators.ValidatorUtils"
"._file_access_ok")
def test_valid_command_unreadable_script_file(self, mock__file_access_ok):
mock__file_access_ok.return_value = validation.ValidationResult(False)
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
command = {"script_file": "foobar", "interpreter": "foo"}
result = validator.validate({"args": {"p": command}},
self.credentials, None, None)
self.assertFalse(result.is_valid, result.msg)
@mock.patch("%s.ValidCommandValidator.check_command_dict" % BASE)
def test_valid_command_fail_check_command_dict(self,
mock_check_command_dict):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
mock_check_command_dict.side_effect = ValueError("foobar")
command = {"foo": "bar"}
result = validator.validate({"args": {"p": command}},
self.credentials, None, None)
self.assertFalse(result.is_valid, result.msg)
self.assertEqual("foobar", result.msg)
def test_valid_command_script_inline(self):
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
command = {"script_inline": "bar", "interpreter": "/bin/sh"}
result = validator.validate({"args": {"p": command}}, self.credentials,
None, None)
self.assertIsNone(result)
@mock.patch("rally.plugins.common.validators.ValidatorUtils"
"._file_access_ok")
def test_valid_command_local_path(self, mock__file_access_ok):
mock__file_access_ok.return_value = validation.ValidationResult(False)
validator = vmtasks.ValidCommandValidator(param_name="p",
required=True)
command = {"remote_path": "bar", "local_path": "foobar"}
result = validator.validate({"args": {"p": command}}, self.credentials,
None, None)
self.assertFalse(result.is_valid, result.msg)
mock__file_access_ok.assert_called_once_with(
filename="foobar", mode=os.R_OK, param_name="p",
required=True)

View File

@ -197,6 +197,89 @@ class RequiredNeutronExtensionsValidatorTestCase(test.TestCase):
self.assertIsNone(result)
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.credentials = copy.deepcopy(credentials)
def test__get_validated_flavor_wrong_value_in_config(self):
result = self.validator._get_validated_flavor(self.config,
self.credentials,
"foo_flavor")
self.assertEqual("Parameter foo_flavor is not specified.",
result[0].msg)
@mock.patch("rally.plugins.openstack.validators"
".openstack_types.Flavor.transform",
return_value="flavor_id")
def test__get_validated_flavor(self, mock_flavor_transform):
clients = mock.Mock()
clients.nova().flavors.get.return_value = "flavor"
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertTrue(result[0].is_valid, result[0].msg)
self.assertEqual("flavor", result[1])
mock_flavor_transform.assert_called_once_with(
clients=clients, resource_config=self.config["args"]["flavor"])
clients.nova().flavors.get.assert_called_once_with(flavor="flavor_id")
clients.side_effect = exceptions.InvalidScenarioArgument("")
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertTrue(result[0].is_valid, result[0].msg)
self.assertEqual("flavor", result[1])
mock_flavor_transform.assert_called_with(
clients=clients, resource_config=self.config["args"]["flavor"])
clients.nova().flavors.get.assert_called_with(flavor="flavor_id")
@mock.patch("rally.plugins.openstack.validators"
".openstack_types.Flavor.transform")
def test__get_validated_flavor_not_found(self, mock_flavor_transform):
clients = mock.MagicMock()
clients.nova().flavors.get.side_effect = nova_exc.NotFound("")
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertFalse(result[0].is_valid, result[0].msg)
self.assertEqual("Flavor '%s' not found" %
self.config["args"]["flavor"],
result[0].msg)
mock_flavor_transform.assert_called_once_with(
clients=clients, resource_config=self.config["args"]["flavor"])
@mock.patch("rally.plugins.openstack.validators"
".types.obj_from_name")
@mock.patch("rally.plugins.openstack.validators"
".flavors_ctx.FlavorConfig")
def test__get_flavor_from_context(self, mock_flavor_config,
mock_obj_from_name):
config = {"context": {"images": {"fake_parameter_name": "foo_image"},
}
}
self.assertRaises(exceptions.InvalidScenarioArgument,
self.validator._get_flavor_from_context,
config, "foo_flavor")
config = {"context": {"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)
@ddt.ddt
class ImageValidOnFlavorValidatorTestCase(test.TestCase):
@ -295,59 +378,6 @@ class ImageValidOnFlavorValidatorTestCase(test.TestCase):
result = validator.validate(self.config, self.credentials, None, None)
self.assertIsNone(result)
def test__get_validated_flavor_wrong_value_in_config(self):
result = self.validator._get_validated_flavor(self.config,
self.credentials,
"foo_flavor")
self.assertEqual("Parameter foo_flavor is not specified.",
result[0].msg)
@mock.patch("rally.plugins.openstack.validators"
".openstack_types.Flavor.transform",
return_value="flavor_id")
def test__get_validated_flavor(self, mock_flavor_transform):
clients = mock.Mock()
clients.nova().flavors.get.return_value = "flavor"
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertTrue(result[0].is_valid, result[0].msg)
self.assertEqual("flavor", result[1])
mock_flavor_transform.assert_called_once_with(
clients=clients, resource_config=self.config["args"]["flavor"])
clients.nova().flavors.get.assert_called_once_with(flavor="flavor_id")
clients.side_effect = exceptions.InvalidScenarioArgument("")
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertTrue(result[0].is_valid, result[0].msg)
self.assertEqual("flavor", result[1])
mock_flavor_transform.assert_called_with(
clients=clients, resource_config=self.config["args"]["flavor"])
clients.nova().flavors.get.assert_called_with(flavor="flavor_id")
@mock.patch("rally.plugins.openstack.validators"
".openstack_types.Flavor.transform")
def test__get_validated_flavor_not_found(self, mock_flavor_transform):
clients = mock.MagicMock()
clients.nova().flavors.get.side_effect = nova_exc.NotFound("")
result = self.validator._get_validated_flavor(self.config,
clients,
"flavor")
self.assertFalse(result[0].is_valid, result[0].msg)
self.assertEqual("Flavor '%s' not found" %
self.config["args"]["flavor"],
result[0].msg)
mock_flavor_transform.assert_called_once_with(
clients=clients, resource_config=self.config["args"]["flavor"])
@mock.patch("rally.plugins.openstack.validators"
".openstack_types.GlanceImage.transform",
return_value="image_id")
@ -452,29 +482,6 @@ class ImageValidOnFlavorValidatorTestCase(test.TestCase):
clients=clients, resource_config=config["args"]["image"])
clients.glance().images.get.assert_called_with("image_id")
@mock.patch("rally.plugins.openstack.validators"
".types.obj_from_name")
@mock.patch("rally.plugins.openstack.validators"
".flavors_ctx.FlavorConfig")
def test__get_flavor_from_context(self, mock_flavor_config,
mock_obj_from_name):
config = {"context": {"images": {"fake_parameter_name": "foo_image"},
}
}
self.assertRaises(exceptions.InvalidScenarioArgument,
self.validator._get_flavor_from_context,
config, "foo_flavor")
config = {"context": {"images": {"fake_parameter_name": "foo_image"},
"flavors": [{"flavor1": "fake_flavor1"}]}
}
result = self.validator._get_flavor_from_context(config, "foo_flavor")
self.assertIsInstance(result[0], validators.ValidationResult)
self.assertTrue(result[0].is_valid)
self.assertEqual("<context flavor: %s>" % result[1].name, result[1].id)
class RequiredClientsValidatorTestCase(test.TestCase):
@ -837,3 +844,47 @@ class VolumeTypeExistsValidatorTestCase(test.TestCase):
self.assertEqual(err_msg.format(fake_user), result.msg)
else:
self.assertIsNone(result)
@ddt.ddt
class WorkbookContainsWorkflowValidatorTestCase(test.TestCase):
def setUp(self):
super(WorkbookContainsWorkflowValidatorTestCase, self).setUp()
self.config = copy.deepcopy(config)
self.credentials = copy.deepcopy(credentials)
@mock.patch("rally.common.yamlutils.safe_load")
@mock.patch("rally.plugins.openstack.validators.os.access")
@mock.patch("rally.plugins.openstack.validators.open")
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(
param_name="definition", workflow_name="workflow_name")
context = {
"args": {
"definition": "fake_path1",
"workflow_name": "wf1"
}
}
result = validator.validate(context, None, None, None)
self.assertIsNone(result)
self.assertEqual(1, mock_open.called)
self.assertEqual(1, mock_access.called)
self.assertEqual(1, mock_safe_load.called)