novaclient代码解析之----novaclient创建过程

郑宜民
2023-12-01

nova --debug list

1. 在已经配置好环境变量的终端中,调用过程,可见首先经过认证过程,在获取合法得token之后调用Nova API 中server/detail来获取各个server list.

park@park-ThinkPad-T420:~/openstack/devstack$ nova --debug list
DEBUG (session:186) REQ: curl -g -i --cacert "/opt/stack/data/CA/int-ca/ca-chain.pem" -X GET http://localhost:5000/v2.0 -H "Accept: application/json" -H "User-Agent: python-keystoneclient"
RESP BODY: {"version": {"status": "stable", "updated": "2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", "links": [{"href": "http://localhost:5000/v2.0/", "rel": "self"}, {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]}}

DEBUG (session:186) REQ: curl -g -i --cacert "/opt/stack/data/CA/int-ca/ca-chain.pem" -X GET http://xxxxx:8774/v2/e46fc3bb07da4987831fc4fe65d532ea/servers/detail -H "User-Agent: python-novaclient" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}067cafad6460a7d21af1f66c4711c78d666d6866"
RESP BODY: {"servers": []}

+----+------+--------+------------+-------------+----------+
| ID | Name | Status | Task State | Power State | Networks |
+----+------+--------+------------+-------------+----------+
+----+------+--------+------------+-------------+----------+

2. novaclient在哪里呢?他是怎么运行起来的呢?代码是最好的答案。

$ vim /usr/local/bin/nova

1 #!/usr/bin/python
  2 
  3 # -*- coding: utf-8 -*-
  4 import re
  5 import sys
  6 
  7 from novaclient.shell import main
  8 ...
见第7行,继续追踪novaclient.shell.main

$ vim /home/park/python-novaclient/novaclient/shell.py, 

813 def main():
814     try:
815         argv = [encodeutils.safe_decode(a) for a in sys.argv[1:]]
816         OpenStackComputeShell().main(argv)
816行,继续找这个main method, 还好继续在这个文件中....

516     def main(self, argv):
517         # Parse args once to find version and debug settings
518         parser = self.get_base_parser()
519         (options, args) = parser.parse_known_args(argv)
520         self.setup_debugging(options.debug)...
靠谱了,开始解析变量和参数了。没错,这里就是novaclient真正的入口。


按照上面的调用关系,我关心两个问题:

1. novaclient是如何根据环境变量和参数进行用户认证的?

2. novaclient各种具体命令是如何和nova API进行交互的呢?

第一个用户认证问题这里不想详细描述,有兴趣同学可以利用下面命令逐一测试


在一个未设置相关环境变量得终端中输入
$ nova --debug --os-username abc --os-project-name 123 --os-auth-url http://localhost:5000/v2.0 list
正常情况下应该会有如下错误,不再一一阐述。


Namespace(all_tenants=0, bypass_url='', debug=True, deleted=False, endpoint_type='publicURL', fields=None, flavor=None, func=<function do_list at 0x7fa3fd8856e0>, help=False, host=None, image=None, insecure=False, instance_name=None, ip=None, ip6=None, minimal=False, name=None, os_auth_system='', os_auth_token='', os_auth_url=u'http://<span style="font-family: Arial, Helvetica, sans-serif;">localhost</span><span style="font-family: Arial, Helvetica, sans-serif;">:5000/v2.0', os_cacert=None, os_cache=False, os_cert=None, os_compute_api_version='1.1', os_domain_id=None, os_domain_name=None, os_key=None, os_password='', os_project_domain_id=None, os_project_domain_name=None, os_project_id=None, os_project_name=u'123', os_region_name='', os_tenant_id='', os_tenant_name='', os_trust_id=None, os_user_domain_id=None, os_user_domain_name=None, os_user_id=None, os_username=u'abc', reservation_id=None, service_name='', service_type=None, sort=None, status=None, tenant=None, timeout=600, timings=False, user=None, volume_service_name='')</span>
OS Password: 
DEBUG (session:186) REQ: curl -g -i -X GET http://<span style="font-family: Arial, Helvetica, sans-serif;">localhost</span>:5000/v2.0 -H "Accept: application/json" -H "User-Agent: python-keystoneclient"
INFO (connectionpool:188) Starting new HTTP connection (1): <span style="font-family: Arial, Helvetica, sans-serif;">localhost</span>
DEBUG (connectionpool:362) "GET /v2.0 HTTP/1.1" 200 339
DEBUG (session:214) RESP: [200] content-length: 339 vary: X-Auth-Token keep-alive: timeout=5, max=100 server: Apache/2.4.7 (Ubuntu) connection: Keep-Alive date: Wed, 28 Jan 2015 08:22:41 GMT content-type: application/json 
RESP BODY: {"version": {"status": "stable", "updated": "2014-04-17T00:00:00Z", "media-types": [{"base": "application/json", "type": "application/vnd.openstack.identity-v2.0+json"}], "id": "v2.0", "links": [{"href": "http://localhost:5000/v2.0/", "rel": "self"}, {"href": "http://docs.openstack.org/", "type": "text/html", "rel": "describedby"}]}}

DEBUG (v2:76) Making authentication request to http://localhost:5000/v2.0/tokens
DEBUG (connectionpool:362) "POST /v2.0/tokens HTTP/1.1" 401 136
DEBUG (session:377) Request returned failure status: 401
DEBUG (shell:910) Authentication failure: Could not find user: abc (Disable debug mode to suppress these details.) (HTTP 401)
Traceback (most recent call last):
  File "/opt/stack/python-novaclient/novaclient/shell.py", line 907, in main
    OpenStackComputeShell().main(argv)
  File "/opt/stack/python-novaclient/novaclient/shell.py", line 834, in main
    args.func(self.cs, args)
  File "/opt/stack/python-novaclient/novaclient/v1_1/shell.py", line 1377, in do_list
    sort_dirs=sort_dirs)
  File "/opt/stack/python-novaclient/novaclient/v1_1/servers.py", line 620, in list
    return self._list("/servers%s%s" % (detail, query_string), "servers")
  File "/opt/stack/python-novaclient/novaclient/base.py", line 66, in _list
    _resp, body = self.api.client.get(url)
  File "/opt/stack/python-keystoneclient/keystoneclient/adapter.py", line 130, in get
    return self.request(url, 'GET', **kwargs)
  File "/opt/stack/python-novaclient/novaclient/client.py", line 152, in request
    **kwargs)
  File "/opt/stack/python-keystoneclient/keystoneclient/adapter.py", line 166, in request
    resp = super(LegacyJsonAdapter, self).request(*args, **kwargs)
  File "/opt/stack/python-keystoneclient/keystoneclient/adapter.py", line 89, in request
    return self.session.request(url, method, **kwargs)
  File "/opt/stack/python-keystoneclient/keystoneclient/utils.py", line 318, in inner
    return func(*args, **kwargs)
  File "/opt/stack/python-keystoneclient/keystoneclient/session.py", line 298, in request
    token = self.get_token(auth)
  File "/opt/stack/python-keystoneclient/keystoneclient/session.py", line 577, in get_token
    _("Authentication failure: %s") % exc)
