summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--doc/source/man/keystone-manage.rst7
-rw-r--r--keystone/cli.py31
-rw-r--r--keystone/common/cache/_memcache_pool.py21
-rw-r--r--keystone/common/config.py2
-rw-r--r--keystone/contrib/federation/controllers.py2
-rw-r--r--keystone/contrib/federation/utils.py16
-rw-r--r--keystone/openstack/common/service.py39
-rw-r--r--keystone/tests/unit/common/test_connection_pool.py16
-rw-r--r--keystone/tests/unit/test_v3_federation.py13
-rw-r--r--requirements-py3.txt4
-rw-r--r--requirements.txt6
-rw-r--r--test-requirements-py3.txt2
-rw-r--r--test-requirements.txt2
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