首页 > 代码库 > nova分析(3)—— nova-api

nova分析(3)—— nova-api

nova-api是nova对外提供Restful API的服务,Horizon、novaclient等均通过该api与nova进行通信。

nova其实对外提供了多个api服务,包括下面这些服务:

nova-api
nova-api-ec2
nova-api-metadata
nova-api-os-compute
其中,nova-api用于启动其他三个服务。下面逐个分析下。

nova-api

入口在 nova.cmd.api:main ,主要是基于WSGI、PasteDeploy、Webob、Routes等框架实现Restful API:

    launcher = service.process_launcher()    for api in CONF.enabled_apis:        should_use_ssl = api in CONF.enabled_ssl_apis        if api == ec2:            server = service.WSGIService(api, use_ssl=should_use_ssl,                                         max_url_len=16384)        else:            server = service.WSGIService(api, use_ssl=should_use_ssl)        launcher.launch_service(server, workers=server.workers or 1)    launcher.wait()

对每一个enabled_api,都会创建一个WSGIService,然后调用launch_service:

def _start_child(self, wrap):        if len(wrap.forktimes) > wrap.workers:            # Limit ourselves to one process a second (over the period of            # number of workers * 1 second). This will allow workers to            # start up quickly but ensure we don‘t fork off children that            # die instantly too quickly.            if time.time() - wrap.forktimes[0] < wrap.workers:                LOG.info(_LI(Forking too fast, sleeping))                time.sleep(1)            wrap.forktimes.pop(0)        wrap.forktimes.append(time.time())        pid = os.fork()        if pid == 0:            # create a green thread to run child            # 实际上最终调用的service的start/wait方法            launcher = self._child_process(wrap.service)            while True:                self._child_process_handle_signal()                status, signo = self._child_wait_for_exit_or_signal(launcher)                if not _is_sighup_and_daemon(signo):                    break                launcher.restart()            os._exit(status)        LOG.info(_LI(Started child %d), pid)        wrap.children.add(pid)        self.children[pid] = wrap        return pid    def launch_service(self, service, workers=1):        wrap = ServiceWrapper(service, workers)        LOG.info(_LI(Starting %d workers), wrap.workers)        while self.running and len(wrap.children) < wrap.workers:            self._start_child(wrap)

既然每个服务都是 service.WSGIService ,那么WSGIService是如何初始化的呢

class WSGIService(object):    """Provides ability to launch API from a ‘paste‘ configuration."""    def __init__(self, name, loader=None, use_ssl=False, max_url_len=None):        """Initialize, but do not start the WSGI server.        :param name: The name of the WSGI server given to the loader.        :param loader: Loads the WSGI application using the given name.        :returns: None        """        self.name = name        # 根据self.name从配置文件中获取manager,若有则实例化对应的manager        self.manager = self._get_manager()        self.loader = loader or wsgi.Loader()        # 通过paste.deploy加载app,对应配置文件为api-paste.ini        self.app = self.loader.load_app(name)        self.host = getattr(CONF, %s_listen % name, "0.0.0.0")        self.port = getattr(CONF, %s_listen_port % name, 0)        self.workers = (getattr(CONF, %s_workers % name, None) or                        processutils.get_worker_count())        if self.workers and self.workers < 1:            worker_name = %s_workers % name            msg = (_("%(worker_name)s value of %(workers)s is invalid, "                     "must be greater than 0") %                   {worker_name: worker_name,                    workers: str(self.workers)})            raise exception.InvalidInput(msg)        self.use_ssl = use_ssl        self.server = wsgi.Server(name,                                  self.app,                                  host=self.host,                                  port=self.port,                                  use_ssl=self.use_ssl,                                  max_url_len=max_url_len)        # Pull back actual port used        self.port = self.server.port        self.backdoor_port = None

即从api-paste.ini加载app后,创建wsgi.Server

Paste Deployment是用于发现和配置WSGI appliaction和server的系统。对于WSGI application,用户提供一个单独的函数(loadapp),用于从配置文件或者python egg中加载WSGI application。因为WSGI application提供了唯一的单独的简单的访问入口,所以application不需要暴露application的内部的实现细节。首先了解几个基本的概念:

  • application: 应用,符合WSGI规范的可调用对象,接受参数(environ,start_response), 调用start_response返回状态和消息头,返回结果作为消息体。

  • filter:过滤器,可调用对象,类型python中的装饰器,接受一个application对象作为参数,返回一个封装后的application。

  • app_factory:可调用对象,接受参数(global,**local_conf),返回application对象

  • composite_factory: 可调用对象,接受参数(loader,global_config,**local_conf), loader有几个方法, get_app用于获取wsgi_app, get_filter用于加载filter, 返回application对象。

  • filter_factory: 可调用对象,接受参数(global_config, **local_conf),返回filter对象

