Merge "Keystone resource client plugin and constrains"
This commit is contained in:
commit
bb4149446a
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…
Reference in New Issue
Block a user