在许多安装文档中,都要source admin-openrc.sh。那么这一步具体有什么用呢?还是要从代码看起。
Auth plugin requires parameters which were not given: auth_url
neutron port-list的入口:
在neutronclient/shell.py中
def main(argv=sys.argv[1:]):
try:
print(_("neutron CLI is deprecated and will be removed "
"in the future. Use openstack CLI instead."), file=sys.stderr)
return NeutronShell(NEUTRON_API_VERSION).run(
list(map(encodeutils.safe_decode, argv)))
except KeyboardInterrupt:
print(_("... terminating neutron client"), file=sys.stderr)
return 130
except exc.NeutronClientException:
return 1
except Exception as e:
print(e)
return 1
if __name__ == "__main__":
sys.exit(main(sys.argv[1:]))
在run 函数中,有一行
self.initialize_app(remainder)
self.initialize_app(remainder)里面重点是这句:
if self.interactive_mode or cmd_name != 'help':
self.authenticate_user()
在self.authenticate_user中,获取用户所有的认证信息的在这一行
cloud_config = os_client_config.OpenStackConfig().get_one_cloud(
cloud=self.options.os_cloud, argparse=self.options,
network_api_version=self.api_version,
verify=not self.options.insecure)
先是新建了一个OpenStackConfig匿名类。在构造函数中,里面最关键的一段是
envvars = _get_os_environ(envvar_prefix=envvar_prefix)
if envvars:
self.cloud_config['clouds'][self.envvar_key] = envvars
if not self.default_cloud:
self.default_cloud = self.envvar_key
def _get_os_environ():
def _get_os_environ(envvar_prefix=None):
ret = defaults.get_defaults()
if not envvar_prefix:
# This makes the or below be OS_ or OS_ which is a no-op
envvar_prefix = 'OS_'
environkeys = [k for k in os.environ.keys()
if (k.startswith('OS_') or k.startswith(envvar_prefix))
and not k.startswith('OS_TEST') # infra CI var
and not k.startswith('OS_STD') # infra CI var
]
for k in environkeys:
newkey = k.split('_', 1)[-1].lower()
ret[newkey] = os.environ[k]
# If the only environ keys are selectors or behavior modification, don't
# return anything
selectors = set([
'OS_CLOUD', 'OS_REGION_NAME',
'OS_CLIENT_CONFIG_FILE', 'OS_CLIENT_SECURE_FILE', 'OS_CLOUD_NAME'])
if set(environkeys) - selectors:
return ret
return None
这个函数将系统变量中以OS_开头且不以OS_TEST, OS_STD开头的用户环境变量去掉开头后返回小写。这样就将admin_openrc.sh里面的变量加载到内存中了
我的admin_openrc.sh长这样:
export OS_PROJECT_DOMAIN_NAME=Default
export OS_USER_DOMAIN_NAME=Default
export OS_PROJECT_NAME=admin
export OS_USERNAME=admin
export OS_PASSWORD=openstack
export OS_AUTH_URL=http://controller:5000/v3
export OS_IDENTITY_API_VERSION=3
export OS_IMAGE_API_VERSION=2
最后,self.cloud_config被加载成下面的形式
self.cloud_config:
{
'clouds':
{
'envvars':
{
u'auth_type': u'password',
u'status': u'active',
'project_name': 'admin',
u'compute_api_version': u'2',
'key': None,
u'database_api_version': u'1.0',
'user_domain_name': 'Default',
'api_timeout': None,
u'baremetal_api_version': u'1',
u'image_api_version': '2',
u'container_infra_api_version': u'1',
u'metering_api_version': u'2',
u'image_api_use_tasks': False,
u'floating_ip_source': u'neutron',
'project_domain_name': 'Default',
u'orchestration_api_version': u'1',
'cacert': None,
u'network_api_version': u'2',
u'message': u'',
u'image_format': u'qcow2',
u'application_catalog_api_version': u'1',
u'key_manager_api_version': u'v1',
'password': 'openstack',
u'workflow_api_version': u'2',
'verify': True,
u'identity_api_version': '3',
u'volume_api_version': u'2',
'username': 'admin',
'cert': None,
u'secgroup_source': u'neutron',
u'container_api_version': u'1',
'auth_url': 'http://controller:5000/v3',
u'dns_api_version': u'2',
u'object_store_api_version': u'1',
u'interface': u'public',
u'disable_vendor_agent': {}
}
}
}
之后,这个OpenStackConfig的实例执行了get_one_cloud方法。get_one_cloud可以分为下面这些主要步骤:
if cloud is None:
if 'cloud' in args:
cloud = args['cloud']
else:
cloud = self.default_cloud
# "envvars"
config = self._get_base_cloud_config(cloud)
def _get_base_cloud_config(self, name):
cloud = dict()
# Only validate cloud name if one was given
if name and name not in self.cloud_config['clouds']:
raise exceptions.OpenStackConfigException(
"Cloud {name} was not found.".format(
name=name))
# name = envvars, our_cloud = self.cloud_config["clouds"]["envvars"]
our_cloud = self.cloud_config['clouds'].get(name, dict())
# Get the defaults
cloud.update(self.defaults)
self._expand_vendor_profile(name, cloud, our_cloud)
if 'auth' not in cloud:
cloud['auth'] = dict()
_auth_update(cloud, our_cloud)
if 'cloud' in cloud:
del cloud['cloud']
return cloud
最后, config为
{
u'auth_type': u'password',
u'status': u'active',
'project_name': 'admin',
u'compute_api_version': u'2',
u'orchestration_api_version': u'1',
u'database_api_version': u'1.0',
'user_domain_name': 'Default',
'api_timeout': None,
u'baremetal_api_version': u'1',
u'key_manager_api_version': u'v1',
u'container_infra_api_version': u'1',
u'metering_api_version': u'2',
u'image_api_use_tasks': False,
u'floating_ip_source': u'neutron',
'auth': {},
'key': None,
'cacert': None,
u'network_api_version': u'2',
u'message': u'',
u'image_format': u'qcow2',
u'application_catalog_api_version': u'1',
u'image_api_version': '2',
'password': 'openstack',
u'workflow_api_version': u'2',
'verify': True,
u'identity_api_version': '3',
u'volume_api_version': u'2',
'username': 'admin',
'cert': None,
u'secgroup_source': u'neutron',
u'container_api_version': u'1',
'auth_url': 'http://controller:5000/v3',
u'dns_api_version': u'2',
u'disable_vendor_agent': {},
u'interface': u'public',
'project_domain_name': 'Default',
u'object_store_api_version': u'1'
}
if 'region_name' not in args:
args['region_name'] = ''
region = self._get_region(cloud=cloud, region_name=args['region_name'])
# region: {'values': {}, 'name': ''}
args['region_name'] = region['name']
region_args = copy.deepcopy(region['values'])
# Regions is a list that we can use to create a list of cloud/region
# objects. It does not belong in the single-cloud dict
config.pop('regions', None)
for arg_list in region_args, args:
for (key, val) in iter(arg_list.items()):
if val is not None:
if key == 'auth' and config[key] is not None:
config[key] = _auth_update(config[key], val)
else:
config[key] = val
config = self.magic_fixes(config)
config = self._normalize_keys(config)
config = self.auth_config_hook(config)
self.magic_fixes将'user_domain_name', 'project_name', 'project_domain_name'填充至auth中,在下面这个函数中进行
def magic_fixes(self, config):
"""Perform the set of magic argument fixups"""
if (('auth' in config and 'token' in config['auth']) or
('auth_token' in config and config['auth_token']) or
('token' in config and config['token'])):
config.setdefault('token', config.pop('auth_token', None))
config = self._fix_backwards_api_timeout(config)
if 'endpoint_type' in config:
config['interface'] = config.pop('endpoint_type')
config = self._fix_backwards_auth_plugin(config)
# 下面这一行是填充参数
config = self._fix_backwards_project(config)
config = self._fix_backwards_interface(config)
config = self._fix_backwards_networks(config)
config = self._handle_domain_id(config)
for key in BOOL_KEYS:
if key in config:
if type(config[key]) is not bool:
config[key] = get_boolean(config[key])
if 'auth' in config and 'auth_url' in config['auth']:
config['auth']['auth_url'] = config['auth']['auth_url'].format(
**config)
return config
self._fix_backwards_project(config),这段代码将原来cloud中出现的所有mappings中键值对移动到cloud["auth"]中。
def _fix_backwards_project(self, cloud):
mappings = {
'domain_id': ('domain_id', 'domain-id'),
'domain_name': ('domain_name', 'domain-name'),
'user_domain_id': ('user_domain_id', 'user-domain-id'),
'user_domain_name': ('user_domain_name', 'user-domain-name'),
'project_domain_id': ('project_domain_id', 'project-domain-id'),
'project_domain_name': (
'project_domain_name', 'project-domain-name'),
'token': ('auth-token', 'auth_token', 'token'),
}
if cloud.get('auth_type', None) == 'v2password':
mappings['tenant_id'] = (
'project_id', 'project-id', 'tenant_id', 'tenant-id')
mappings['tenant_name'] = (
'project_name', 'project-name', 'tenant_name', 'tenant-name')
else:
mappings['project_id'] = (
'tenant_id', 'tenant-id', 'project_id', 'project-id')
mappings['project_name'] = (
'tenant_name', 'tenant-name', 'project_name', 'project-name')
for target_key, possible_values in mappings.items():
target = None
for key in possible_values:
if key in cloud:
target = str(cloud[key])
del cloud[key]
if key in cloud['auth']:
target = str(cloud['auth'][key])
del cloud['auth'][key]
if target:
cloud['auth'][target_key] = target
return cloud
经过上面的移动,config变为
{
'auth_type': u'password',
'username': 'admin',
u'compute_api_version': u'2',
'help': , u'database_api_version': u'1.0',
u'metering_api_version': u'2',
'auth_url': 'http://controller:5000/v3',
u'network_api_version': {'network': '2.0'},
u'message': u'',
u'image_format': u'qcow2',
'networks': [],
u'image_api_version': '2',
'verify': True,
'service_type': 'network',
u'dns_api_version': u'2',
'auth_strategy': 'keystone',
u'status': u'active',
u'container_infra_api_version': u'1',
'verbose_level': 1,
'region_name': '',
'insecure': False,
'api_timeout': None,
u'baremetal_api_version': u'1',
# 注意,这里auth被填充了
'auth':
{
'user_domain_name': 'Default',
'project_name': 'admin',
'project_domain_name': 'Default'
},
u'image_api_use_tasks': False,
u'floating_ip_source': u'neutron',
'key': None, 'cacert': None,
'password': 'openstack',
u'application_catalog_api_version': u'1',
'retries': 0,
u'key_manager_api_version': u'v1',
u'workflow_api_version': u'2',
u'orchestration_api_version': u'1',
u'identity_api_version': '3',
u'volume_api_version': u'2',
'cert': None,
u'secgroup_source': u'neutron',
u'container_api_version': u'1',
u'object_store_api_version': u'1',
u'interface': 'public',
u'disable_vendor_agent': {}
}
if validate:
loader = self._get_auth_loader(config)
config = self._validate_auth(config, loader)
auth_plugin = loader.load_from_options(**config['auth'])
else:
auth_plugin = None
先是加载了认证组件
loader = self._get_auth_loader(config)
def _get_auth_loader(self, config):
if config['auth_type'] in (None, "None", ''):
config['auth_type'] = 'admin_token'
config['auth']['token'] = 'notused'
elif config['auth_type'] == 'token_endpoint':
config['auth_type'] = 'admin_token'
return loading.get_plugin_loader(config['auth_type'])
这里,auth_type是"password"
def get_plugin_loader(name):
try:
mgr = stevedore.DriverManager(namespace=PLUGIN_NAMESPACE,
invoke_on_load=True,
name=name)
except RuntimeError:
raise exceptions.NoMatchingPlugin(name)
return mgr.driver
对应的entry_point:
[keystoneauth1.plugin]
......
password = keystoneauth1.loading._plugins.identity.generic:Password
......
所以,加载keystoneauth1.loading._plugins.identity.generic.Password类
Password类如下所示:
class Password(loading.BaseGenericLoader):
@property
def plugin_class(self):
return identity.Password
def get_options(cls):
options = super(Password, cls).get_options()
options.extend([
loading.Opt('user-id', help='User id'),
loading.Opt('username',
help='Username',
deprecated=[loading.Opt('user-name')]),
loading.Opt('user-domain-id', help="User's domain id"),
loading.Opt('user-domain-name', help="User's domain name"),
loading.Opt('password',
secret=True,
prompt='Password: ',
help="User's password"),
])
return options
然后self._validate_auth将和认证信息有关的参数进一步填充到auth里面
config: {
'auth_type': u'password',
u'status': u'active',
u'compute_api_version': u'2',
'help': ,
u'database_api_version': u'1.0',
u'metering_api_version': u'2',
u'network_api_version': {'network': '2.0'},
u'message': u'', u'image_format': u'qcow2',
'networks': [],
u'image_api_version': '2',
'verify': True,
'service_type': 'network',
u'dns_api_version': u'2',
'auth_strategy': 'keystone',
u'container_infra_api_version': u'1',
'verbose_level': 1, 'region_name': '',
'insecure': False,
'api_timeout': None,
u'baremetal_api_version': u'1',
# auth被进一步填充了
'auth': {
'username': 'admin',
'project_name': 'admin',
'user_domain_name': 'Default',
'auth_url': 'http://controller:5000/v3',
'password': 'openstack',
'project_domain_name': 'Default'
},
u'image_api_use_tasks': False,
u'floating_ip_source': u'neutron',
'key': None,
'cacert': None,
u'application_catalog_api_version': u'1',
'retries': '0',
u'key_manager_api_version': u'v1',
u'workflow_api_version': u'2',
u'orchestration_api_version': u'1',
u'identity_api_version': '3',
u'volume_api_version': u'2',
'cert': None,
u'secgroup_source': u'neutron',
u'container_api_version': u'1',
u'object_store_api_version': u'1',
u'interface': 'public',
u'disable_vendor_agent': {}
}
最后,利用config["auth"]创建用于验证的组件
auth_plugin = loader.load_from_options(**config['auth'])
def load_from_options(self, **kwargs):
missing_required = [o for o in self.get_options()
if o.required and kwargs.get(o.dest) is None]
if missing_required:
raise exceptions.MissingRequiredOptions(missing_required)
return self.create_plugin(**kwargs)
看到这里,大家就明白为什么不执行source admin-openrc.sh的话,会出现错误了。
首先,o.required只有下面这个
class BaseIdentityLoader(base.BaseLoader):
def get_options(self):
options = super(BaseIdentityLoader, self).get_options()
options.extend([
opts.Opt('auth-url',
required=True,
help='Authentication URL'),
])
return options
如果不执行source admin-openrc.sh,则auth-url这个环境变量不存在,会抛出
exceptions.MissingRequiredOptions(['auth-url'])异常
如下:
class MissingRequiredOptions(OptionError):
def __init__(self, options):
self.options = options
names = ", ".join(o.dest for o in options)
m = 'Auth plugin requires parameters which were not given: %s'
super(MissingRequiredOptions, self).__init__(m % names)
这时,抛出本文开头的那个错误
for (key, value) in config.items():
if hasattr(value, 'format') and key not in FORMAT_EXCLUSIONS:
config[key] = value.format(**config)
force_ipv4 = config.pop('force_ipv4', self.force_ipv4)
prefer_ipv6 = config.pop('prefer_ipv6', True)
if not prefer_ipv6:
force_ipv4 = True
if cloud is None:
cloud_name = ''
else:
cloud_name = str(cloud)
return cloud_config.CloudConfig(
name=cloud_name,
region=config['region_name'],
config=config,
force_ipv4=force_ipv4,
auth_plugin=auth_plugin,
openstack_config=self,
session_constructor=self._session_constructor,
app_name=self._app_name,
app_version=self._app_version,
)
self.client_manager = clientmanager.ClientManager(
retries=self.options.retries,
raise_errors=False,
session=auth_session,
url=self.options.os_url,
token=self.options.os_token,
region_name=cloud_config.get_region_name(),
api_version=cloud_config.get_api_version('network'),
service_type=cloud_config.get_service_type('network'),
service_name=cloud_config.get_service_name('network'),
endpoint_type=interface,
auth=auth,
insecure=not verify,
log_credentials=True)
return
以openstack_dashboard.api.neutron为例
def neutronclient(request):
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
LOG.debug('neutronclient connection created using token "%s" and url "%s"'
% (request.user.token.id, base.url_for(request, 'network')))
LOG.debug('user_id=%(user)s, tenant_id=%(tenant)s' %
{'user': request.user.id, 'tenant': request.user.tenant_id})
c = neutron_client.Client(token=request.user.token.id,
auth_url=base.url_for(request, 'identity'),
endpoint_url=base.url_for(request, 'network'),
insecure=insecure, ca_cert=cacert)
return c
其实身份信息在request数据结构里都包含了,只需要将利用这个数据结构对neutron_client.Client进行初始化即可。