diff --git a/contrib/heat_keystone/heat_keystone/client.py b/contrib/heat_keystone/heat_keystone/client.py index 82eee5a74..4e7a01993 100644 --- a/contrib/heat_keystone/heat_keystone/client.py +++ b/contrib/heat_keystone/heat_keystone/client.py @@ -69,6 +69,20 @@ class KeystoneClientPlugin(keystone.KeystoneClientPlugin): raise exceptions.KeystoneGroupNotFound(group_id=group) + def get_service_id(self, service): + try: + service_obj = self.client().client.services.get(service) + return service_obj.id + except keystone_exceptions.NotFound: + service_list = self.client().client.services.list(name=service) + + if len(service_list) == 1: + return service_list[0].id + elif len(service_list) > 1: + raise exceptions.KeystoneServiceNameConflict(service=service) + else: + raise exceptions.KeystoneServiceNotFound(service_id=service) + class KeystoneRoleConstraint(constraints.BaseCustomConstraint): @@ -100,3 +114,12 @@ class KeystoneGroupConstraint(constraints.BaseCustomConstraint): def validate_with_client(self, client, group): client.client_plugin('keystone').get_group_id(group) + + +class KeystoneServiceConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exceptions.KeystoneServiceNotFound, + exceptions.KeystoneServiceNameConflict,) + + def validate_with_client(self, client, service): + client.client_plugin('keystone').get_service_id(service) diff --git a/contrib/heat_keystone/heat_keystone/exceptions.py b/contrib/heat_keystone/heat_keystone/exceptions.py index 9cb58f78f..ea114cc61 100644 --- a/contrib/heat_keystone/heat_keystone/exceptions.py +++ b/contrib/heat_keystone/heat_keystone/exceptions.py @@ -29,3 +29,12 @@ class KeystoneDomainNotFound(exception.HeatException): class KeystoneGroupNotFound(exception.HeatException): msg_fmt = _("Keystone group %(group_id)s does not found") + + +class KeystoneServiceNotFound(exception.HeatException): + msg_fmt = _("Keystone service %(service_id)s does not found") + + +class KeystoneServiceNameConflict(exception.HeatException): + msg_fmt = _("Keystone has more than one service with same name " + "%(service)s. Please use service id instead of name") diff --git a/contrib/heat_keystone/heat_keystone/tests/test_client.py b/contrib/heat_keystone/heat_keystone/tests/test_client.py index 9b9ad10ce..814986d12 100644 --- a/contrib/heat_keystone/heat_keystone/tests/test_client.py +++ b/contrib/heat_keystone/heat_keystone/tests/test_client.py @@ -12,13 +12,16 @@ # under the License. import mock -import testtools +import six + +from keystoneclient import exceptions as keystone_exceptions from .. import client # noqa from .. import exceptions # noqa +from heat.tests import common -class KeystoneRoleConstraintTest(testtools.TestCase): +class KeystoneRoleConstraintTest(common.HeatTestCase): def test_expected_exceptions(self): self.assertEqual((exceptions.KeystoneRoleNotFound,), @@ -38,7 +41,7 @@ class KeystoneRoleConstraintTest(testtools.TestCase): client_plugin_mock.get_role_id.assert_called_once_with('role_1') -class KeystoneProjectConstraintTest(testtools.TestCase): +class KeystoneProjectConstraintTest(common.HeatTestCase): def test_expected_exceptions(self): self.assertEqual((exceptions.KeystoneProjectNotFound,), @@ -58,7 +61,7 @@ class KeystoneProjectConstraintTest(testtools.TestCase): client_plugin_mock.get_project_id.assert_called_once_with('project_1') -class KeystoneGroupConstraintTest(testtools.TestCase): +class KeystoneGroupConstraintTest(common.HeatTestCase): def test_expected_exceptions(self): self.assertEqual((exceptions.KeystoneGroupNotFound,), @@ -78,7 +81,7 @@ class KeystoneGroupConstraintTest(testtools.TestCase): client_plugin_mock.get_group_id.assert_called_once_with('group_1') -class KeystoneDomainConstraintTest(testtools.TestCase): +class KeystoneDomainConstraintTest(common.HeatTestCase): def test_expected_exceptions(self): self.assertEqual((exceptions.KeystoneDomainNotFound,), @@ -96,3 +99,117 @@ class KeystoneDomainConstraintTest(testtools.TestCase): 'domain_1')) client_plugin_mock.get_domain_id.assert_called_once_with('domain_1') + + +class KeystoneServiceConstraintTest(common.HeatTestCase): + + sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd151' + + def test_expected_exceptions(self): + self.assertEqual((exceptions.KeystoneServiceNotFound, + exceptions.KeystoneServiceNameConflict,), + client.KeystoneServiceConstraint.expected_exceptions, + "KeystoneServiceConstraint expected exceptions error") + + def test_constrain(self): + constrain = client.KeystoneServiceConstraint() + client_mock = mock.MagicMock() + client_plugin_mock = mock.MagicMock() + client_plugin_mock.get_service_id.return_value = self.sample_uuid + client_mock.client_plugin.return_value = client_plugin_mock + + self.assertIsNone(constrain.validate_with_client(client_mock, + self.sample_uuid)) + + client_plugin_mock.get_service_id.assert_called_once_with( + self.sample_uuid + ) + + +class KeystoneClientPluginServiceTest(common.HeatTestCase): + + sample_uuid = '477e8273-60a7-4c41-b683-fdb0bc7cd152' + sample_name = 'sample_service' + + def _get_mock_service(self): + srv = mock.MagicMock() + srv.id = self.sample_uuid + srv.name = self.sample_name + return srv + + def setUp(self): + super(KeystoneClientPluginServiceTest, self).setUp() + self._client = mock.MagicMock() + self._client.client = mock.MagicMock() + self._client.client.services = mock.MagicMock() + + @mock.patch.object(client.KeystoneClientPlugin, 'client') + def test_get_service_id(self, client_keystone): + + self._client.client.services.get.return_value = (self + ._get_mock_service()) + + client_keystone.return_value = self._client + client_plugin = client.KeystoneClientPlugin( + context=mock.MagicMock() + ) + + self.assertEqual(self.sample_uuid, + client_plugin.get_service_id(self.sample_uuid)) + + @mock.patch.object(client.KeystoneClientPlugin, 'client') + def test_get_service_id_with_name(self, client_keystone): + self._client.client.services.get.side_effect = (keystone_exceptions + .NotFound) + self._client.client.services.list.return_value = [ + self._get_mock_service() + ] + + client_keystone.return_value = self._client + client_plugin = client.KeystoneClientPlugin( + context=mock.MagicMock() + ) + + self.assertEqual(self.sample_uuid, + client_plugin.get_service_id(self.sample_name)) + + @mock.patch.object(client.KeystoneClientPlugin, 'client') + def test_get_service_id_with_name_conflict(self, client_keystone): + self._client.client.services.get.side_effect = (keystone_exceptions + .NotFound) + self._client.client.services.list.return_value = [ + self._get_mock_service(), + self._get_mock_service() + ] + + client_keystone.return_value = self._client + client_plugin = client.KeystoneClientPlugin( + context=mock.MagicMock() + ) + + ex = self.assertRaises(exceptions.KeystoneServiceNameConflict, + client_plugin.get_service_id, + self.sample_name) + msg = ("Keystone has more than one service with same name " + "%s. Please use service id instead of name" % + self.sample_name) + self.assertEqual(msg, six.text_type(ex)) + + @mock.patch.object(client.KeystoneClientPlugin, 'client') + def test_get_service_id_not_found(self, client_keystone): + self._client.client.services.get.side_effect = (keystone_exceptions + .NotFound) + self._client.client.services.list.return_value = [ + ] + + client_keystone.return_value = self._client + client_plugin = client.KeystoneClientPlugin( + context=mock.MagicMock() + ) + + ex = self.assertRaises(exceptions.KeystoneServiceNotFound, + client_plugin.get_service_id, + self.sample_name) + msg = ("Keystone service %s does not found" % + self.sample_name) + self.assertEqual(msg, six.text_type(ex)) diff --git a/contrib/heat_keystone/setup.cfg b/contrib/heat_keystone/setup.cfg index d43e14f21..0dbffa1c1 100644 --- a/contrib/heat_keystone/setup.cfg +++ b/contrib/heat_keystone/setup.cfg @@ -1,6 +1,5 @@ [metadata] name = heat-contrib-keystone -version = 0.1 summary = Heat resources for Keystone description-file = README.md @@ -35,6 +34,7 @@ heat.constraints = keystone.domain=heat_keystone.client:KeystoneDomainConstraint keystone.project=heat_keystone.client:KeystoneProjectConstraint keystone.group=heat_keystone.client:KeystoneGroupConstraint + keystone.service=heat_keystone.client:KeystoneServiceConstraint [global] setup-hooks =