searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

nova源代码阅读-nova-api路由请求

2023-05-23 04:28:44
106
0
       nova-api是访问并使用Nova所提供的各种服务的公共接口。作为客户端和Nova的中间层,nova-api扮演了一个桥梁,或者说中间人的角色。nova-api把客户端的请求传达给Nova,待Nova处理完请求后再将处理结果返回给客户端。
        若想弄清nova-api的路由请求,可以从Nova如何设置这些路由请求来看。如图所示即为请求的的传递路径。nova-api启动时,会被初始化为一个WSGI Server,然后nova-api通过nova/aip/openstack/compute:APIRouterV21与多个WSGI Application相关联,当有外部请求到达nova-api后,这个请求会通过APIRouterV21的父类nova/wsgi:Router发送到相应的WSGI Application中,每个WSGI Application都会有相应的Controller类,这个类中会实现其要执行的方法,从Controller中相应的方法开始,Nova内部的通信机制正式运行,即请求到达Nova内部并被内部相关服务处理。总之,nova-api作为WSGI Server,一方面可以接收外部的请求,另一方面可以调用内部相关服务处理外部来的请求。
       接下来详细介绍一下request的路由路径,Nova使用Python Paste作为工具加载WSGI stack,而WSGI stack通过etc/nova/api-paste.ini文件来配置,例如:
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
       从以上配置可以看出nova-api提供的Endpoint有哪些。“/”对应的是version API,可以通过这个API获得所访问的API提供哪些版本的API,以及API所支持的Microversion信息。“/v2.1”就是Nova当前的API。例如:
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21
        nova.api.auth:pipeline_factory_v21是这个API Stack的一个工厂函数,负责加载每一个Middleware。根据配置可以选择没有验证的noauth2 stack或keystone stack。noauth2一般被functional测试所使用,在实际生产中,都是使用Keystone来进行验证的。从配置文件中可以看到整个stack当中都包含了哪些Middleware。在这之前的Middleware都会对请求或返回进行一些处理。比如,添加请求id用来帮助调试,对错误返回进行统一的包装,以及对请求进行Token验证等。例如:
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
       可以找到Nova v2.1 API的入口,nova.api.openstack.compute:APIRouterV21.factory也是一个工厂函数,用来创建Nova v2.1 API。APIRouterV21主要用来完成路由规则的创建:
# nova/api/openstack/compute/routes.py

def _create_controller(main_controller, action_controller_list):
    """This is a helper method to create controller with a
    list of action controller.
    """

    controller = wsgi.Resource(main_controller())
    for ctl in action_controller_list:
        controller.register_actions(ctl())
    return controller


agents_controller = functools.partial(
    _create_controller, agents.AgentController, [])
    ......
    
ROUTE_LIST = (
    # NOTE: This is a redirection from '' to '/'. The request to the '/v2.1'
    # or '/2.0' without the ending '/' will get a response with status code
    # '302' returned.
    ('', '/'),
    ('/', {
        'GET': [version_controller, 'show']
    }),
    ('/versions/{id}', {
        'GET': [version_controller, 'show']
    }),
    ......
)

