diff options
-rw-r--r-- | doc/source/man/keystone-manage.rst | 7 | ||||
-rw-r--r-- | keystone/cli.py | 31 | ||||
-rw-r--r-- | keystone/common/cache/_memcache_pool.py | 21 | ||||
-rw-r--r-- | keystone/common/config.py | 2 | ||||
-rw-r--r-- | keystone/contrib/federation/controllers.py | 2 | ||||
-rw-r--r-- | keystone/contrib/federation/utils.py | 16 | ||||
-rw-r--r-- | keystone/openstack/common/service.py | 39 | ||||
-rw-r--r-- | keystone/tests/unit/common/test_connection_pool.py | 16 | ||||
-rw-r--r-- | keystone/tests/unit/test_v3_federation.py | 13 | ||||
-rw-r--r-- | requirements-py3.txt | 4 | ||||
-rw-r--r-- | requirements.txt | 6 | ||||
-rw-r--r-- | test-requirements-py3.txt | 2 | ||||
-rw-r--r-- | test-requirements.txt | 2 |
13 files changed, 107 insertions, 54 deletions
diff --git a/doc/source/man/keystone-manage.rst b/doc/source/man/keystone-manage.rst index b2ea39246..feeeb142c 100644 --- a/doc/source/man/keystone-manage.rst +++ b/doc/source/man/keystone-manage.rst @@ -7,9 +7,9 @@ Keystone Management Utility --------------------------- :Author: openstack@lists.openstack.org -:Date: 2014-10-16 +:Date: 2015-4-7 :Copyright: OpenStack Foundation -:Version: 2014.2 +:Version: 2015.1 :Manual section: 1 :Manual group: cloud computing @@ -42,6 +42,9 @@ Available commands: * ``db_sync``: Sync the database. * ``db_version``: Print the current migration version of the database. +* ``domain_config_upload``: Upload domain configuration file. +* ``fernet_rotate``: Rotate keys in the Fernet key repository. +* ``fernet_setup``: Setup a Fernet key repository. * ``mapping_purge``: Purge the identity mapping table. * ``pki_setup``: Initialize the certificates used to sign tokens. * ``saml_idp_metadata``: Generate identity provider metadata. diff --git a/keystone/cli.py b/keystone/cli.py index c50cebf44..1cb32d087 100644 --- a/keystone/cli.py +++ b/keystone/cli.py @@ -21,7 +21,7 @@ from oslo_config import cfg from oslo_log import log import pbr.version -from keystone import assignment +from keystone import backends from keystone.common import driver_hints from keystone.common import openssl from keystone.common import sql @@ -30,8 +30,6 @@ from keystone.common import utils from keystone import config from keystone import exception from keystone.i18n import _, _LW -from keystone import identity -from keystone import resource from keystone import token @@ -296,16 +294,16 @@ class MappingPurge(BaseApp): def get_domain_id(name): try: - identity.Manager() - # init assignment manager to avoid KeyError in resource.core - assignment.Manager() - resource_manager = resource.Manager() return resource_manager.get_domain_by_name(name)['id'] except KeyError: raise ValueError(_("Unknown domain '%(name)s' specified by " "--domain-name") % {'name': name}) validate_options() + drivers = backends.load_backends() + resource_manager = drivers['resource_api'] + mapping_manager = drivers['id_mapping_api'] + # Now that we have validated the options, we know that at least one # option has been specified, and if it was the --all option then this # was the only option specified. @@ -322,7 +320,6 @@ class MappingPurge(BaseApp): if CONF.command.type is not None: mapping['type'] = CONF.command.type - mapping_manager = identity.MappingManager() mapping_manager.purge_mappings(mapping) @@ -337,21 +334,9 @@ class DomainConfigUploadFiles(object): self.load_backends() def load_backends(self): - """Load the backends needed for uploading domain configs. - - We only need the resource and domain_config managers, but there are - some dependencies which mean we have to load the assignment and - identity managers as well. - - The order of loading the backends is important, since the resource - manager depends on the assignment manager, which in turn depends on - the identity manager. - - """ - identity.Manager() - assignment.Manager() - self.resource_manager = resource.Manager() - self.domain_config_manager = resource.DomainConfigManager() + drivers = backends.load_backends() + self.resource_manager = drivers['resource_api'] + self.domain_config_manager = drivers['domain_config_api'] def valid_options(self): """Validate the options, returning True if they are indeed valid. diff --git a/keystone/common/cache/_memcache_pool.py b/keystone/common/cache/_memcache_pool.py index b15332db0..bc559781a 100644 --- a/keystone/common/cache/_memcache_pool.py +++ b/keystone/common/cache/_memcache_pool.py @@ -35,11 +35,22 @@ from keystone.i18n import _ LOG = log.getLogger(__name__) -# This 'class' is taken from http://stackoverflow.com/a/22520633/238308 -# Don't inherit client from threading.local so that we can reuse clients in -# different threads -_MemcacheClient = type('_MemcacheClient', (object,), - dict(memcache.Client.__dict__)) + +class _MemcacheClient(memcache.Client): + """Thread global memcache client + + As client is inherited from threading.local we have to restore object + methods overloaded by threading.local so we can reuse clients in + different threads + """ + __delattr__ = object.__delattr__ + __getattribute__ = object.__getattribute__ + __new__ = object.__new__ + __setattr__ = object.__setattr__ + + def __del__(self): + pass + _PoolItem = collections.namedtuple('_PoolItem', ['ttl', 'connection']) diff --git a/keystone/common/config.py b/keystone/common/config.py index ebc1c2fa7..7a8c385a3 100644 --- a/keystone/common/config.py +++ b/keystone/common/config.py @@ -326,7 +326,7 @@ FILE_OPTIONS = { 'deployments. Small workloads (single process) ' 'like devstack can use the dogpile.cache.memory ' 'backend.'), - cfg.MultiStrOpt('backend_argument', default=[], + cfg.MultiStrOpt('backend_argument', default=[], secret=True, help='Arguments supplied to the backend module. ' 'Specify this option once per argument to be ' 'passed to the dogpile.cache backend. Example ' diff --git a/keystone/contrib/federation/controllers.py b/keystone/contrib/federation/controllers.py index 7ffaad547..cdbba4162 100644 --- a/keystone/contrib/federation/controllers.py +++ b/keystone/contrib/federation/controllers.py @@ -268,7 +268,7 @@ class Auth(auth_controllers.Auth): def federated_sso_auth(self, context, protocol_id): try: - remote_id_name = CONF.federation.remote_id_attribute + remote_id_name = utils.get_remote_id_parameter(protocol_id) remote_id = context['environment'][remote_id_name] except KeyError: msg = _('Missing entity ID from environment') diff --git a/keystone/contrib/federation/utils.py b/keystone/contrib/federation/utils.py index afc4d42e8..6fca2143d 100644 --- a/keystone/contrib/federation/utils.py +++ b/keystone/contrib/federation/utils.py @@ -191,10 +191,7 @@ def validate_groups_cardinality(group_ids, mapping_id): raise exception.MissingGroups(mapping_id=mapping_id) -def validate_idp(idp, protocol, assertion): - """Validate the IdP providing the assertion is registered for the mapping. - """ - +def get_remote_id_parameter(protocol): # NOTE(marco-fargetta): Since we support any protocol ID, we attempt to # retrieve the remote_id_attribute of the protocol ID. If it's not # registered in the config, then register the option and try again. @@ -210,10 +207,19 @@ def validate_idp(idp, protocol, assertion): except AttributeError: pass if not remote_id_parameter: - LOG.debug('Cannot find "remote_id_attibute" in configuration ' + LOG.debug('Cannot find "remote_id_attribute" in configuration ' 'group %s. Trying default location in ' 'group federation.', protocol) remote_id_parameter = CONF.federation.remote_id_attribute + + return remote_id_parameter + + +def validate_idp(idp, protocol, assertion): + """Validate the IdP providing the assertion is registered for the mapping. + """ + + remote_id_parameter = get_remote_id_parameter(protocol) if not remote_id_parameter or not idp['remote_ids']: LOG.debug('Impossible to identify the IdP %s ', idp['id']) # If nothing is defined, the administrator may want to diff --git a/keystone/openstack/common/service.py b/keystone/openstack/common/service.py index cfae56b74..d209b8e8e 100644 --- a/keystone/openstack/common/service.py +++ b/keystone/openstack/common/service.py @@ -199,18 +199,30 @@ class ServiceWrapper(object): class ProcessLauncher(object): - def __init__(self): - """Constructor.""" + _signal_handlers_set = set() + + @classmethod + def _handle_class_signals(cls, *args, **kwargs): + for handler in cls._signal_handlers_set: + handler(*args, **kwargs) + def __init__(self, wait_interval=0.01): + """Constructor. + + :param wait_interval: The interval to sleep for between checks + of child process exit. + """ self.children = {} self.sigcaught = None self.running = True + self.wait_interval = wait_interval rfd, self.writepipe = os.pipe() self.readpipe = eventlet.greenio.GreenPipe(rfd, 'r') self.handle_signal() def handle_signal(self): - _set_signals_handler(self._handle_signal) + self._signal_handlers_set.add(self._handle_signal) + _set_signals_handler(self._handle_class_signals) def _handle_signal(self, signo, frame): self.sigcaught = signo @@ -230,15 +242,12 @@ class ProcessLauncher(object): def _child_process_handle_signal(self): # Setup child signal handlers differently - def _sigterm(*args): - signal.signal(signal.SIGTERM, signal.SIG_DFL) - raise SignalExit(signal.SIGTERM) - def _sighup(*args): signal.signal(signal.SIGHUP, signal.SIG_DFL) raise SignalExit(signal.SIGHUP) - signal.signal(signal.SIGTERM, _sigterm) + # Parent signals with SIGTERM when it wants us to go away. + signal.signal(signal.SIGTERM, signal.SIG_DFL) if _sighup_supported(): signal.signal(signal.SIGHUP, _sighup) # Block SIGINT and let the parent send us a SIGTERM @@ -329,8 +338,8 @@ class ProcessLauncher(object): def _wait_child(self): try: - # Block while any of child processes have exited - pid, status = os.waitpid(0, 0) + # Don't block if no child processes have exited + pid, status = os.waitpid(0, os.WNOHANG) if not pid: return None except OSError as exc: @@ -359,6 +368,10 @@ class ProcessLauncher(object): while self.running: wrap = self._wait_child() if not wrap: + # Yield to other threads if no children have exited + # Sleep for a short time to avoid excessive CPU usage + # (see bug #1095346) + eventlet.greenthread.sleep(self.wait_interval) continue while self.running and len(wrap.children) < wrap.workers: self._start_child(wrap) @@ -383,8 +396,14 @@ class ProcessLauncher(object): if not _is_sighup_and_daemon(self.sigcaught): break + cfg.CONF.reload_config_files() + for service in set( + [wrap.service for wrap in self.children.values()]): + service.reset() + for pid in self.children: os.kill(pid, signal.SIGHUP) + self.running = True self.sigcaught = None except eventlet.greenlet.GreenletExit: diff --git a/keystone/tests/unit/common/test_connection_pool.py b/keystone/tests/unit/common/test_connection_pool.py index 74d0420ce..3813e0339 100644 --- a/keystone/tests/unit/common/test_connection_pool.py +++ b/keystone/tests/unit/common/test_connection_pool.py @@ -10,9 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. +import threading import time import mock +import six from six.moves import queue import testtools from testtools import matchers @@ -117,3 +119,17 @@ class TestConnectionPool(core.TestCase): # after it is available. connection_pool.put_nowait(conn) _acquire_connection() + + +class TestMemcacheClientOverrides(core.BaseTestCase): + + def test_client_stripped_of_threading_local(self): + """threading.local overrides are restored for _MemcacheClient""" + client_class = _memcache_pool._MemcacheClient + # get the genuine thread._local from MRO + thread_local = client_class.__mro__[2] + self.assertTrue(thread_local is threading.local) + for field in six.iterkeys(thread_local.__dict__): + if field not in ('__dict__', '__weakref__'): + self.assertNotEqual(id(getattr(thread_local, field, None)), + id(getattr(client_class, field, None))) diff --git a/keystone/tests/unit/test_v3_federation.py b/keystone/tests/unit/test_v3_federation.py index 2207f3500..c1a4a677a 100644 --- a/keystone/tests/unit/test_v3_federation.py +++ b/keystone/tests/unit/test_v3_federation.py @@ -3678,6 +3678,7 @@ class WebSSOTests(FederatedTokenTests): SSO_TEMPLATE_PATH = os.path.join(core.dirs.etc(), SSO_TEMPLATE_NAME) TRUSTED_DASHBOARD = 'http://horizon.com' ORIGIN = urllib.parse.quote_plus(TRUSTED_DASHBOARD) + PROTOCOL_REMOTE_ID_ATTR = uuid.uuid4().hex def setUp(self): super(WebSSOTests, self).setUp() @@ -3705,6 +3706,18 @@ class WebSSOTests(FederatedTokenTests): resp = self.api.federated_sso_auth(context, self.PROTOCOL) self.assertIn(self.TRUSTED_DASHBOARD, resp.body) + def test_federated_sso_auth_with_protocol_specific_remote_id(self): + self.config_fixture.config( + group=self.PROTOCOL, + remote_id_attribute=self.PROTOCOL_REMOTE_ID_ATTR) + + environment = {self.PROTOCOL_REMOTE_ID_ATTR: self.REMOTE_IDS[0]} + context = {'environment': environment} + query_string = {'origin': self.ORIGIN} + self._inject_assertion(context, 'EMPLOYEE_ASSERTION', query_string) + resp = self.api.federated_sso_auth(context, self.PROTOCOL) + self.assertIn(self.TRUSTED_DASHBOARD, resp.body) + def test_federated_sso_auth_bad_remote_id(self): environment = {self.REMOTE_ID_ATTR: self.IDP} context = {'environment': environment} diff --git a/requirements-py3.txt b/requirements-py3.txt index 1f1c16b45..3780d5fa1 100644 --- a/requirements-py3.txt +++ b/requirements-py3.txt @@ -16,8 +16,8 @@ SQLAlchemy>=0.9.7,<=0.9.99 sqlalchemy-migrate>=0.9.5 passlib iso8601>=0.1.9 -python-keystoneclient>=1.1.0 -keystonemiddleware>=1.5.0 +python-keystoneclient>=1.1.0,<1.4.0 +keystonemiddleware>=1.5.0,<1.6.0 oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0 oslo.config>=1.9.3,<1.10.0 # Apache-2.0 # oslo.messaging tries to pull in eventlet diff --git a/requirements.txt b/requirements.txt index ed8684e9d..87e136642 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,8 +16,8 @@ SQLAlchemy>=0.9.7,<=0.9.99 sqlalchemy-migrate>=0.9.5 passlib iso8601>=0.1.9 -python-keystoneclient>=1.1.0 -keystonemiddleware>=1.5.0 +python-keystoneclient>=1.1.0,<1.4.0 +keystonemiddleware>=1.5.0,<1.6.0 oslo.concurrency>=1.8.0,<1.9.0 # Apache-2.0 oslo.config>=1.9.3,<1.10.0 # Apache-2.0 oslo.messaging>=1.8.0,<1.9.0 # Apache-2.0 @@ -32,6 +32,6 @@ oauthlib>=0.6 pysaml2 dogpile.cache>=0.5.3 jsonschema>=2.0.0,<3.0.0 -pycadf>=0.8.0 +pycadf>=0.8.0,<0.9.0 posix_ipc msgpack-python>=0.4.0 diff --git a/test-requirements-py3.txt b/test-requirements-py3.txt index 5e60aa940..c29a82035 100644 --- a/test-requirements-py3.txt +++ b/test-requirements-py3.txt @@ -14,7 +14,7 @@ bashate>=0.2 # Apache-2.0 # python-memcached>=1.48 # Optional dogpile backend: MongoDB -pymongo>=2.6.3 +pymongo>=2.6.3,<3.0 # Optional backend: LDAP # python-ldap does not install on py3 diff --git a/test-requirements.txt b/test-requirements.txt index ea80cb162..ace34704b 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -12,7 +12,7 @@ pysqlite python-memcached>=1.48 # Optional dogpile backend: MongoDB -pymongo>=2.6.3 +pymongo>=2.6.3,<3.0 # Optional backend: LDAP # authenticate against an existing LDAP server |