本文地址:http://blog.csdn.net/spch2008/article/details/9380231
QuantumClient端的身份验证
第一部分:
QuantumClent\shell.py QuantumShell类中,有一个run方法,负责接收用户命令并执行。
#命令 quantum port-show c5975e89-c3cb-4cce-a751-5931258c42d1
def run(self, argv):
#argv ==》 ['port-show', 'c5975e89-c3cb-4cce-a751-5931258c42d1']
#调用参数解释器解析参数
self.options, remainder = self.parser.parse_known_args(argv)
#self.options
'''Namespace(debug=False, help=<quantumclient.shell.QuantumShell object at 0x18f5fd0>,
os_auth_strategy='keystone', os_auth_url='http://172.16.4.1:5000/v2.0/', os_password='admin',
os_region_name='', os_tenant_name='admin', os_token='', os_url='', os_username='admin')
'''
#remainder
#['port-show', 'c5975e89-c3cb-4cce-a751-5931258c42d1']
self.initialize_app(remainder)
result = self.run_subcommand(remainder)
return result
类中有一个参数解释器,负责提取linux中的环境变量。
def build_option_parser(self, description, version):
# Global arguments
parser.add_argument(
'--os-auth-strategy', metavar='<auth-strategy>',
default=env('OS_AUTH_STRATEGY', default='keystone'))
parser.add_argument(
'--os-auth-url', metavar='<auth-url>',
default=env('OS_AUTH_URL'),
help='Authentication URL (Env: OS_AUTH_URL)')
parser.add_argument(
'--os-tenant-name', metavar='<auth-tenant-name>',
default=env('OS_TENANT_NAME'),
help='Authentication tenant name (Env: OS_TENANT_NAME)')
parser.add_argument(
'--os-username', metavar='<auth-username>',
default=utils.env('OS_USERNAME'),
help='Authentication username (Env: OS_USERNAME)')
parser.add_argument(
'--os-password', metavar='<auth-password>',
default=utils.env('OS_PASSWORD'),
help='Authentication password (Env: OS_PASSWORD)')
parser.add_argument(
'--os-token', metavar='<token>',
default=env('OS_TOKEN'),
help='Defaults to env[OS_TOKEN]')
return parser
我得环境变量如下:
declare -x OS_AUTH_URL="http://172.16.4.1:5000/v2.0/"
declare -x OS_PASSWORD="admin"
declare -x OS_TENANT_NAME="admin"
declare -x OS_USERNAME="admin"
由于环境变量中没有配置验证方式,即OS_AUTH_STRATEGY,从上述代码中可以看出,将默认采用keystone进行身份验证。
initialize_app函数中调用authenticate_user,进行初步的身份验证。
def authenticate_user(self):
if self.options.os_auth_strategy == 'keystone':
# Validate password flow auth
if not self.options.os_username:
raise exc.CommandError(
"You must provide a username via"
" either --os-username or env[OS_USERNAME]")
if not self.options.os_password:
raise exc.CommandError(
"You must provide a password via"
" either --os-password or env[OS_PASSWORD]")
self.client_manager = clientmanager.ClientManager(
token=self.options.os_token,
url=self.options.os_url,
auth_url=self.options.os_auth_url,
tenant_name=self.options.os_tenant_name,
username=self.options.os_username,
password=self.options.os_password,
region_name=self.options.os_region_name,
api_version=self.api_version,
auth_strategy=self.options.os_auth_strategy, )
return
验证是否指定了相应的变量,只有这些变量有值,才能进行下一步的验证,否则提示用户输入。将解析所得的参数传入ClientManager中,也
就是说有了ClientManager对象,就有了目前解析所得的这些参数。这里比较重要的一个变量就是client_manager,后面它将用于向keystone进行身份验证,取得token。
记住这个client_manager。
def run_subcommand(self, argv):
subcommand = self.command_manager.find_command(argv)
cmd_factory, cmd_name, sub_argv = subcommand
cmd = cmd_factory(self, self.options)
return run_command(cmd, cmd_parser, sub_argv)
def run_command(cmd, cmd_parser, sub_argv):
return cmd.run(known_args)
这两段代码有点无关紧要了,只是取得命令对应的类(ShowPort),创建对象实例。注意:cmd_factory将self(即QuantumShell对象)与self.options传入,
初始化ShowPort。
'port-show': utils.import_class(
'quantumclient.quantum.v2_0.port.ShowPort'),
下面将视野转向quantumclient.quantum.v2_0.port.ShowPort类啦
第二部分:
ShowPort最顶层的基类为cliff.command
class Command(object):
__metaclass__ = abc.ABCMeta
def __init__(self, app, app_args):
self.app = app
self.app_args = app_args
return
其中的app为QuantumShell对象,app_args为QuantumShell对象中的self.options。
分析第一部分的cmd.run,cmd即ShowPort对象。调用的是父类ShowCommand的get_data方法。
def get_data(self, parsed_args):
self.log.debug('get_data(%s)' % parsed_args)
quantum_client = self.get_client()
obj_shower = getattr(quantum_client, "show_%s" % self.resource)
data = obj_shower(_id, **params)
而其中的get_client为调用ShowCommand父类QuantumCommand的get_client()方法
class QuantumCommand(command.OpenStackCommand):
api = 'network'
log = logging.getLogger(__name__ + '.QuantumCommand')
def get_client(self):
return self.app.client_manager.quantum
好了,到这里终于又回到QuantumShell对象的client_manager变量了。
找到quantumclient.common.clientmanager.py,此文件中有两个类
class ClientCache(object):
def __init__(self, factory):
self.factory = factory
self._handle = None
def __get__(self, instance, owner):
# Tell the ClientManager to login to keystone
if self._handle is None:
self._handle = self.factory(instance)
return self._handle
class ClientManager(object):
quantum = ClientCache(quantum_client.make_client)
def initialize(self):
if not self._url:
httpclient = HTTPClient(username=self._username,
tenant_name=self._tenant_name,
password=self._password,
region_name=self._region_name,
auth_url=self._auth_url)
httpclient.authenticate()
# Populate other password flow attributes
self._token = httpclient.auth_token
self._url = httpclient.endpoint_url
这里用到了一个
python描述符的技术。当get_client方法中调用client_manager.quantum时,会触发ClientCache中的__get__方法。
而__get__中会触发quantum_client的make_client方法。
API_VERSIONS = {
'2.0': 'quantumclient.v2_0.client.Client',
}
def make_client(instance):
quantum_client = utils.get_client_class(
API_NAME,
instance._api_version[API_NAME],
API_VERSIONS,
)
instance.initialize()
client = quantum_client(token=instance._token)
return client
instance即ClientManager对象,initialize负责向keystone索取token,后面通信将采用此token。而quantum_client为quantumclient.v2_0.client.Client类。
client为quantumclient\v2_0\client.py中的Client类,其构造函数__init__
def __init__(self, **kwargs):
self.httpclient = HTTPClient(**kwargs)
self.version = '2.0'
self.format = 'json'
self.action_prefix = "/v%s" % (self.version)
self.retries = 0
self.retry_interval = 1
其中,将token传入HTTPClient对象中,用于后续身份验证。
port-showt对应于quantumclient\v2_0\client.py中的show-port, 在ShowCommand中的get_data方法中通过getattr(quantum_client, "show_%s" % self.resource)
取得该方法。
@APIParamsCall
def show_port(self, port, **_params):
return self.get(self.port_path % (port), params=_params)
def get(self, action, body=None, headers=None, params=None):
return self.retry_request("GET", action, body=body,
headers=headers, params=params)
def retry_request(self, method, action, body=None,
return self.do_request(method, action, body=body,
headers=headers, params=params)
def do_request(self, method, action, body=None, headers=None, params=None):
resp, replybody = self.httpclient.do_request(action, method, body=body)
通过上述追踪,最终调用的是httpclient的do_request方法。
def do_request(self, url, method, **kwargs):
kwargs['headers']['X-Auth-Token'] = self.auth_token
最终在do_request中,将token加入请求信息头部,用于后续身份验证。