# Router类对WSGI routers模块进行了简单的封装
class APIRouterV21(base_wsgi.Router):
    def __init__(self, custom_routes=None):
        super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper())

        if custom_routes is None:
            custom_routes = tuple()

        for path, methods in ROUTE_LIST + custom_routes:
            if isinstance(methods, six.string_types):
                self.map.redirect(path, methods)
                continue

            for method, controller_info in methods.items():
                controller = controller_info[0]()
                action = controller_info[1]
                self.map.create_route(path, method, controller, action)

    @classmethod
    def factory(cls, global_config, **local_config):
        """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
        return cls()
       从上面的代码可以看出,ROUTE_LIST中保存了URL与Controller之间的对应关系。APIRouterV21基于ROUTE_LIST,使用ROUTES模块作为URL映射的工具,将各个模块所实现的API对应的URL注册到mapper中,并把每个资源都封装成一个nova.api.openstack.wsgi.Resource对象。当解析每个URL请求时,可以通过URL映射找到API对应的Resource Object:
class Router(object):
    """WSGI middleware that maps incoming requests to WSGI apps."""

    def __init__(self, mapper):
        self.map = mapper
        # 使用routes模块将mapper与_dispatch()关联起来
        # routes.middleware.RoutesMiddleware会调用mapper.routematch()函数来
        # 获取URL的controller等参数,将其保存在match中,并设置environ变量供
        # _dispatch()使用
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, req):
        # 根据mapper将请求路由到适当的WSGI应用,即资源上每个资源会在自己的
        # __call__()方法中,根据HTTP请求的URL将其路由到对应的Controller上的方法
        return self._router

    @staticmethod
    @webob.dec.wsgify(RequestClass=Request)
    def _dispatch(req):
        # 读取HTTP请求的environ信息并根据前面设置的environ找到URL对应的Controller
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return webob.exc.HTTPNotFound()
        app = match['controller']
        return app
        追溯到nova.api.openstack.APIRouterV21的父类,可以看到,请求会调用Python routes模块提供的RoutesMiddleware来解析之前创建的URL mapping,然后会通过_dispatch()函数回调回来,并取出其中的Resource对象,再调用Resource对象的__call__()方法,这其中进行了一些API所需的处理,如Microversion解析和请求数据类型的解析。Resource对象会将请求的API映射到对应的Controller方法上,并且根据请求找到对应Microversion的Controller方法。每个API对应的Controller方法都在nova/api/openstack/compute/routes.py目录下的各个API对应的模块中,这些模块注册就是setup.cfg文件中所描述的。
         例如,Keypair可以根据nova/api/openstack/compute/routes.py中的ROUTE_LIST定位到对应的KeypairAPI Controller为nova.api.openstack.computer.keypairs.KeypairController。首先可以看一下新资源的controller方法是如何实现的。
class KeypairController(wsgi.Controller):

    """Keypair API controller for the OpenStack API."""

    _view_builder_class = keypairs_view.ViewBuilder

    def __init__(self):
        super(KeypairController, self).__init__()
        self.api = compute_api.KeypairAPI()

    @wsgi.Controller.api_version("2.10")
    @wsgi.response(201)
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v210)
    def create(self, req, body):
        user_id = body['keypair'].get('user_id')
        return self._create(req, body, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @wsgi.response(201)
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v22)
    def create(self, req, body):
        return self._create(req, body, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v20, "2.0", "2.0")
    @validation.schema(keypairs.create, "2.1", "2.1")
    def create(self, req, body):
        return self._create(req, body)

    def _create(self, req, body, user_id=None, **keypair_filters):
        ......

    @wsgi.Controller.api_version("2.1", "2.1")
    @validation.query_schema(keypairs.delete_query_schema_v20)
    @wsgi.response(202)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        self._delete(req, id)

    @wsgi.Controller.api_version("2.2", "2.9")    # noqa
    @validation.query_schema(keypairs.delete_query_schema_v20)
    @wsgi.response(204)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        self._delete(req, id)

    @wsgi.Controller.api_version("2.10")    # noqa
    @validation.query_schema(keypairs.delete_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.delete_query_schema_v210, '2.10', '2.74')
    @wsgi.response(204)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        self._delete(req, id, user_id=user_id)

    def _delete(self, req, id, user_id=None):
        """Delete a keypair with a given name."""
        ......

    @wsgi.Controller.api_version("2.10")
    @validation.query_schema(keypairs.show_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.show_query_schema_v210, '2.10', '2.74')
    @wsgi.expected_errors(404)
    def show(self, req, id):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        return self._show(req, id, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @validation.query_schema(keypairs.show_query_schema_v20)
    @wsgi.expected_errors(404)
    def show(self, req, id):
        return self._show(req, id, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @validation.query_schema(keypairs.show_query_schema_v20)
    @wsgi.expected_errors(404)
    def show(self, req, id):
        return self._show(req, id)

    def _show(self, req, id, user_id=None, **keypair_filters):
        """Return data for the given key name."""
        ......

    @wsgi.Controller.api_version("2.35")
    @validation.query_schema(keypairs.index_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.index_query_schema_v235, '2.35', '2.74')
    @wsgi.expected_errors(400)
    def index(self, req):
        user_id = self._get_user_id(req)
        return self._index(req, links=True, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.10", "2.34")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v210)
    @wsgi.expected_errors(())
    def index(self, req):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        return self._index(req, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v20)
    @wsgi.expected_errors(())
    def index(self, req):
        return self._index(req, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v20)
    @wsgi.expected_errors(())
    def index(self, req):
        return self._index(req)

    def _index(self, req, user_id=None, links=False, **keypair_filters):
        """List of keypairs for a user."""
       ......
        在KeypairController中,公共方法有4类,即index、create、get和delete,这在API中分别对应如下几种。
('/os-keypairs', {
    'GET': [keypairs_controller, 'index'],
    'POST': [keypairs_controller, 'create']
}),
('/os-keypairs/{id}', {
    'GET': [keypairs_controller, 'show'],
    'DELETE': [keypairs_controller, 'delete']
})
        可以发现这4类方法有多个声明,这多个声明代表对应不同版本的Microversion。方法所对应的Microversion通过decorator "wsgi.Controller.api_version" 来指定,分别对应以下版本。
@wsgi.Controller.api_version("2.1", "2.1") # Microversion为2.1版本
@wsgi.Controller.api_version("2.10", "2.34") # Microversion为2.10到2.34版本
@wsgi.Controller.api_version("2.35")  # Microversion为2.35到最新版本
        方法中的其他decorator也十分重要,分别代码的意思如下所述。
@wsgi.expected_errors(404) # API所允许的错误返回代码,拦截了所有未预期的错误
@validation.query_schema(keypairs.index_query_schema_v20) # 在Microversion为版本2.0时,请求所对应的JSON-Schema
@wsgi.response(204) # API请求正常返回码
       API输入请求的格式验证是通过JSON-Schema进行的,比如,create方法对应Microversion 2.1的JSON-Schema,位于nova/api/openstack/compute/schemas/keypairs目录下:
create = {
    'type': 'object',
    'properties': {
        'keypair': {
            'type': 'object',
            'properties': {
                'name': parameter_types.name,
                'public_key': {'type': 'string'},
            },
            'required': ['name'],
            'additionalProperties': False,
        },
    },
    'required': ['keypair'],
    'additionalProperties': False,
}
        这个JSON-Schema表示接受一个字典,root层级只接受一个key,即keypair,并且必须出现。keypair的value同样是一个字典,接受两个key,即name和public_key。其中,name必须提供,public可选择性提供。不能有任何其他的key。public_key接受一个字符串。name接受的类型在parameter_types.name中:
# nova/api/validation/parameter_types.py

name = {
    # NOTE: Nova v2.1 API contains some 'name' parameters such
    # as keypair, server, flavor, aggregate and so on. They are
    # stored in the DB and Nova specific parameters.
    # This definition is used for all their parameters.
    'type': 'string', 'minLength': 1, 'maxLength': 255,
    'format': 'name'
}
         name同样接受一个字符串,最短为1个字符,最长为255个字符。name格式的定义可以到nova/api/validation/validators目录下找到:
@jsonschema.FormatChecker.cls_checks('name', exception.InvalidName)
def _validate_name(instance):
    regex = parameter_types.valid_name_regex
    try:
        if re.search(regex.regex, instance):
            return True
    except TypeError:
        # The name must be string type. If instance isn't string type, the
        # TypeError will be raised at here.
        pass
    raise exception.InvalidName(reason=regex.reason)
           这是将一个FormatChecker注册到schema validator中。定义一个正则表达式,然后通过正则表达式来验证这个Instance。
0条评论
0 / 1000
罗****兵
3文章数
1粉丝数
罗****兵
3 文章 | 1 粉丝
罗****兵
3文章数
1粉丝数
罗****兵
3 文章 | 1 粉丝
原创

nova源代码阅读-nova-api路由请求

2023-05-23 04:28:44
106
0
       nova-api是访问并使用Nova所提供的各种服务的公共接口。作为客户端和Nova的中间层,nova-api扮演了一个桥梁,或者说中间人的角色。nova-api把客户端的请求传达给Nova,待Nova处理完请求后再将处理结果返回给客户端。
        若想弄清nova-api的路由请求,可以从Nova如何设置这些路由请求来看。如图所示即为请求的的传递路径。nova-api启动时,会被初始化为一个WSGI Server,然后nova-api通过nova/aip/openstack/compute:APIRouterV21与多个WSGI Application相关联,当有外部请求到达nova-api后,这个请求会通过APIRouterV21的父类nova/wsgi:Router发送到相应的WSGI Application中,每个WSGI Application都会有相应的Controller类,这个类中会实现其要执行的方法,从Controller中相应的方法开始,Nova内部的通信机制正式运行,即请求到达Nova内部并被内部相关服务处理。总之,nova-api作为WSGI Server,一方面可以接收外部的请求,另一方面可以调用内部相关服务处理外部来的请求。
       接下来详细介绍一下request的路由路径,Nova使用Python Paste作为工具加载WSGI stack,而WSGI stack通过etc/nova/api-paste.ini文件来配置,例如:
[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21
       从以上配置可以看出nova-api提供的Endpoint有哪些。“/”对应的是version API,可以通过这个API获得所访问的API提供哪些版本的API,以及API所支持的Microversion信息。“/v2.1”就是Nova当前的API。例如:
[composite:openstack_compute_api_v21]
use = call:nova.api.auth:pipeline_factory_v21
noauth2 = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler noauth2 osapi_compute_app_v21
keystone = cors http_proxy_to_wsgi compute_req_id faultwrap request_log sizelimit osprofiler authtoken keystonecontext osapi_compute_app_v21
        nova.api.auth:pipeline_factory_v21是这个API Stack的一个工厂函数,负责加载每一个Middleware。根据配置可以选择没有验证的noauth2 stack或keystone stack。noauth2一般被functional测试所使用,在实际生产中,都是使用Keystone来进行验证的。从配置文件中可以看到整个stack当中都包含了哪些Middleware。在这之前的Middleware都会对请求或返回进行一些处理。比如,添加请求id用来帮助调试,对错误返回进行统一的包装,以及对请求进行Token验证等。例如:
[app:osapi_compute_app_v21]
paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory
       可以找到Nova v2.1 API的入口,nova.api.openstack.compute:APIRouterV21.factory也是一个工厂函数,用来创建Nova v2.1 API。APIRouterV21主要用来完成路由规则的创建:
# nova/api/openstack/compute/routes.py

def _create_controller(main_controller, action_controller_list):
    """This is a helper method to create controller with a
    list of action controller.
    """

    controller = wsgi.Resource(main_controller())
    for ctl in action_controller_list:
        controller.register_actions(ctl())
    return controller


agents_controller = functools.partial(
    _create_controller, agents.AgentController, [])
    ......
    
ROUTE_LIST = (
    # NOTE: This is a redirection from '' to '/'. The request to the '/v2.1'
    # or '/2.0' without the ending '/' will get a response with status code
    # '302' returned.
    ('', '/'),
    ('/', {
        'GET': [version_controller, 'show']
    }),
    ('/versions/{id}', {
        'GET': [version_controller, 'show']
    }),
    ......
)

# Router类对WSGI routers模块进行了简单的封装
class APIRouterV21(base_wsgi.Router):
    def __init__(self, custom_routes=None):
        super(APIRouterV21, self).__init__(nova.api.openstack.ProjectMapper())

        if custom_routes is None:
            custom_routes = tuple()

        for path, methods in ROUTE_LIST + custom_routes:
            if isinstance(methods, six.string_types):
                self.map.redirect(path, methods)
                continue

            for method, controller_info in methods.items():
                controller = controller_info[0]()
                action = controller_info[1]
                self.map.create_route(path, method, controller, action)

    @classmethod
    def factory(cls, global_config, **local_config):
        """Simple paste factory, :class:`nova.wsgi.Router` doesn't have one."""
        return cls()
       从上面的代码可以看出,ROUTE_LIST中保存了URL与Controller之间的对应关系。APIRouterV21基于ROUTE_LIST,使用ROUTES模块作为URL映射的工具,将各个模块所实现的API对应的URL注册到mapper中,并把每个资源都封装成一个nova.api.openstack.wsgi.Resource对象。当解析每个URL请求时,可以通过URL映射找到API对应的Resource Object:
