一、简介
openstack的各个模块中,都有相应的客户端模块实现,其作用是为用户访问具体模块提供了接口,并且也作为模块之间相互访问的途径。Cinder也一样,有着自己的cinder-client。
二、argparse简单介绍
argparse是python用于解析命令行参数和选项的标准模块,作为optparse的一个替代被添加到Python2.7。Cinder-client主要就是调用了argparse这个工具包。
使用步骤:
import argparse
parser = argparse.ArgumentParser()
parser.add_argument()
parser.parse_args()
首先导入该模块;然后创建一个解析对象;然后向该对象中添加你要关注的命令行参数和选项,每一个add_argument方法对应一个你要关注的参数或选项;最后调用parse_args()方法进行解析;解析成功之后即可使用。
方法 ArgumentParser(prog=None, usage=None,description=None, epilog=None, parents=[],formatter_class=argparse.HelpFormatter, prefix_chars="-",fromfile_prefix_chars=None, argument_default=None,conflict_handler="error", add_help=True)
这些参数都有默认值,当调用 parser.print_help()或者运行程序时,由于参数不正确(此时python解释器其实也是调用了pring_help()方法)时,会打印这些描述信息,一般只需要传递description参数
方法add_argument(name or flags...[, action][, nargs][, const][, default][, type][, choices][, required][, help][, metavar][, dest])
其中:
name or flags:命令行参数名或者选项,如上面的address或者-p,--port.其中命令行参数如果没给定,且没有设置defualt,则出错。但是如果是选项的话,则设置为None。,parse_args()运行时,会用"-"来认证可选参数,剩下的即为位置参数
nargs:命令行参数的个数,一般使用通配符表示,其中,"?"表示只用一个,"*"表示0到多个," "表示至少一个。nargs="*" 表示参数可设置零个或多个;nargs=" " " 表示参数可设置一个或多个;nargs="?"表示参数可设置零个或一个
default:默认值。
type:参数的类型,默认是字符串string类型,还有float、int等类型。
help:和ArgumentParser方法中的参数作用相似,出现的场合也一致。
dest:如果提供dest,例如dest="a",那么可以通过args.a访问该参数
action:参数出发的动作
store:保存参数,默认
store_const:保存一个被定义为参数规格一部分的值(常量),而不是一个来自参数解析而来的值。
store_ture/store_false:保存相应的布尔值
append:将值保存在一个列表中。
append_const:将一个定义在参数规格中的值(常量)保存在一个列表中。
count:参数出现的次数
parser.add_argument("-v", "--verbosity", action="count", default=0, help="increase output verbosity")
version:打印程序版本信息
choice:允许的参数值
三、cinderclient代码入口查找
第一种方式:D:\官网代码\python-cinderclient-stable-pike\setup.cfg[entry_points]console_scripts = cinder = cinderclient.shell:main第二种方式:[root@test bin]# pwd/usr/bin[root@test bin]# ls |grep cindercindercinder-allcinder-apicinder-backupcinder-managecinder-rootwrapcinder-rtstoolcinder-schedulercinder-volumecinder-volume-usage-audit[root@test bin]# cat cinder#!/usr/bin/python# PBR Generated from u"console_scripts"import sysfrom cinderclient.shell import mainif __name__ == "__main__": sys.exit(main())[root@test bin]#
四、cinderclient代码分析
D:\官网代码\python-cinderclient-stable-pike\cinderclient\shell.pydef main(): try: if sys.version_info >= (3, 0):-----sys.version获取python的版本,默认情况下, 使用系统自带的python版本,python2.6或者python 2.7 """ >>> print sys.version_info (2, 6, 6, "final", 0) >>> """ OpenStackCinderShell().main(sys.argv[1:])---sys.argv[1:],输入的cinder命令行,sys.argv[0]表示程序本身,sys.argv[1:]表示 输入的参数 else: OpenStackCinderShell().main([encodeutils.safe_decode(item)----走如下分支,步骤一 for item in sys.argv[1:]]) except KeyboardInterrupt: print("... terminating cinder client", file=sys.stderr) sys.exit(130) except Exception as e: logger.debug(e, exc_info=1) print("ERROR: %s" % six.text_type(e), file=sys.stderr) sys.exit(1)
对步骤一进行详解
from cinderclient import api_versionsfrom cinderclient import clientD:\官网代码\python-cinderclient-stable-pike\cinderclient\shell.pyclass OpenStackCinderShell(object): def __init__(self): self.ks_logger = None self.client_logger = None def main(self, argv): # Parse args once to find version and debug settings 解析args参数一次,查找version和debug设置信息 parser = self.get_base_parser() """ get_base_parser:获取基本的命令行解析器;调用add_argument方法实现添加具体命令行参数; 构造参数解析类ArgumentParser的实例parser,然后通过实例调用方法parser.add_argument增加一些固有的参数,比如:--debug,--help, --os_auth_type等参数 """ (options, args) = parser.parse_known_args(argv) """ parse_known_args()方法的作用就是当仅获取到基本设置时,如果运行命令中传入了之后才会获取到的其他配置,不会报错; 而是将多出来的部分保存起来,留到后面使用,解析的参数按属性的方式存储到Namespace对象; options的值为命名空间namespace的对象 """ self.setup_debugging(options.debug)----打开debug信息 api_version_input = True self.options = options do_help = ("help" in argv) or (-----查看是不是需要对命令行进行help查询 "--help" in argv) or ("-h" in argv) or not argv #确定使用API的版本,默认情况下,是版本3 if not options.os_volume_api_version: api_version = api_versions.get_api_version( DEFAULT_MAJOR_OS_VOLUME_API_VERSION) else: api_version = api_versions.get_api_version( options.os_volume_api_version) # build available subcommands based on version #根据api版本号,去查找其对应的版本的扩展版本,其实本质上就是获取 D:\官网代码\python-cinderclient-stable-pike\cinderclient\v2\contrib\list_extensions.py模块中的类 major_version_string = "%s" % api_version.ver_major self.extensions = client.discover_extensions(major_version_string) self._run_extension_hooks("__pre_parse_args__") #基于版本api版本,创建对应的子命令解释器,同时根据对应的api_version版本,加载 D:\官网代码\python-cinderclient-stable-pike\cinderclient不同版本的shell.py文件 D:\官网代码\python-cinderclient-stable-pike\cinderclient\v2\shell.py模块 subcommand_parser = self.get_subcommand_parser(api_version, do_help, args) self.parser = subcommand_parser if options.help or not argv:---如果命令行后面跟的是help命令,那么就打印该命令的help信息,直接返回 subcommand_parser.print_help() return 0 argv = self._delimit_metadata_args(argv) # 命令行参数的解析; args = subcommand_parser.parse_args(argv) self._run_extension_hooks("__post_parse_args__", args) # Short-circuit and deal with help right away. if args.func == self.do_help: self.do_help(args) return 0 elif args.func == self.do_bash_completion: self.do_bash_completion(args) return 0 #提取命令行参数中的基本的租户等信息存放到一个元祖里,为后面方法的调用做具体参数的准备 (os_username, os_password, os_tenant_name, os_auth_url, os_region_name, os_tenant_id, endpoint_type, service_type, service_name, volume_service_name, os_endpoint, cacert, os_auth_type) = ( args.os_username, args.os_password, args.os_tenant_name, args.os_auth_url, args.os_region_name, args.os_tenant_id, args.os_endpoint_type, args.service_type, args.service_name, args.volume_service_name, args.os_endpoint, args.os_cacert, args.os_auth_type) auth_session = None #对参数的认证权限的一些处理,比如是否提供租户、是否提供密码等 if os_auth_type and os_auth_type != "keystone": auth_plugin = loading.load_auth_from_argparse_arguments( self.options) auth_session = loading.load_session_from_argparse_arguments( self.options, auth=auth_plugin) else: auth_plugin = None if not service_type: service_type = client.SERVICE_TYPES[major_version_string] # FIXME(usrleon): Here should be restrict for project id same as # for os_username or os_password but for compatibility it is not. # V3 stuff project_info_provided = ((self.options.os_tenant_name or self.options.os_tenant_id) or (self.options.os_project_name and (self.options.os_project_domain_name or self.options.os_project_domain_id)) or self.options.os_project_id) # NOTE(e0ne): if auth_session exists it means auth plugin created # session and we don"t need to check for password and other # authentification-related things. if not utils.isunauthenticated(args.func) and not auth_session: if not os_password: # No password, If we"ve got a tty, try prompting for it if hasattr(sys.stdin, "isatty") and sys.stdin.isatty(): # Check for Ctl-D try: os_password = getpass.getpass("OS Password: ") # Initialize options.os_password with password # input from tty. It is used in _get_keystone_session. options.os_password = os_password except EOFError: pass # No password because we didn"t have a tty or the # user Ctl-D when prompted. if not os_password: raise exc.CommandError("You must provide a password " "through --os-password, " "env[OS_PASSWORD] " "or, prompted response.") if not project_info_provided: raise exc.CommandError(_( "You must provide a tenant_name, tenant_id, " "project_id or project_name (with " "project_domain_name or project_domain_id) via " " --os-tenant-name (env[OS_TENANT_NAME])," " --os-tenant-id (env[OS_TENANT_ID])," " --os-project-id (env[OS_PROJECT_ID])" " --os-project-name (env[OS_PROJECT_NAME])," " --os-project-domain-id " "(env[OS_PROJECT_DOMAIN_ID])" " --os-project-domain-name " "(env[OS_PROJECT_DOMAIN_NAME])" )) if not os_auth_url: raise exc.CommandError( "You must provide an authentication URL " "through --os-auth-url or env[OS_AUTH_URL].") if not project_info_provided: raise exc.CommandError(_( "You must provide a tenant_name, tenant_id, " "project_id or project_name (with " "project_domain_name or project_domain_id) via " " --os-tenant-name (env[OS_TENANT_NAME])," " --os-tenant-id (env[OS_TENANT_ID])," " --os-project-id (env[OS_PROJECT_ID])" " --os-project-name (env[OS_PROJECT_NAME])," " --os-project-domain-id " "(env[OS_PROJECT_DOMAIN_ID])" " --os-project-domain-name " "(env[OS_PROJECT_DOMAIN_NAME])" )) if not os_auth_url and not auth_plugin: raise exc.CommandError( "You must provide an authentication URL " "through --os-auth-url or env[OS_AUTH_URL].") #没有提供认证会话的,那么与keystone建立认证会话 if not auth_session: auth_session = self._get_keystone_session() insecure = self.options.insecure self.cs = client.Client(---------------步骤二,本质上是一个http请求 api_version, os_username, os_password, os_tenant_name, os_auth_url, region_name=os_region_name, tenant_id=os_tenant_id, endpoint_type=endpoint_type, extensions=self.extensions, service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, bypass_url=os_endpoint, retries=options.retries, http_log_debug=args.debug, insecure=insecure, cacert=cacert, auth_system=os_auth_type, auth_plugin=auth_plugin, session=auth_session, logger=self.ks_logger if auth_session else self.client_logger) try: # 如果所要调用的方法没有标志为unauthenticated,则需要进行身份验证操作; if not utils.isunauthenticated(args.func): self.cs.authenticate() except exc.Unauthorized: raise exc.CommandError("OpenStack credentials are not valid.") except exc.AuthorizationFailure: raise exc.CommandError("Unable to authorize user.") endpoint_api_version = None # Try to get the API version from the endpoint URL. If that fails fall # back to trying to use what the user specified via # --os-volume-api-version or with the OS_VOLUME_API_VERSION environment # variable. Fail safe is to use the default API setting. try: endpoint_api_version = self.cs.get_volume_api_version_from_endpoint() except exc.UnsupportedVersion: endpoint_api_version = options.os_volume_api_version if api_version_input: logger.warning("Cannot determine the API version from " "the endpoint URL. Falling back to the " "user-specified version: %s", endpoint_api_version) else: logger.warning("Cannot determine the API version from the " "endpoint URL or user input. Falling back " "to the default API version: %s", endpoint_api_version) profile = osprofiler_profiler and options.profile if profile: osprofiler_profiler.init(options.profile) try: args.func(self.cs, args)----实现根据解析的命令行参数调用具体的方法,假如使用的命令行为cinder list,该处args.func = do_list,
说明这里调用的具体方法是do_list; finally: if profile: trace_id = osprofiler_profiler.get().get_base_id() print("Trace ID: %s" % trace_id) print("To display trace use next command:\n" "osprofiler trace show --html %s " % trace_id)
对步骤二详解
D:\官网代码\python-cinderclient-stable-pike\cinderclient\client.pydef Client(version, *args, **kwargs): """Initialize client object based on given version. api_version, client_class = _get_client_class_and_version(version)----对步骤2.1 详解根据api版本version版本号,获取对应目录下的Client函数 return client_class(api_version=api_version,*args, **kwargs)-------对步骤2.2的详解对步骤2.1详解D:\官网代码\python-cinderclient-stable-pike\cinderclient\client.pydef _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( "cinderclient.v%s.client.Client" % version.ver_major) 对步骤2.2 的详解---假如使用的版本为v2版本D:\官网代码\python-cinderclient-stable-pike\cinderclient\v2\client.pyfrom cinderclient import clientfrom cinderclient import api_versionsfrom cinderclient.v2 import availability_zonesfrom cinderclient.v2 import cgsnapshotsfrom cinderclient.v2 import consistencygroupsfrom cinderclient.v2 import capabilitiesfrom cinderclient.v2 import limitsfrom cinderclient.v2 import poolsfrom cinderclient.v2 import qos_specsfrom cinderclient.v2 import quota_classesfrom cinderclient.v2 import quotasfrom cinderclient.v2 import servicesfrom cinderclient.v2 import volumesfrom cinderclient.v2 import volume_snapshotsfrom cinderclient.v2 import volume_typesfrom cinderclient.v2 import volume_type_accessfrom cinderclient.v2 import volume_encryption_typesfrom cinderclient.v2 import volume_backupsfrom cinderclient.v2 import volume_backups_restorefrom cinderclient.v2 import volume_transfersclass Client(object): def __init__(self, username=None, api_key=None, project_id=None, auth_url="", insecure=False, timeout=None, tenant_id=None, proxy_tenant_id=None, proxy_token=None, region_name=None, endpoint_type="publicURL", extensions=None, service_type="volumev2", service_name=None, volume_service_name=None, bypass_url=None, retries=0, http_log_debug=False, cacert=None, auth_system="keystone", auth_plugin=None, session=None, api_version=None, logger=None, **kwargs): # FIXME(comstud): Rename the api_key argument above when we # know it"s not being used as keyword argument password = api_key self.version = "2.0" self.limits = limits.LimitsManager(self) # extensions------引入统一目录下,不同资源的管理类 self.volumes = volumes.VolumeManager(self) self.volume_snapshots = volume_snapshots.SnapshotManager(self) self.volume_types = volume_types.VolumeTypeManager(self) self.volume_type_access = volume_type_access.VolumeTypeAccessManager(self) self.volume_encryption_types = volume_encryption_types.VolumeEncryptionTypeManager(self) self.qos_specs = qos_specs.QoSSpecsManager(self) self.quota_classes = quota_classes.QuotaClassSetManager(self) self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) self.services = services.ServiceManager(self) self.consistencygroups = consistencygroups. ConsistencygroupManager(self) self.cgsnapshots = cgsnapshots.CgsnapshotManager(self) self.availability_zones = availability_zones.AvailabilityZoneManager(self) self.pools = pools.PoolManager(self) self.capabilities = capabilities.CapabilitiesManager(self) self.api_version = api_version or api_versions.APIVersion(self.version) # Add in any extensions... if extensions: for extension in extensions: if extension.manager_class: setattr(self, extension.name, extension.manager_class(self)) if not logger: logger = logging.getLogger(__name__) self.client = client._construct_http_client(----本质上就是调用http模块的,建立session连接,同时拼接url的请求头,请求体 username=username, password=password, project_id=project_id, auth_url=auth_url, insecure=insecure, timeout=timeout, tenant_id=tenant_id, proxy_tenant_id=tenant_id, proxy_token=proxy_token, region_name=region_name, endpoint_type=endpoint_type, service_type=service_type, service_name=service_name, volume_service_name=volume_service_name, bypass_url=bypass_url, retries=retries, http_log_debug=http_log_debug, cacert=cacert, auth_system=auth_system, auth_plugin=auth_plugin, session=session, api_version=self.api_version, logger=logger, **kwargs) def authenticate(self): """Authenticate against the server. Normally this is called automatically when you first access the API, but you can call this method to force authentication right now. Returns on success; raises :exc:`exceptions.Unauthorized` if the credentials are wrong. """ self.client.authenticate() def get_volume_api_version_from_endpoint(self): return self.client.get_volume_api_version_from_endpoint()
五、添加一个新的命令行,这个命令行的功能为获取卷的连接信息,
1)命令行设计的样式如下所示:
cinder create-target volume_id hostname ip initiatorusage: cinder create [--platform
2)代码实现,在v2版本的shell.py文件中新增一个do_create_target函数
D:\官网代码\python-cinderclient-stable-ocata\cinderclient\v2\[email protected]("volume", metavar="
输出内容如下:
[root@test ~]# cinder help create-targetusage: cinder create-target [--platform
3)验证命令行
[root@test ~]# cinder --debug create-target a0ac29de-3a16-4b07-9aac-de63ccdf8fda 10.27.244.149 my03n010027244149.sncloud.com \
iqn.1994-05.com.redhat:7329936b16d9DEBUG:keystoneauth:REQ: curl -g -i -X POST http://10.27.241.34:8776/v2/d432ed8741cc427da398e4239f44deb4/volumes/a0ac29de-3a16-4b07-9aac-de63ccdf8fda/action -H "User-Agent: python-cinderclient" -H "Content-Type: application/json" -H "Accept: application/json" -H "X-Auth-Token: {SHA1}10e60c88cab7620ad3864cb1110d2b9c64a8170f" -d { "os-initialize_connection": { "connector": { "initiator": "iqn.1994-05.com.redhat:7329936b16d9", "ip": "10.27.244.149", "platform": "x86_64", "host": "my03n010027244149.sncloud.com", "do_local_attach": "false", "os_type": "linux2", "multipath": "false" } }}RESP BODY: { "connection_info": { "driver_volume_type": "iscsi", "data": { "target_luns": [0], "target_iqns": ["iqn.2010-10.org.openstack:volume-a0ac29de-3a16-4b07-9aac-de63ccdf8fda"], "auth_password": "24do7AqfnLDZ5DyB", "target_discovered": false, "encrypted": false, "qos_specs": null, "target_iqn": "iqn.2010-10.org.openstack:volume-a0ac29de-3a16-4b07-9aac-de63ccdf8fda", "target_portal": "10.27.244.144:3260", "volume_id": "a0ac29de-3a16-4b07-9aac-de63ccdf8fda", "target_lun": 0, "access_mode": "rw", "auth_username": "2y35wC68BsvU8M37tWCn", "auth_method": "CHAP", "target_portals": ["10.27.244.144:3260"] } }}
文章来源:微点阅读 https://www.weidianyuedu.com