OpenstackSDK 源码详解

  • 前段时间一直在做openstacksdk的二次开发工作,对openstacksdk源码了解得比较深入,现趁着国庆假期,好好总结一下。毕竟记忆不好,过段时间可能就忘得差不多了,哈哈。
  • openstacksdk是基于当前最新版openstacksdk-0.17.2版本,可从 GitHub:OpenstackSDK 获取到最新的源码。openstacksdk的官方使用手册为:SDK 文档
  • 实验使用的openstack是Queen版本。

SDK 的历史背景

据官方文档介绍,openstacksdk项目是由shadeos-client-configpython-openstacksdk三个子项目合并而成的。由于我对这三个项目不太熟悉,所以也没什么好说的,感兴趣的读者可以自行查看每个项目的主要用途。为什么合并三个项目,我理解的原因有几个吧:

  • 方便开发者使用,虽然openstack的子项目都会有restful api的调用接口,但对于开发者来说,调用sdk比使用rest方便,也不需要处理形形色色的各种突发情况;
  • 方便维护,访问openstack服务的client端底层都需要配置session(即config配置)以及处理python-request,而这三个项目提供的服务接口都有着几乎相同的底层处理,部分服务接口也雷同,维护起来麻烦;
  • 项目功能重复,比如shade和python-openstacksdk虽然实现方式有所不同,但是提供的功能类似,没必要维护多个相同的服务组件。

不管如何,上面说的都不是重点。由于openstacksdk融合了三个子项目,为了向下兼容,也导致了openstacksdk项目的复杂度。第一次看源码的人可能会很纳闷,明明是有个具体的框架,但有些模块偏偏却脱离在框架之外,让人摸不着头脑。说白了就是代码的组织架构不够清晰,但也没办法,毕竟sdk还在开发过程中,后续应该会变得更好吧。

SDK 的使用

SDK使用不是这篇文章的重点,因此只会简单的描述一下。具体的可参考官文的 User Guides

总的来说,使用sdk很简单,其支持三种形式的配置方式,分别是配置文件函数传参系统环境变量三种。这三种可以混合使用,如果有重复配置,则优先顺序应该是:函数传参 > 系统环境变量 > 配置文件。同时,使用sdk不仅支持user/password方式,也支持传入token形式使用,但是两种认证方式的配置有些许不同,token和password有某些关于auth的配置是冲突的。

  1. 配置文件方式
    sdk会默认寻找名称为 clouds.yaml 的配置文件,其搜寻顺序为:当前目录~/.config/openstack/etc/openstack。只要找到符合的配置文件,就会读取并解析内容。
    clouds.yaml的配置文件写法为:

    clouds:
      mordred:
        region_name: RegionOne
        auth_type: password
        auth:
          username: 'mordred'
          password: XXXXXXX
          project_name: 'shade'
          auth_url: 'https://identity.example.com'
    
    import openstack
    
    # Initialize cloud
    conn = openstack.connect(cloud='mordred')
    
    for server in conn.compute.servers():
        print(server.to_dict())
    
  2. 函数传参方式

    import openstack
    
    # Initialize cloud
    auth_type = "token"
    auth = {
          
    	"project_id": "xxxxx",
        "user_domain_id": "default",
        "token": "xxxxxx"
        "auth_url": "https://identity.example.com"
    }
    conn = openstack.connect(auth=auth, auth_type=auth_type)
    
    for server in conn.compute.servers():
        print(server.to_dict())
    
  3. 系统环境变量方式

    admin-openrc.sh文件设置如下:

    export OS_PROJECT_DOMAIN_ID=default
    export OS_USER_DOMAIN_ID=default
    export OS_PROJECT_NAME=admin
    export OS_TENANT_NAME=admin
    export OS_USERNAME=admin
    export OS_PASSWORD=ADMIN_PASS
    export OS_AUTH_URL=http://controller:35357/v3
    export OS_IDENTITY_API_VERSION=3
    

    在shell中执行 source admin-openrc.sh,然后执行如下代码即可:

    import openstack
    
    # Initialize cloud
    conn = openstack.connect()
    
    for server in conn.compute.servers():
        print(server.to_dict())
    

SDK 的源码解析

从上面的使用例子我们可以看到,openstacksdk的入口函数是openstack.connect(*args, **kwargs)。这个函数基于传入的参数,生成了连接openstack服务的实例,然后我们就可以使用该实例访问openstack各组件服务了,具体的调用方式就是:conn.{service}.{operator}。其中service就是openstack的组件,比如常用的compute(nova)、image(glance)、identity(keystone) 和 block_storage(cinder)等等了。

循例的,要了解SDK的源码实现,首先还是先介绍下源码目录结构吧。

源码目录结构

先不多说,呈上源码目录结构的缩减版:

openstack/
|--_meta/            # 存放框架中的metaclass类的实现
|--config/           # os-client-config项目代码主要存放在此
|--|--cloud/         # shade项目代码主要存放在此
|--|--{services}/    # 各个服务组件的代码,应该是python-openstacksdk项目代码,以identity为例
|--|--identity/
|--|--|--v2/
|--|--|--|--_proxy.py             # v2版本的proxy接口
|--|--|--|--{resources}           # v2版本resource类,比如role资源
|--|--|--v3/
|--|--|--|--_proxy.py             # v3版本的proxy接口
|--|--|--|--{resources}           # v3版本的role资源类
|--|--|--identity_service.py      # identity的服务入口
|--resource.py                    # 定义资源类的基类
|--proxy.py                       # 定义proxy接口的基类
|--connection.py                  # 管理各openstack服务的类,暴露给用户使用
|--service_description.py         # 服务的描述类,conn通过该类找到对应的组件服务接口
|--service_filter.py              # 划分并路由到组件服务的不同版本的类
|--task_manager.py                # 管理组件服务的rest请求处理

