From a8c1b3a387e152971e8a4aac2059f32c4a70b3f0 Mon Sep 17 00:00:00 2001 From: Jose Idar Date: Wed, 17 Jun 2015 12:09:10 -0500 Subject: [PATCH] Volumes API refactor * Added v1/2 common models * Added v1/2 common client * Bugfix in datasets * Added dataset tagging on create Change-Id: Ic65317dc53cc0dff0d0aa4af08cf9acdd805afca --- cloudcafe/blockstorage/datasets.py | 23 +++++--- .../blockstorage/volumes_api/common/client.py | 54 +++++++++++++++++-- .../volumes_api/common/models/requests.py | 12 +++++ .../volumes_api/common/models/responses.py | 40 ++++++++++++++ .../volumes_api/v1/models/requests.py | 4 ++ .../volumes_api/v1/models/responses.py | 4 ++ .../volumes_api/v2/models/requests.py | 4 ++ .../volumes_api/v2/models/responses.py | 4 ++ 8 files changed, 135 insertions(+), 10 deletions(-) create mode 100644 cloudcafe/blockstorage/volumes_api/common/models/requests.py create mode 100644 cloudcafe/blockstorage/volumes_api/common/models/responses.py diff --git a/cloudcafe/blockstorage/datasets.py b/cloudcafe/blockstorage/datasets.py index d462ad3f..846b33bb 100644 --- a/cloudcafe/blockstorage/datasets.py +++ b/cloudcafe/blockstorage/datasets.py @@ -30,9 +30,11 @@ except Exception as ex: warnings.warn(msg) # Dummy class to prevent errors when non-integrating consumers import # this class while compute services are unavailable + class ComputeDatasets(object): pass + class BlockstorageDatasets(ModelBasedDatasetToolkit): """Collection of dataset generators for blockstorage data driven tests""" _volumes = VolumesAutoComposite() @@ -52,7 +54,7 @@ class BlockstorageDatasets(ModelBasedDatasetToolkit): or vtype.name == cls._volumes.config.default_volume_type): return vtype - raise Exception("Unable to get configured default volume type") + raise Exception("Unable to get configured default volume type") @classmethod def default_volume_type(cls): @@ -69,7 +71,7 @@ class BlockstorageDatasets(ModelBasedDatasetToolkit): @classmethod def volume_types( cls, max_datasets=None, randomize=None, model_filter=None, - filter_mode=ModelBasedDatasetToolkit.INCLUSION_MODE): + filter_mode=ModelBasedDatasetToolkit.INCLUSION_MODE, tags=None): """Returns a DatasetList of all VolumeTypes Filters should be dictionaries with model attributes as keys and lists of attributes as key values @@ -87,11 +89,18 @@ class BlockstorageDatasets(ModelBasedDatasetToolkit): dataset_list.append_new_dataset(vol_type.name, data) # Apply modifiers - return cls._modify_dataset_list( + dataset_list = cls._modify_dataset_list( dataset_list, max_datasets=max_datasets, randomize=randomize) + # Apply Tags + if tags: + dataset_list.apply_test_tags(*tags) + + return dataset_list + @classmethod - def configured_volume_types(cls, max_datasets=None, randomize=False): + def configured_volume_types( + cls, max_datasets=None, randomize=False, tags=None): """Returns a DatasetList of permuations of Volume Types and Images. Requests all available images and volume types from API, and applies pre-configured image and volume_type filters. @@ -103,7 +112,8 @@ class BlockstorageDatasets(ModelBasedDatasetToolkit): max_datasets=max_datasets, randomize=randomize, model_filter=volume_type_filter, - filter_mode=volume_type_filter_mode) + filter_mode=volume_type_filter_mode, + tags=tags) class ComputeIntegrationDatasets(ComputeDatasets, BlockstorageDatasets): @@ -172,7 +182,8 @@ class ComputeIntegrationDatasets(ComputeDatasets, BlockstorageDatasets): volume_type_list, model_filter=volume_type_filter, filter_mode=volume_type_filter_mode) - # Create dataset from all combinations of all images and volume types + # Create dataset from all combinations of all images, flavors, and + # volume types dataset_list = DatasetList() for vtype in volume_type_list: for flavor in flavor_list: diff --git a/cloudcafe/blockstorage/volumes_api/common/client.py b/cloudcafe/blockstorage/volumes_api/common/client.py index ee4b9166..10b30e44 100644 --- a/cloudcafe/blockstorage/volumes_api/common/client.py +++ b/cloudcafe/blockstorage/volumes_api/common/client.py @@ -55,6 +55,7 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): def create_snapshot(self): pass + # Volumes def list_all_volumes(self, requestslib_kwargs=None): """GET /volumes""" @@ -95,6 +96,16 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): response_entity_type=self.response_models.VolumeResponse, requestslib_kwargs=requestslib_kwargs) + def set_volume_status(self, volume_id, status, requestslib_kwargs=None): + url = '{0}/volumes/{1}/action'.format(self.url, volume_id) + + request_entity = self.request_models.StatusResetRequest( + status=status) + + return self.request( + 'POST', url, request_entity=request_entity, + requestslib_kwargs=None) + # Volume Types def list_all_volume_types(self, requestslib_kwargs=None): @@ -165,6 +176,7 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): return self.request( 'DELETE', url, requestslib_kwargs=requestslib_kwargs) + # Snapshots def list_all_snapshots(self, requestslib_kwargs=None): """GET /snapshots""" @@ -172,8 +184,7 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): url = '{0}/snapshots'.format(self.url) return self.request( - 'GET', url, - response_entity_type= + 'GET', url, response_entity_type= self.response_models.VolumeSnapshotListResponse, requestslib_kwargs=requestslib_kwargs) @@ -183,8 +194,7 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): url = '{0}/snapshots/detail'.format(self.url) return self.request( - 'GET', url, - response_entity_type= + 'GET', url, response_entity_type= self.response_models.VolumeSnapshotListResponse, requestslib_kwargs=requestslib_kwargs) @@ -208,3 +218,39 @@ class BaseVolumesClient(AutoMarshallingHTTPClient): 'DELETE', url, response_entity_type=self.response_models.VolumeSnapshotResponse, requestslib_kwargs=requestslib_kwargs) + + def set_snapshot_status( + self, snapshot_id, status, requestslib_kwargs=None): + url = '{0}/snapshots/{1}/action'.format(self.url, snapshot_id) + + request_entity = self.request_models.StatusResetRequest( + status=status) + + return self.request( + 'POST', url, request_entity=request_entity, + requestslib_kwargs=None) + + # Quotas + def list_quotas(self, target_tenant_id, requestslib_kwargs=None): + """GET /{admin_tenant_id}/os-quota-sets/{target_tenant_id}""" + + url = '{url}/os-quota-sets/{target_tenant_id}'.format( + url=self.url, target_tenant_id=target_tenant_id) + + return self.request( + 'GET', url, + response_entity_type=self.response_models.QuotaListResponse, + requestslib_kwargs=requestslib_kwargs) + + def list_quotas_usage(self, target_tenant_id, requestslib_kwargs=None): + """GET /{admin_tenant_id}/os-quota-sets/{target_tenant_id}""" + + url = '{url}/os-quota-sets/{target_tenant_id}'.format( + url=self.url, target_tenant_id=target_tenant_id) + + params = {'usage': True} + + return self.request( + 'GET', url, + response_entity_type=self.response_models.QuotaUsageResponse, + params=params, requestslib_kwargs=requestslib_kwargs) diff --git a/cloudcafe/blockstorage/volumes_api/common/models/requests.py b/cloudcafe/blockstorage/volumes_api/common/models/requests.py new file mode 100644 index 00000000..a621510d --- /dev/null +++ b/cloudcafe/blockstorage/volumes_api/common/models/requests.py @@ -0,0 +1,12 @@ +from cafe.engine.models.base import AutoMarshallingModel +import json + + +class StatusResetRequest(AutoMarshallingModel): + def __init__(self, status=None): + super(StatusResetRequest, self).__init__() + self.status = status + + def _obj_to_json(self): + data = {"os-reset_status": {"status": self.status}} + return json.dumps(data) diff --git a/cloudcafe/blockstorage/volumes_api/common/models/responses.py b/cloudcafe/blockstorage/volumes_api/common/models/responses.py new file mode 100644 index 00000000..cb65ac0a --- /dev/null +++ b/cloudcafe/blockstorage/volumes_api/common/models/responses.py @@ -0,0 +1,40 @@ +from cafe.engine.models.base import AutoMarshallingDictModel +import json + + +class QuotaUsageResponse(AutoMarshallingDictModel): + """ This model represents the content of a dictionary of dictionaries. + The key-names are arbitrary, and so no conversion to a namespace is + attempted + """ + + @classmethod + def _json_to_obj(cls, json_dict): + quotaset = QuotaListResponse() + + data = json.loads(json_dict).get('quota_set') + quotaset['id'] = data.get('id') + for k, v in data.items(): + if type(v) == type(dict()): + quotaset[k] = _QuotaUsageResponseItem(**v) + + return quotaset + + +class _QuotaUsageResponseItem(object): + + def __init__(self, in_use=None, limit=None, reserved=None): + self.in_use = in_use + self.limit = limit + self.reserved = reserved + + +class QuotaListResponse(AutoMarshallingDictModel): + """ This model represents the content of a dictionary of dictionaries. + The key-names are arbitrary, and so no conversion to a namespace is + attempted + """ + + @classmethod + def _json_to_obj(cls, json_dict): + return QuotaListResponse(**json.loads(json_dict).get('quota_set')) diff --git a/cloudcafe/blockstorage/volumes_api/v1/models/requests.py b/cloudcafe/blockstorage/volumes_api/v1/models/requests.py index f5650911..49114641 100644 --- a/cloudcafe/blockstorage/volumes_api/v1/models/requests.py +++ b/cloudcafe/blockstorage/volumes_api/v1/models/requests.py @@ -21,6 +21,10 @@ from cafe.engine.models.base import AutoMarshallingModel from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ CommonModelProperties +# Import common requests +from cloudcafe.blockstorage.volumes_api.common.models.requests import \ + StatusResetRequest + class VolumeRequest(CommonModelProperties, AutoMarshallingModel): diff --git a/cloudcafe/blockstorage/volumes_api/v1/models/responses.py b/cloudcafe/blockstorage/volumes_api/v1/models/responses.py index b4261bc3..b5395ddd 100644 --- a/cloudcafe/blockstorage/volumes_api/v1/models/responses.py +++ b/cloudcafe/blockstorage/volumes_api/v1/models/responses.py @@ -19,6 +19,10 @@ from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ CommonModelProperties +# Import common responses +from cloudcafe.blockstorage.volumes_api.common.models.responses import \ + QuotaUsageResponse, QuotaListResponse + class VolumeResponse(CommonModelProperties, _VolumesAPIBaseModel): obj_model_key = 'volume' diff --git a/cloudcafe/blockstorage/volumes_api/v2/models/requests.py b/cloudcafe/blockstorage/volumes_api/v2/models/requests.py index 3bc43d3c..24be9714 100644 --- a/cloudcafe/blockstorage/volumes_api/v2/models/requests.py +++ b/cloudcafe/blockstorage/volumes_api/v2/models/requests.py @@ -21,6 +21,10 @@ from cafe.engine.models.base import AutoMarshallingModel from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ CommonModelProperties +# Import common requests +from cloudcafe.blockstorage.volumes_api.common.models.requests import \ + StatusResetRequest + class VolumeRequest(CommonModelProperties, AutoMarshallingModel): diff --git a/cloudcafe/blockstorage/volumes_api/v2/models/responses.py b/cloudcafe/blockstorage/volumes_api/v2/models/responses.py index 3c0c35f3..c6fa1b58 100644 --- a/cloudcafe/blockstorage/volumes_api/v2/models/responses.py +++ b/cloudcafe/blockstorage/volumes_api/v2/models/responses.py @@ -19,6 +19,10 @@ from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ from cloudcafe.blockstorage.volumes_api.common.models.automarshalling import \ CommonModelProperties +# Import common responses +from cloudcafe.blockstorage.volumes_api.common.models.responses import \ + QuotaUsageResponse, QuotaListResponse + class VolumeResponse(CommonModelProperties, _VolumesAPIBaseModel): obj_model_key = 'volume'