286 lines
11 KiB
Python
286 lines
11 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 abc
|
|
import traceback
|
|
|
|
import six
|
|
|
|
from rally.common import logging
|
|
from rally.common.plugin import plugin
|
|
from rally import exceptions
|
|
|
|
LOG = logging.getLogger(__name__)
|
|
|
|
|
|
@logging.log_deprecated_args("Use 'platform' arg instead", "0.10.0",
|
|
["namespace"], log_function=LOG.warning)
|
|
def configure(name, platform="default", namespace=None):
|
|
if namespace:
|
|
platform = namespace
|
|
|
|
def wrapper(cls):
|
|
return plugin.configure(name=name, platform=platform)(cls)
|
|
|
|
return wrapper
|
|
|
|
|
|
@plugin.base()
|
|
@six.add_metaclass(abc.ABCMeta)
|
|
class Validator(plugin.Plugin):
|
|
"""A base class for all validators."""
|
|
|
|
def __init__(self):
|
|
pass
|
|
|
|
@abc.abstractmethod
|
|
def validate(self, context, config, plugin_cls, plugin_cfg):
|
|
"""Method that validates something.
|
|
|
|
:param context: a validation context
|
|
:param config: dict, configuration of workload
|
|
:param plugin_cls: plugin class
|
|
:param plugin_cfg: dict, with exact configuration of the plugin
|
|
:returns: None if succeeded
|
|
:raises ValidationError: if the config doesn't pass the validator
|
|
"""
|
|
|
|
@staticmethod
|
|
def fail(msg):
|
|
raise ValidationError(msg)
|
|
|
|
@classmethod
|
|
def _get_doc(cls):
|
|
doc = ""
|
|
if cls.__doc__ is not None:
|
|
doc = cls.__doc__
|
|
if cls.__init__.__doc__ is not None:
|
|
if not cls.__init__.__doc__.startswith("\n"):
|
|
doc += "\n"
|
|
doc += cls.__init__.__doc__
|
|
return doc
|
|
|
|
|
|
@configure(name="required_platform")
|
|
class RequiredPlatformValidator(Validator):
|
|
|
|
def __init__(self, platform, **kwargs):
|
|
"""Validates specification of specified platform for the workload.
|
|
|
|
:param platform: name of the platform
|
|
"""
|
|
super(RequiredPlatformValidator, self).__init__()
|
|
self.platform = platform
|
|
self._kwargs = kwargs
|
|
|
|
def validate(self, context, config, plugin_cls, plugin_cfg):
|
|
try:
|
|
pvalidator_cls = RequiredPlatformValidator.get(
|
|
"required_platform",
|
|
platform=self.platform,
|
|
allow_hidden=True)
|
|
except exceptions.PluginNotFound:
|
|
# There is no specific validation for this platform
|
|
|
|
if self.platform not in context["platforms"]:
|
|
self.fail("There is no specification for %s platform in "
|
|
"selected environment." % self.platform)
|
|
|
|
if self.platform == "openstack":
|
|
# NOTE(andreykurilin): We had in-tree openstack plugins for a
|
|
# long time. It will be a hard task to remove this logic
|
|
# easily, since even rally-openstack project (the new
|
|
# location for openstack plugins) use common
|
|
# "required_platform" validator.
|
|
admin = self._kwargs.get("admin", False)
|
|
users = self._kwargs.get("users", False)
|
|
if not (admin or users):
|
|
self.fail(
|
|
"You should specify admin=True or users=True or both "
|
|
"for validating openstack platform.")
|
|
|
|
context = context["platforms"].get(self.platform, {})
|
|
|
|
if admin and context.get("admin") is None:
|
|
self.fail("No admin credential for %s" % self.platform)
|
|
if users and len(context.get("users", ())) == 0:
|
|
if context.get("admin") is None:
|
|
self.fail("No user credentials for %s" % self.platform)
|
|
else:
|
|
# NOTE(andreykurilin): It is a case when the plugin
|
|
# requires 'users' for launching, but there are no
|
|
# specified users in deployment. Let's assume that
|
|
# 'users' context can create them via admin user
|
|
# and do not fail."
|
|
pass
|
|
else:
|
|
pvalidator = pvalidator_cls(**self._kwargs)
|
|
pvalidator.validate(context=context,
|
|
config=config,
|
|
plugin_cls=plugin_cls,
|
|
plugin_cfg=plugin_cfg)
|
|
|
|
|
|
def add(name, **kwargs):
|
|
"""Add validator to the plugin class meta.
|
|
|
|
Add validator name and arguments to validators list stored in the
|
|
plugin meta by 'validators' key. This would be used to iterate
|
|
and execute through all validators during execution of subtask.
|
|
|
|
:param name: str, name of the validator plugin
|
|
:param kwargs: dict, arguments used to initialize validator class
|
|
instance
|
|
"""
|
|
|
|
def wrapper(plugin):
|
|
if issubclass(plugin, RequiredPlatformValidator):
|
|
raise exceptions.RallyException(
|
|
"Cannot add a validator to RequiredPlatformValidator")
|
|
elif issubclass(plugin, Validator) and name != "required_platform":
|
|
raise exceptions.RallyException(
|
|
"Only RequiredPlatformValidator can be added "
|
|
"to other validators as a validator")
|
|
|
|
plugin._meta_setdefault("validators", [])
|
|
plugin._meta_get("validators").append((name, (), kwargs,))
|
|
return plugin
|
|
|
|
return wrapper
|
|
|
|
|
|
def add_default(name, **kwargs):
|
|
"""Add validator to the plugin class default meta.
|
|
|
|
Validator is added to all subclasses by default
|
|
|
|
:param name: str, full name of the validator plugin
|
|
:param kwargs: dict, validator plugin arguments
|
|
"""
|
|
|
|
def wrapper(plugin):
|
|
plugin._default_meta_setdefault("validators", [])
|
|
plugin._default_meta_get("validators").append((name, (), kwargs,))
|
|
return plugin
|
|
return wrapper
|
|
|
|
|
|
# this class doesn't inherit from rally.exceptions.RallyException, since
|
|
# ValidationError should be used only for inner purpose.
|
|
class ValidationError(Exception):
|
|
def __init__(self, message):
|
|
super(ValidationError, self).__init__(message)
|
|
self.message = message
|
|
|
|
|
|
class ValidatablePluginMixin(object):
|
|
|
|
@staticmethod
|
|
def _load_validators(plugin):
|
|
validators = plugin._meta_get("validators", default=[])
|
|
return [(Validator.get(name), args, kwargs)
|
|
for name, args, kwargs in validators]
|
|
|
|
@classmethod
|
|
def validate(cls, name, context, config, plugin_cfg,
|
|
allow_hidden=False, vtype=None):
|
|
"""Execute all validators stored in meta of plugin.
|
|
|
|
Iterate during all validators stored in the meta of Validator
|
|
and execute proper validate() method and add validation result
|
|
to the list.
|
|
|
|
:param name: full name of the plugin to validate
|
|
:param context: a validation context
|
|
:param config: dict with configuration of specified workload
|
|
:param plugin_cfg: dict, with exact configuration of the plugin
|
|
:param allow_hidden: do not ignore hidden plugins
|
|
:param vtype: Type of validation. Allowed types: syntax, platform,
|
|
semantic. HINT: To specify several types use tuple or list with
|
|
types
|
|
:returns: list of ValidationResult(is_valid=False) instances
|
|
"""
|
|
try:
|
|
plugin = cls.get(name, allow_hidden=allow_hidden)
|
|
except exceptions.PluginNotFound as e:
|
|
return [e.format_message()]
|
|
|
|
if vtype is None:
|
|
semantic = True
|
|
syntax = True
|
|
platform = True
|
|
else:
|
|
if not isinstance(vtype, (list, tuple)):
|
|
vtype = [vtype]
|
|
wrong_types = set(vtype) - {"semantic", "syntax", "platform"}
|
|
if wrong_types:
|
|
raise ValueError("Wrong type of validation: %s" %
|
|
", ".join(wrong_types))
|
|
semantic = "semantic" in vtype
|
|
syntax = "syntax" in vtype
|
|
platform = "platform" in vtype
|
|
|
|
syntax_validators = []
|
|
platform_validators = []
|
|
regular_validators = []
|
|
|
|
plugin_validators = cls._load_validators(plugin)
|
|
for validator, args, kwargs in plugin_validators:
|
|
if issubclass(validator, RequiredPlatformValidator):
|
|
if platform:
|
|
platform_validators.append((validator, args, kwargs))
|
|
else:
|
|
validators_of_validators = cls._load_validators(validator)
|
|
if validators_of_validators:
|
|
if semantic:
|
|
regular_validators.append((validator, args, kwargs))
|
|
if platform:
|
|
# Load platform validators from each validator
|
|
platform_validators.extend(validators_of_validators)
|
|
else:
|
|
if syntax:
|
|
syntax_validators.append((validator, args, kwargs))
|
|
|
|
results = []
|
|
for validators in (syntax_validators, platform_validators,
|
|
regular_validators):
|
|
for validator_cls, args, kwargs in validators:
|
|
validator = validator_cls(*args, **kwargs)
|
|
result = None
|
|
try:
|
|
validator.validate(context=context,
|
|
config=config,
|
|
plugin_cls=plugin,
|
|
plugin_cfg=plugin_cfg)
|
|
except ValidationError as e:
|
|
result = e.message
|
|
except Exception:
|
|
# Unexpected error is returned. save traceback as well
|
|
result = traceback.format_exc()
|
|
if result:
|
|
results.append(
|
|
"%(base)s plugin '%(pname)s' doesn't pass %(vname)s "
|
|
"validation. Details: %(error)s" % {
|
|
"base": cls.__name__,
|
|
"pname": name,
|
|
"vname": validator_cls.get_fullname(),
|
|
"error": result
|
|
}
|
|
)
|
|
if results:
|
|
break
|
|
|
|
return results
|