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时来讲解。