diff --git a/rally-jobs/cinder.yaml b/rally-jobs/cinder.yaml index b034b38f..abca9813 100755 --- a/rally-jobs/cinder.yaml +++ b/rally-jobs/cinder.yaml @@ -1115,6 +1115,28 @@ failure_rate: max: 0 + CinderQos.create_qos_associate_and_disassociate_type: + - + args: + consumer: "both" + write_iops_sec: "10" + read_iops_sec: "1000" + runner: + type: "constant" + times: 2 + concurrency: 1 + context: + users: + tenants: 1 + users_per_tenant: 1 + volume_types: [ + "test_type1", + "test_type2" + ] + sla: + failure_rate: + max: 0 + CinderVolumeTypes.create_get_and_delete_encryption_type: - args: diff --git a/rally/plugins/openstack/scenarios/cinder/qos_specs.py b/rally/plugins/openstack/scenarios/cinder/qos_specs.py index 67a3e065..483ee08b 100644 --- a/rally/plugins/openstack/scenarios/cinder/qos_specs.py +++ b/rally/plugins/openstack/scenarios/cinder/qos_specs.py @@ -99,3 +99,36 @@ class CreateAndSetQos(cinder_utils.CinderBasic): qos = self.admin_cinder.create_qos(create_specs) self.admin_cinder.set_qos(qos=qos, set_specs_args=set_specs) + + +@validation.add("required_services", services=[consts.Service.CINDER]) +@validation.add("required_platform", platform="openstack", admin=True) +@validation.add("required_contexts", contexts=("volume_types")) +@scenario.configure( + context={"admin_cleanup": ["cinder"]}, + name="CinderQos.create_qos_associate_and_disassociate_type", + platform="openstack") +class CreateQosAssociateAndDisassociateType(cinder_utils.CinderBasic): + def run(self, consumer, write_iops_sec, read_iops_sec): + """Create a qos, Associate and Disassociate the qos from volume type. + + :param consumer: Consumer behavior + :param write_iops_sec: random write limitation + :param read_iops_sec: random read limitation + """ + specs = { + "consumer": consumer, + "write_iops_sec": write_iops_sec, + "read_iops_sec": read_iops_sec + } + + qos = self.admin_cinder.create_qos(specs) + + vt_idx = self.context["iteration"] % len(self.context["volume_types"]) + volume_type = self.context["volume_types"][vt_idx] + + self.admin_cinder.qos_associate_type(qos_specs=qos, + volume_type=volume_type["id"]) + + self.admin_cinder.qos_disassociate_type(qos_specs=qos, + volume_type=volume_type["id"]) diff --git a/rally/plugins/openstack/services/storage/block.py b/rally/plugins/openstack/services/storage/block.py index 9e52f332..0789755e 100644 --- a/rally/plugins/openstack/services/storage/block.py +++ b/rally/plugins/openstack/services/storage/block.py @@ -228,6 +228,26 @@ class BlockStorage(service.UnifiedService): return self._impl.set_qos(qos=qos, set_specs_args=set_specs_args) + @service.should_be_overridden + def qos_associate_type(self, qos_specs, volume_type): + """Associate qos specs from volume type. + + :param qos_specs: The qos specs to be associated with + :param volume_type: The volume type id to be associated with + :rtype: :class:`QoSSpecs` + """ + return self._impl.qos_associate_type(qos_specs, volume_type) + + @service.should_be_overridden + def qos_disassociate_type(self, qos_specs, volume_type): + """Disassociate qos specs from volume type. + + :param qos_specs: The qos specs to be associated with + :param volume_type: The volume type id to be disassociated with + :rtype: :class:`QoSSpecs` + """ + return self._impl.qos_disassociate_type(qos_specs, volume_type) + @service.should_be_overridden def create_snapshot(self, volume_id, force=False, name=None, description=None, metadata=None): diff --git a/rally/plugins/openstack/services/storage/cinder_common.py b/rally/plugins/openstack/services/storage/cinder_common.py index a7b9e37f..79c29157 100644 --- a/rally/plugins/openstack/services/storage/cinder_common.py +++ b/rally/plugins/openstack/services/storage/cinder_common.py @@ -249,6 +249,34 @@ class CinderMixin(object): return self._get_client().qos_specs.set_keys(qos_id, set_specs_args) + def qos_associate_type(self, qos_specs, vol_type_id): + """Associate qos specs from volume type. + + :param qos_specs: The qos specs to be associated with + :param vol_type_id: The volume type id to be associated with + :returns: base on client response return True if the request + has been accepted or not + """ + aname = "cinder_v%s.qos_associate_type" % self.version + with atomic.ActionTimer(self, aname): + tuple_res = self._get_client().qos_specs.associate(qos_specs, + vol_type_id) + return (tuple_res[0].status_code == 202) + + def qos_disassociate_type(self, qos_specs, vol_type_id): + """Disassociate qos specs from volume type. + + :param qos_specs: The qos specs to be disassociated with + :param vol_type_id: The volume type id to be disassociated with + :returns: base on client response return True if the request + has been accepted or not + """ + aname = "cinder_v%s.qos_disassociate_type" % self.version + with atomic.ActionTimer(self, aname): + tuple_res = self._get_client().qos_specs.disassociate(qos_specs, + vol_type_id) + return (tuple_res[0].status_code == 202) + def delete_snapshot(self, snapshot): """Delete the given snapshot. @@ -559,6 +587,24 @@ class UnifiedCinderMixin(object): self._impl.set_qos(qos.id, set_specs_args) return self._unify_qos(qos) + def qos_associate_type(self, qos_specs, vol_type_id): + """Associate qos specs from volume type. + + :param qos_specs: The qos specs to be associated with + :param vol_type_id: The volume type id to be associated with + """ + self._impl.qos_associate_type(qos_specs, vol_type_id) + return self._unify_qos(qos_specs) + + def qos_disassociate_type(self, qos_specs, vol_type_id): + """Disassociate qos specs from volume type. + + :param qos_specs: The qos specs to be disassociated with + :param vol_type_id: The volume type id to be disassociated with + """ + self._impl.qos_disassociate_type(qos_specs, vol_type_id) + return self._unify_qos(qos_specs) + def delete_snapshot(self, snapshot): """Delete the given backup. diff --git a/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.json b/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.json new file mode 100644 index 00000000..6c7d6371 --- /dev/null +++ b/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.json @@ -0,0 +1,34 @@ +{ + "CinderQos.create_qos_associate_and_disassociate_type": [ + { + "args": { + "consumer": "both", + "write_iops_sec": "10", + "read_iops_sec": "1000" + }, + "runner": { + "type": "constant", + "times": 5, + "concurrency": 2 + }, + "context": { + "users": { + "tenants": 2, + "users_per_tenant": 2 + }, + "volume_types": [ + "test_type1", + "test_type2", + "test_type3", + "test_type4", + "test_type5" + ] + }, + "sla": { + "failure_rate": { + "max": 0 + } + } + } + ] +} diff --git a/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.yaml b/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.yaml new file mode 100644 index 00000000..ae5bf263 --- /dev/null +++ b/samples/tasks/scenarios/cinder/creat-qos-and-associate-type.yaml @@ -0,0 +1,25 @@ +--- + CinderQos.create_qos_associate_and_disassociate_type: + - + args: + consumer: "both" + write_iops_sec: "10" + read_iops_sec: "1000" + runner: + type: "constant" + times: 5 + concurrency: 2 + context: + users: + tenants: 2 + users_per_tenant: 2 + volume_types: [ + "test_type1", + "test_type2", + "test_type3", + "test_type4", + "test_type5", + ] + sla: + failure_rate: + max: 0 diff --git a/tests/unit/plugins/openstack/scenarios/cinder/test_qos_specs.py b/tests/unit/plugins/openstack/scenarios/cinder/test_qos_specs.py index 8df5535a..0e975ecb 100644 --- a/tests/unit/plugins/openstack/scenarios/cinder/test_qos_specs.py +++ b/tests/unit/plugins/openstack/scenarios/cinder/test_qos_specs.py @@ -110,3 +110,26 @@ class CinderQosTestCase(test.ScenarioTestCase): mock_service.create_qos.assert_called_once_with(create_specs_args) mock_service.set_qos.assert_called_once_with( qos=qos, set_specs_args=set_specs_args) + + def test_create_qos_associate_and_disassociate_type(self): + mock_service = self.mock_cinder.return_value + context = self._get_context() + context.update({ + "volume_types": [{"id": "fake_id", + "name": "fake_name"}], + "iteration": 1}) + + qos = mock.MagicMock() + specs = {"consumer": "both", + "write_iops_sec": "10", + "read_iops_sec": "1000"} + + scenario = qos_specs.CreateQosAssociateAndDisassociateType(context) + mock_service.create_qos.return_value = qos + + scenario.run("both", "10", "1000") + mock_service.create_qos.assert_called_once_with(specs) + mock_service.qos_associate_type.assert_called_once_with( + qos_specs=qos, volume_type="fake_id") + mock_service.qos_disassociate_type.assert_called_once_with( + qos_specs=qos, volume_type="fake_id") diff --git a/tests/unit/plugins/openstack/services/storage/test_block.py b/tests/unit/plugins/openstack/services/storage/test_block.py index 0049cee4..2e10691a 100644 --- a/tests/unit/plugins/openstack/services/storage/test_block.py +++ b/tests/unit/plugins/openstack/services/storage/test_block.py @@ -145,6 +145,22 @@ class BlockTestCase(test.TestCase): self.service._impl.set_qos.assert_called_once_with( qos="qos", set_specs_args=set_specs_args) + def test_qos_associate_type(self): + self.assertEqual( + self.service._impl.qos_associate_type.return_value, + self.service.qos_associate_type(qos_specs="fake_qos", + volume_type="fake_type")) + self.service._impl.qos_associate_type.assert_called_once_with( + "fake_qos", "fake_type") + + def test_qos_disassociate_type(self): + self.assertEqual( + self.service._impl.qos_disassociate_type.return_value, + self.service.qos_disassociate_type(qos_specs="fake_qos", + volume_type="fake_type")) + self.service._impl.qos_disassociate_type.assert_called_once_with( + "fake_qos", "fake_type") + def test_create_snapshot(self): self.assertEqual( self.service._impl.create_snapshot.return_value, diff --git a/tests/unit/plugins/openstack/services/storage/test_cinder_common.py b/tests/unit/plugins/openstack/services/storage/test_cinder_common.py index 6a37653e..a66751d3 100644 --- a/tests/unit/plugins/openstack/services/storage/test_cinder_common.py +++ b/tests/unit/plugins/openstack/services/storage/test_cinder_common.py @@ -267,6 +267,16 @@ class CinderMixinTestCase(test.ScenarioTestCase): self.cinder.qos_specs.set_keys.assert_called_once_with("qos", set_specs_args) + def test_qos_associate_type(self): + self.service.qos_associate_type("qos", "type_id") + self.cinder.qos_specs.associate.assert_called_once_with( + "qos", "type_id") + + def test_qos_disassociate_type(self): + self.service.qos_disassociate_type("qos", "type_id") + self.cinder.qos_specs.disassociate.assert_called_once_with( + "qos", "type_id") + def test_delete_snapshot(self): snapshot = mock.Mock() self.service.delete_snapshot(snapshot) @@ -571,6 +581,24 @@ class UnifiedCinderMixinTestCase(test.TestCase): set_specs_args) self.service._unify_qos.assert_called_once_with(qos) + def test_qos_associate_type(self): + self.service._unify_qos = mock.MagicMock() + self.assertEqual( + self.service._unify_qos.return_value, + self.service.qos_associate_type("qos", "type_id")) + self.service._impl.qos_associate_type.assert_called_once_with( + "qos", "type_id") + self.service._unify_qos.assert_called_once_with("qos") + + def test_qos_disassociate_type(self): + self.service._unify_qos = mock.MagicMock() + self.assertEqual( + self.service._unify_qos.return_value, + self.service.qos_disassociate_type("qos", "type_id")) + self.service._impl.qos_disassociate_type.assert_called_once_with( + "qos", "type_id") + self.service._unify_qos.assert_called_once_with("qos") + def test_delete_snapshot(self): self.service.delete_snapshot("snapshot") self.service._impl.delete_snapshot.assert_called_once_with("snapshot")