Horizon 源码阅读(四)—— 调用Novaclient流程

2024-06-10 22:48

本文主要是介绍Horizon 源码阅读(四)—— 调用Novaclient流程,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

一、写在前面

这篇文章主要介绍一下Openstack(Kilo)关于horizon 调用NovaClient的一个分析。

        如果转载,请保留作者信息。

        邮箱地址:jpzhang.ht@gmail.com


二、novaclient目录结构

novaclient/

         |---client.py --------主要提供HTTPClient类,也提供根据版本创建Client对象的函数

         |---base.py  --------提供基本的Manager基类

         |---shell.py  --------命令解析,创建相应版本的Client类对象,调用相应版本的shell.py中的函数

         ...

         |---v2

                |---client.py ---------版本Client类,拥有一系列Manager类对象,这些Manager可以调用相应的组件

                |---flavors.py --------具体的Manager类,使用HTTPClient对象与对应的组件进行通信

                ...

                |---shell.py   ————提供每个Command对应的方法


、以创建虚拟机为例分析源码

/openstack_dashboard/api/nova.py

horizon 调用APi创建虚拟机

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):return Server(novaclient(request).servers.create(name, 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), request)

返回一个创建后的Server对象,调用novaclient(request).servers.create()传入参数,发送创建虚拟机的请求。

novaclient(request).servers.create(

        name, image, flavor….), request


调用流程:

novaclient(request)-> servers -> create

1、novaclient(request):返回一个novaclient对象。

 /openstack_dashboard/api/nova.py

def novaclient(request):# 获取是否SSL证书检查,默认是禁用insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)#获取CA证书使用来验证SSL连接,默认是Nonecacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)#from novaclient.v2 import client as nova_client 返回Client类<strong>[1]</strong>c = nova_client.Client(request.user.username,request.user.token.id,project_id=request.user.tenant_id,auth_url=base.url_for(request, 'compute'),insecure=insecure,cacert=cacert,http_log_debug=settings.DEBUG)#设置Token ID 值c.client.auth_token = request.user.token.id#设置访问地址:例如 http://hty-nova:8774/v2/ea4d1859494c490495b027f174de307cc.client.management_url = base.url_for(request, 'compute')return c


novaclient/v2/__init__.py

from novaclient.v2.client import Client 

[1]处代码分析,返回一个Client对象

class Client(object):"""顶级对象访问OpenStack计算API。Top-level object to access the OpenStack Compute API."""def __init__(self, username=None, api_key=None, project_id=None,auth_url=None, insecure=False, timeout=None,...):password = api_keyself.projectid = project_id...# extensions 扩展self.agents = agents.AgentsManager(self)self.dns_domains = floating_ip_dns.FloatingIPDNSDomainManager(self)...# Add in any extensions...在添加任何扩展if extensions:for extension in extensions:if extension.manager_class:setattr(self, extension.name,extension.manager_class(self))#构建HTTP客户端self.client = client._construct_http_client(username=username,password=password,...**kwargs)
...

这个client里面有一个Client类,拥有一堆的Manager负责管理各种资源,只需引用这些Manager就可以操作资源,然后创建一系列的Manager类来负责处理资源,在这些Manager类中主要使用HTTPClient来发送请求对相应的组件进行操作,最后,将client版本能够实现的功能封装成函数,这些函数进而能够被相应的command调用。

2、novaclient(request).servers.create():

novaclient/v2/client.py

引用ServerManager操作server 

class Client(object):def __init__(self, username=None, api_key=None, project_id=None,…)….#负责管理servers,只需引用Manager就可以操作serversself.servers = servers.ServerManager(self)…

novaclient/v2/servers.py

创建虚拟机create() 函数

