openstack实现对数据的通用校验源码分析:validation(以nova为例)

validation是实现对前端对发送过来的api请求的数据(body)的验证功能,当数据不符合要求时前端返回相应的报错信息的功能
以nova项目为例:

nova中的通用validation是在/nova/nova/api/validation文件下代码结构如下:

├── __init__.py
├── parameter_types.py
└── validators.py

首先我们进入init.py文件查看一下代码:

import functools
from nova.api.openstack import api_version_request as api_version
from nova.api.validation import validators
def schema(request_body_schema, min_version=None, max_version=None):
    def add_validator(func):
        @functools.wraps(func)
        def wrapper(*args, **kwargs):
            min_ver = api_version.APIVersionRequest(min_version)
            max_ver = api_version.APIVersionRequest(max_version)
            if 'req' in kwargs:
                ver = kwargs['req'].api_version_request
                legacy_v2 = kwargs['req'].is_legacy_v2()
            else:
                ver = args[1].api_version_request
                legacy_v2 = args[1].is_legacy_v2()

            if legacy_v2:
                if min_version is None or min_version == '2.0':
                    schema_validator = validators._SchemaValidator(
                        request_body_schema, legacy_v2)
                    schema_validator.validate(kwargs['body'])
            elif ver.matches(min_ver, max_ver):
                schema_validator = validators._SchemaValidator(
                    request_body_schema, legacy_v2)
                schema_validator.validate(kwargs['body'])
            return func(*args, **kwargs)
        return wrapper
    return add_validator

从上面的代码中我们可以看到定义了一个装饰器函数schema。下面看看该装饰器在什么地方用到:

lic@lic-Lenovo-G480:/opt/stack/nova/nova/api$ grep -r validation.schema

openstack/compute/keypairs.py:    @validation.schema(keypairs.create_v210)
openstack/compute/keypairs.py:    @validation.schema(keypairs.create_v22)
.
此处省略。。。
.
openstack/compute/aggregates.py:    @validation.schema(aggregates.remove_host)
openstack/compute/aggregates.py:    @validation.schema(aggregates.set_metadata)
openstack/compute/hosts.py:    @validation.schema(hosts.update)

结果发现是在nova的api接口函数中调用,我们以openstack/compute/hosts.py为例,先打开文件看到如下代码

from nova.api.openstack.compute.schemas import host    
    @extensions.expected_errors((400, 404, 501))
    @validation.schema(hosts.update)
    def update(self, req, id, body):
        def read_enabled(orig_val):
            val = orig_val.strip().lower()
            return val == "enable"

        context = req.environ['nova.context']
        context.can(hosts_policies.BASE_POLICY_NAME)
        status = body.get('status')
        maint_mode = body.get('maintenance_mode')
        if status is not None:
            status = read_enabled(status)
        if maint_mode is not None:
            maint_mode = read_enabled(maint_mode)
        result = {'host': id}
        if status is not None:
            result['status'] = self._set_enabled_status(context, id, status)
        if maint_mode is not None:
            result['maintenance_mode'] = self._set_host_maintenance(context,
                                                                    id,
                                                                    maint_mode)
        return result

我们来看看 @validation.schema(hosts.update)中的参数hosts.update是什么,打开nova/nova/api/openstack/compute/schemas/host.py文件看一看:

update = {
    'type': 'object',
    'properties': {
        'status': {
             'type': 'string',
             'enum': ['enable', 'disable',
                      'Enable', 'Disable',
                      'ENABLE', 'DISABLE'],
        },
        'maintenance_mode': {
             'type': 'string',
             'enum': ['enable', 'disable',
                      'Enable', 'Disable',
                      'ENABLE', 'DISABLE'],
        },
        'anyOf': [
            {'required': ['status']},
            {'required': ['maintenance_mode']}
        ],
    },
    'additionalProperties': False
}

让我们回到该装饰器函数看看下面这段代码里的request_body_schema就是上面的update对象。

schema_validator = validators._SchemaValidator(
    request_body_schema, legacy_v2)

我们看一下validators._SchemaValidator函数是啥,打开validators.py文件:

class _SchemaValidator(object):
    validator = None
    validator_org = jsonschema.Draft4Validator
    def __init__(self, schema, relax_additional_properties=False):
        validators = {
            'minimum': self._validate_minimum,
            'maximum': self._validate_maximum,
        }
        if relax_additional_properties:
            validators[
                'additionalProperties'] = _soft_validate_additional_properties
        validator_cls = jsonschema.validators.extend(self.validator_org,validators)
        format_checker = FormatChecker()
        self.validator = validator_cls(schema, format_checker=format_checker)
    def validate(self, *args, **kwargs):
        try:
            self.validator.validate(*args, **kwargs)
        except jsonschema.ValidationError as ex:
            if isinstance(ex.cause, exception.InvalidName):
                detail = ex.cause.format_message()
            elif len(ex.path) > 0:
                # NOTE: For whole OpenStack message consistency, this error
                #       message has been written as the similar format of WSME.
                detail = _("Invalid input for field/attribute %(path)s."
                           " Value: %(value)s. %(message)s") % {
                               'path': ex.path.pop(), 'value': ex.instance,
                               'message': ex.message
                           }
            else:
                detail = ex.message
            raise exception.ValidationError(detail=detail)
        except TypeError as ex:
            # NOTE: If passing non string value to patternProperties parameter,
            #       TypeError happens. Here is for catching the TypeError.
            detail = six.text_type(ex)
            raise exception.ValidationError(detail=detail)

