请大家静下心来看源码分析的文章,看完后相信你会有收获的!
本文来自于微信公众号“扶艾”,欢迎大家关注获取更多精彩内容!
我们分析就分析全套的,创建虚拟机的源码分析总共分为三篇文章,我们将会用通俗易懂的语言来描述整个流程。第一篇文章将分析客户端部分的源码,也就是从点击创建虚拟机按钮后到发送http请求这部分的源码;第二篇文章将分析nova api如何接收到http请求的源码;第三篇文章将分析从筛选计算节点到创建虚拟机成功部分的源码。
本篇文章将分析虚拟机从点击创建按钮到发送http请求到nova api的源码分析。
从点击界面的创建虚拟机按钮后,在输入虚拟机的名称选择镜像和网络后那实际上做了什么工作呢?跟着我的思路来走一走!
这部分主要分析下客户端的源码,这部分源码分在两个地方:
点击创建按钮之后,实际执行了路径1这个文件中class LaunchInstance的handle方法。至于为什么界面点击创建按钮后执行这个,大家可以去看下我们的关于界面二次开发的文章,看完你会对前端界面的源码架构有个清晰的理解。
路径1. /usr/share/openstack-dashboard/openstack_dashboard/dashboards/project/instances/workflows/create_instance.py
部分源码:
from openstack_dashboard import api
...
class LaunchInstance(workflows.Workflow):
...
def handle(self, request, context):
...
api.nova.server_create(request,
context['name'],
image_id,
context['flavor'],
context['keypair_id'],
normalize_newlines(custom_script),
context['security_group_ids'],
block_device_mapping=dev_mapping_1,
block_device_mapping_v2=dev_mapping_2,
nics=nics,
availability_zone=avail_zone,
instance_count=int(context['count']),
admin_pass=context['admin_pass'],
disk_config=context.get('disk_config'),
config_drive=context.get('config_drive'),
scheduler_hints=scheduler_hints)
这里可以看到handle方法在收集了界面输入的相关参数后调用了api.nova.server_create()函数。那么我们继续看下api.nova.server_create()函数。
路径2. /usr/share/openstack-dashboard/openstack_dashboard/api/nova.py
部分源码
from novaclient import client as nova_client
...
def server_create(request, name, image, flavor, key_name, user_data,
security_groups, block_device_mapping=None,
block_device_mapping_v2=None, nics=None,
availability_zone=None, instance_count=1, admin_pass=None,
disk_config=None, config_drive=None, meta=None,
scheduler_hints=None, description=None):
...
return Server(get_novaclient_with_instance_desc(request).servers.create(
name.strip(), image, flavor, userdata=user_data,
security_groups=security_groups,
key_name=key_name, block_device_mapping=block_device_mapping,
block_device_mapping_v2=block_device_mapping_v2,
nics=nics, availability_zone=availability_zone,
min_count=instance_count, admin_pass=admin_pass,
disk_config=disk_config, config_drive=config_drive,
meta=meta, scheduler_hints=scheduler_hints, **kwargs), request)
def get_novaclient_with_instance_desc(request):
microversion = get_microversion(request, "instance_description")
return novaclient(request, version=microversion)
def novaclient(request_auth_params, version=None):
(
username,
token_id,
project_id,
project_domain_id,
nova_url,
auth_url
) = request_auth_params
if version is None:
version = VERSIONS.get_active_version()['version']
c = nova_client.Client(version,
username,
token_id,
project_id=project_id,
project_domain_id=project_domain_id,
auth_url=auth_url,
insecure=INSECURE,
cacert=CACERT,
http_log_debug=settings.DEBUG,
auth_token=token_id,
endpoint_override=nova_url)
return c
路径2这个server_create函数实际没有做什么工作,它在最后返回了这样一个东西Server(get_novaclient_with_instance_desc(request).servers.create(…)),看着挺复杂的一个东西,但不要被它给吓到了。只从这个地方看,可以知道这是Server()这个类,也就是说这里实际返回的是Server的对象,它的参数是get_novaclient_with_instance_desc(request).servers.create()。
至于为什么这样做,这里我可以先告诉大家,这么做是因为Server是一个专门用于封装虚拟机信息的封装类,它将创建虚拟机返回的值封装成一个对象。这里创建虚拟机返回的就是这台虚拟机创建成功之后这台虚拟机的详细信息。其实,用到这个Server类的不止创建虚拟机,包括获取指定虚拟机的详细信息(server_get)以及获取所有虚拟机信息(server_list)都用到了这个类,像server_list返回的是多台虚拟机的信息,而每台虚拟机的信息实际就是Server的一个对象。大家如果对封装这部分源码感兴趣的可以自行深入,这里我们就不将Server展开了。
这里继续看Server的参数get_novaclient_with_instance_desc(request).servers.create()。通过以上源码可以看到这个方法return novaclient,而novaclient方法返回的是nova_client下的一个Client类。也就是说Server这个参数就是这个Client类下的servers这个属性的create方法。也就是说Server类的这个复杂的参数就是create方法的返回值。如果大家对这个地方不理解,没关系,继续向下看!
查看上面代码块的第一行可以看到nova_client是从novaclient模块导入的。所以,这里就已经引入了nova客户端部分的源码了。我们继续看引入的是什么
路径3. /usr/lib/python2.7/site-packages/novaclient/client.py
部分源码
def Client():
...
api_version, client_class = _get_client_class_and_version()
...
return client_class(api_version=api_version,
auth_url=auth_url,
direct_use=False,
username=username,
**kwargs)
def _get_client_class_and_version(version):
if not isinstance(version, api_versions.APIVersion):
version = api_versions.get_api_version(version)
else:
api_versions.check_major_version(version)
if version.is_latest():
raise exceptions.UnsupportedVersion(
_("The version should be explicit, not latest."))
return version, importutils.import_class(
"novaclient.v%s.client.Client" % version.ver_major)
看路径3的代码,发现Client是一个方法,不过它的返回值是一个类,而这个类是通过另一个方法_get_client_class_and_version获取的,而这个方法它实际导入的是novaclient.v2.client.Client类,也就是下面路径4中的Client类。
路径4. /usr/lib/python2.7/site-packages/novaclient/v2/client.py
部分源码
from novaclient import client
from novaclient.v2 import servers
class Client(object):
def __init__():
...
self.servers = servers.ServerManager(self)
...
self.client = client._construct_http_client()
看了路径4的代码,这里面已经出现了servers这个属性了,它的值是servers.ServerManager。我们继续看下面路径5的源码,看到ServerManager有一个方法是create方法。至此,路径2中Server(get_novaclient_with_instance_desc(request).servers.create(…))这个已经明了,实际这个参数就是获取了nova客户端,并调用了其中ServerManager的create方法。
路径5. /usr/lib/python2.7/site-packages/novaclient/v2/servers.py
部分源码
class ServerManager(base.BootingManagerWithFind):
def _boot()
...
reurn self._create(resource_url, body, response_key,
return_raw=return_raw, **kwargs)
...
def create(self, name, image, flavor, meta=None, files=None,
reservation_id=None, min_count=None,
max_count=None, security_groups=None, userdata=None,
key_name=None, availability_zone=None,
block_device_mapping=None, block_device_mapping_v2=None,
nics=None, scheduler_hints=None,
config_drive=None, disk_config=None, admin_pass=None,
access_ip_v4=None, access_ip_v6=None, **kwargs)
...
boot_kwargs = dict(
meta=meta, files=files, userdata=userdata,
reservation_id=reservation_id, min_count=min_count,
max_count=max_count, security_groups=security_groups,
key_name=key_name, availability_zone=availability_zone,
scheduler_hints=scheduler_hints, config_drive=config_drive,
disk_config=disk_config, admin_pass=admin_pass,
access_ip_v4=access_ip_v4, access_ip_v6=access_ip_v6, **kwargs)
if block_device_mapping:
resource_url = "/os-volumes_boot"
boot_kwargs['block_device_mapping'] = block_device_mapping
elif block_device_mapping_v2:
resource_url = "/os-volumes_boot"
boot_kwargs['block_device_mapping_v2'] = block_device_mapping_v2
else:
resource_url = "/servers"
if nics:
boot_kwargs['nics'] = nics
response_key = "server"
return self._boot(resource_url, response_key, **boot_args, **boot_kwargs)
路径5这里create方法整合参数,并准备了创建虚拟机要用到的url,而后调用了ServerManager的_boot方法,_boot方法中也是在整合参数,将整合的参数放到了body这个字典中,然后调用了self._create()方法。而这个_create的方法在ServerManager的父类里,继承关系是这样的Manager <= ManagerWithFind(Manager) <= BootingManagerWithFind(ManagerWithFind) <= ServerManager(base.BootingManagerWithFind),_create方法就在Manager这个父类里。
这里为什么这么继承呢?根据源码来看Manager这个类是nova客户端这边管理虚拟机(server)、镜像(image)、模版(flavor)相关操作(创建、删除、查询、更新)的一个类,而ManagerWithFind继承Manager,ManagerWithFind不止有Manager的功能,它还有find的功能,BootingManagerWithFind就是说还有启动虚拟机的功能,ServerManager就具有以上所有功能。大家如果有兴趣可以深入研究下客户端这边这个源码结构,还是比较有趣的,对代码框架设计还是有一定帮助,其中还有hookmixin的使用。我们言归正传,继续看这个_create方法吧。
路径6. /usr/lib/python2.7/site-packages/novaclient/base.py
部分源码:
class Manager():
def __init__(self, api):
self.api = api
def _create(self, url, body, response_key, return_raw=False, **kwargs):
self.run_hooks('modify_body_for_create', body, **kwargs)
resp, body = self.api.client.post(url, body=body)
if return_raw:
return self.convert_into_with_meta(body[response_key], resp)
with self.completion_cache('human_id', self.resource_class, mode="a"):
with self.completion_cache('uuid', self.resource_class, mode="a"):
return self.resource_class(self, body[response_key], resp=resp))
看路径6这里是已经在准备发送请求了,调用了api.client.post方法。这个self.api是在init方法中赋值的,是在哪传过来的呢,注意看上面路径4中ServerManager有个参数是self,也就是说这个self.api就是路径4中Client类的对象,self.api.client则是路径4中self.client,它等于client._construct_http_client(),在路径7下看到_construct_http_client返回的是SessionClient,从这里看这个类没有post方法,那么我们再去它的父类看看,最终在路径8中的Adapter类中找到了post方法,真是山高路远。根据Adapter类的描述,这个Adapter是大部分组件客户端都会用到的,也就是说其它组件的客户端发送请求也会继承这个类,原来看到过kilo版本的这部分源码,post这些方法仍然是在novaclient这个模块的,现在来看应该是将这部分从各个客户端模块给拿出来统一到这里来了。
路径7. /usr/lib/python2.7/site-packages/novaclient/client.py
部分源码:
from keystoneauth1 import adapter
from keystoneauth1 import session as ksession
class SessionClient(adapter.LegacyJsonAdapter):
...
def request(self, url, method, **kwargs):
...
resp, body = super(SessionClient, self)request(url,
method,
raise_exc=False,
**kwargs)
...
return resp, body
def _construct_http_client():
...
if not session:
if not auth and auth_token:
auth = identity.Token(auth_url=auth_url,
token=auth_token,
project_id=project_id,
project_name=project_name,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name)
elif not auth:
auth = identity.Password(username=username,
user_id=user_id,
password=password,
project_id=project_id,
project_name=project_name,
auth_url=auth_url,
project_domain_id=project_domain_id,
project_domain_name=project_domain_name,
user_domain_id=user_domain_id,
user_domain_name=user_domain_name)
session = ksession.Session(auth=auth,
verify=(cacert or not insecure),
timeout=timeout,
cert=cert,
user_agent=user_agent)
return SessionClient(api_version=api_version,
auth=auth,
endpoint_override=endpoint_override,
interface=endpoint_type,
logger=logger,
region_name=region_name,
service_name=service_name,
service_type=service_type,
session=session,
timings=timings,
user_agent=user_agent,
**kwargs)
路径8. /usr/lib/python2.7/site-packages/keystoneauth1/adapter.py
部分源码:
class Adapter()
...
def __init__(self, session,...):
...
self.session = session
...
def request(self, url, method, **kwargs):
...
return self.session.request(url, method, **kwargs)
def post(self, url, **kwargs):
return self.request(url, 'POST', **kwargs)
class LegacyJsonAdapter(Adapter):
...
def request(self, *args, **kwargs):
...
resp = super(LegacyJsonAdapter, self).request(*args, **kwargs)
try:
body = resp.json()
except ValueError:
body = None
return resp, body
继续看看这个post方法,post方法调用的是request发送请求,而看看路径7中的request实际上是调用了父类中的request方法,然后看路径8中它的父类LegacyJsonAdapter也调用了父类的request方法。而Adapter的request又是调用的self.session.request,而则个session是哪来的呢?看路径7中的session,它实际也是keystoneauth1这个模块中来的,所以请求最后通过keystoneauth1/session.py中Session的request方法发出去了。这里不细说了。
至此,请求已经发出去了,下面将看看nova api是如何接收这个请求的,更多详情请看《OpenStack源码阅读-创建虚拟机(二)》