class ServerManager(base.BootingManagerWithFind):resource_class = Server # 资源类,上文定义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, **kwargs):"""Create (boot) a new server.创建(启动)新的服务器。"""#判断虚拟机创建数量if not min_count:min_count = 1if not max_count:max_count = min_countif min_count > max_count:min_count = max_count# 组拼参数boot_args = [name, image, flavor]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, **kwargs)#block_device_mapping:(可选扩展)的块设备映射此虚拟机的字典。if block_device_mapping:resource_url = "/os-volumes_boot"boot_kwargs['block_device_mapping'] = block_device_mappingelif block_device_mapping_v2:resource_url = "/os-volumes_boot"boot_kwargs['block_device_mapping_v2'] = block_device_mapping_v2else:resource_url = “/servers”# nics(可选扩展)的NIC的有序列表要添加到该虚拟机,与有关连接的网络,固定的IP地址,端口等。if nics:boot_kwargs['nics'] = nicsresponse_key = “server"#调用_boot()return self.<strong>_boot</strong>(resource_url, response_key, *boot_args,**boot_kwargs)def <strong>_boot</strong>(self, resource_url, response_key, name, image, flavor,meta=None, files=None, userdata=None,reservation_id=None, return_raw=False, min_count=None,max_count=None, security_groups=None, key_name=None,availability_zone=None, block_device_mapping=None,block_device_mapping_v2=None, nics=None, scheduler_hints=None,config_drive=None, admin_pass=None, disk_config=None, **kwargs):"""Create (boot) a new server. 创建(启动)新的服务器。"""# 调用Restful API带的body参数body = {"server": {"name": name,"imageRef": str(base.getid(image)) if image else '',"flavorRef": str(base.getid(flavor)),}}if userdata:if hasattr(userdata, 'read'):userdata = userdata.read()if six.PY3:userdata = userdata.encode("utf-8")else:userdata = encodeutils.safe_encode(userdata)userdata_b64 = base64.b64encode(userdata).decode('utf-8')body["server"]["user_data"] = userdata_b64if meta:body["server"]["metadata"] = metaif reservation_id:body["server"]["reservation_id"] = reservation_idif key_name:body["server"]["key_name"] = key_nameif scheduler_hints:body['os:scheduler_hints'] = scheduler_hintsif config_drive:body["server"]["config_drive"] = config_driveif admin_pass:body["server"]["adminPass"] = admin_passif not min_count:min_count = 1if not max_count:max_count = min_countbody["server"]["min_count"] = min_countbody["server"]["max_count"] = max_countif security_groups:body["server"]["security_groups"] = [{'name': sg}for sg in security_groups]# Files are a slight bit tricky. They're passed in a "personality"# list to the POST. Each item is a dict giving a file name and the# base64-encoded contents of the file. We want to allow passing# either an open file *or* some contents as files here.if files:personality = body['server']['personality'] = []for filepath, file_or_string in sorted(files.items(),key=lambda x: x[0]):if hasattr(file_or_string, 'read'):data = file_or_string.read()else:data = file_or_stringif six.PY3 and isinstance(data, str):data = data.encode('utf-8')cont = base64.b64encode(data).decode('utf-8')personality.append({'path': filepath,'contents': cont,})if availability_zone:body["server"]["availability_zone"] = availability_zone# Block device mappings are passed as a list of dictionariesif block_device_mapping:body['server']['block_device_mapping'] = \self._parse_block_device_mapping(block_device_mapping)elif block_device_mapping_v2:body['server']['block_device_mapping_v2'] = block_device_mapping_v2if nics is not None:# NOTE(tr3buchet): nics can be an empty listall_net_data = []for nic_info in nics:net_data = {}# if value is empty string, do not send value in bodyif nic_info.get('net-id'):net_data['uuid'] = nic_info['net-id']if (nic_info.get('v4-fixed-ip') andnic_info.get('v6-fixed-ip')):raise base.exceptions.CommandError(_("Only one of 'v4-fixed-ip' and 'v6-fixed-ip' may be"" provided."))elif nic_info.get('v4-fixed-ip'):net_data['fixed_ip'] = nic_info['v4-fixed-ip']elif nic_info.get('v6-fixed-ip'):net_data['fixed_ip'] = nic_info['v6-fixed-ip']if nic_info.get('port-id'):net_data['port'] = nic_info['port-id']all_net_data.append(net_data)body['server']['networks'] = all_net_dataif disk_config is not None:body['server']['OS-DCF:diskConfig'] = disk_config# 调用父类基类Manager._create()方法# ServerManager->BootingManagerWithFind->ManagerWithFind->Managerreturn self.<strong>_create</strong>(resource_url, body, response_key,return_raw=return_raw, **kwargs)

