Nova源码解析学习:
一、浏览代码的工具:
二、源码地图:
1、每个子项目的源码根目录下都有一个setup.py和setuo.cfg文件
2、Setup.py中的setup函数有大量的参数需要设置(包括项目的名称、作者、版本等),setup.cfg文件的出现将setup函数解脱出来,它使用pdf工具去读取和过滤setup.cfg中的数据,将解析后的结果作为自己的参数。
3、Setup.cfg文件的内容由很多section组成,能帮助我们更好的去理解代码的section唯有entry_points(对于一个Python包来说,entry points可以简单地理解为通过setuptools注册的外部可以直接调用的接口)。
4、Entry points都是在运行时动态导入的,有点类似于可扩展的插件。
5、Entry points组中有一个特殊的存在-console_scripts,里面的每一个entry points都表示一个可执行脚本会被生成并且安装。
console_scripts =
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-compute = nova.cmd.compute:main
nova-conductor = nova.cmd.conductor:main
nova-manage = nova.cmd.manage:main
nova-novncproxy = nova.cmd.novncproxy:main
nova-policy = nova.cmd.policy: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-status = nova.cmd.status:main
由此可知nova项目安装后会包含21个可执行程序
三、结构分析:
1、console_scripts中主要服务:
nova-api:Nova对外提供的RESTful API服务,目前提供两种API服务,即nova-api-metadata和nova-apipos-compute
Nova-api根据配置文件/etc/nova/nova.conf的enable_apis选项设置启动这两种服务。
nova-api-metadata :接受虚拟机实例metadata(元数据)相关请求,目前这部分工作由Neutron项目完成,而nova-api-metadata API服务只在多计算节点部署,并且使用nova-network情况下使用。
nova-api-os-compute :Openstack API服务
nova-compute:Computer服务
nova-conductor:Conductor服务
nova-manage :提供很多与Nova维护和管理相关的功能,比如用户创建,VPN管理等。
nova-novncproxy:Nova提供了 novncproxy代理支持用户通过vnc访问虚拟机。提供完整的vnc访问功能,涉及几个Nova服务(nova-consoleauth提供认证授权, nova-novncproxy用于支持基于浏览器的vnc客户端,nova-xvpvncproxy用于支持基于Java的vnc客户端。)
nova-rootwrap :用于在Openstack在运行过程中以root身份运行某些shell命令
nova-scheduler :Scheduler服务。
Nova-api会读取api-paste.ini,从中加载整个WSGI stack。最终API的入口点都位于nova.api.openstack.compute路径中。
api.py: 通常是供其它组件调用的封装库
rpcapi.py:这个是RPC请求的封装,或者说是RPC封装的client端,该模块封装了所有RPC请求调用。
manager.py: 这个才是真正服务的功能实现,也是RPC的服务端,即处理RPC请求的入口,实现的方法和rpcapi实现的方法一一对应。
Nova中各个服务之间的通信使用了基于AMQP实现的RPC机制,其中nova-compute、 nova-conductor 和 nova-scheduler 在启动时都会注册一个 RPC Server,而 nova-api 因为Nova内部并没有服务会调用它提供的接口,所以无需注册。
比如对一个虚拟机执行关机操作的流程为:
API节点:
Nova-api接收用户请求-》nova-api调用compute/api.py-》compute/api调用rpcapi.py-》rpcapi.py向目标计算机发起stop_instance()RPC请求
计算节点:
收到MQ RPC消息-》解析stop_instance()请求-》调用compute/manager.py的callback方法stop_instance()-》调用libvirt关机虚拟机
Nova主要目录结构解析:
cmd:这是服务的启动脚本,即所有服务的main函数。看服务怎么初始化,就从这里开始。
db: 封装数据库访问API,目前支持的driver为sqlalchemy,还包括migrate repository。
conf:Nova的配置项声明都在这里,想看Nova配置的作用和默认值可以从这个目录入手。
locale: 本地化处理。
image: 封装image API,其实就是调用python-glanceclient。
network: 封装网络服务接口,根据配置不同,可能调用nova-network或者neutron。
volume: 封装数据卷访问接口,通常是Cinder的client封装,调用python-cinderclient。
virt: 这是所有支持的hypervisor驱动,主流的如libvirt、xen等。
objects: 对象模型,封装了所有实体对象的CURD操作,相对直接调用db的model更安全,并且支持版本控制。
policies: policy校验实现。
tests: 单元测试和功能测试代码。
注:
1、 pbr库是一个使用统一方式管理setuptools包的库。pbr库通过一个setup钩子函数读取并过滤setup.cfg中的数据,以填充默认值并提供更多合理的操作;然后将结果作为参数返回给setup.py。
2、OpenStack社区将所有组件中的具有共性的组件剥离出来,并统一放在oslo组件下。oslo中的组件不仅可以在OpenStack项目中使用,也可以单独作为第三方工具包供其他项目使用。
3、oslo.config项目是oslo组件中用于OpenStack配置文件的一个项目
4、oslo项目创建了oslo.policy子项目为所有OpenStack服务提供RBAC策略实施支持。
需要注意的是Nova支持同时创建多台虚拟机,因此在调度时需要选择多个宿主机。
Nova-api
1、入口为nova/api/openstack/compute/servers.py的create方法,该方法检查了一堆参数以及policy后,调用compute_api的create方法。
def create(self, req, body):
"""Creates a new server for a given user."""
context = req.environ['nova.context']
server_dict = body['server']
password = self._get_server_admin_password(server_dict)
name = common.normalize_name(server_dict['name'])
description = name
............
flavor_id = self._flavor_id_from_req_data(body)
try:
flavor = flavors.get_flavor_by_flavor_id(
flavor_id, ctxt=context, read_deleted="no")
supports_multiattach = common.supports_multiattach_volume(req)
supports_port_resource_request = \
common.supports_port_resource_request(req)
instances, resv_id = self.compute_api.create(
context,
flavor,
image_uuid,
display_name=name,
display_description=description,
hostname=hostname,
availability_zone=availability_zone,
forced_host=host, forced_node=node,
metadata=server_dict.get('metadata', {}),
admin_password=password,
check_server_group_quota=True,
supports_multiattach=supports_multiattach,
supports_port_resource_request=supports_port_resource_request,
**create_kwargs)
......................
self.compute_task_api.schedule_and_build_instances(
context,
build_requests=build_requests,
request_spec=request_specs,
image=boot_meta,
admin_password=admin_password,
injected_files=injected_files,
requested_networks=requested_networks,
block_device_mapping=block_device_mapping,
tags=tags)
def schedule_and_build_instances(self, context, build_requests,
request_spec, image,
admin_password, injected_files,
requested_networks, block_device_mapping,
tags=None):
self.conductor_compute_rpcapi.schedule_and_build_instances(
context, build_requests, request_spec, image,
admin_password, injected_files, requested_networks,
block_device_mapping, tags)
cctxt = self.client.prepare(version=version)
cctxt.cast(context, 'build_instances', **kwargs)
其中cast表示异步调用,build_instances是远程调用的方法,kwargs是传递的参数。参数是字典类型,没有复杂对象结构,因此不需要特别的序列化操作。
Ps:截至到现在,虽然目录由api->compute->conductor,但仍在nova-api进程中运行,直到cast方法执行,该方法由于是异步调用,因此nova-api任务完成,此时会响应用户请求,虚拟机状态为building。
前景摘要:由于是向nova-conductor发起的RPC调用,而前面说了接收端肯定是manager.py。因此进程跳到nova-conductor服务。
def _schedule_instances(self, context, request_spec,
instance_uuids=None, return_alternates=False):
scheduler_utils.setup_instance_group(context, request_spec)
with timeutils.StopWatch() as timer:
host_lists = self.query_client.select_destinations(
context, request_spec, instance_uuids, return_objects=True,
return_alternates=return_alternates)
LOG.debug('Took %0.2f seconds to select destinations for %s '
'instance(s).', timer.elapsed(), len(instance_uuids))
return host_lists
scheduler_client和compute_api以及compute_task_api都是一样对服务的client SDK调用,不过scheduler没有api.py,而是有个单独的client目录,实现在client目录的__init__.py,
2、这里仅仅是调用query.py下的SchedulerQueryClient的select_destinations实现,然后又很直接地调用了scheduler_rpcapi的select_destinations方法,终于又到了RPC调用环节(nova/scheduler/client/query)。
def select_destinations(self, context, spec_obj, instance_uuids,
return_objects=False, return_alternates=False):
return self.scheduler_rpcapi.select_destinations(context, spec_obj,
instance_uuids, return_objects, return_alternates)
3、毫无疑问,RPC封装同样是在scheduler的rpcapi中实现,在select_destinations方法中。该方法RPC调用代码如下:
return cctxt.call(ctxt, 'select_destinations', **msg_args)
注意这里调用的call方法,即同步RPC调用,此时nova-conductor并不会退出,而是堵塞等待直到nova-scheduler返回。因此当前状态为nova-conductor为blocked状态,等待nova-scheduler返回,nova-scheduler接管任务。
同理找到scheduler的manager.py模块的select_destinations方法,该方法会调用driver方法,这里的driver其实就是调度算法实现,通常用的比较多的就是Filter Scheduler算法,对应filter_scheduler.py模块,该模块首先通过host_manager拿到所有的计算节点信息,然后通过filters过滤掉不满足条件的计算节点,剩下的节点通过weigh方法计算权值,最后选择权值高的作为候选计算节点返回。最后nova-scheduler返回调度结果的hosts集合,任务结束,返回到nova-conductor服务。
回到scheduler/manager.py的build_instances方法,nova-conductor等待nova-scheduler返回后,拿到调度的计算节点列表。因为可能同时启动多个虚拟机,因此循环调用了compute_rpcapi的build_and_run_instance方法。
看到xxxrpc立即想到对应的代码位置,位于compute/rpcapi模块,该方法向nova-compute发起RPC请求(位于build_and_run_instance方法中):
cctxt.cast(ctxt, 'build_and_run_instance', **kwargs)
def _build_and_run_instance(self, context, instance, image, injected_files,
admin_password, requested_networks, security_groups,
block_device_mapping, node, limits, filter_properties,
request_spec=None, accel_uuids=None):
......................
self.driver.spawn(context, instance, image_meta,
injected_files, admin_password,
allocs, network_info=network_info,
block_device_info=block_device_info,
accel_info=accel_info)
......................
def spawn(self, context, instance, image_meta, injected_files,
admin_password, allocations, network_info=None,
block_device_info=None, power_on=True, accel_info=None):
.....................