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
This commit is contained in:
parent
69a3599767
commit
693b95649d
28
contrib/heat_keystone/README.md
Normal file
28
contrib/heat_keystone/README.md
Normal file
@ -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.
|
0
contrib/heat_keystone/heat_keystone/__init__.py
Normal file
0
contrib/heat_keystone/heat_keystone/__init__.py
Normal file
102
contrib/heat_keystone/heat_keystone/client.py
Normal file
102
contrib/heat_keystone/heat_keystone/client.py
Normal file
@ -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)
|
31
contrib/heat_keystone/heat_keystone/exceptions.py
Normal file
31
contrib/heat_keystone/heat_keystone/exceptions.py
Normal file
@ -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")
|
98
contrib/heat_keystone/heat_keystone/tests/test_client.py
Normal file
98
contrib/heat_keystone/heat_keystone/tests/test_client.py
Normal file
@ -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')
|
41
contrib/heat_keystone/setup.cfg
Normal file
41
contrib/heat_keystone/setup.cfg
Normal file
@ -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
|
||||
|
29
contrib/heat_keystone/setup.py
Normal file
29
contrib/heat_keystone/setup.py
Normal file
@ -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)
|
Loading…
x
Reference in New Issue
Block a user