summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorricolin <rico.l@inwinstack.com>2015-11-24 16:09:06 +0800
committerricolin <rico.l@inwinstack.com>2016-09-19 12:30:23 +0800
commitcd090780eb634b76e7ba0b9148479f94d719bd28 (patch)
tree7c592ab96c0306c8d22a7ccf444fb3aee01b34fa
parent49226daee01c1cca4b289f6511fa80b257ee259b (diff)
downloadheat-cd090780eb634b76e7ba0b9148479f94d719bd28.tar.gz
Add resource OS::Cinder::QoSAssociation
Add association resource between volume types and QoS specs. blueprint update-cinder-resources Change-Id: I448bfeed7914308779ab36fe33966e57acaec02b
-rw-r--r--etc/heat/policy.json3
-rw-r--r--heat/engine/resources/openstack/cinder/qos_specs.py94
-rw-r--r--heat/tests/common.py4
-rw-r--r--heat/tests/openstack/cinder/test_qos_specs.py110
-rw-r--r--releasenotes/notes/bp-update-cinder-resources-e23e62762f167d29.yaml5
5 files changed, 210 insertions, 6 deletions
diff --git a/etc/heat/policy.json b/etc/heat/policy.json
index c9aae5ff7..7a498d4db 100644
--- a/etc/heat/policy.json
+++ b/etc/heat/policy.json
@@ -92,5 +92,6 @@
"resource_types:OS::Neutron::QoSPolicy": "rule:project_admin",
"resource_types:OS::Neutron::QoSBandwidthLimitRule": "rule:project_admin",
"resource_types:OS::Nova::HostAggregate": "rule:project_admin",
- "resource_types:OS::Cinder::QoSSpecs": "rule:project_admin"
+ "resource_types:OS::Cinder::QoSSpecs": "rule:project_admin",
+ "resource_types:OS::Cinder::QoSAssociation": "rule:project_admin"
}
diff --git a/heat/engine/resources/openstack/cinder/qos_specs.py b/heat/engine/resources/openstack/cinder/qos_specs.py
index 70174a35f..e77abb163 100644
--- a/heat/engine/resources/openstack/cinder/qos_specs.py
+++ b/heat/engine/resources/openstack/cinder/qos_specs.py
@@ -12,9 +12,11 @@
# under the License.
from heat.common.i18n import _
+from heat.engine import constraints
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
+from heat.engine import translation
class QoSSpecs(resource.Resource):
@@ -89,7 +91,99 @@ class QoSSpecs(resource.Resource):
super(QoSSpecs, self).handle_delete()
+class QoSAssociation(resource.Resource):
+ """A resource to associate cinder QoS specs with volume types.
+
+ Usage of this resource restricted to admins only by default policy.
+ """
+
+ support_status = support.SupportStatus(version='8.0.0')
+
+ default_client_name = 'cinder'
+
+ required_service_extension = 'qos-specs'
+
+ PROPERTIES = (
+ QOS_SPECS, VOLUME_TYPES,
+ ) = (
+ 'qos_specs', 'volume_types',
+ )
+
+ properties_schema = {
+ QOS_SPECS: properties.Schema(
+ properties.Schema.STRING,
+ _('ID or Name of the QoS specs.'),
+ required=True,
+ constraints=[
+ constraints.CustomConstraint('cinder.qos_specs')
+ ],
+ ),
+ VOLUME_TYPES: properties.Schema(
+ properties.Schema.LIST,
+ _('List of volume type IDs or Names to be attached to QoS specs.'),
+ schema=properties.Schema(
+ properties.Schema.STRING,
+ _('A volume type to attach specs.'),
+ constraints=[
+ constraints.CustomConstraint('cinder.vtype')
+ ],
+ ),
+ update_allowed=True,
+ required=True,
+
+ ),
+ }
+
+ def translation_rules(self, props):
+ return [
+ translation.TranslationRule(
+ props,
+ translation.TranslationRule.RESOLVE,
+ [self.VOLUME_TYPES],
+ client_plugin=self.client_plugin(),
+ finder='get_volume_type'
+ ),
+ translation.TranslationRule(
+ props,
+ translation.TranslationRule.RESOLVE,
+ [self.QOS_SPECS],
+ client_plugin=self.client_plugin(),
+ finder='get_qos_specs'
+ )
+ ]
+
+ def _find_diff(self, update_prps, stored_prps):
+ add_prps = list(set(update_prps or []) - set(stored_prps or []))
+ remove_prps = list(set(stored_prps or []) - set(update_prps or []))
+ return add_prps, remove_prps
+
+ def handle_create(self):
+ for vt in self.properties[self.VOLUME_TYPES]:
+ self.client().qos_specs.associate(self.properties[self.QOS_SPECS],
+ vt)
+
+ def handle_update(self, json_snippet, tmpl_diff, prop_diff):
+ """Associate volume types to QoS."""
+
+ qos_specs = self.properties[self.QOS_SPECS]
+ new_associate_vts = prop_diff.get(self.VOLUME_TYPES)
+ old_associate_vts = self.properties[self.VOLUME_TYPES]
+ add_associate_vts, remove_associate_vts = self._find_diff(
+ new_associate_vts, old_associate_vts)
+ for vt in add_associate_vts:
+ self.client().qos_specs.associate(qos_specs, vt)
+ for vt in remove_associate_vts:
+ self.client().qos_specs.disassociate(qos_specs, vt)
+
+ def handle_delete(self):
+ volume_types = self.properties[self.VOLUME_TYPES]
+ for vt in volume_types:
+ self.client().qos_specs.disassociate(
+ self.properties[self.QOS_SPECS], vt)
+
+
def resource_mapping():
return {
'OS::Cinder::QoSSpecs': QoSSpecs,
+ 'OS::Cinder::QoSAssociation': QoSAssociation,
}
diff --git a/heat/tests/common.py b/heat/tests/common.py
index 2e631ff60..23679e081 100644
--- a/heat/tests/common.py
+++ b/heat/tests/common.py
@@ -245,6 +245,10 @@ class HeatTestCase(testscenarios.WithScenarios,
validate = self.patchobject(cinder.VolumeConstraint, 'validate')
validate.return_value = True
+ def stub_QoSSpecsConstraint_validate(self):
+ validate = self.patchobject(cinder.QoSSpecsConstraint, 'validate')
+ validate.return_value = True
+
def stub_SnapshotConstraint_validate(self):
validate = self.patchobject(
cinder.VolumeSnapshotConstraint, 'validate')
diff --git a/heat/tests/openstack/cinder/test_qos_specs.py b/heat/tests/openstack/cinder/test_qos_specs.py
index 094cc0a84..e00eda73e 100644
--- a/heat/tests/openstack/cinder/test_qos_specs.py
+++ b/heat/tests/openstack/cinder/test_qos_specs.py
@@ -34,6 +34,20 @@ QOS_SPECS_TEMPLATE = {
}
}
+QOS_ASSOCIATE_TEMPLATE = {
+ 'heat_template_version': '2015-10-15',
+ 'description': 'Cinder QoS specs association example',
+ 'resources': {
+ 'my_qos_associate': {
+ 'type': 'OS::Cinder::QoSAssociation',
+ 'properties': {
+ 'volume_types': ['ceph', 'lvm'],
+ 'qos_specs': 'foobar'
+ }
+ }
+ }
+}
+
class QoSSpecsTest(common.HeatTestCase):
@@ -55,12 +69,12 @@ class QoSSpecsTest(common.HeatTestCase):
self.value = mock.MagicMock()
self.value.id = '927202df-1afb-497f-8368-9c2d2f26e5db'
self.value.name = 'foobar'
- self.value.specs = {"foo": "bar", "foo1": "bar1"}
+ self.value.specs = {'foo': 'bar', 'foo1': 'bar1'}
self.qos_specs.create.return_value = self.value
def test_resource_mapping(self):
mapping = qos_specs.resource_mapping()
- self.assertEqual(1, len(mapping))
+ self.assertEqual(2, len(mapping))
self.assertEqual(qos_specs.QoSSpecs,
mapping['OS::Cinder::QoSSpecs'])
self.assertIsInstance(self.my_qos_specs,
@@ -78,9 +92,9 @@ class QoSSpecsTest(common.HeatTestCase):
def test_qos_specs_handle_update_specs(self):
self._set_up_qos_specs_environment()
resource_id = self.my_qos_specs.resource_id
- prop_diff = {'specs': {"foo": "bar", "bar": "bar"}}
- set_expected = {"bar": "bar"}
- unset_expected = ["foo1"]
+ prop_diff = {'specs': {'foo': 'bar', 'bar': 'bar'}}
+ set_expected = {'bar': 'bar'}
+ unset_expected = ['foo1']
self.my_qos_specs.handle_update(
json_snippet=None, tmpl_diff=None, prop_diff=prop_diff
@@ -99,3 +113,89 @@ class QoSSpecsTest(common.HeatTestCase):
resource_id = self.my_qos_specs.resource_id
self.my_qos_specs.handle_delete()
self.qos_specs.disassociate_all.assert_called_once_with(resource_id)
+
+
+class QoSAssociationTest(common.HeatTestCase):
+
+ def setUp(self):
+ super(QoSAssociationTest, self).setUp()
+ self.ctx = utils.dummy_context()
+ self.qos_specs_id = 'foobar'
+ self.patchobject(c_plugin.CinderClientPlugin, 'has_extension',
+ return_value=True)
+ self.patchobject(c_plugin.CinderClientPlugin, 'get_qos_specs',
+ return_value=self.qos_specs_id)
+ self.stack = stack.Stack(
+ self.ctx, 'cinder_qos_associate_test_stack',
+ template.Template(QOS_ASSOCIATE_TEMPLATE)
+ )
+ self.my_qos_associate = self.stack['my_qos_associate']
+ cinder_client = mock.MagicMock()
+ self.cinderclient = mock.MagicMock()
+ self.my_qos_associate.client = cinder_client
+ cinder_client.return_value = self.cinderclient
+ self.qos_specs = self.cinderclient.qos_specs
+ self.stub_QoSSpecsConstraint_validate()
+ self.stub_VolumeTypeConstraint_validate()
+
+ self.vt_ceph = 'ceph'
+ self.vt_lvm = 'lvm'
+ self.vt_new_ceph = 'new_ceph'
+
+ def test_resource_mapping(self):
+ mapping = qos_specs.resource_mapping()
+ self.assertEqual(2, len(mapping))
+ self.assertEqual(qos_specs.QoSAssociation,
+ mapping['OS::Cinder::QoSAssociation'])
+ self.assertIsInstance(self.my_qos_associate,
+ qos_specs.QoSAssociation)
+
+ def _set_up_qos_associate_environment(self):
+ self.my_qos_associate.handle_create()
+
+ def test_qos_associate_handle_create(self):
+ self.patchobject(c_plugin.CinderClientPlugin, 'get_volume_type',
+ side_effect=[self.vt_ceph, self.vt_lvm])
+ self._set_up_qos_associate_environment()
+ self.cinderclient.qos_specs.associate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_ceph
+ )
+ self.qos_specs.associate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_lvm
+ )
+
+ def test_qos_associate_handle_update(self):
+ self.patchobject(c_plugin.CinderClientPlugin, 'get_volume_type',
+ side_effect=[self.vt_lvm, self.vt_ceph,
+ self.vt_new_ceph,
+ self.vt_ceph])
+ self._set_up_qos_associate_environment()
+ prop_diff = {'volume_types': [self.vt_lvm, self.vt_new_ceph]}
+ self.my_qos_associate.handle_update(
+ json_snippet=None, tmpl_diff=None, prop_diff=prop_diff
+ )
+ self.qos_specs.associate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_new_ceph
+ )
+ self.qos_specs.disassociate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_ceph
+ )
+
+ def test_qos_associate_handle_delete_specs(self):
+ self.patchobject(c_plugin.CinderClientPlugin, 'get_volume_type',
+ side_effect=[self.vt_ceph, self.vt_lvm,
+ self.vt_ceph, self.vt_lvm])
+ self._set_up_qos_associate_environment()
+ self.my_qos_associate.handle_delete()
+ self.qos_specs.disassociate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_ceph
+ )
+ self.qos_specs.disassociate.assert_any_call(
+ self.qos_specs_id,
+ self.vt_lvm
+ )
diff --git a/releasenotes/notes/bp-update-cinder-resources-e23e62762f167d29.yaml b/releasenotes/notes/bp-update-cinder-resources-e23e62762f167d29.yaml
new file mode 100644
index 000000000..45e1f7769
--- /dev/null
+++ b/releasenotes/notes/bp-update-cinder-resources-e23e62762f167d29.yaml
@@ -0,0 +1,5 @@
+---
+features:
+ - OS::Cinder::QoSAssociation resource plugin is added to support cinder QoS
+ Specs Association with Volume Types, which is provided by cinder
+ ``qos-specs`` API extension.