接下来就是分析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请求就被分发给了对应的资源来进行对应的操作了