Merge "[validators] Port 'number' validator to plugin base"

This commit is contained in:
Jenkins 2017-04-20 10:49:10 +00:00 committed by Gerrit Code Review
commit 35772486e0
12 changed files with 168 additions and 119 deletions

View File

@ -14,10 +14,10 @@ import random
from rally.common.i18n import _
from rally.common import utils
from rally.common import validation
from rally import exceptions
from rally.task import atomic
from rally.task import scenario
from rally.task import validation
"""Dummy scenarios for testing Rally engine at scale."""
@ -63,8 +63,8 @@ class Dummy(scenario.Scenario):
utils.interruptable_sleep(sleep)
@validation.number("size_of_message",
minval=1, integer_only=True, nullable=True)
@validation.add("number", param_name="size_of_message", minval=1,
integer_only=True, nullable=True)
@scenario.configure(name="Dummy.dummy_exception")
class DummyException(scenario.Scenario):
@ -86,8 +86,8 @@ class DummyException(scenario.Scenario):
raise DummyScenarioException(message)
@validation.number("exception_probability",
minval=0, maxval=1, integer_only=False, nullable=True)
@validation.add("number", param_name="exception_probability",
minval=0, maxval=1, integer_only=False, nullable=True)
@scenario.configure(name="Dummy.dummy_exception_probability")
class DummyExceptionProbability(scenario.Scenario):

View File

@ -103,3 +103,63 @@ class RequiredParameterValidator(validation.Validator):
msg = ("%s parameters are not defined in "
"the benchmark config file") % ", ".join(missing)
return self.fail(msg)
@validation.configure(name="number")
class NumberValidator(validation.Validator):
"""Checks that parameter is a number that pass specified condition.
Ensure a parameter is within the range [minval, maxval]. This is a
closed interval so the end points are included.
:param param_name: Name of parameter to validate
:param minval: Lower endpoint of valid interval
:param maxval: Upper endpoint of valid interval
:param nullable: Allow parameter not specified, or parameter=None
:param integer_only: Only accept integers
"""
def __init__(self, param_name, minval=None, maxval=None, nullable=False,
integer_only=False):
self.param_name = param_name
self.minval = minval
self.maxval = maxval
self.nullable = nullable
self.integer_only = integer_only
def validate(self, credentials, config, plugin_cls, plugin_cfg):
value = config.get("args", {}).get(self.param_name)
num_func = float
if self.integer_only:
# NOTE(boris-42): Force check that passed value is not float, this
# is important cause int(float_numb) won't raise exception
if type(value) == float:
return self.fail("%(name)s is %(val)s which hasn't int type"
% {"name": self.param_name, "val": value})
num_func = int
# None may be valid if the scenario sets a sensible default.
if self.nullable and value is None:
return
try:
number = num_func(value)
if self.minval is not None and number < self.minval:
return self.fail(
"%(name)s is %(val)s which is less than the minimum "
"(%(min)s)" % {"name": self.param_name,
"val": number,
"min": self.minval})
if self.maxval is not None and number > self.maxval:
return self.fail(
"%(name)s is %(val)s which is greater than the maximum "
"(%(max)s)" % {"name": self.param_name,
"val": number,
"max": self.maxval})
except (ValueError, TypeError):
return self.fail("%(name)s is %(val)s which is not a valid "
"%(type)s" % {"name": self.param_name,
"val": value,
"type": num_func.__name__})

View File

@ -31,7 +31,7 @@ class Keystone(scenario.OpenStackScenario):
self.clients("keystone")
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_glance")
class ValidateGlance(scenario.OpenStackScenario):
@ -54,7 +54,7 @@ class ValidateGlance(scenario.OpenStackScenario):
list(glance_client.images.list(name=image_name))
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_nova")
class ValidateNova(scenario.OpenStackScenario):
@ -75,7 +75,7 @@ class ValidateNova(scenario.OpenStackScenario):
nova_client.flavors.list()
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_ceilometer")
class ValidateCeilometer(scenario.OpenStackScenario):
@ -96,7 +96,7 @@ class ValidateCeilometer(scenario.OpenStackScenario):
ceilometer_client.meters.list()
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_cinder")
class ValidateCinder(scenario.OpenStackScenario):
@ -117,7 +117,7 @@ class ValidateCinder(scenario.OpenStackScenario):
cinder_client.volume_types.list()
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_neutron")
class ValidateNeutron(scenario.OpenStackScenario):
@ -138,7 +138,7 @@ class ValidateNeutron(scenario.OpenStackScenario):
neutron_client.list_networks()
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(name="Authenticate.validate_heat")
class ValidateHeat(scenario.OpenStackScenario):
@ -159,7 +159,7 @@ class ValidateHeat(scenario.OpenStackScenario):
list(heat_client.stacks.list(limit=0))
@validation.number("repetitions", minval=1)
@validation.add("number", param_name="repetitions", minval=1)
@validation.add("required_platform", platform="openstack", users=True)
@validation.required_services(consts.Service.MONASCA)
@scenario.configure(name="Authenticate.validate_monasca")