AuthorizationFailure: Authentication failure: Could not find user: abc (Disable debug mode to suppress these details.) (HTTP 401)
ERROR (AuthorizationFailure): Authentication failure: Could not find user: abc (Disable debug mode to suppress these details.) (HTTP 401)

继续看main函数,

756         self.cs = client.Client(
757             options.os_compute_api_version,
758             os_username, os_password, os_tenant_name,
759             tenant_id=os_tenant_id, user_id=os_user_id,
760             auth_url=os_auth_url, insecure=insecure,
761             region_name=os_region_name, endpoint_type=endpoint_type,
762             extensions=self.extensions, service_type=service_type,
763             service_name=service_name, auth_system=os_auth_system,
764             auth_plugin=auth_plugin, auth_token=auth_token,
765             volume_service_name=volume_service_name,
766             timings=args.timings, bypass_url=bypass_url,
767             os_cache=os_cache, http_log_debug=options.debug,
768             cacert=cacert, timeout=timeout,
769             session=keystone_session, auth=keystone_auth)
770 

一直到这里之前,novaclient都在纠缠一些变量的获取,这里是生成了一个Client的对象,这个对象是干嘛的呢?

$ vim /home/park/python-novaclient/novaclient/client.py, 

def get_client_class(version):
    version_map = {
        '1.1': 'novaclient.v2.client.Client',
        '2': 'novaclient.v2.client.Client',
        '3': 'novaclient.v2.client.Client',
    }
    try:
        client_path = version_map[str(version)]
    except (KeyError, ValueError):
        msg = _("Invalid client version '%(version)s'. must be one of: "
                "%(keys)s") % {'version': version,
                               'keys': ', '.join(version_map.keys())}
        raise exceptions.UnsupportedVersion(msg)

    return importutils.import_class(client_path)


def Client(version, *args, **kwargs):
    client_class = get_client_class(version)
    return client_class(*args, **kwargs)
原来是为了找到和version相匹配的Client,所有的都指向了V2,我们来看看这个v2.client都干了什么。