以nova-api-os-compute为例看看这个配置文件是咋回事

# 这儿是整个api的入口,将不同版本转发到compiste处理[composite:osapi_compute]use = call:nova.api.openstack.urlmap:urlmap_factory/: oscomputeversions/v1.1: openstack_compute_api_v2/v2: openstack_compute_api_v2/v3: openstack_compute_api_v3# 比如v3版本api在这儿处理,在使用keystone配置时,要经过request_id faultwrap sizelimit authtoken keystonecontext这些filter的处理最后再交给osapi_compute_app_v3这个app来处理# 注意这些filter的顺序:先把前n-1个filter逆序,然后逐个应用到app上[composite:openstack_compute_api_v3]use = call:nova.api.auth:pipeline_factory_v3noauth = request_id faultwrap sizelimit noauth_v3 osapi_compute_app_v3keystone = request_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v3# filter部分定义了这个filter由哪个类来处理[filter:request_id]paste.filter_factory = nova.openstack.common.middleware.request_id:RequestIdMiddleware.factory[filter:compute_req_id]paste.filter_factory = nova.api.compute_req_id:ComputeReqIdMiddleware.factory[filter:faultwrap]paste.filter_factory = nova.api.openstack:FaultWrapper.factory[filter:noauth]paste.filter_factory = nova.api.openstack.auth:NoAuthMiddleware.factory[filter:noauth_v3]paste.filter_factory = nova.api.openstack.auth:NoAuthMiddlewareV3.factory[filter:ratelimit]paste.filter_factory = nova.api.openstack.compute.limits:RateLimitingMiddleware.factory[filter:sizelimit]paste.filter_factory = nova.api.sizelimit:RequestBodySizeLimiter.factory# 这儿是v3的处理app,即APIRouterV3.factory[app:osapi_compute_app_v3]paste.app_factory = nova.api.openstack.compute:APIRouterV3.factory

 APIRouterV3会加载setup.cfg中配置的extensions:

        # 初始化api_extension_manager并加载所有的API_EXTENSION_NAMESPACE对应的extensions。        # 这儿加载的就是setup.cfg中配置的nova.api.v3.extensions部分,比如:        #   nova.api.v3.extensions =        #       access_ips = nova.api.openstack.compute.plugins.v3.access_ips:AccessIPs        #       admin_actions = nova.api.openstack.compute.plugins.v3.admin_actions:AdminActions        #       admin_password = nova.api.openstack.compute.plugins.v3.admin_password:AdminPassword        #       agents = nova.api.openstack.compute.plugins.v3.agents:Agents        #       ...        # 当然如果有第三方的应用也声明了此namespace的extension,这里也会一并load进来。        # 其中的check_func为check_load_extension,其所作的工作主要包括:        #   a) 检测是否为相V3APIExtensionBase类型        #   b) extension的黑白名单过滤和验证        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(            namespace=self.API_EXTENSION_NAMESPACE, # ‘nova.api.v3.extensions‘            check_func=_check_load_extension,            invoke_on_load=True,            invoke_kwds={"extension_info": self.loaded_extension_info})        mapper = PlainMapper()        self.resources = {}        # NOTE(cyeoh) Core API support is rewritten as extensions        # but conceptually still have core        if list(self.api_extension_manager):            # NOTE(cyeoh): Stevedore raises an exception if there are            # no plugins detected. I wonder if this is a bug.            # Extensions define what resources they want to add through a get_resources function            self.api_extension_manager.map(self._register_resources,                                           mapper=mapper)            # Extensions define what resources they want to add through            # a get_controller_extensions function            self.api_extension_manager.map(self._register_controllers)        # 检测core extensions是否全部加载成功        missing_core_extensions = self.get_missing_core_extensions(            self.loaded_extension_info.get_extensions().keys())        if not self.init_only and missing_core_extensions:            LOG.critical(_("Missing core API extensions: %s"),                         missing_core_extensions)            raise exception.CoreAPIMissing(                missing_apis=missing_core_extensions)

以 servers = nova.api.openstack.compute.plugins.v3.servers:Servers 这个extension为例来看看加载过程是怎么样的。从前面的代码可以看到对每个extension,会调用get_resources和get_controller_extensions两个函数,其所作的工作分别为新增资源和扩展现有资源以添加action。

class Servers(extensions.V3APIExtensionBase):    """Servers."""    name = "Servers"    alias = "servers"    version = 1    def get_resources(self):        member_actions = {action: POST}        collection_actions = {detail: GET}        resources = [            extensions.ResourceExtension(                servers,                ServersController(extension_info=self.extension_info),                member_name=server, collection_actions=collection_actions,                member_actions=member_actions)]        return resources    def get_controller_extensions(self):        return []