class Router(object):
    """WSGI middleware that maps incoming requests to WSGI apps."""

    def __init__(self, mapper):
        self.map = mapper
        # 使用routes模块将mapper与_dispatch()关联起来
        # routes.middleware.RoutesMiddleware会调用mapper.routematch()函数来
        # 获取URL的controller等参数,将其保存在match中,并设置environ变量供
        # _dispatch()使用
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)

    @webob.dec.wsgify(RequestClass=Request)
    def __call__(self, req):
        # 根据mapper将请求路由到适当的WSGI应用,即资源上每个资源会在自己的
        # __call__()方法中,根据HTTP请求的URL将其路由到对应的Controller上的方法
        return self._router

    @staticmethod
    @webob.dec.wsgify(RequestClass=Request)
    def _dispatch(req):
        # 读取HTTP请求的environ信息并根据前面设置的environ找到URL对应的Controller
        match = req.environ['wsgiorg.routing_args'][1]
        if not match:
            return webob.exc.HTTPNotFound()
        app = match['controller']
        return app
        追溯到nova.api.openstack.APIRouterV21的父类,可以看到,请求会调用Python routes模块提供的RoutesMiddleware来解析之前创建的URL mapping,然后会通过_dispatch()函数回调回来,并取出其中的Resource对象,再调用Resource对象的__call__()方法,这其中进行了一些API所需的处理,如Microversion解析和请求数据类型的解析。Resource对象会将请求的API映射到对应的Controller方法上,并且根据请求找到对应Microversion的Controller方法。每个API对应的Controller方法都在nova/api/openstack/compute/routes.py目录下的各个API对应的模块中,这些模块注册就是setup.cfg文件中所描述的。
         例如,Keypair可以根据nova/api/openstack/compute/routes.py中的ROUTE_LIST定位到对应的KeypairAPI Controller为nova.api.openstack.computer.keypairs.KeypairController。首先可以看一下新资源的controller方法是如何实现的。