原来是一个classs,从上面的代码可以看出来hosts.update对象里面定义的就是所期望的json数据格式,看装饰里面还有一个chema_validator.validate(kwargs[‘body’])函数,kwargs[‘body’]就是从前端发送过来的body一组json数据,这个函数就是用来严重发送过来的数据是否符合,而具体是怎么实现验证功能的呢,由代码我们可以看出主要是借用了一个jsonschema的包,jsonschema的具体使用方法我会在后面的文章中介绍。好了那么这了已经介绍完了验证的过程了,那又是怎么把验证的消息发送到前端的呢。我们看下面两行代码在检测到发送过来的body不对时会raise一个ValidationError的错误。

detail = six.text_type(ex)
    raise exception.ValidationError(detail=detail)

ValidationError是自己定义的一个异常,如下代码所示,结合jsonschema.ValidationError异常抛出的信息。

class NovaException(Exception):
    msg_fmt = _("An unknown exception occurred.")
    code = 500
    headers = {}
    safe = False

    def __init__(self, message=None, **kwargs):
        self.kwargs = kwargs

        if 'code' not in self.kwargs:
            try:
                self.kwargs['code'] = self.code
            except AttributeError:
                pass

        if not message:
            try:
                message = self.msg_fmt % kwargs

            except Exception:
                exc_info = sys.exc_info()
                LOG.exception(_LE('Exception in string format operation'))
                for name, value in six.iteritems(kwargs):
                    LOG.error("%s: %s" % (name, value))  # noqa

                if CONF.fatal_exception_format_errors:
                    six.reraise(*exc_info)
                else:
                    message = self.msg_fmt

        self.message = message
        super(NovaException, self).__init__(message)
    def format_message(self):
        return self.args[0]

class Invalid(NovaException):
    msg_fmt = _("Bad Request - Invalid Parameters")
    code = 400

class ValidationError(Invalid):
    msg_fmt = "%(detail)”

那又是怎样把错误信息发送到前端的呢,我们看api接口函数中还调用了另外一个装饰器@extensions.expected_errors((400, 404, 501))。我们打开代码看一下:

def expected_errors(errors):
    def decorator(f):
        @functools.wraps(f)
        def wrapped(*args, **kwargs):
            try:
                return f(*args, **kwargs)
            except Exception as exc:
                if isinstance(exc, webob.exc.WSGIHTTPException):
                    if isinstance(errors, int):
                        t_errors = (errors,)
                    else:
                        t_errors = errors
                    if exc.code in t_errors:
                        raise
                elif isinstance(exc, exception.Forbidden):
                    raise
                elif isinstance(exc, exception.ValidationError):
                    raise
                elif isinstance(exc, exception.Unauthorized):
                    raise

                LOG.exception(_LE("Unexpected exception in API method"))
                msg = _('Unexpected API Error. Please report this at '
                    'http://bugs.launchpad.net/nova/ and attach the Nova '
                    'API log if possible.\n%s') % type(exc)
                raise webob.exc.HTTPInternalServerError(explanation=msg)

        return wrapped

    return decorator

其中有下面这句代码,当检验到异常类型是exception.ValidationError时raise了。

isinstance(exc, exception.ValidationError):
    raise

这里也没有把消息发送到前端呀,那是在什么地方做的呢,应该就是在wsgi里面实现的了,在解析http时产生的action,调用到相应的接口,由上面的分析,会raise一个异常,再把异常信息返回给前端。具体是怎么实现的呢需要理解openstack的wsgi相关的内容了,就留到下次分析wsgi时来讲解。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
"Failed to start openstack-nova-compute.service: Unit not found" 这个错误通常是由于缺少相关的服务单元文件导致的。openstack-nova-compute.service 是 OpenStack Nova Compute 服务的一个单元,用于管理计算节点上的虚拟机实例。 要解决这个问题,你可以尝试以下几个步骤: 1. 检查服务单元文件是否存在:首先确认 openstack-nova-compute.service 文件是否存在于正确的位置。在大多数 Linux 发行版中,服务单元文件通常位于 /etc/systemd/system/ 或者 /lib/systemd/system/ 目录下。确保该文件存在,并且具有正确的权限。 2. 检查服务是否正确安装:确保 OpenStack Nova Compute 服务已经正确地安装在计算节点上。你可以使用适合你的 Linux 发行版的包管理工具来安装或者重新安装该服务。 3. 检查服务配置文件:确认 openstack-nova-compute 服务的配置文件是否正确。在大多数情况下,配置文件位于 /etc/nova/nova.conf 或者 /etc/nova/nova-compute.conf。检查配置文件中的选项是否正确设置,并且确保与其他组件的配置相匹配。 4. 检查依赖关系:OpenStack Nova Compute 服务可能依赖于其他服务或者模块。确保这些依赖项已经正确地安装和配置。你可以查看 OpenStack 官方文档或者相关的文档资源来获取更多关于依赖关系的信息。 如果你仍然遇到问题,可以提供更多的错误信息或者日志,以便我们能够更好地帮助你解决这个问题。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值