diff --git a/nova/api/openstack/identity.py b/nova/api/openstack/identity.py index 4bcc0a026a0d..833d3b57b23a 100644 --- a/nova/api/openstack/identity.py +++ b/nova/api/openstack/identity.py @@ -13,12 +13,15 @@ # under the License. from keystoneauth1 import exceptions as kse -from keystoneauth1 import session +from keystoneauth1 import loading as ks_loading from oslo_log import log as logging import webob +import nova.conf from nova.i18n import _ + +CONF = nova.conf.CONF LOG = logging.getLogger(__name__) @@ -29,7 +32,9 @@ def verify_project_id(context, project_id): an HTTPBadRequest is emitted. """ - sess = session.Session(auth=context.get_auth_plugin()) + sess = ks_loading.load_session_from_conf_options( + CONF, 'keystone', auth=context.get_auth_plugin()) + failure = webob.exc.HTTPBadRequest( explanation=_("Project ID %s is not a valid project.") % project_id) diff --git a/nova/conf/__init__.py b/nova/conf/__init__.py index f0bd45d81576..547390164a68 100644 --- a/nova/conf/__init__.py +++ b/nova/conf/__init__.py @@ -40,6 +40,7 @@ from nova.conf import hyperv from nova.conf import ipv6 from nova.conf import ironic from nova.conf import key_manager +from nova.conf import keystone from nova.conf import libvirt from nova.conf import mks from nova.conf import netconf @@ -93,6 +94,7 @@ mks.register_opts(CONF) ipv6.register_opts(CONF) ironic.register_opts(CONF) key_manager.register_opts(CONF) +keystone.register_opts(CONF) libvirt.register_opts(CONF) netconf.register_opts(CONF) network.register_opts(CONF) diff --git a/nova/conf/keystone.py b/nova/conf/keystone.py new file mode 100644 index 000000000000..56d811ddbbcd --- /dev/null +++ b/nova/conf/keystone.py @@ -0,0 +1,34 @@ +# +# Licensed under the Apache License, Version 2.0 (the "License"); you may +# not use this file except in compliance with the License. You may obtain +# a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT +# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the +# License for the specific language governing permissions and limitations +# under the License. + +from keystoneauth1 import loading as ks_loading +from oslo_config import cfg + + +keystone_group = cfg.OptGroup( + 'keystone', + title='Keystone Options', + help='Configuration options for the identity service') + + +def register_opts(conf): + conf.register_group(keystone_group) + + ks_loading.register_session_conf_options(conf, keystone_group.name) + + +def list_opts(): + return { + keystone_group: ( + ks_loading.get_session_conf_options()) + } diff --git a/nova/tests/unit/test_identity.py b/nova/tests/unit/test_identity.py index bd2285ba7781..1f0c320c6359 100644 --- a/nova/tests/unit/test_identity.py +++ b/nova/tests/unit/test_identity.py @@ -16,6 +16,8 @@ import mock from keystoneauth1 import exceptions as kse +from keystoneauth1 import loading as ks_loading +from keystoneauth1.session import Session import webob from nova.api.openstack import identity @@ -67,81 +69,91 @@ class IdentityValidationTest(test.NoDBTestCase): """ - @mock.patch('keystoneauth1.session.Session.get') - def test_good_id(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_good_id(self, mock_load): """Test response 200. This indicates we have permissions, and we have definitively found the project exists. """ - get.return_value = FakeResponse(200) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(200) + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_no_project(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_no_project(self, mock_load): """Test response 404. This indicates that we have permissions, and we have definitively found the project does not exist. """ - get.return_value = FakeResponse(404) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(404) + mock_load.return_value = session self.assertRaises(webob.exc.HTTPBadRequest, identity.verify_project_id, mock.MagicMock(), "foo") - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_unknown_id(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_unknown_id(self, mock_load): """Test response 403. This indicates we don't have permissions. We fail open here and assume the project exists. """ - get.return_value = FakeResponse(403) + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(403) + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_unknown_error(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_unknown_error(self, mock_load): """Test some other return from keystone. If we got anything else, something is wrong on the keystone side. We don't want to fail on our side. """ - get.return_value = FakeResponse(500, "Oh noes!") + session = mock.create_autospec(Session) + session.get.return_value = FakeResponse(500, "Oh noes!") + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - get.assert_called_once_with( + session.get.assert_called_once_with( '/projects/foo', endpoint_filter={'service_type': 'identity', 'version': (3, 0)}, raise_exc=False) - @mock.patch('keystoneauth1.session.Session.get') - def test_early_fail(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_early_fail(self, mock_load): """Test if we get a keystoneauth exception. If we get a random keystoneauth exception, fall back and assume the project exists. """ - get.side_effect = kse.ConnectionError() + session = mock.create_autospec(Session) + session.get.side_effect = kse.ConnectionError() + mock_load.return_value = session self.assertTrue(identity.verify_project_id(mock.MagicMock(), "foo")) - @mock.patch('keystoneauth1.session.Session.get') - def test_wrong_version(self, get): + @mock.patch.object(ks_loading, 'load_session_from_conf_options') + def test_wrong_version(self, mock_load): """Test endpoint not found. EndpointNotFound will be made when the keystone v3 API is not @@ -149,7 +161,9 @@ class IdentityValidationTest(test.NoDBTestCase): registered as the root endpoint. We treat this the same as 404. """ - get.side_effect = kse.EndpointNotFound() + session = mock.create_autospec(Session) + session.get.side_effect = kse.EndpointNotFound() + mock_load.return_value = session self.assertRaises(webob.exc.HTTPBadRequest, identity.verify_project_id, mock.MagicMock(), "foo") diff --git a/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml b/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml new file mode 100644 index 000000000000..21f017842813 --- /dev/null +++ b/releasenotes/notes/add_keystone_option-138dff5efb9a53aa.yaml @@ -0,0 +1,7 @@ +--- +upgrade: + - | + A new ``keystone`` config section is added so that you can + set session link attributes for communicating with keystone. This + allows the use of custom certificates to secure the link between + Nova and Keystone.