From 693b95649df476dd08f5fb9158e6c0cdef0a2a8e Mon Sep 17 00:00:00 2001 From: Kanagaraj Manickam Date: Thu, 5 Mar 2015 08:58:44 +0530 Subject: [PATCH] Keystone resource client plugin and constrains Adds Keystone client plugin and custom constrains for role, project, group and domain Change-Id: I10b4a18c4aa303c72a1e1316b28d738cc44b134c Implements: blueprint keystone-resources --- contrib/heat_keystone/README.md | 28 +++++ .../heat_keystone/heat_keystone/__init__.py | 0 contrib/heat_keystone/heat_keystone/client.py | 102 ++++++++++++++++++ .../heat_keystone/heat_keystone/exceptions.py | 31 ++++++ .../heat_keystone/resources/__init__.py | 0 .../heat_keystone/tests/__init__.py | 0 .../heat_keystone/tests/test_client.py | 98 +++++++++++++++++ contrib/heat_keystone/setup.cfg | 41 +++++++ contrib/heat_keystone/setup.py | 29 +++++ 9 files changed, 329 insertions(+) create mode 100644 contrib/heat_keystone/README.md create mode 100644 contrib/heat_keystone/heat_keystone/__init__.py create mode 100644 contrib/heat_keystone/heat_keystone/client.py create mode 100644 contrib/heat_keystone/heat_keystone/exceptions.py create mode 100644 contrib/heat_keystone/heat_keystone/resources/__init__.py create mode 100644 contrib/heat_keystone/heat_keystone/tests/__init__.py create mode 100644 contrib/heat_keystone/heat_keystone/tests/test_client.py create mode 100644 contrib/heat_keystone/setup.cfg create mode 100644 contrib/heat_keystone/setup.py diff --git a/contrib/heat_keystone/README.md b/contrib/heat_keystone/README.md new file mode 100644 index 000000000..0bf823c3a --- /dev/null +++ b/contrib/heat_keystone/README.md @@ -0,0 +1,28 @@ +Keystone plugin for OpenStack Heat +================================== + +This plugin enables Keystone resources in a Heat template for +following resources types: +- Keystone Role (OS::Keystone:Role) +- Keystone Project (OS::Keystone:Project) +- Keystone Group (OS::Keystone:Group) +- Keystone User (OS::Keystone:User) + +And it provides Custom Constrains for following keystone entities +- Keystone role +- Keystone domain +- Keystone project +- Keystone group + +### 1. Install the Keystone plugin in Heat + +NOTE: These instructions assume the value of heat.conf plugin_dirs includes +the default directory /usr/lib/heat. + +To install the plugin, from this directory run: + sudo python ./setup.py install + +### 2. Restart heat + +Only the process "heat-engine" needs to be restarted to load the newly +installed plugin. diff --git a/contrib/heat_keystone/heat_keystone/__init__.py b/contrib/heat_keystone/heat_keystone/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/heat_keystone/heat_keystone/client.py b/contrib/heat_keystone/heat_keystone/client.py new file mode 100644 index 000000000..82eee5a74 --- /dev/null +++ b/contrib/heat_keystone/heat_keystone/client.py @@ -0,0 +1,102 @@ +# +# 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. + +import exceptions + +from keystoneclient import exceptions as keystone_exceptions + +from heat.engine.clients.os import keystone +from heat.engine import constraints + + +class KeystoneClientPlugin(keystone.KeystoneClientPlugin): + + def get_role_id(self, role): + try: + role_obj = self.client().client.roles.get(role) + return role_obj.id + except keystone_exceptions.NotFound: + role_list = self.client().client.roles.list(name=role) + for role_obj in role_list: + if role_obj.name == role: + return role_obj.id + + raise exceptions.KeystoneRoleNotFound(role_id=role) + + def get_project_id(self, project): + try: + project_obj = self.client().client.projects.get(project) + return project_obj.id + except keystone_exceptions.NotFound: + project_list = self.client().client.projects.list(name=project) + for project_obj in project_list: + if project_obj.name == project: + return project_obj.id + + raise exceptions.KeystoneProjectNotFound(project_id=project) + + def get_domain_id(self, domain): + try: + domain_obj = self.client().client.domains.get(domain) + return domain_obj.id + except keystone_exceptions.NotFound: + domain_list = self.client().client.domains.list(name=domain) + for domain_obj in domain_list: + if domain_obj.name == domain: + return domain_obj.id + + raise exceptions.KeystoneDomainNotFound(domain_id=domain) + + def get_group_id(self, group): + try: + group_obj = self.client().client.groups.get(group) + return group_obj.id + except keystone_exceptions.NotFound: + group_list = self.client().client.groups.list(name=group) + for group_obj in group_list: + if group_obj.name == group: + return group_obj.id + + raise exceptions.KeystoneGroupNotFound(group_id=group) + + +class KeystoneRoleConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exceptions.KeystoneRoleNotFound,) + + def validate_with_client(self, client, role): + client.client_plugin('keystone').get_role_id(role) + + +class KeystoneDomainConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exceptions.KeystoneDomainNotFound,) + + def validate_with_client(self, client, domain): + client.client_plugin('keystone').get_domain_id(domain) + + +class KeystoneProjectConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exceptions.KeystoneProjectNotFound,) + + def validate_with_client(self, client, project): + client.client_plugin('keystone').get_project_id(project) + + +class KeystoneGroupConstraint(constraints.BaseCustomConstraint): + + expected_exceptions = (exceptions.KeystoneGroupNotFound,) + + def validate_with_client(self, client, group): + client.client_plugin('keystone').get_group_id(group) diff --git a/contrib/heat_keystone/heat_keystone/exceptions.py b/contrib/heat_keystone/heat_keystone/exceptions.py new file mode 100644 index 000000000..9cb58f78f --- /dev/null +++ b/contrib/heat_keystone/heat_keystone/exceptions.py @@ -0,0 +1,31 @@ +# +# 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 heat.common import exception +from heat.common.i18n import _ + + +class KeystoneRoleNotFound(exception.HeatException): + msg_fmt = _("Keystone role %(role_id)s does not found") + + +class KeystoneProjectNotFound(exception.HeatException): + msg_fmt = _("Keystone project %(project_id)s does not found") + + +class KeystoneDomainNotFound(exception.HeatException): + msg_fmt = _("Keystone domain %(domain_id)s does not found") + + +class KeystoneGroupNotFound(exception.HeatException): + msg_fmt = _("Keystone group %(group_id)s does not found") diff --git a/contrib/heat_keystone/heat_keystone/resources/__init__.py b/contrib/heat_keystone/heat_keystone/resources/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/heat_keystone/heat_keystone/tests/__init__.py b/contrib/heat_keystone/heat_keystone/tests/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/contrib/heat_keystone/heat_keystone/tests/test_client.py b/contrib/heat_keystone/heat_keystone/tests/test_client.py new file mode 100644 index 000000000..26080c0da --- /dev/null +++ b/contrib/heat_keystone/heat_keystone/tests/test_client.py @@ -0,0 +1,98 @@ +# +# 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. + +import mock +import testtools + +from .. import client # noqa +from .. import exceptions # noqa + + +class KeystoneRoleConstraintTest(testtools.TestCase): + + def test_expected_exceptions(self): + self.assertEqual((exceptions.KeystoneRoleNotFound,), + client.KeystoneRoleConstraint.expected_exceptions, + "KeystoneRoleConstraint expected exceptions error") + + def test_constrain(self): + constrain = client.KeystoneRoleConstraint() + client_mock = mock.MagicMock() + client_plugin_mock = mock.MagicMock() + client_plugin_mock.get_role_id.return_value = None + client_mock.client_plugin.return_value = client_plugin_mock + + self.assertIsNone(constrain.validate_with_client(client_mock, + 'role_1')) + + client_plugin_mock.get_role_id.assert_called_once_with('role_1') + + +class KeystoneProjectConstraintTest(testtools.TestCase): + + def test_expected_exceptions(self): + self.assertEqual((exceptions.KeystoneProjectNotFound,), + client.KeystoneProjectConstraint.expected_exceptions, + "KeystoneProjectConstraint expected exceptions error") + + def test_constrain(self): + constrain = client.KeystoneProjectConstraint() + client_mock = mock.MagicMock() + client_plugin_mock = mock.MagicMock() + client_plugin_mock.get_project_id.return_value = None + client_mock.client_plugin.return_value = client_plugin_mock + + self.assertIsNone(constrain.validate_with_client(client_mock, + 'project_1')) + + client_plugin_mock.get_project_id.assert_called_once_with('project_1') + + +class KeystoneGroupConstraintTest(testtools.TestCase): + + def test_expected_exceptions(self): + self.assertEqual((exceptions.KeystoneGroupNotFound,), + client.KeystoneGroupConstraint.expected_exceptions, + "KeystoneGroupConstraint expected exceptions error") + + def test_constrain(self): + constrain = client.KeystoneGroupConstraint() + client_mock = mock.MagicMock() + client_plugin_mock = mock.MagicMock() + client_plugin_mock.get_group_id.return_value = None + client_mock.client_plugin.return_value = client_plugin_mock + + self.assertIsNone(constrain.validate_with_client(client_mock, + 'group_1')) + + client_plugin_mock.get_group_id.assert_called_once_with('group_1') + + +class KeystoneDomainConstraintTest(testtools.TestCase): + + def test_expected_exceptions(self): + self.assertEqual((exceptions.KeystoneDomainNotFound,), + client.KeystoneDomainConstraint.expected_exceptions, + "KeystoneDomainConstraint expected exceptions error") + + def test_constrain(self): + constrain = client.KeystoneDomainConstraint() + client_mock = mock.MagicMock() + client_plugin_mock = mock.MagicMock() + client_plugin_mock.get_domain_id.return_value = None + client_mock.client_plugin.return_value = client_plugin_mock + + self.assertIsNone(constrain.validate_with_client(client_mock, + 'domain_1')) + + client_plugin_mock.get_domain_id.assert_called_once_with('domain_1') \ No newline at end of file diff --git a/contrib/heat_keystone/setup.cfg b/contrib/heat_keystone/setup.cfg new file mode 100644 index 000000000..178a9996d --- /dev/null +++ b/contrib/heat_keystone/setup.cfg @@ -0,0 +1,41 @@ +[metadata] +name = heat-contrib-keystone +summary = Heat resources for Keystone +description-file = + README.md +author = OpenStack +author-email = openstack-dev@lists.openstack.org +home-page = http://www.openstack.org/ +classifier = + Environment :: OpenStack + Intended Audience :: Information Technology + Intended Audience :: System Administrators + License :: OSI Approved :: Apache Software License + Operating System :: POSIX :: Linux + Programming Language :: Python + Programming Language :: Python :: 2 + Programming Language :: Python :: 2.7 + Programming Language :: Python :: 2.6 + +[files] +packages = + heat_keystone + +# Copy to /usr/lib/heat for plugin loading +data_files = + lib/heat/keystone = heat_keystone/resources/* + +[entry_points] +heat.clients = + keystone=heat_keystone.client:KeystoneClientPlugin + +heat.constraints = + keystone.role=heat_keystone.client:KeystoneRoleConstraint + keystone.domain=heat_keystone.client:KeystoneDomainConstraint + keystone.project=heat_keystone.client:KeystoneProjectConstraint + keystone.group=heat_keystone.client:KeystoneGroupConstraint + +[global] +setup-hooks = + pbr.hooks.setup_hook + diff --git a/contrib/heat_keystone/setup.py b/contrib/heat_keystone/setup.py new file mode 100644 index 000000000..62f38a592 --- /dev/null +++ b/contrib/heat_keystone/setup.py @@ -0,0 +1,29 @@ +#!/usr/bin/env python +# +# 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. + +# THIS FILE IS MANAGED BY THE GLOBAL REQUIREMENTS REPO - DO NOT EDIT +import setuptools + +# In python < 2.7.4, a lazy loading of package `pbr` will break +# setuptools if some other modules registered functions in `atexit`. +# solution from: http://bugs.python.org/issue15881#msg170215 +try: + import multiprocessing # noqa +except ImportError: + pass + +setuptools.setup( + setup_requires=['pbr'], + pbr=True)