前面说了,openstacksdk是由三个子项目合并而来的。由目录结构就可以看出,三个项目的代码都扁平地放在openstack目录下。而其余的*.py文件就是负责整合这三个项目的框架代码。其中,框架中比较重要的类有三个,其分别为:

  • Connection 类是负责建立用户层到服务层的服务连接,即如何组织这三个子项目,将其功能提供给上层用户调用,就是Connection类要做的事情。
  • Proxy 类是代理各组件的服务,为用户提供该组件服务的可使用接口的类,其子类由各个组件进行定义。Proxy类继承了keystoneauth.adapter,其是openstack用于认证和访问组件rest服务的通用库。同样以identity为例,其identity/v2/_proxy.py里定义了所有keystone v2版本的可使用服务接口。
  • Resource 类代表的是组件的远程资源,比如identity有role、project、user等资源类型,其定义在identity/v2/{name}.py中。一般地,用户调用proxy接口的方法,proxy调用resource构建 rest 请求中需要的所有东西,如header、url、data等等,然后resource调用proxy中的keystoneauth里的request方法与远程服务组件进行交互,然后resource处理其返回response,获取与该资源类相关的属性值,然后proxy返回特定resource类的实例给用户。

源码的架构组织

通过目录结构的介绍,我们对sdk源码已经有了大致的认识了。下面通过架构图,能够更好地了解其框架组织结构:

在这里插入图片描述

其中,CloudRegion为Connection相关的配置(由原os-client-config模块进行适配),用于处理使用openstacksdk的各种配置;OpenstackCloud为一种形式的接口调用(原shade项目模块),其提供所有的服务接口都放在一个类里实现了,即实例化该类,就可以调用各组件的服务处理接口了;各种Proxy我猜是原python-openstacksdk的接口,它是上面所说的以组件区分服务的一种组织形式,是区别OpenstackCloud的另一种服务使用方式。但是,不管是OpenStackCloud方式,还是Proxy的调用方式,最终都是通过keystoneauth库去调用openstack组件服务的Restful API接口进行处理的。

源码的详细解析

在了解SDK项目的组织架构后,下面我们详细地对SDK源码进行解读分析。

首先,OpenstackCloud定义在 openstack/cloud目录下,其就是一个类里面定义了一堆关于network、images、compute等的函数方法,就是在一个大类里填充了所有关于openstack操作的函数,非常暴力简单,没什么好说的,直接用就好,本文章也不会对其进行过多的解析,有兴趣的自己看看代码吧。
CloudRegion类定义并解析了openstack的客户端配置,比如前面所说的用户密码,连接方式,服务组件的接口的API版本等等。虽然很复杂,但是也没什么好讲的。其实是太复杂了,不好把握,哈哈哈。无论如何,这里配置其实只是和keystoneauth库session需要用到的配置相关,和框架服务倒没什么关系。
个人觉得sdk的精华部分大概也就剩Connection–>Proxy–>Resource这条线了哈。 Connection到Proxy这条路径经过了ServiceDescription这个类,而不是直接Connection里初始化Proxy的原因,是为了在使用时才初始化对应组件的proxy实例。大家都知道,openstack组件有很多,如果client只是使用了image服务,但是却要实例化上百个proxy,其性能是很低下的,也没有必要。而ServiceDescription就是用来解决这种情况的,其就是相当于Connection只是维护了所有服务组件的指针数组,在真正使用某个服务组件的接口时,才实例化该proxy服务,并将指针数组中的特定一个指针指向该实例。那么,后续又用到该服务时就不用重新初始化一遍了,直接返回该实例引用就行了。

Connection类

Connection 是用户使用时接触到的最上层的类了。正如上面用户使用的例子所介绍,一般会使用openstack.connnect()方法生成Conenction实例。connect()方法做了两件事情,一是解析配置,并生成一个CloudRegion的实例,一是生成Connection实例。代码如下所示:

def connect(*agrs, **kwargs):
    cloud_region = openstack.config.get_cloud_region(
        cloud=cloud,
        app_name=app_name, app_version=app_version,
        load_yaml_config=load_yaml_config,
        load_envvars=load_envvars,
        options=options, **kwargs)
    return openstack.connection.Connection(config=cloud_region)

下面,我们看看Connection类内部都做了什么事情。首先,看看它类实现的代码,这里做了些调整,为了减少代码篇幅,把profile及一些不太重要的内容给去掉了,profile后续应该会deprecate了:

from openstack._meta import connection as _meta
from openstack import cloud as _cloud

# 类定义其实相当于是这样的,即继承了OpenstackCloud类,拥有OpentackCloud的所有方法:
# class Connection(_cloud.OpenStackCloud):
#     __metaclass__ = _meta.ConnectionMeta
class Connection(six.with_metaclass(_meta.ConnectionMeta,
                                    _cloud.OpenStackCloud)):

    def __init__(self, cloud=None, config=None, session=None,
                 app_name=None, app_version=None,
                 extra_services=None,
                 strict=False,
                 use_direct_get=False,
                 task_manager=None,
                 **kwargs):
        """Create a connection to a cloud."""
        # config即上面我们传进来的CloudRegion实例,负责管理连接配置的内容的
        # 如果没有,后续会创建,但是默认是不读取配置文件和系统环境变量
        self.config = config
        self._extra_services = {
    }

        # 即允许注册额外的services
        if extra_services:
            
  • 4
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值