diff --git a/contrib/cinder_volume_type/README.md b/contrib/cinder_volume_type/README.md new file mode 100644 index 0000000000..f89258137e --- /dev/null +++ b/contrib/cinder_volume_type/README.md @@ -0,0 +1,49 @@ +Cinder volume_type plugin for OpenStack Heat +============================================ + +This plugin enables using Cinder volume_types as resources in a Heat template. + + +### 1. Install the Cinder volume_type 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 new installed +plugin. + +### Template Format + +Here's an example cinder volume_type and cinder volume resources: +```yaml +heat_template_version: 2013-05-23 +description: Heat Cinder creation with volume_type example +resources: + my_volume_type: + type: OS::Cinder::VolumeType + properties: + name: volumeBackend + metadata: {volume_backend_name: lvmdriver} + my_volume: + type: OS::Cinder::Volume + properties: + size: 1 + volume_type: {get_resource: my_volume_type} +``` + +### Issues with the Cinder volume_type plugin + +By default only users who have the admin role can manage volume +types because of the default policy in +Cinder: ```"volume_extension:types_manage": "rule:admin_api"``` + +To let the possibility to all users to create volume type, the rule must be +replaced with the following: ```"volume_extension:types_manage": ""``` + +The following error occurs if the policy has not been correctly set: + ERROR: Policy doesn't allow volume_extension:types_manage to be performed. \ No newline at end of file diff --git a/contrib/cinder_volume_type/cinder_volume_type/__init__.py b/contrib/cinder_volume_type/cinder_volume_type/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/cinder_volume_type/cinder_volume_type/resources/__init__.py b/contrib/cinder_volume_type/cinder_volume_type/resources/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/cinder_volume_type/cinder_volume_type/resources/cinder_volume_type.py b/contrib/cinder_volume_type/cinder_volume_type/resources/cinder_volume_type.py new file mode 100644 index 0000000000..4b5ab5fb0a --- /dev/null +++ b/contrib/cinder_volume_type/cinder_volume_type/resources/cinder_volume_type.py @@ -0,0 +1,77 @@ +# +# 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.i18n import _ +from heat.engine import properties +from heat.engine import resource + + +class CinderVolumeType(resource.Resource): + """ + A resource for creating OpenStack virtual hardware templates. + + Note that default cinder security policy usage of this resource + is limited to being used by administrators only. + """ + + PROPERTIES = ( + NAME, METADATA, + ) = ( + 'name', 'metadata', + ) + + properties_schema = { + NAME: properties.Schema( + properties.Schema.STRING, + _('Name of the volume type.'), + required=True + ), + METADATA: properties.Schema( + properties.Schema.MAP, + _('The extra specs key and value pairs of the volume type.'), + update_allowed=True + ), + } + + def handle_create(self): + vtype_name = self.properties.get(self.NAME) + volume_type = self.cinder().volume_types.create(vtype_name) + self.resource_id_set(volume_type.id) + vtype_metadata = self.properties.get(self.METADATA) + if vtype_metadata: + volume_type.set_keys(vtype_metadata) + + def handle_update(self, json_snippet, tmpl_diff, prop_diff): + """Update the key-value pairs of cinder volume type.""" + if self.METADATA in prop_diff: + volume_type = self.cinder().volume_types.get(self.resource_id) + old_keys = volume_type.get_keys() + volume_type.unset_keys(old_keys) + new_keys = prop_diff.get(self.METADATA) + if new_keys is not None: + volume_type.set_keys(new_keys) + + def handle_delete(self): + if self.resource_id is None: + return + + try: + self.cinder().volume_types.delete(self.resource_id) + except Exception as e: + self.client_plugin('cinder').ignore_not_found(e) + + +def resource_mapping(): + return { + 'OS::Cinder::VolumeType': CinderVolumeType + } diff --git a/contrib/cinder_volume_type/cinder_volume_type/tests/__init__.py b/contrib/cinder_volume_type/cinder_volume_type/tests/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/contrib/cinder_volume_type/cinder_volume_type/tests/test_cinder_volume_type.py b/contrib/cinder_volume_type/cinder_volume_type/tests/test_cinder_volume_type.py new file mode 100644 index 0000000000..e018f607ce --- /dev/null +++ b/contrib/cinder_volume_type/cinder_volume_type/tests/test_cinder_volume_type.py @@ -0,0 +1,101 @@ +# +# 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 + +from heat.engine import parser +from heat.engine import resource +from heat.engine import template +from heat.tests import common +from heat.tests import utils + +from ..resources.cinder_volume_type import CinderVolumeType # noqa +from ..resources.cinder_volume_type import resource_mapping # noqa + +volume_type_template = { + 'heat_template_version': '2013-05-23', + 'resources': { + 'my_volume_type': { + 'type': 'OS::Cinder::VolumeType', + 'properties': { + 'name': 'volumeBackend', + 'metadata': {'volume_backend_name': 'lvmdriver'} + } + } + } +} + + +class CinderVolumeTypeTest(common.HeatTestCase): + def setUp(self): + super(CinderVolumeTypeTest, self).setUp() + + self.ctx = utils.dummy_context() + + # For unit testing purpose. Register resource provider + # explicitly. + resource._register_class('OS::Cinder::VolumeType', CinderVolumeType) + + self.stack = parser.Stack( + self.ctx, 'cinder_volume_type_test_stack', + template.Template(volume_type_template) + ) + + self.my_volume_type = self.stack['my_volume_type'] + cinder = mock.MagicMock() + self.cinderclient = mock.MagicMock() + self.my_volume_type.cinder = cinder + cinder.return_value = self.cinderclient + self.volume_types = self.cinderclient.volume_types + + def test_resource_mapping(self): + mapping = resource_mapping() + self.assertEqual(1, len(mapping)) + self.assertEqual(CinderVolumeType, mapping['OS::Cinder::VolumeType']) + self.assertIsInstance(self.my_volume_type, CinderVolumeType) + + def test_volume_type_handle_create(self): + value = mock.MagicMock() + volume_type_id = '927202df-1afb-497f-8368-9c2d2f26e5db' + value.id = volume_type_id + self.volume_types.create.return_value = value + self.my_volume_type.handle_create() + value.set_keys.assert_called_once_with( + {'volume_backend_name': 'lvmdriver'}) + self.assertEqual(volume_type_id, self.my_volume_type.resource_id) + + def test_volume_type_handle_update_matadata(self): + value = mock.MagicMock() + self.volume_types.get.return_value = value + value.get_keys.return_value = {'volume_backend_name': 'lvmdriver'} + + new_keys = {'volume_backend_name': 'lvmdriver', + 'capabilities:replication': 'True'} + prop_diff = {'metadata': new_keys} + self.my_volume_type.handle_update(json_snippet=None, + tmpl_diff=None, + prop_diff=prop_diff) + value.unset_keys.assert_called_once_with( + {'volume_backend_name': 'lvmdriver'}) + value.set_keys.assert_called_once_with(new_keys) + + def test_volume_type_handle_delete(self): + self.resource_id = None + self.assertIsNone(self.my_volume_type.handle_delete()) + volume_type_id = '927202df-1afb-497f-8368-9c2d2f26e5db' + self.my_volume_type.resource_id = volume_type_id + self.volume_types.delete.return_value = None + self.assertIsNone(self.my_volume_type.handle_delete()) + exc = self.cinderclient.HTTPClientError('Not Found.') + self.volume_types.delete.side_effect = exc + self.assertIsNone(self.my_volume_type.handle_delete()) diff --git a/contrib/cinder_volume_type/setup.cfg b/contrib/cinder_volume_type/setup.cfg new file mode 100644 index 0000000000..2771554ede --- /dev/null +++ b/contrib/cinder_volume_type/setup.cfg @@ -0,0 +1,27 @@ +[metadata] +name = heat-contrib-cinder-volume-type +summary = Heat resource for managing cinder volume_types +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] +# Copy to /usr/lib/heat for plugin loading +data_files = + lib/heat/cinder_volume_type = cinder_volume_type/resources/* + +[global] +setup-hooks = + pbr.hooks.setup_hook diff --git a/contrib/cinder_volume_type/setup.py b/contrib/cinder_volume_type/setup.py new file mode 100644 index 0000000000..62f38a592f --- /dev/null +++ b/contrib/cinder_volume_type/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)