Add OS::Cinder::QoSSpecs

Add resource for Cinder QoS to define Volume specs.

blueprint update-cinder-resources

Change-Id: Ib30ddd90b661100f2c7ec532d7e9d9ed745925f7
This commit is contained in:
ricolin 2015-11-23 22:21:38 +08:00
parent 74506411d5
commit 831e23d2af
3 changed files with 202 additions and 1 deletions

View File

@ -90,5 +90,6 @@
"resource_types:OS::Manila::ShareType": "rule:project_admin",
"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::Nova::HostAggregate": "rule:project_admin",
"resource_types:OS::Cinder::QoSSpecs": "rule:project_admin"
}

View File

@ -0,0 +1,99 @@
#
# 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 oslo_log import log as logging
from heat.common.i18n import _
from heat.engine import properties
from heat.engine import resource
from heat.engine import support
LOG = logging.getLogger(__name__)
class QoSSpecs(resource.Resource):
"""A resource for creating cinder QoS specs.
Users can ask for a specific volume type. Part of that volume type is a
string that defines the QoS of the volume IO (fast, normal, or slow).
Backends that can handle all of the demands of the volume type become
candidates for scheduling. Usage of this resource restricted to admins
only by default policy.
"""
support_status = support.SupportStatus(version='7.0.0')
default_client_name = 'cinder'
entity = 'qos_specs'
required_service_extension = 'qos-specs'
PROPERTIES = (
NAME, SPECS,
) = (
'name', 'specs',
)
properties_schema = {
NAME: properties.Schema(
properties.Schema.STRING,
_('Name of the QoS.'),
),
SPECS: properties.Schema(
properties.Schema.MAP,
_('The specs key and value pairs of the QoS.'),
required=True,
update_allowed=True
),
}
def _find_diff(self, update_prps, stored_prps):
remove_prps = list(
set(stored_prps.keys() or []) - set(update_prps.keys() or [])
)
add_prps = dict(set(update_prps.items() or []) - set(
stored_prps.items() or []))
return add_prps, remove_prps
def handle_create(self):
name = (self.properties[self.NAME] or
self.physical_resource_name())
specs = self.properties[self.SPECS]
qos = self.client().qos_specs.create(name, specs)
self.resource_id_set(qos.id)
def handle_update(self, json_snippet, tmpl_diff, prop_diff):
"""Update the specs for QoS."""
new_specs = prop_diff.get(self.SPECS)
old_specs = self.properties[self.SPECS]
add_specs, remove_specs = self._find_diff(new_specs, old_specs)
if self.resource_id is not None:
# Set new specs to QoS Specs
if add_specs:
self.client().qos_specs.set_keys(self.resource_id, add_specs)
# Unset old specs from QoS Specs
if remove_specs:
self.client().qos_specs.unset_keys(self.resource_id,
remove_specs)
def handle_delete(self):
if self.resource_id is not None:
self.client().qos_specs.disassociate_all(self.resource_id)
super(QoSSpecs, self).handle_delete()
def resource_mapping():
return {
'OS::Cinder::QoSSpecs': QoSSpecs,
}

View File

@ -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.clients.os import cinder as c_plugin
from heat.engine.resources.openstack.cinder import qos_specs
from heat.engine import stack
from heat.engine import template
from heat.tests import common
from heat.tests import utils
QOS_SPECS_TEMPLATE = {
'heat_template_version': '2015-10-15',
'description': 'Cinder QoS specs creation example',
'resources': {
'my_qos_specs': {
'type': 'OS::Cinder::QoSSpecs',
'properties': {
'name': 'foobar',
'specs': {"foo": "bar", "foo1": "bar1"}
}
}
}
}
class QoSSpecsTest(common.HeatTestCase):
def setUp(self):
super(QoSSpecsTest, self).setUp()
self.ctx = utils.dummy_context()
self.patchobject(c_plugin.CinderClientPlugin, 'has_extension',
return_value=True)
self.stack = stack.Stack(
self.ctx, 'cinder_qos_spec_test_stack',
template.Template(QOS_SPECS_TEMPLATE)
)
self.my_qos_specs = self.stack['my_qos_specs']
cinder_client = mock.MagicMock()
self.cinderclient = mock.MagicMock()
self.my_qos_specs.client = cinder_client
cinder_client.return_value = self.cinderclient
self.qos_specs = self.cinderclient.qos_specs
self.value = mock.MagicMock()
self.value.id = '927202df-1afb-497f-8368-9c2d2f26e5db'
self.value.name = 'foobar'
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(qos_specs.QoSSpecs,
mapping['OS::Cinder::QoSSpecs'])
self.assertIsInstance(self.my_qos_specs,
qos_specs.QoSSpecs)
def _set_up_qos_specs_environment(self):
self.qos_specs.create.return_value = self.value
self.my_qos_specs.handle_create()
def test_qos_specs_handle_create_specs(self):
self._set_up_qos_specs_environment()
self.assertEqual(1, self.qos_specs.create.call_count)
self.assertEqual(self.value.id, self.my_qos_specs.resource_id)
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"]
self.my_qos_specs.handle_update(
json_snippet=None, tmpl_diff=None, prop_diff=prop_diff
)
self.qos_specs.set_keys.assert_called_once_with(
resource_id,
set_expected
)
self.qos_specs.unset_keys.assert_called_once_with(
resource_id,
unset_expected
)
def test_qos_specs_handle_delete_specs(self):
self._set_up_qos_specs_environment()
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)