$ vim /home/park/python-novaclient/novaclient/V2/client.py, 

    def __init__(self, username=None, api_key=None, project_id=None,
                 auth_url=None, insecure=False, timeout=None,
                 proxy_tenant_id=None, proxy_token=None, region_name=None,
                 endpoint_type='publicURL', extensions=None,
                 service_type='compute', service_name=None,
                 volume_service_name=None, timings=False, bypass_url=None,
                 os_cache=False, no_cache=True, http_log_debug=False,
                 auth_system='keystone', auth_plugin=None, auth_token=None,
                 cacert=None, tenant_id=None, user_id=None,
                 connection_pool=False, session=None, auth=None,
                 **kwargs):
        """
        :param str username: Username
        :param str api_key: API Key
        :param str project_id: Project ID
        :param str auth_url: Auth URL
        :param bool insecure: Allow insecure
        :param float timeout: API timeout, None or 0 disables
        :param str proxy_tenant_id: Tenant ID
        :param str proxy_token: Proxy Token
        :param str region_name: Region Name
        :param str endpoint_type: Endpoint Type
        :param str extensions: Exensions
        :param str service_type: Service Type
        :param str service_name: Service Name
        :param str volume_service_name: Volume Service Name
        :param bool timings: Timings
        :param str bypass_url: Bypass URL
        :param bool os_cache: OS cache
        :param bool no_cache: No cache
        :param bool http_log_debug: Enable debugging for HTTP connections
        :param str auth_system: Auth system
        :param str auth_plugin: Auth plugin
        :param str auth_token: Auth token
        :param str cacert: cacert
        :param str tenant_id: Tenant ID
        :param str user_id: User ID
        :param bool connection_pool: Use a connection pool
        :param str session: Session
        :param str auth: Auth
        """
        # FIXME(comstud): Rename the api_key argument above when we
        # know it's not being used as keyword argument

        # NOTE(cyeoh): In the novaclient context (unlike Nova) the
        # project_id is not the same as the tenant_id. Here project_id
        # is a name (what the Nova API often refers to as a project or
        # tenant name) and tenant_id is a UUID (what the Nova API
        # often refers to as a project_id or tenant_id).

        password = api_key
        self.projectid = project_id
        self.tenant_id = tenant_id
        self.user_id = user_id
        self.flavors = flavors.FlavorManager(self)
        self.flavor_access = flavor_access.FlavorAccessManager(self)
        self.images = images.ImageManager(self)
        self.limits = limits.LimitsManager(self)
        self.servers = servers.ServerManager(self)
        self.versions = versions.VersionManager(self)

        # extensions
        self.agents = agents.AgentsManager(self)
        self.dns_domains = floating_ip_dns.FloatingIPDNSDomainManager(self)
        self.dns_entries = floating_ip_dns.FloatingIPDNSEntryManager(self)
        self.cloudpipe = cloudpipe.CloudpipeManager(self)
        self.certs = certs.CertificateManager(self)
        self.floating_ips = floating_ips.FloatingIPManager(self)
        self.floating_ip_pools = floating_ip_pools.FloatingIPPoolManager(self)
        self.fping = fping.FpingManager(self)
        self.volumes = volumes.VolumeManager(self)
        self.volume_snapshots = volume_snapshots.SnapshotManager(self)
        self.volume_types = volume_types.VolumeTypeManager(self)
        self.keypairs = keypairs.KeypairManager(self)
        self.networks = networks.NetworkManager(self)
        self.quota_classes = quota_classes.QuotaClassSetManager(self)
        self.quotas = quotas.QuotaSetManager(self)
        self.security_groups = security_groups.SecurityGroupManager(self)
        self.security_group_rules = \
            security_group_rules.SecurityGroupRuleManager(self)
        self.security_group_default_rules = \
            security_group_default_rules.SecurityGroupDefaultRuleManager(self)
        self.usage = usage.UsageManager(self)
        self.virtual_interfaces = \
            virtual_interfaces.VirtualInterfaceManager(self)
        self.aggregates = aggregates.AggregateManager(self)
        self.hosts = hosts.HostManager(self)
        self.hypervisors = hypervisors.HypervisorManager(self)
        self.hypervisor_stats = hypervisors.HypervisorStatsManager(self)
        self.services = services.ServiceManager(self)
        self.fixed_ips = fixed_ips.FixedIPsManager(self)
        self.floating_ips_bulk = floating_ips_bulk.FloatingIPBulkManager(self)
        self.os_cache = os_cache or not no_cache
        self.availability_zones = \
            availability_zones.AvailabilityZoneManager(self)
        self.server_groups = server_groups.ServerGroupsManager(self)

        # Add in any extensions...
        if extensions:
            for extension in extensions:
                if extension.manager_class:
                    setattr(self, extension.name,
                            extension.manager_class(self))

        self.client = client._construct_http_client(
            username=username,
            password=password,
            user_id=user_id,
            project_id=project_id,
            tenant_id=tenant_id,
            auth_url=auth_url,
            auth_token=auth_token,
            insecure=insecure,
            timeout=timeout,
            auth_system=auth_system,
            auth_plugin=auth_plugin,
            proxy_token=proxy_token,
            proxy_tenant_id=proxy_tenant_id,
            region_name=region_name,
            endpoint_type=endpoint_type,
            service_type=service_type,
            service_name=service_name,
            volume_service_name=volume_service_name,
            timings=timings,
            bypass_url=bypass_url,
            os_cache=self.os_cache,
            http_log_debug=http_log_debug,
            cacert=cacert,
            connection_pool=connection_pool,
            session=session,
            auth=auth,
            **kwargs)
构建基础信息,extension信息等,extension信息指向了一些manager,这里以后再阐述。

最后,通过self.client = client._construct_http_client创建了一个基于http的client,到这里client已经具备了http和api通信的能力了。

但是仍然没有解决上面提到的第二个问题,具体命令是如何执行的呢?回到novaclient/shell.py中:

在创建Client之后会找到下面一小行代码

args.func(self.cs, args)
这里才是真正执行具体命令的接口。

至此,一个完整的Client算是构建完成。




 类似资料: