OpenStack 关于admin-openrc.sh的作用(Queens版本)

在许多安装文档中,都要source admin-openrc.sh。那么这一步具体有什么用呢?还是要从代码看起。

不执行source admin-openrc.sh,直接执行neutron port-list会出现以下错误:

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可以分为下面这些主要步骤:

1.获取用户身份认证信息

        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'
}

2.获取region信息

        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)

3.将region信息合并到config并更新config

        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': {}
}

4.创建auth_plugin

        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)

这时,抛出本文开头的那个错误

5.构造CloudConfig

        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,
        )

6.回到self.authenticate_user, 接着将构造clientmanager

        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

2.WEB端的认证

以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进行初始化即可。

你可能感兴趣的:(OpenStack)