class KeypairController(wsgi.Controller):

    """Keypair API controller for the OpenStack API."""

    _view_builder_class = keypairs_view.ViewBuilder

    def __init__(self):
        super(KeypairController, self).__init__()
        self.api = compute_api.KeypairAPI()

    @wsgi.Controller.api_version("2.10")
    @wsgi.response(201)
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v210)
    def create(self, req, body):
        user_id = body['keypair'].get('user_id')
        return self._create(req, body, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @wsgi.response(201)
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v22)
    def create(self, req, body):
        return self._create(req, body, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @wsgi.expected_errors((400, 403, 409))
    @validation.schema(keypairs.create_v20, "2.0", "2.0")
    @validation.schema(keypairs.create, "2.1", "2.1")
    def create(self, req, body):
        return self._create(req, body)

    def _create(self, req, body, user_id=None, **keypair_filters):
        ......

    @wsgi.Controller.api_version("2.1", "2.1")
    @validation.query_schema(keypairs.delete_query_schema_v20)
    @wsgi.response(202)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        self._delete(req, id)

    @wsgi.Controller.api_version("2.2", "2.9")    # noqa
    @validation.query_schema(keypairs.delete_query_schema_v20)
    @wsgi.response(204)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        self._delete(req, id)

    @wsgi.Controller.api_version("2.10")    # noqa
    @validation.query_schema(keypairs.delete_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.delete_query_schema_v210, '2.10', '2.74')
    @wsgi.response(204)
    @wsgi.expected_errors(404)
    def delete(self, req, id):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        self._delete(req, id, user_id=user_id)

    def _delete(self, req, id, user_id=None):
        """Delete a keypair with a given name."""
        ......

    @wsgi.Controller.api_version("2.10")
    @validation.query_schema(keypairs.show_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.show_query_schema_v210, '2.10', '2.74')
    @wsgi.expected_errors(404)
    def show(self, req, id):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        return self._show(req, id, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @validation.query_schema(keypairs.show_query_schema_v20)
    @wsgi.expected_errors(404)
    def show(self, req, id):
        return self._show(req, id, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @validation.query_schema(keypairs.show_query_schema_v20)
    @wsgi.expected_errors(404)
    def show(self, req, id):
        return self._show(req, id)

    def _show(self, req, id, user_id=None, **keypair_filters):
        """Return data for the given key name."""
        ......

    @wsgi.Controller.api_version("2.35")
    @validation.query_schema(keypairs.index_query_schema_v275, '2.75')
    @validation.query_schema(keypairs.index_query_schema_v235, '2.35', '2.74')
    @wsgi.expected_errors(400)
    def index(self, req):
        user_id = self._get_user_id(req)
        return self._index(req, links=True, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.10", "2.34")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v210)
    @wsgi.expected_errors(())
    def index(self, req):
        # handle optional user-id for admin only
        user_id = self._get_user_id(req)
        return self._index(req, type=True, user_id=user_id)

    @wsgi.Controller.api_version("2.2", "2.9")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v20)
    @wsgi.expected_errors(())
    def index(self, req):
        return self._index(req, type=True)

    @wsgi.Controller.api_version("2.1", "2.1")  # noqa
    @validation.query_schema(keypairs.index_query_schema_v20)
    @wsgi.expected_errors(())
    def index(self, req):
        return self._index(req)

    def _index(self, req, user_id=None, links=False, **keypair_filters):
        """List of keypairs for a user."""
       ......
        在KeypairController中,公共方法有4类,即index、create、get和delete,这在API中分别对应如下几种。
('/os-keypairs', {
    'GET': [keypairs_controller, 'index'],
    'POST': [keypairs_controller, 'create']
}),
('/os-keypairs/{id}', {
    'GET': [keypairs_controller, 'show'],
    'DELETE': [keypairs_controller, 'delete']
})
        可以发现这4类方法有多个声明,这多个声明代表对应不同版本的Microversion。方法所对应的Microversion通过decorator "wsgi.Controller.api_version" 来指定,分别对应以下版本。
@wsgi.Controller.api_version("2.1", "2.1") # Microversion为2.1版本
@wsgi.Controller.api_version("2.10", "2.34") # Microversion为2.10到2.34版本
@wsgi.Controller.api_version("2.35")  # Microversion为2.35到最新版本
        方法中的其他decorator也十分重要,分别代码的意思如下所述。
@wsgi.expected_errors(404) # API所允许的错误返回代码,拦截了所有未预期的错误
@validation.query_schema(keypairs.index_query_schema_v20) # 在Microversion为版本2.0时,请求所对应的JSON-Schema
@wsgi.response(204) # API请求正常返回码
       API输入请求的格式验证是通过JSON-Schema进行的,比如,create方法对应Microversion 2.1的JSON-Schema,位于nova/api/openstack/compute/schemas/keypairs目录下:
create = {
    'type': 'object',
    'properties': {
        'keypair': {
            'type': 'object',
            'properties': {
                'name': parameter_types.name,
                'public_key': {'type': 'string'},
            },
            'required': ['name'],
            'additionalProperties': False,
        },
    },
    'required': ['keypair'],
    'additionalProperties': False,
}
        这个JSON-Schema表示接受一个字典,root层级只接受一个key,即keypair,并且必须出现。keypair的value同样是一个字典,接受两个key,即name和public_key。其中,name必须提供,public可选择性提供。不能有任何其他的key。public_key接受一个字符串。name接受的类型在parameter_types.name中:
# nova/api/validation/parameter_types.py

name = {
    # NOTE: Nova v2.1 API contains some 'name' parameters such
    # as keypair, server, flavor, aggregate and so on. They are
    # stored in the DB and Nova specific parameters.
    # This definition is used for all their parameters.
    'type': 'string', 'minLength': 1, 'maxLength': 255,
    'format': 'name'
}
         name同样接受一个字符串,最短为1个字符,最长为255个字符。name格式的定义可以到nova/api/validation/validators目录下找到:
@jsonschema.FormatChecker.cls_checks('name', exception.InvalidName)
def _validate_name(instance):
    regex = parameter_types.valid_name_regex
    try:
        if re.search(regex.regex, instance):
            return True
    except TypeError:
        # The name must be string type. If instance isn't string type, the
        # TypeError will be raised at here.
        pass
    raise exception.InvalidName(reason=regex.reason)
           这是将一个FormatChecker注册到schema validator中。定义一个正则表达式,然后通过正则表达式来验证这个Instance。
文章来自个人专栏
文章 | 订阅
0条评论
0 / 1000
请输入你的评论
0
0