View File

@ -21,7 +21,7 @@ from rally.task import validation
"""Scenarios for Cinder Volume Backup."""
@validation.number("size", minval=1, integer_only=True)
@validation.add("number", param_name="size", minval=1, integer_only=True)
@validation.restricted_parameters(["name", "display_name"],
subdict="create_volume_kwargs")
@validation.restricted_parameters("name",

View File

@ -118,7 +118,7 @@ class AuthenticateUserAndValidateToken(KeystoneBasic):
self.admin_keystone.validate_token(token)
@validation.number("users_per_tenant", minval=1)
@validation.add("number", param_name="users_per_tenant", minval=1)
@validation.add("required_platform", platform="openstack", admin=True)
@scenario.configure(context={"admin_cleanup": ["keystone"]},
name="KeystoneBasic.create_tenant_with_users")

View File

@ -205,9 +205,8 @@ class AttachSecurityServiceToShareNetwork(utils.ManilaScenario):
@validation.validate_share_proto()
@validation.required_services(consts.Service.MANILA)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(
context={"cleanup": ["manila"]},
name=("ManilaShares.create_and_list_share"))
@scenario.configure(context={"cleanup": ["manila"]},
name=("ManilaShares.create_and_list_share"))
class CreateAndListShare(utils.ManilaScenario):
def run(self, share_proto, size=1, min_sleep=0, max_sleep=0, detailed=True,
@ -231,14 +230,16 @@ class CreateAndListShare(utils.ManilaScenario):
self._list_shares(detailed=detailed)
@validation.number("sets", minval=1, integer_only=True)
@validation.number("set_size", minval=1, integer_only=True)
@validation.number("key_min_length", minval=1, maxval=256, integer_only=True)
@validation.number("key_max_length", minval=1, maxval=256, integer_only=True)
@validation.number(
"value_min_length", minval=1, maxval=1024, integer_only=True)
@validation.number(
"value_max_length", minval=1, maxval=1024, integer_only=True)
@validation.add("number", param_name="sets", minval=1, integer_only=True)
@validation.add("number", param_name="set_size", minval=1, integer_only=True)
@validation.add("number", param_name="key_min_length", minval=1, maxval=256,
integer_only=True)
@validation.add("number", param_name="key_max_length", minval=1, maxval=256,
integer_only=True)
@validation.add("number", param_name="value_min_length", minval=1, maxval=1024,
integer_only=True)
@validation.add("number", param_name="value_max_length", minval=1, maxval=1024,
integer_only=True)
@validation.required_services(consts.Service.MANILA)
@validation.add("required_platform", platform="openstack", users=True)
@validation.required_contexts(manila_consts.SHARES_CONTEXT_NAME)

View File

@ -97,7 +97,8 @@ class CreateAndDeleteNetworks(utils.NeutronScenario):
self._delete_network(network["network"])
@validation.number("subnets_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="subnets_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -123,7 +124,8 @@ class CreateAndListSubnets(utils.NeutronScenario):
self._list_subnets()
@validation.number("subnets_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="subnets_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -154,7 +156,8 @@ class CreateAndUpdateSubnets(utils.NeutronScenario):
self._update_subnet(subnet, subnet_update_args)
@validation.number("subnets_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="subnets_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.required_openstack(users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -213,7 +216,8 @@ class CreateAndDeleteSubnets(utils.NeutronScenario):
self._delete_subnet(subnet)
@validation.number("subnets_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="subnets_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -241,7 +245,8 @@ class CreateAndListRouters(utils.NeutronScenario):
self._list_routers()
@validation.number("subnets_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="subnets_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@scenario.configure(context={"cleanup": ["neutron"]},
name="NeutronNetworks.create_and_update_routers")
@ -330,7 +335,8 @@ class SetAndClearRouterGateway(utils.NeutronScenario):
self._remove_gateway_router(router)
@validation.number("ports_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="ports_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -353,7 +359,8 @@ class CreateAndListPorts(utils.NeutronScenario):
self._list_ports()
@validation.number("ports_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="ports_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},
@ -379,7 +386,8 @@ class CreateAndUpdatePorts(utils.NeutronScenario):
self._update_port(port, port_update_args)
@validation.number("ports_per_network", minval=1, integer_only=True)
@validation.add("number", param_name="ports_per_network", minval=1,
integer_only=True)
@validation.required_services(consts.Service.NEUTRON)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["neutron"]},

View File

@ -33,7 +33,8 @@ LOG = logging.getLogger(__name__)
@validation.flavor_exists("master_flavor")
@validation.flavor_exists("worker_flavor")
@validation.required_contexts("users", "sahara_image")
@validation.number("workers_count", minval=1, integer_only=True)
@validation.add("number", param_name="workers_count", minval=1,
integer_only=True)
@validation.required_services(consts.Service.SAHARA)
@validation.add("required_platform", platform="openstack", users=True)
@scenario.configure(context={"cleanup": ["sahara"]},
@ -124,7 +125,8 @@ class CreateAndDeleteCluster(utils.SaharaScenario):
@validation.flavor_exists("worker_flavor")
@validation.required_services(consts.Service.SAHARA)
@validation.required_contexts("users", "sahara_image")
@validation.number("workers_count", minval=1, integer_only=True)
@validation.add("number", param_name="workers_count", minval=1,
integer_only=True)
@scenario.configure(context={"cleanup": ["sahara"]},
name="SaharaClusters.create_scale_delete_cluster")
class CreateScaleDeleteCluster(utils.SaharaScenario):

View File

@ -38,8 +38,8 @@ LOG = logging.getLogger(__name__)
flavor={"type": "nova_flavor"})
@validation.image_valid_on_flavor("flavor", "image", fail_on_404_image=False)
@validation.valid_command("command")
@validation.number("port", minval=1, maxval=65535, nullable=True,
integer_only=True)
@validation.add("number", param_name="port", minval=1, maxval=65535,
nullable=True, integer_only=True)
@validation.external_network_exists("floating_network")
@validation.required_param_or_context(arg_name="image",
ctx_name="image_command_customizer")
@ -393,8 +393,8 @@ EOF
flavor={"type": "nova_flavor"})
@validation.image_valid_on_flavor("flavor", "image")
@validation.valid_command("command")
@validation.number("port", minval=1, maxval=65535, nullable=True,
integer_only=True)
@validation.add("number", param_name="port", minval=1, maxval=65535,
nullable=True, integer_only=True)
@validation.external_network_exists("floating_network")
@validation.required_services(consts.Service.NOVA, consts.Service.CINDER)
@validation.add("required_platform", platform="openstack", users=True)

View File

@ -100,59 +100,6 @@ def validator(fn):
return wrap_given
@validator
def number(config, clients, deployment, param_name, minval=None, maxval=None,
nullable=False, integer_only=False):
"""Checks that parameter is number that pass specified condition.
Ensure a parameter is within the range [minval, maxval]. This is a
closed interval so the end points are included.
:param param_name: Name of parameter to validate
:param minval: Lower endpoint of valid interval
:param maxval: Upper endpoint of valid interval
:param nullable: Allow parameter not specified, or parameter=None
:param integer_only: Only accept integers
"""
val = config.get("args", {}).get(param_name)
num_func = float
if integer_only:
# NOTE(boris-42): Force check that passed value is not float, this is
# important cause int(float_numb) won't raise exception
if type(val) == float:
return ValidationResult(False,
"%(name)s is %(val)s which hasn't int type"
% {"name": param_name, "val": val})
num_func = int
# None may be valid if the scenario sets a sensible default.
if nullable and val is None:
return ValidationResult(True)
try:
number = num_func(val)
if minval is not None and number < minval:
return ValidationResult(
False,
"%(name)s is %(val)s which is less than the minimum "
"(%(min)s)"
% {"name": param_name, "val": number, "min": minval})
if maxval is not None and number > maxval:
return ValidationResult(
False,
"%(name)s is %(val)s which is greater than the maximum "
"(%(max)s)"
% {"name": param_name, "val": number, "max": maxval})
return ValidationResult(True)
except (ValueError, TypeError):
return ValidationResult(
False,
"%(name)s is %(val)s which is not a valid %(type)s"
% {"name": param_name, "val": val, "type": num_func.__name__})
def _file_access_ok(filename, mode, param_name, required=True):
if not filename:
return ValidationResult(not required,
@ -747,3 +694,5 @@ _deprecated_platform_validator = deprecated_validator(
required_openstack = functools.partial(
_deprecated_platform_validator, platform="openstack")
number = deprecated_validator("number", "number", "0.10.0")

View File

@ -117,3 +117,59 @@ class RequiredParameterValidatorTestCase(test.TestCase):
self.assertEqual(err_msg, result.msg)
else:
self.assertIsNone(result)
class NumberValidatorTestCase(test.TestCase):
@staticmethod
def get_validator(minval=None, maxval=None, nullable=False,
integer_only=False):
validator_cls = validation.Validator.get("number")
return validator_cls("foo", minval=minval, maxval=maxval,
nullable=nullable, integer_only=integer_only)
def test_number_not_nullable(self):
result = self.get_validator().validate({}, {}, None, None)
self.assertIsNotNone(result)
self.assertFalse(result.is_valid)
self.assertEqual("foo is None which is not a valid float",
"%s" % result)
def test_number_nullable(self):
self.assertIsNone(self.get_validator(nullable=True).validate(
{}, {}, None, None))
def test_number_min_max_value(self):
validator = self.get_validator(minval=4, maxval=10)
result = validator.validate({}, {"args": {validator.param_name: 3.9}},
None, None)
self.assertIsNotNone(result)
self.assertFalse(result.is_valid)
self.assertEqual("foo is 3.9 which is less than the minimum (4)",
"%s" % result)
result = validator.validate({}, {"args": {validator.param_name: 4.1}},
None, None)
self.assertIsNone(result)
result = validator.validate({}, {"args": {validator.param_name: 11}},
None, None)
self.assertIsNotNone(result)
self.assertFalse(result.is_valid)
self.assertEqual("foo is 11.0 which is greater than the maximum (10)",
"%s" % result)
def test_number_integer_only(self):
validator = self.get_validator(integer_only=True)
result = validator.validate({}, {"args": {validator.param_name: 3.9}},
None, None)
self.assertFalse(result.is_valid, result.msg)
self.assertIsNotNone(result)
self.assertFalse(result.is_valid)
self.assertEqual("foo is 3.9 which hasn't int type", "%s" % result)
result = validator.validate({}, {"args": {validator.param_name: 3}},
None, None)
self.assertIsNone(result)

View File

@ -179,33 +179,6 @@ class ValidatorsTestCase(test.TestCase):
common_validation.ValidationResult(True))
return wrap_validator
def test_number_not_nullable(self):
validator = self._unwrap_validator(validation.number, param_name="n")
self.assertFalse(validator({}, None, None).is_valid)
def test_number_nullable(self):
validator = self._unwrap_validator(validation.number, param_name="n",
nullable=True)
self.assertTrue(validator({}, None, None).is_valid)
def test_number_min_max_value(self):
validator = self._unwrap_validator(validation.number,
param_name="a", minval=4, maxval=10)
result = validator({"args": {"a": 3.9}}, None, None)
self.assertFalse(result.is_valid, result.msg)
result = validator({"args": {"a": 4.1}}, None, None)
self.assertTrue(result.is_valid, result.msg)
result = validator({"args": {"a": 11}}, None, None)
self.assertFalse(result.is_valid, result.msg)
def test_number_integer_only(self):
validator = self._unwrap_validator(validation.number,
param_name="b", integer_only=True)
result = validator({"args": {"b": 3.9}}, None, None)
self.assertFalse(result.is_valid, result.msg)
result = validator({"args": {"b": 3}}, None, None)
self.assertTrue(result.is_valid, result.msg)
@mock.patch(MODULE + "os.access")
def test__file_access_ok(self, mock_access):
mock_access.return_value = True