接下来就是分析nova模块的代码

1. 目前的Nova主要由API,Compute,Conductor,Scheduler组成
    API:其它模块的通过HTTP协议进入Nova模块的接口;
    Compute:用来交互并管理虚拟机的生命周期;
    Scheduler:从可用池中根据各种策略选择最合适的计算节点来创建新的虚拟机;
    Conductor:为数据库的访问提供统一的接口层。

2. Nova代码结构:

[root@Alljun-Lee nova]# ls
api-guide    contrib           doc          LICENSE      openstack-common.conf  releasenotes      setup.cfg  test-requirements.txt  tox.ini
babel.cfg    CONTRIBUTING.rst  etc          MAINTAINERS  plugins                requirements.txt  setup.py   tests-py3.txt
bandit.yaml  devstack          HACKING.rst  nova         README.rst             run_tests.sh      tags       tools

3. Nova体系结构图:

4. 注意:我们每关注一个新的项目都可以从setup.cfg文件开始
  进入文件并找到'[entry_points]',里面有'console_scripts',
  它对应的是每一个脚本文件,用来初始化nova模块内的各个子服务;

console_scripts =
    nova-all = nova.cmd.all:main  #用来启动所有Nova服务的辅助脚本
    nova-api = nova.cmd.api:main
    nova-api-metadata = nova.cmd.api_metadata:main
    nova-api-os-compute = nova.cmd.api_os_compute:main
    nova-cells = nova.cmd.cells:main
    nova-cert = nova.cmd.cert:main
    nova-compute = nova.cmd.compute:main
    nova-conductor = nova.cmd.conductor:main
    nova-console = nova.cmd.console:main
    nova-consoleauth = nova.cmd.consoleauth:main
    nova-dhcpbridge = nova.cmd.dhcpbridge:main
    nova-idmapshift = nova.cmd.idmapshift:main
    nova-manage = nova.cmd.manage:main
    nova-network = nova.cmd.network:main
    nova-novncproxy = nova.cmd.novncproxy:main
    nova-rootwrap = oslo_rootwrap.cmd:main
    nova-rootwrap-daemon = oslo_rootwrap.cmd:daemon
    nova-scheduler = nova.cmd.scheduler:main
    nova-serialproxy = nova.cmd.serialproxy:main
    nova-spicehtml5proxy = nova.cmd.spicehtml5proxy:main
    nova-xvpvncproxy = nova.cmd.xvpvncproxy:main

其中每个服务都会被创建成一个或者多个WSGI Server,例如:


for api in CONF.enabled_apis:


enabled_apis----default=['osapi_compute', 'metadata']


下面会将这两个初始化为WSGI的service,这个名字对应文件'etc/nova/api-paste.ini'内的'[composite:osapi_compute/metadata]'


server = service.WSGIService(api, use_ssl=should_use_ssl)


launcher.launch_service(server, workers=server.workers or 1)

5. 现在进入文件'nova/service.py'

