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

一、写在前面

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

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

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

原文地址:http://blog.csdn.net/u011521019/article/details/48324739


二、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创建虚拟机

[python]  view plain  copy
  1. def server_create(request, name, image, flavor, key_name, user_data,  
  2.                   security_groups, block_device_mapping=None,  
  3.                   block_device_mapping_v2=None, nics=None,  
  4.                   availability_zone=None, instance_count=1, admin_pass=None,  
  5.                   disk_config=None, config_drive=None, meta=None):  
  6.     return Server(novaclient(request).servers.create(  
  7.         name, image, flavor, userdata=user_data,  
  8.         security_groups=security_groups,  
  9.         key_name=key_name, block_device_mapping=block_device_mapping,  
  10.         block_device_mapping_v2=block_device_mapping_v2,  
  11.         nics=nics, availability_zone=availability_zone,  
  12.         min_count=instance_count, admin_pass=admin_pass,  
  13.         disk_config=disk_config, config_drive=config_drive,  
  14.         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

[python]  view plain  copy
  1. def novaclient(request):  
  2.     # 获取是否SSL证书检查,默认是禁用  
  3.     insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY'False)  
  4.     #获取CA证书使用来验证SSL连接,默认是None  
  5.     cacert = getattr(settings, 'OPENSTACK_SSL_CACERT'None)  
  6.   
  7.     #from novaclient.v2 import client as nova_client 返回Client类  
  8.     <strong>[1]</strong>c = nova_client.Client(request.user.username,  
  9.                            request.user.token.id,  
  10.                            project_id=request.user.tenant_id,  
  11.                            auth_url=base.url_for(request, 'compute'),  
  12.                            insecure=insecure,  
  13.                            cacert=cacert,  
  14.                            http_log_debug=settings.DEBUG)  
  15.     #设置Token ID 值  
  16.     c.client.auth_token = request.user.token.id  
  17.     #设置访问地址:例如 http://hty-nova:8774/v2/ea4d1859494c490495b027f174de307c  
  18.     c.client.management_url = base.url_for(request, 'compute')  
  19.     return c  


novaclient/v2/__init__.py

from novaclient.v2.client import Client 

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

[python]  view plain  copy
  1. class Client(object):  
  2.     """ 
  3.     顶级对象访问OpenStack计算API。 
  4.     Top-level object to access the OpenStack Compute API. 
  5.  
  6.     """  
  7.   
  8.     def __init__(self, username=None, api_key=None, project_id=None,  
  9.                  auth_url=None, insecure=False, timeout=None,  
  10.                  ...):  
  11.   
  12.         password = api_key  
  13.         self.projectid = project_id  
  14.         ...  
  15.   
  16.         # extensions 扩展  
  17.         self.agents = agents.AgentsManager(self)  
  18.         self.dns_domains = floating_ip_dns.FloatingIPDNSDomainManager(self)  
  19.         ...  
  20.   
  21.         # Add in any extensions...在添加任何扩展  
  22.         if extensions:  
  23.             for extension in extensions:  
  24.                 if extension.manager_class:  
  25.                     setattr(self, extension.name,  
  26.                             extension.manager_class(self))  
  27.         #构建HTTP客户端  
  28.         self.client = client._construct_http_client(  
  29.             username=username,  
  30.             password=password,  
  31.             ...  
  32.             **kwargs)  
  33. ...  

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

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

novaclient/v2/client.py

引用ServerManager操作server 

[python]  view plain  copy
  1. class Client(object):  
  2.     def __init__(self, username=None, api_key=None, project_id=None,…)  
  3.              ….  
  4.                   #负责管理servers,只需引用Manager就可以操作servers  
  5.                    self.servers = servers.ServerManager(self)  
  6.                    …  

novaclient/v2/servers.py

创建虚拟机create() 函数

[python]  view plain  copy
  1. class ServerManager(base.BootingManagerWithFind):  
  2.     resource_class = Server # 资源类,上文定义  
  3.   
  4.     def create(self, name, image, flavor, meta=None, files=None,  
  5.                reservation_id=None, min_count=None,  
  6.                max_count=None, security_groups=None, userdata=None,  
  7.                key_name=None, availability_zone=None,  
  8.                block_device_mapping=None, block_device_mapping_v2=None,  
  9.                nics=None, scheduler_hints=None,  
  10.                config_drive=None, disk_config=None, **kwargs):  
  11.         """ 
  12.         Create (boot) a new server.创建(启动)新的服务器。 
  13.          
  14.         """  
  15.        #判断虚拟机创建数量  
  16.         if not min_count:  
  17.             min_count = 1  
  18.         if not max_count:  
  19.             max_count = min_count  
  20.         if min_count > max_count:  
  21.             min_count = max_count  
  22.   
  23.         # 组拼参数  
  24.         boot_args = [name, image, flavor]  
  25.   
  26.         boot_kwargs = dict(  
  27.             meta=meta, files=files, userdata=userdata,  
  28.             reservation_id=reservation_id, min_count=min_count,  
  29.             max_count=max_count, security_groups=security_groups,  
  30.             key_name=key_name, availability_zone=availability_zone,  
  31.             scheduler_hints=scheduler_hints, config_drive=config_drive,  
  32.             disk_config=disk_config, **kwargs)  
  33.   
  34.        #block_device_mapping:(可选扩展)的块设备映射此虚拟机的字典。  
  35.         if block_device_mapping:  
  36.             resource_url = "/os-volumes_boot"  
  37.             boot_kwargs['block_device_mapping'] = block_device_mapping  
  38.         elif block_device_mapping_v2:  
  39.             resource_url = "/os-volumes_boot"  
  40.             boot_kwargs['block_device_mapping_v2'] = block_device_mapping_v2  
  41.         else:  
  42.             resource_url = “/servers”  
  43.   
  44.         # nics(可选扩展)的NIC的有序列表要添加到该虚拟机,与有关连接的网络,固定的IP地址,端口等。  
  45.         if nics:  
  46.             boot_kwargs['nics'] = nics  
  47.   
  48.         response_key = “server"  
  49.        #调用_boot()  
  50.         return self.<strong>_boot</strong>(resource_url, response_key, *boot_args,  
  51.                           **boot_kwargs)  
  52.   
  53.   
  54.     def <strong>_boot</strong>(self, resource_url, response_key, name, image, flavor,  
  55.               meta=None, files=None, userdata=None,  
  56.               reservation_id=None, return_raw=False, min_count=None,  
  57.               max_count=None, security_groups=None, key_name=None,  
  58.               availability_zone=None, block_device_mapping=None,  
  59.               block_device_mapping_v2=None, nics=None, scheduler_hints=None,  
  60.               config_drive=None, admin_pass=None, disk_config=None, **kwargs):  
  61.         """ 
  62.         Create (boot) a new server. 创建(启动)新的服务器。 
  63.         """  
  64.         # 调用Restful API带的body参数  
  65.         body = {"server": {  
  66.             "name": name,  
  67.             "imageRef": str(base.getid(image)) if image else '',  
  68.             "flavorRef": str(base.getid(flavor)),  
  69.         }}  
  70.         if userdata:  
  71.             if hasattr(userdata, 'read'):  
  72.                 userdata = userdata.read()  
  73.   
  74.             if six.PY3:  
  75.                 userdata = userdata.encode("utf-8")  
  76.             else:  
  77.                 userdata = encodeutils.safe_encode(userdata)  
  78.   
  79.             userdata_b64 = base64.b64encode(userdata).decode('utf-8')  
  80.             body["server"]["user_data"] = userdata_b64  
  81.         if meta:  
  82.             body["server"]["metadata"] = meta  
  83.         if reservation_id:  
  84.             body["server"]["reservation_id"] = reservation_id  
  85.         if key_name:  
  86.             body["server"]["key_name"] = key_name  
  87.         if scheduler_hints:  
  88.             body['os:scheduler_hints'] = scheduler_hints  
  89.         if config_drive:  
  90.             body["server"]["config_drive"] = config_drive  
  91.         if admin_pass:  
  92.             body["server"]["adminPass"] = admin_pass  
  93.         if not min_count:  
  94.             min_count = 1  
  95.         if not max_count:  
  96.             max_count = min_count  
  97.         body["server"]["min_count"] = min_count  
  98.         body["server"]["max_count"] = max_count  
  99.   
  100.         if security_groups:  
  101.             body["server"]["security_groups"] = [{'name': sg}  
  102.                                                  for sg in security_groups]  
  103.   
  104.         # Files are a slight bit tricky. They're passed in a "personality"  
  105.         # list to the POST. Each item is a dict giving a file name and the  
  106.         # base64-encoded contents of the file. We want to allow passing  
  107.         # either an open file *or* some contents as files here.  
  108.         if files:  
  109.             personality = body['server']['personality'] = []  
  110.             for filepath, file_or_string in sorted(files.items(),  
  111.                                                    key=lambda x: x[0]):  
  112.                 if hasattr(file_or_string, 'read'):  
  113.                     data = file_or_string.read()  
  114.                 else:  
  115.                     data = file_or_string  
  116.   
  117.                 if six.PY3 and isinstance(data, str):  
  118.                     data = data.encode('utf-8')  
  119.                 cont = base64.b64encode(data).decode('utf-8')  
  120.                 personality.append({  
  121.                     'path': filepath,  
  122.                     'contents': cont,  
  123.                 })  
  124.         
  125.         if availability_zone:  
  126.             body["server"]["availability_zone"] = availability_zone  
  127.   
  128.         # Block device mappings are passed as a list of dictionaries  
  129.         if block_device_mapping:  
  130.             body['server']['block_device_mapping'] = \  
  131.                 self._parse_block_device_mapping(block_device_mapping)  
  132.         elif block_device_mapping_v2:  
  133.             body['server']['block_device_mapping_v2'] = block_device_mapping_v2  
  134.   
  135.         if nics is not None:  
  136.             # NOTE(tr3buchet): nics can be an empty list  
  137.             all_net_data = []  
  138.             for nic_info in nics:  
  139.                 net_data = {}  
  140.                 # if value is empty string, do not send value in body  
  141.                 if nic_info.get('net-id'):  
  142.                     net_data['uuid'] = nic_info['net-id']  
  143.                 if (nic_info.get('v4-fixed-ip'and  
  144.                         nic_info.get('v6-fixed-ip')):  
  145.                     raise base.exceptions.CommandError(_(  
  146.                         "Only one of 'v4-fixed-ip' and 'v6-fixed-ip' may be"  
  147.                         " provided."))  
  148.                 elif nic_info.get('v4-fixed-ip'):  
  149.                     net_data['fixed_ip'] = nic_info['v4-fixed-ip']  
  150.                 elif nic_info.get('v6-fixed-ip'):  
  151.                     net_data['fixed_ip'] = nic_info['v6-fixed-ip']  
  152.                 if nic_info.get('port-id'):  
  153.                     net_data['port'] = nic_info['port-id']  
  154.                 all_net_data.append(net_data)  
  155.             body['server']['networks'] = all_net_data  
  156.   
  157.         if disk_config is not None:  
  158.             body['server']['OS-DCF:diskConfig'] = disk_config  
  159.         # 调用父类基类Manager._create()方法  
  160.         # ServerManager->BootingManagerWithFind->ManagerWithFind->Manager  
  161.         return self.<strong>_create</strong>(resource_url, body, response_key,  
  162.                             return_raw=return_raw, **kwargs)  

novaclient/base.py

[python]  view plain  copy
  1. class Manager(base.HookableMixin):  
  2.     """ 
  3.     Managers interact with a particular type of API (servers, flavors, images, 
  4.     etc.) and provide CRUD operations for them. 
  5.     """  
  6.     # 管理者与特定类型的API(servers, flavors, images,etc)进行交互,并为他们提供CRUD操作。  
  7.   
  8.     resource_class = None  
  9.     cache_lock = threading.RLock()  
  10.   
  11.     def __init__(self, api):# api 即 Client对象,从<span style="font-family: Arial, Helvetica, sans-serif;">novaclient/v2/client.py:self.servers = servers.ServerManager(self)传入</span>  
  12.         self.api = api  
  13.   
  14.     def <strong>_create</strong>(self, url, body, response_key, return_raw=False, **kwargs):  
  15.         # 运行指定类型的所有挂钩。  
  16.         self.run_hooks('modify_body_for_create', body, **kwargs)  
  17.         # self.api 即novaclient/v2/client.py:self.servers = servers.ServerManager(self) class -> Client(object)对象;  
  18.         # client: self.client HTTPClient客户端对象 novaclient/client.py->def _construct_http_client() -> class HTTPClient(object)->def post()  
  19.         # 发起post请求  
  20.         _resp, body = self.api.client.<strong>post</strong>(url, body=body)  
  21.         if return_raw:  
  22.             return body[response_key]  
  23.   
  24.         with self.completion_cache('human_id'self.resource_class, mode="a"):  
  25.             with self.completion_cache('uuid'self.resource_class, mode="a"):  
  26.                 return self.resource_class(self, body[response_key])  

novaclient/client.py/class HTTPClient(object)

[python]  view plain  copy
  1. #调用POST 发送请求  
[python]  view plain  copy
  1. def post(self, url, **kwargs):  
  2.         return self.<strong>_cs_request</strong>(url, 'POST', **kwargs)s  
  3.   
  4.   
  5. def <strong>_cs_request</strong>(self, url, method, **kwargs):  
  6.         if not self.management_url:  
  7.             self.authenticate()  
  8.         if url is None:  
  9.             # To get API version information, it is necessary to GET  
  10.             # a nova endpoint directly without "v2/<tenant-id>".  
  11.             magic_tuple = parse.urlsplit(self.management_url)  
  12.             scheme, netloc, path, query, frag = magic_tuple  
  13.             path = re.sub(r'v[1-9]/[a-z0-9]+$''', path)  
  14.             url = parse.urlunsplit((scheme, netloc, path, NoneNone))  
  15.         else:  
  16.             if self.service_catalog:  
  17.                 url = self.get_service_url(self.service_type) + url  
  18.             else:  
  19.                 # NOTE(melwitt): The service catalog is not available  
  20.                 #                when bypass_url is used.  
  21.                 url = self.management_url + url  
  22.   
  23.         # Perform the request once. If we get a 401 back then it  
  24.         # might be because the auth token expired, so try to  
  25.         # re-authenticate and try again. If it still fails, bail.  
  26.         try:  
  27.             kwargs.setdefault('headers', {})['X-Auth-Token'] = self.auth_token  
  28.             if self.projectid:  
  29.                 kwargs['headers']['X-Auth-Project-Id'] = self.projectid  
  30.   
  31.             resp, body = self.<strong>_time_request</strong>(url, method, **kwargs)  
  32.             return resp, body  
  33.         """有可能出现没有认证的情况,需要先认证再发送请求 """  
  34.         except exceptions.Unauthorized as e:  
  35.             ...  
  36.   
  37. def <strong>_time_request</strong>(self, url, method, **kwargs):  
  38.     start_time = time.time()  
  39.     resp, body = self.<strong>request</strong>(url, method, **kwargs)  
  40.     self.times.append(("%s %s" % (method, url),  
  41.                        start_time, time.time()))  
  42.     return resp, body  
  43.   
  44.   
  45. def <strong>request</strong>(self, url, method, **kwargs):  
  46.     """ 构造请求报文参数 """  
  47.     kwargs.setdefault('headers', kwargs.get('headers', {}))  
  48.     kwargs['headers']['User-Agent'] = self.USER_AGENT  
  49.     kwargs['headers']['Accept'] = 'application/json'  
  50.     if 'body' in kwargs:  
  51.         kwargs['headers']['Content-Type'] = 'application/json'  
  52.         kwargs['data'] = json.dumps(kwargs['body'])  
  53.         del kwargs['body']  
  54.     if self.timeout is not None:  
  55.         kwargs.setdefault('timeout'self.timeout)  
  56.     kwargs['verify'] = self.verify_cert  
  57.   
  58.     self.http_log_req(method, url, kwargs)  
  59.   
  60.     request_func = requests.request  
  61.     session = self._get_session(url)  
  62.     if session:  
  63.         request_func = session.request  
  64.   
  65.     """ 这里使用了第三方的 requests 库,发起post请求"""  
  66.     resp = request_func(  
  67.         method,  
  68.         url,  
  69.         **kwargs)  
  70.   
  71.     self.http_log_resp(resp)  
  72.   
  73.     if resp.text:  
  74.         # TODO(dtroyer): verify the note below in a requests context  
  75.         # NOTE(alaski): Because force_exceptions_to_status_code=True  
  76.         # httplib2 returns a connection refused event as a 400 response.  
  77.         # To determine if it is a bad request or refused connection we need  
  78.         # to check the body.  httplib2 tests check for 'Connection refused'  
  79.         # or 'actively refused' in the body, so that's what we'll do.  
  80.         """ 根据请求返回的结果决定是否抛出异常 """  
  81.         if resp.status_code == 400:  
  82.             if ('Connection refused' in resp.text or  
  83.                     'actively refused' in resp.text):  
  84.                 raise exceptions.ConnectionRefused(resp.text)  
  85.         try:  
  86.             body = json.loads(resp.text)  
  87.         except ValueError:  
  88.             body = None  
  89.     else:  
  90.         body = None  
  91.     """ 根据请求返回的结果决定是否抛出异常 """  
  92.     if resp.status_code >= 400:  
  93.         raise exceptions.from_response(resp, body, url, method)  
  94.     # 返回调用结果  
  95.     return resp, body  
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值