Openstack Nova API服务流程

本文主要分析Nova OS API的服务流程。

模块nova/cmd/api_os_compute.py是该API服务的启动脚本,在main方法中,通过调用WSGIService(‘osapi_compute’, use_ssl=should_use_ssl)创建了一个WSGIService对象,该对象负责osapi_compute服务的初始化、启动、终止等。并通过调用service.serve(server, workers=server.workers)启动了这项服务。

def main():
    config.parse_args(sys.argv)
    logging.setup("nova")
    utils.monkey_patch()
    objects.register_all()

    gmr.TextGuruMeditation.setup_autorun(version)

    should_use_ssl = 'osapi_compute' in CONF.enabled_ssl_apis
    server = service.WSGIService('osapi_compute', use_ssl=should_use_ssl)
    service.serve(server, workers=server.workers)
    service.wait()

在WSGIService的__init__方法中,self.name = name指定了WSGI服务的名字,self.loader = loader or wsgi.Loader()则创建了用于加载WSGI应用的装载器,随后通过调用self.loader.loadapp(name)对WSGI应用进行了加载,而self.host、self.port则指定了该服务监听的主机号和端口——具体的配置信息写在了service_opts列表中,最后self.server = wsgi.Server(name, self.app, …)创建了一个wsgi.Server对象,可见WSGIService类是对wsgi.Service类的又一次封装。

    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.manager = self._get_manager()
        self.loader = loader or wsgi.Loader()
        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)

接下来,我们来分析WSGI应用的加载过程。在nova/wsgi.py模块中,可以看到Loader类的定义,它的__init__方法用于确定配置文件的路径,该配置文件是一个paste.deploy库规定的配置文件,它与WSGI应用的加载密切相关。在wsgi_opts列表中,可以看到该配置文件的文件名为api-paste.ini,可以确定它的的路径为/etc/nova/api-paste.ini。

    def __init__(self, config_path=None):
        """Initialize the loader, and attempt to find the config.

        :param config_path: Full or relative path to the paste config.
        :returns: None

        """
        self.config_path = None

        config_path = config_path or CONF.api_paste_config
        if not os.path.isabs(config_path):
            self.config_path = CONF.find_file(config_path)
        elif os.path.exists(config_path):
            self.config_path = config_path

Loader类的load_app方法进一步通过调用deploy.loadapp方法来加载应用,这个方法有两个参数,一个是配置文件的路径,另一个是需要加载的应用的名字。

    def load_app(self, name):
        """Return the paste URLMap wrapped WSGI application.

        :param name: Name of the application to load.
        :returns: Paste URLMap object wrapping the requested application.
        :raises: `nova.exception.PasteAppNotFound`

        """
        try:
            LOG.debug("Loading app %(name)s from %(path)s",
                      {'name': name, 'path': self.config_path})
            return deploy.loadapp("config:%s" % self.config_path, name=name)
        except LookupError as err:
            LOG.error(err)
            raise exception.PasteAppNotFound(name=name, path=self.config_path)

paste/deploy/loadwsgi.py模块中的loadapp方法进一步调用了loadobj方法。

def loadapp(uri, name=None, **kw):
    return loadobj(APP, uri, name=name, **kw)

同样的,loadobj方法调用了loadcontext方法。

def loadobj(object_type, uri, name=None, relative_to=None,
            global_conf=None):
    context = loadcontext(
        object_type, uri, name=name, relative_to=relative_to,
        global_conf=global_conf)
    return context.create()

前面提过,deploy.loadapp的第一个参数是配置文件api-paste.ini的路径,即/etc/nova/api-paste.ini,与loadcontext方法的uri参数对应,这里uri = config: /etc/nova/api-paste.ini。第二个参数是将要加载的应用的名字,即osapi_compute,与loadcontext方法的name参数对应。通过调用url.split(‘:’, 1)方法,获得scheme和path的值,分别为config、/etc/nova/api-paste.ini。在最后的return语句中,_loader为一个字典对象,_loader[‘config’]的值为_loadconfig方法,所以最后返回的对象由_loadconfig方法确定。