novaclient/base.py

class Manager(base.HookableMixin):"""Managers interact with a particular type of API (servers, flavors, images,etc.) and provide CRUD operations for them."""# 管理者与特定类型的API(servers, flavors, images,etc)进行交互,并为他们提供CRUD操作。resource_class = Nonecache_lock = threading.RLock()def __init__(self, api):# api 即 Client对象,从<span style="font-family: Arial, Helvetica, sans-serif;">novaclient/v2/client.py:self.servers = servers.ServerManager(self)传入</span>self.api = apidef <strong>_create</strong>(self, url, body, response_key, return_raw=False, **kwargs):# 运行指定类型的所有挂钩。self.run_hooks('modify_body_for_create', body, **kwargs)# self.api 即novaclient/v2/client.py:self.servers = servers.ServerManager(self) class -> Client(object)对象;# client: self.client HTTPClient客户端对象 novaclient/client.py->def _construct_http_client() -> class HTTPClient(object)->def post()# 发起post请求_resp, body = self.api.client.<strong>post</strong>(url, body=body)if return_raw:return body[response_key]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])

novaclient/client.py/class HTTPClient(object)

#调用POST 发送请求
def post(self, url, **kwargs):return self.<strong>_cs_request</strong>(url, 'POST', **kwargs)sdef <strong>_cs_request</strong>(self, url, method, **kwargs):if not self.management_url:self.authenticate()if url is None:# To get API version information, it is necessary to GET# a nova endpoint directly without "v2/<tenant-id>".magic_tuple = parse.urlsplit(self.management_url)scheme, netloc, path, query, frag = magic_tuplepath = re.sub(r'v[1-9]/[a-z0-9]+$', '', path)url = parse.urlunsplit((scheme, netloc, path, None, None))else:if self.service_catalog:url = self.get_service_url(self.service_type) + urlelse:# NOTE(melwitt): The service catalog is not available#                when bypass_url is used.url = self.management_url + url# Perform the request once. If we get a 401 back then it# might be because the auth token expired, so try to# re-authenticate and try again. If it still fails, bail.try:kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_tokenif self.projectid:kwargs['headers']['X-Auth-Project-Id'] = self.projectidresp, body = self.<strong>_time_request</strong>(url, method, **kwargs)return resp, body"""有可能出现没有认证的情况,需要先认证再发送请求 """except exceptions.Unauthorized as e:...def <strong>_time_request</strong>(self, url, method, **kwargs):start_time = time.time()resp, body = self.<strong>request</strong>(url, method, **kwargs)self.times.append(("%s %s" % (method, url),start_time, time.time()))return resp, bodydef <strong>request</strong>(self, url, method, **kwargs):""" 构造请求报文参数 """kwargs.setdefault('headers', kwargs.get('headers', {}))kwargs['headers']['User-Agent'] = self.USER_AGENTkwargs['headers']['Accept'] = 'application/json'if 'body' in kwargs:kwargs['headers']['Content-Type'] = 'application/json'kwargs['data'] = json.dumps(kwargs['body'])del kwargs['body']if self.timeout is not None:kwargs.setdefault('timeout', self.timeout)kwargs['verify'] = self.verify_certself.http_log_req(method, url, kwargs)request_func = requests.requestsession = self._get_session(url)if session:request_func = session.request""" 这里使用了第三方的 requests 库,发起post请求"""resp = request_func(method,url,**kwargs)self.http_log_resp(resp)if resp.text:# TODO(dtroyer): verify the note below in a requests context# NOTE(alaski): Because force_exceptions_to_status_code=True# httplib2 returns a connection refused event as a 400 response.# To determine if it is a bad request or refused connection we need# to check the body.  httplib2 tests check for 'Connection refused'# or 'actively refused' in the body, so that's what we'll do.""" 根据请求返回的结果决定是否抛出异常 """if resp.status_code == 400:if ('Connection refused' in resp.text or'actively refused' in resp.text):raise exceptions.ConnectionRefused(resp.text)try:body = json.loads(resp.text)except ValueError:body = Noneelse:body = None""" 根据请求返回的结果决定是否抛出异常 """if resp.status_code >= 400:raise exceptions.from_response(resp, body, url, method)# 返回调用结果return resp, body





     