这里需要注意的是,V2 API中会区分Core API和Extension API。在V3 API中,所有的API均作为extension的形式提供。如V2 API中的servers是Core API,在V3 API中也是一个普通的extension,此extension会在get_resources方法中返回servers资源对象,而get_controller_extensions方法会返回空list。

而当disk_config等需要对servers资源扩展action时,其所需做的就是在get_controller_extensions中返回对servers资源的扩展:

class Disk_config(extensions.ExtensionDescriptor):    """Disk Management Extension."""    name = "DiskConfig"    alias = ALIAS    namespace = XMLNS_DCF    updated = "2011-09-27T00:00:00Z"    def get_controller_extensions(self):        servers_extension = extensions.ControllerExtension(                self, servers, ServerDiskConfigController())        images_extension = extensions.ControllerExtension(                self, images, ImageDiskConfigController())        return [servers_extension, images_extension]

最后再来看看V3版本API是如何避免各Extension之间代码耦合的

V3中,主要是利用了Stevedore避免了各Extension之间的代码耦合,从而避免了上述问题。

还是以当前对核心资源“虚拟机”的create方法扩展来看,在servers资源对应的Controller nova.api.openstack.compute.pulgins.v3.servers:ServersController中,将create作为了一个extension的namespace,并会在初始化时就load所有的extensions:

# Look for implementation of extension point of server creation        self.create_extension_manager =           stevedore.enabled.EnabledExtensionManager(              namespace=self.EXTENSION_CREATE_NAMESPACE, #nova.api.v3.extensions.server.create              check_func=_check_load_extension(server_create),              invoke_on_load=True,              invoke_kwds={"extension_info": self.extension_info},              propagate_map_exceptions=True)        if not list(self.create_extension_manager):            LOG.debug("Did not find any server create extensions")

然后在create方法中,会通过map方法依次调用各个Extensions的server_create方法:

        # Query extensions which want to manipulate the keyword        # arguments.        # NOTE(cyeoh): This is the hook that extensions use        # to replace the extension specific code below.        # When the extensions are ported this will also result        # in some convenience function from this class being        # moved to the extension        if list(self.create_extension_manager):            self.create_extension_manager.map(self._create_extension_point,                                              server_dict, create_kwargs)    def _create_extension_point(self, ext, server_dict, create_kwargs):        handler = ext.obj        LOG.debug("Running _create_extension_point for %s", ext.obj)        handler.server_create(server_dict, create_kwargs)

在nova的setup.cfg中也配置了此namespace对应的所有的extensions,比如 nova.api.openstack.compute.plugins.v3.config_drive:ConfigDrive,其server_create方法如下所示,会更新ServersController:create方法中传入的create_kwargs字典:

    def server_create(self, server_dict, create_kwargs):        create_kwargs[config_drive] = server_dict.get(ATTRIBUTE_NAME)

最终,ServersController:create方法会以如下方式调用内部API来创建VM,即直接使用被各个extensions更新过的create_kwargs,而不用感知其实际的内容。

            (instances, resv_id) = self.compute_api.create(context,                            inst_type,                            image_uuid,                            display_name=name,                            display_description=name,                            metadata=server_dict.get(metadata, {}),                            admin_password=password,                            requested_networks=requested_networks,                            **create_kwargs)

由上面的分析可知,如上所有的所谓的Extension,最终均依赖的还是一个内部的API,如果此API本身不具有扩展性,那么如上所有的Extension,均只能在此内部API支持的功能的基础上进行发挥。 比如文中提到的创建VM的内部create API,此API的参数是固定的:

    def create(self, context, instance_type,               image_href, kernel_id=None, ramdisk_id=None,               min_count=None, max_count=None,               display_name=None, display_description=None,               key_name=None, key_data=http://www.mamicode.com/None, security_group=None,               availability_zone=None, user_data=http://www.mamicode.com/None, metadata=None,               injected_files=None, admin_password=None,               block_device_mapping=None, access_ip_v4=None,               access_ip_v6=None, requested_networks=None, config_drive=None,               auto_disk_config=None, scheduler_hints=None, legacy_bdm=True):        """Provision instances, sending instance information to the        scheduler.  The scheduler will determine where the instance(s)        go and will handle creating the DB entries.        Returns a tuple of (instances, reservation_id)        """

参考文档:

http://blog.csdn.net/cloudresearch/article/details/19050595

http://www.choudan.net/2013/07/30/OpenStack-API分析%28一%29.html

http://www.choudan.net/2013/07/31/OpenStack-API分析%28二%29.html