def loadcontext(object_type, uri, name=None, relative_to=None,
                global_conf=None):
    if '#' in uri:
        if name is None:
            uri, name = uri.split('#', 1)
        else:
            # @@: Ignore fragment or error?
            uri = uri.split('#', 1)[0]
    if name is None:
        name = 'main'
    if ':' not in uri:
        raise LookupError("URI has no scheme: %r" % uri)
    scheme, path = uri.split(':', 1)
    scheme = scheme.lower()
    if scheme not in _loaders:
        raise LookupError(
            "URI scheme not known: %r (from %s)"
            % (scheme, ', '.join(_loaders.keys())))
    return _loaders[scheme](
        object_type,
        uri, path, name=name, relative_to=relative_to,
        global_conf=global_conf)

在_loadconfig方法中,首先判断path是否是绝对路径,显然是它一个绝对路径。而loader变量是一个ConfigLoader类对象,最后通过调用loader.get_context方法来获得方法的返回值。

def _loadconfig(object_type, uri, path, name, relative_to,
                global_conf):
    isabs = os.path.isabs(path)
    # De-Windowsify the paths:
    path = path.replace('\\', '/')
    if not isabs:
        if not relative_to:
            raise ValueError(
                "Cannot resolve relative uri %r; no relative_to keyword "
                "argument given" % uri)
        relative_to = relative_to.replace('\\', '/')
        if relative_to.endswith('/'):
            path = relative_to + path
        else:
            path = relative_to + '/' + path
    if path.startswith('///'):
        path = path[2:]
    path = unquote(path)
    loader = ConfigLoader(path)
    if global_conf:
        loader.update_defaults(global_conf, overwrite=False)
    return loader.get_context(object_type, name, global_conf)

在ConfigLoader类的__init__方法中,创建了一个NicerConfigParser对象,并赋值给self.parser。之后,根据路径打开配置文件,通过这个self.parser对象对配置文件进行了读取。根据名字,我们可推测出,这个parser对象用于配置文件的解析。

    def __init__(self, filename):
        self.filename = filename = filename.strip()
        defaults = {
            'here': os.path.dirname(os.path.abspath(filename)),
            '__file__': os.path.abspath(filename)
            }
        self.parser = NicerConfigParser(filename, defaults=defaults)
        self.parser.optionxform = str  # Don't lower-case keys
        with open(filename) as f:
            self.parser.read_file(f)

在ConfigLoader的get_context方法中,首先调用absolute_name方法对参数name进行判断,name即为我们需要加载的应用的名字——osapi_compute。具体来说,absolute_name方法将判断传入的name参数是否符合”^[a-zA-Z]+:”正则式,若符合则返回True,否则返回False。所以代码中的条件判断为False,随后调用self.find_config_section方法获得可配置文件的section。

    def get_context(self, object_type, name=None, global_conf=None):
        if self.absolute_name(name):
            return loadcontext(object_type, name,
                               relative_to=os.path.dirname(self.filename),
                               global_conf=global_conf)
        section = self.find_config_section(
            object_type, name=name)
        ...

至此,本文还未涉及过api-paste.ini文件的具体内容,在下面粘贴了该文件的部分片段。配置文件由一个个的section组成,section定义了应用的创建方式,例如use=call:nova.api.openstack.urlmap:urlmap_factory规定,需要调用nova/api/openstack/urlmap.py模块中的urlmap_factory方法来创建osapi_compute应用。同时section还可以定义路径与应用的映射关系,如/v2: openstack_compute_api_v2,它表示http请求的url若以/v2开,则都交由openstack_compute_api_v2应用处理。

关于paste.deploy配置文件的定义,读者可以参考官方文档,本文不做详细描述。

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
/v1.1: openstack_compute_api_v2
/v2: openstack_compute_api_v2
/v2.1: openstack_compute_api_v21
/v3: openstack_compute_api_v3

[composite:openstack_compute_api_v2]
use = call:nova.api.auth:pipeline_factory
noauth = compute_req_id faultwrap sizelimit noauth ratelimit osapi_compute_app_v2
keystone = compute_req_id faultwrap sizelimit authtoken keystonecontext ratelimit osapi_compute_app_v2
keystone_nolimit = compute_req_id faultwrap sizelimit authtoken keystonecontext osapi_compute_app_v2
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值