class WSGIService(service.Service):
    """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
        # NOTE(danms): Name can be metadata, os_compute, or ec2, per
        # nova.service's enabled_apis
        self.binary = 'nova-%s' % name
        self.topic = None
        self.manager = self._get_manager()
        self.loader = loader or wsgi.Loader()
        # 从配置文件'etc/nova/api-paste.ini'加载wsgi的application,这个很重要
        self.app = self.loader.load_app(name)
        # inherit all compute_api worker counts from osapi_compute
        if name.startswith('openstack_compute_api'):
            wname = 'osapi_compute'
        else:
            wname = 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' % wname, 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

6. 进入配置文件'etc/nova/api-paste.ini'

6.1 找到对应的wsgi Service

[composite:osapi_compute]
use = call:nova.api.openstack.urlmap:urlmap_factory
/: oscomputeversions
# starting in Liberty the v21 implementation replaces the v2
# implementation and is suggested that you use it as the default. If
# this causes issues with your clients you can rollback to the 
# *frozen* v2 api by commenting out the above stanza and using the 
# following instead::
# /v2: openstack_compute_api_legacy_v2
# if rolling back to v2 fixes your issue please file a critical bug 
# at - https://bugs.launchpad.net/nova/+bugs
#
# v21 is an exactly feature match for v2, except it has more stringent
# input validation on the wsgi surface (prevents fuzzing early on the 
# API). It also provides new features via API microversions which are 
# opt into for clients. Unaware clients will receive the same frozen
# v2 API feature set, but with some relaxed validation
/v2: openstack_compute_api_v21_legacy_v2_compatible
/v2.1: openstack_compute_api_v21



6.1.1 其中使用'nova.api.openstack.urlmap模块的urlmap_factory函数来进行API的分发'


  例如创建云主机,http://192.168.1.9:8774/v2/14fd316568bc4f6992ba161fd4e23001/servers,


  用的是v2版本的api,根据路由解析可以很明显的看到最后调用的是‘paste.app_factory = nova.api.openstack.compute:APIRouterV21.factory’


6.1.2 到了具体的执行函数

# vim nova/api/openstack/compute/__init__.py
  class APIRouterV21(nova.api.openstack.APIRouterV21):
    """Routes requests on the OpenStack API to the appropriate controller
    and method.
    """
    def __init__(self, init_only=None):
        self._loaded_extension_info = extension_info.LoadedExtensionInfo()
        super(APIRouterV21, self).__init__(init_only)

    def _register_extension(self, ext):
        return self.loaded_extension_info.register_extension(ext.obj)

    @property
    def loaded_extension_info(self):
        return self._loaded_extension_info



6.1.3 进入nova.api.openstack.APIRouterV21

# vim nova/api/openstack/__init__.py
  class APIRouterV21(base_wsgi.Router):
    """Routes requests on the OpenStack v2.1 API to the appropriate controller
    and method.
    """

    def __init__(self, init_only=None, v3mode=False):
        # TODO(cyeoh): bp v3-api-extension-framework. Currently load
        # all extensions but eventually should be able to exclude
        # based on a config file
        # TODO(oomichi): We can remove v3mode argument after moving all v3 APIs
        # to v2.1.
        def _check_load_extension(ext):
            if (self.init_only is None or ext.obj.alias in
                self.init_only) and isinstance(ext.obj,
                                               extensions.V21APIExtensionBase):

                # Check whitelist is either empty or if not then the extension
                # is in the whitelist
                if (not CONF.osapi_v21.extensions_whitelist or
                        ext.obj.alias in CONF.osapi_v21.extensions_whitelist):

                    # Check the extension is not in the blacklist
                    blacklist = CONF.osapi_v21.extensions_blacklist
                    if ext.obj.alias not in blacklist:
                        return self._register_extension(ext)
            return False

        if not CONF.osapi_v21.enabled:
            LOG.info(_LI("V2.1 API has been disabled by configuration"))
            LOG.warning(_LW("In the M release you must run the v2.1 API."))
            return

        # 配置文件内的黑白名单,主要是用来加载扩展api的,但是M版以后将会废除这两个配置项
        if (CONF.osapi_v21.extensions_blacklist or
                CONF.osapi_v21.extensions_whitelist):
            LOG.warning(
                _LW('In the M release you must run all of the API. '
                'The concept of API extensions will be removed from '
                'the codebase to ensure there is a single Compute API.'))

        self.init_only = init_only
        LOG.debug("v21 API Extension Blacklist: %s",
                  CONF.osapi_v21.extensions_blacklist)
        LOG.debug("v21 API Extension Whitelist: %s",
                  CONF.osapi_v21.extensions_whitelist)

        in_blacklist_and_whitelist = set(
            CONF.osapi_v21.extensions_whitelist).intersection(
                CONF.osapi_v21.extensions_blacklist)
        if len(in_blacklist_and_whitelist) != 0:
            LOG.warning(_LW("Extensions in both blacklist and whitelist: %s"),
                        list(in_blacklist_and_whitelist))

        # 使用stevedore的enabled的EnabledExtensionManager类来加载位于setup.cfg中
        # 命名空间为'nova.api.v21.extensions'的所有的资源
        self.api_extension_manager = stevedore.enabled.EnabledExtensionManager(
            namespace=self.api_extension_namespace(),
            check_func=_check_load_extension,
            invoke_on_load=True,
            invoke_kwds={"extension_info": self.loaded_extension_info})

        if v3mode:
            mapper = PlainMapper()
        else:
            mapper = ProjectMapper()

        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.

            # 我们查看目录'nova/api/openstack/compute/'下的任一个类都有方法'get_resources()'
            # 这个函数的作用就是循环调用‘get_resources()’方法内的action
            # 最后调用方法self._register_resources(ext, mapper)来注册为WSGI的服务
            self._register_resources_check_inherits(mapper)
            # 注册各个api对应的controllers,然后将它们使用mapper来建立路由规则
            self.api_extension_manager.map(self._register_controllers)

        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(_LC("Missing core API extensions: %s"),
                         missing_core_extensions)
            raise exception.CoreAPIMissing(
                missing_apis=missing_core_extensions)

        LOG.info(_LI("Loaded extensions: %s"),
                 sorted(self.loaded_extension_info.get_extensions().keys()))
        # 调用父类的'__init__()'方法
        super(APIRouterV21, self).__init__(mapper)



6.1.4 进入父类

# vim nova/wsgi.py
  class Router(object):
    """WSGI middleware that maps incoming requests to WSGI apps."""

    def __init__(self, mapper):
        """Create a router for the given routes.Mapper.

        Each route in `mapper` must specify a 'controller', which is a
        WSGI app to call.  You'll probably want to specify an 'action' as
        well and have your controller be an object that can route
        the request to the action-specific method.

        Examples:
          mapper = routes.Mapper()
          sc = ServerController()

          # Explicit mapping of one route to a controller+action
          mapper.connect(None, '/svrlist', controller=sc, action='list')

          # Actions are all implicitly defined
          mapper.resource('server', 'servers', controller=sc)

          # Pointing to an arbitrary WSGI app.  You can specify the
          # {path_info:.*} parameter so the target app can be handed just that
          # section of the URL.
          mapper.connect(None, '/v1.0/{path_info:.*}', controller=BlogApp())

        """
        self.map = mapper
        # 使用routes模块将mapper和self._dispatch关联起来
        # 'routes.middleware.RoutesMiddleware'会调用mapper.routematch()函数
        # 来获取 url(会对它进行解析并拿到各个参数和api) 的controller(这些前面就已经被解析出来了)等参数,
        # 保存在match中,并设置environ变量供_dispatch()使用
        self._router = routes.middleware.RoutesMiddleware(self._dispatch,
                                                          self.map)



6.1.5 到这里一个HTTP请求就被分发给了对应的资源来进行对应的操作了