这篇关于Horizon 源码阅读(四)—— 调用Novaclient流程的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1049493

相关文章

C#提取PDF表单数据的实现流程

《C#提取PDF表单数据的实现流程》PDF表单是一种常见的数据收集工具,广泛应用于调查问卷、业务合同等场景,凭借出色的跨平台兼容性和标准化特点,PDF表单在各行各业中得到了广泛应用,本文将探讨如何使用... 目录引言使用工具C# 提取多个PDF表单域的数据C# 提取特定PDF表单域的数据引言PDF表单是一

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

Go中sync.Once源码的深度讲解

《Go中sync.Once源码的深度讲解》sync.Once是Go语言标准库中的一个同步原语,用于确保某个操作只执行一次,本文将从源码出发为大家详细介绍一下sync.Once的具体使用,x希望对大家有... 目录概念简单示例源码解读总结概念sync.Once是Go语言标准库中的一个同步原语,用于确保某个操

Python调用另一个py文件并传递参数常见的方法及其应用场景

《Python调用另一个py文件并传递参数常见的方法及其应用场景》:本文主要介绍在Python中调用另一个py文件并传递参数的几种常见方法,包括使用import语句、exec函数、subproce... 目录前言1. 使用import语句1.1 基本用法1.2 导入特定函数1.3 处理文件路径2. 使用ex

使用MongoDB进行数据存储的操作流程

《使用MongoDB进行数据存储的操作流程》在现代应用开发中,数据存储是一个至关重要的部分,随着数据量的增大和复杂性的增加,传统的关系型数据库有时难以应对高并发和大数据量的处理需求,MongoDB作为... 目录什么是MongoDB?MongoDB的优势使用MongoDB进行数据存储1. 安装MongoDB

Idea调用WebService的关键步骤和注意事项

《Idea调用WebService的关键步骤和注意事项》:本文主要介绍如何在Idea中调用WebService,包括理解WebService的基本概念、获取WSDL文件、阅读和理解WSDL文件、选... 目录前言一、理解WebService的基本概念二、获取WSDL文件三、阅读和理解WSDL文件四、选择对接

Python实现NLP的完整流程介绍

《Python实现NLP的完整流程介绍》这篇文章主要为大家详细介绍了Python实现NLP的完整流程,文中的示例代码讲解详细,具有一定的借鉴价值,感兴趣的小伙伴可以跟随小编一起学习一下... 目录1. 编程安装和导入必要的库2. 文本数据准备3. 文本预处理3.1 小写化3.2 分词(Tokenizatio

Java调用Python代码的几种方法小结

《Java调用Python代码的几种方法小结》Python语言有丰富的系统管理、数据处理、统计类软件包,因此从java应用中调用Python代码的需求很常见、实用,本文介绍几种方法从java调用Pyt... 目录引言Java core使用ProcessBuilder使用Java脚本引擎总结引言python

Java汇编源码如何查看环境搭建

《Java汇编源码如何查看环境搭建》:本文主要介绍如何在IntelliJIDEA开发环境中搭建字节码和汇编环境,以便更好地进行代码调优和JVM学习,首先,介绍了如何配置IntelliJIDEA以方... 目录一、简介二、在IDEA开发环境中搭建汇编环境2.1 在IDEA中搭建字节码查看环境2.1.1 搭建步

SpringBoot使用minio进行文件管理的流程步骤

《SpringBoot使用minio进行文件管理的流程步骤》MinIO是一个高性能的对象存储系统,兼容AmazonS3API,该软件设计用于处理非结构化数据,如图片、视频、日志文件以及备份数据等,本文... 目录一、拉取minio镜像二、创建配置文件和上传文件的目录三、启动容器四、浏览器登录 minio五、