Add resource OS::Cinder::QoSAssociation
Add association resource between volume types and QoS specs. blueprint update-cinder-resources Change-Id: I448bfeed7914308779ab36fe33966e57acaec02b
This commit is contained in:
parent
49226daee0
commit
cd090780eb
@ -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"
|
||||
}
|
||||
|
@ -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,
|
||||
}
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
)
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user