diff options
Diffstat (limited to 'keystone-moon/keystone/identity')
-rw-r--r-- | keystone-moon/keystone/identity/backends/ldap.py | 2 | ||||
-rw-r--r-- | keystone-moon/keystone/identity/backends/sql.py | 2 | ||||
-rw-r--r-- | keystone-moon/keystone/identity/core.py | 128 | ||||
-rw-r--r-- | keystone-moon/keystone/identity/mapping_backends/sql.py | 2 |
4 files changed, 115 insertions, 19 deletions
diff --git a/keystone-moon/keystone/identity/backends/ldap.py b/keystone-moon/keystone/identity/backends/ldap.py index 7a3cb03b..1f33bacb 100644 --- a/keystone-moon/keystone/identity/backends/ldap.py +++ b/keystone-moon/keystone/identity/backends/ldap.py @@ -32,7 +32,7 @@ CONF = cfg.CONF LOG = log.getLogger(__name__) -class Identity(identity.Driver): +class Identity(identity.IdentityDriverV8): def __init__(self, conf=None): super(Identity, self).__init__() if conf is None: diff --git a/keystone-moon/keystone/identity/backends/sql.py b/keystone-moon/keystone/identity/backends/sql.py index 8bda9a1b..d37240eb 100644 --- a/keystone-moon/keystone/identity/backends/sql.py +++ b/keystone-moon/keystone/identity/backends/sql.py @@ -70,7 +70,7 @@ class UserGroupMembership(sql.ModelBase, sql.DictBase): primary_key=True) -class Identity(identity.Driver): +class Identity(identity.IdentityDriverV8): # NOTE(henry-nash): Override the __init__() method so as to take a # config parameter to enable sql to be used as a domain-specific driver. def __init__(self, conf=None): diff --git a/keystone-moon/keystone/identity/core.py b/keystone-moon/keystone/identity/core.py index 612a1859..061b82e1 100644 --- a/keystone-moon/keystone/identity/core.py +++ b/keystone-moon/keystone/identity/core.py @@ -44,6 +44,14 @@ MEMOIZE = cache.get_memoization_decorator(section='identity') DOMAIN_CONF_FHEAD = 'keystone.' DOMAIN_CONF_FTAIL = '.conf' +# The number of times we will attempt to register a domain to use the SQL +# driver, if we find that another process is in the middle of registering or +# releasing at the same time as us. +REGISTRATION_ATTEMPTS = 10 + +# Config Registration Types +SQL_DRIVER = 'SQL' + def filter_user(user_ref): """Filter out private items in a user dict. @@ -67,7 +75,7 @@ def filter_user(user_ref): return user_ref -@dependency.requires('domain_config_api') +@dependency.requires('domain_config_api', 'resource_api') class DomainConfigs(dict): """Discover, store and provide access to domain specific configs. @@ -95,7 +103,7 @@ class DomainConfigs(dict): def _assert_no_more_than_one_sql_driver(self, domain_id, new_config, config_file=None): - """Ensure there is more than one sql driver. + """Ensure there is no more than one sql driver. Check to see if the addition of the driver in this new config would cause there to now be more than one sql driver. @@ -108,6 +116,13 @@ class DomainConfigs(dict): (self.driver.is_sql or self._any_sql)): # The addition of this driver would cause us to have more than # one sql driver, so raise an exception. + + # TODO(henry-nash): This method is only used in the file-based + # case, so has no need to worry about the database/API case. The + # code that overrides config_file below is therefore never used + # and should be removed, and this method perhaps moved inside + # _load_config_from_file(). This is raised as bug #1466772. + if not config_file: config_file = _('Database at /domains/%s/config') % domain_id raise exception.MultipleSQLDriversInConfig(source=config_file) @@ -177,24 +192,99 @@ class DomainConfigs(dict): def _load_config_from_database(self, domain_id, specific_config): - def _assert_not_sql_driver(domain_id, new_config): - """Ensure this is not an sql driver. + def _assert_no_more_than_one_sql_driver(domain_id, new_config): + """Ensure adding driver doesn't push us over the limit of 1 - Due to multi-threading safety concerns, we do not currently support - the setting of a specific identity driver to sql via the Identity - API. + The checks we make in this method need to take into account that + we may be in a multiple process configuration and ensure that + any race conditions are avoided. """ - if new_config['driver'].is_sql: - reason = _('Domain specific sql drivers are not supported via ' - 'the Identity API. One is specified in ' - '/domains/%s/config') % domain_id - raise exception.InvalidDomainConfig(reason=reason) + if not new_config['driver'].is_sql: + self.domain_config_api.release_registration(domain_id) + return + + # To ensure the current domain is the only SQL driver, we attempt + # to register our use of SQL. If we get it we know we are good, + # if we fail to register it then we should: + # + # - First check if another process has registered for SQL for our + # domain, in which case we are fine + # - If a different domain has it, we should check that this domain + # is still valid, in case, for example, domain deletion somehow + # failed to remove its registration (i.e. we self heal for these + # kinds of issues). + + domain_registered = 'Unknown' + for attempt in range(REGISTRATION_ATTEMPTS): + if self.domain_config_api.obtain_registration( + domain_id, SQL_DRIVER): + LOG.debug('Domain %s successfully registered to use the ' + 'SQL driver.', domain_id) + return + + # We failed to register our use, let's find out who is using it + try: + domain_registered = ( + self.domain_config_api.read_registration( + SQL_DRIVER)) + except exception.ConfigRegistrationNotFound: + msg = ('While attempting to register domain %(domain)s to ' + 'use the SQL driver, another process released it, ' + 'retrying (attempt %(attempt)s).') + LOG.debug(msg, {'domain': domain_id, + 'attempt': attempt + 1}) + continue + + if domain_registered == domain_id: + # Another process already registered it for us, so we are + # fine. In the race condition when another process is + # in the middle of deleting this domain, we know the domain + # is already disabled and hence telling the caller that we + # are registered is benign. + LOG.debug('While attempting to register domain %s to use ' + 'the SQL driver, found that another process had ' + 'already registered this domain. This is normal ' + 'in multi-process configurations.', domain_id) + return + + # So we don't have it, but someone else does...let's check that + # this domain is still valid + try: + self.resource_api.get_domain(domain_registered) + except exception.DomainNotFound: + msg = ('While attempting to register domain %(domain)s to ' + 'use the SQL driver, found that it was already ' + 'registered to a domain that no longer exists ' + '(%(old_domain)s). Removing this stale ' + 'registration and retrying (attempt %(attempt)s).') + LOG.debug(msg, {'domain': domain_id, + 'old_domain': domain_registered, + 'attempt': attempt + 1}) + self.domain_config_api.release_registration( + domain_registered, type=SQL_DRIVER) + continue + + # The domain is valid, so we really do have an attempt at more + # than one SQL driver. + details = ( + _('Config API entity at /domains/%s/config') % domain_id) + raise exception.MultipleSQLDriversInConfig(source=details) + + # We fell out of the loop without either registering our domain or + # being able to find who has it...either we were very very very + # unlucky or something is awry. + msg = _('Exceeded attempts to register domain %(domain)s to use ' + 'the SQL driver, the last domain that appears to have ' + 'had it is %(last_domain)s, giving up') % { + 'domain': domain_id, 'last_domain': domain_registered} + raise exception.UnexpectedError(msg) domain_config = {} domain_config['cfg'] = cfg.ConfigOpts() config.configure(conf=domain_config['cfg']) - domain_config['cfg'](args=[], project='keystone') + domain_config['cfg'](args=[], project='keystone', + default_config_files=[]) # Override any options that have been passed in as specified in the # database. @@ -206,7 +296,7 @@ class DomainConfigs(dict): domain_config['cfg_overrides'] = specific_config domain_config['driver'] = self._load_driver(domain_config) - _assert_not_sql_driver(domain_id, domain_config) + _assert_no_more_than_one_sql_driver(domain_id, domain_config) self[domain_id] = domain_config def _setup_domain_drivers_from_database(self, standard_driver, @@ -1066,7 +1156,7 @@ class Manager(manager.Manager): @six.add_metaclass(abc.ABCMeta) -class Driver(object): +class IdentityDriverV8(object): """Interface description for an Identity driver.""" def _get_list_limit(self): @@ -1280,6 +1370,9 @@ class Driver(object): # end of identity +Driver = manager.create_legacy_driver(IdentityDriverV8) + + @dependency.provider('id_mapping_api') class MappingManager(manager.Manager): """Default pivot point for the ID Mapping backend.""" @@ -1291,7 +1384,7 @@ class MappingManager(manager.Manager): @six.add_metaclass(abc.ABCMeta) -class MappingDriver(object): +class MappingDriverV8(object): """Interface description for an ID Mapping driver.""" @abc.abstractmethod @@ -1350,3 +1443,6 @@ class MappingDriver(object): """ raise exception.NotImplemented() # pragma: no cover + + +MappingDriver = manager.create_legacy_driver(MappingDriverV8) diff --git a/keystone-moon/keystone/identity/mapping_backends/sql.py b/keystone-moon/keystone/identity/mapping_backends/sql.py index b2f9cb95..7ab4ef52 100644 --- a/keystone-moon/keystone/identity/mapping_backends/sql.py +++ b/keystone-moon/keystone/identity/mapping_backends/sql.py @@ -36,7 +36,7 @@ class IDMapping(sql.ModelBase, sql.ModelDictMixin): @dependency.requires('id_generator_api') -class Mapping(identity.MappingDriver): +class Mapping(identity.MappingDriverV8): def get_public_id(self, local_entity): # NOTE(henry-nash): Since the Public ID is regeneratable, rather |