diff --git a/cloudcafe/blockstorage/volumes_api/common/client.py b/cloudcafe/blockstorage/volumes_api/common/client.py new file mode 100644 index 00000000..ee4b9166 --- /dev/null +++ b/cloudcafe/blockstorage/volumes_api/common/client.py @@ -0,0 +1,210 @@ +""" +Copyright 2015 Rackspace + +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 abc +from cafe.engine.http.client import AutoMarshallingHTTPClient + + +class BaseVolumesClient(AutoMarshallingHTTPClient): + __metaclass__ = abc.ABCMeta + + def __init__( + self, url, auth_token, serialize_format=None, + deserialize_format=None): + + super(BaseVolumesClient, self).__init__( + serialize_format, deserialize_format) + + self.url = url + self.auth_token = auth_token + self.default_headers['X-Auth-Token'] = auth_token + self.default_headers['Content-Type'] = 'application/{0}'.format( + self.serialize_format) + self.default_headers['Accept'] = 'application/{0}'.format( + self.deserialize_format) + + @abc.abstractproperty + def request_models(self): + pass + + @abc.abstractproperty + def response_models(self): + pass + + @abc.abstractmethod + def create_volume(self): + pass + + @abc.abstractmethod + def update_volume(self): + pass + + @abc.abstractmethod + def create_snapshot(self): + pass + + def list_all_volumes(self, requestslib_kwargs=None): + + """GET /volumes""" + + url = '{0}/volumes'.format(self.url) + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeListResponse, + requestslib_kwargs=requestslib_kwargs) + + def list_all_volumes_info(self, requestslib_kwargs=None): + + """GET /volumes/detail""" + + url = '{0}/volumes/detail'.format(self.url) + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeListResponse, + requestslib_kwargs=requestslib_kwargs) + + def get_volume_info(self, volume_id, requestslib_kwargs=None): + + """GET /volumes/{volume_id}""" + + url = '{0}/volumes/{1}'.format(self.url, volume_id) + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeResponse, + requestslib_kwargs=requestslib_kwargs) + + def delete_volume(self, volume_id, requestslib_kwargs=None): + + """DELETE /volumes/{volume_id}""" + + url = '{0}/volumes/{1}'.format(self.url, volume_id) + return self.request( + 'DELETE', url, + response_entity_type=self.response_models.VolumeResponse, + requestslib_kwargs=requestslib_kwargs) + + # Volume Types + def list_all_volume_types(self, requestslib_kwargs=None): + + """GET /types """ + url = '{0}/types'.format(self.url) + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeTypeListResponse, + requestslib_kwargs=requestslib_kwargs) + + def get_volume_type_info(self, volume_type_id, requestslib_kwargs=None): + + """GET /types/{volume_type_id}""" + + url = '{0}/types/{1}'.format(self.url, volume_type_id) + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeTypeResponse, + requestslib_kwargs=requestslib_kwargs) + + def create_volume_type( + self, name, extra_specs=None, requestslib_kwargs=None): + """ POST /types""" + + url = '{url}/types'.format(url=self.url) + + request_entity = self.request_models.VolumeTypeCreateRequest( + name=name, extra_specs=extra_specs) + + return self.request( + 'POST', url, request_entity=request_entity, + response_entity_type=self.response_models.VolumeTypeResponse, + requestslib_kwargs=requestslib_kwargs) + + def delete_volume_type(self, volume_type_id, requestslib_kwargs=None): + """ DELETE /types/{volume_type_id} """ + + url = '{url}/types/{volume_type_id}'.format( + url=self.url, volume_type_id=volume_type_id) + + return self.request( + 'DELETE', url, requestslib_kwargs=requestslib_kwargs) + + def update_volume_type_extra_specs( + self, volume_type_id, extra_specs=None, requestslib_kwargs=None): + """ POST /types/{volume_type_id}/extra_specs """ + + extra_specs = extra_specs or dict() + url = '{url}/types/{volume_type_id}/extra_specs'.format( + url=self.url, volume_type_id=volume_type_id) + + request_entity = self.request_models.VolumeTypeExtraSpecsUpdateRequest( + extra_specs=extra_specs) + + return self.request( + 'POST', url, request_entity=request_entity, + response_entity_type=self.response_models.VolumeTypeResponse, + requestslib_kwargs=requestslib_kwargs) + + def delete_volume_type_extra_spec( + self, volume_type_id, extra_spec_key, requestslib_kwargs=None): + """ DELETE /types/{volume_type_id}/extra_specs/{extra_spec_key} """ + + url = '{url}/types/{vtype_id}/extra_specs/{extra_spec_key}'.format( + url=self.url, vtype_id=volume_type_id, + extra_spec_key=extra_spec_key) + + return self.request( + 'DELETE', url, requestslib_kwargs=requestslib_kwargs) + + def list_all_snapshots(self, requestslib_kwargs=None): + + """GET /snapshots""" + + url = '{0}/snapshots'.format(self.url) + + return self.request( + 'GET', url, + response_entity_type= + self.response_models.VolumeSnapshotListResponse, + requestslib_kwargs=requestslib_kwargs) + + def list_all_snapshots_info(self, requestslib_kwargs=None): + + """GET /snapshots/detail""" + + url = '{0}/snapshots/detail'.format(self.url) + return self.request( + 'GET', url, + response_entity_type= + self.response_models.VolumeSnapshotListResponse, + requestslib_kwargs=requestslib_kwargs) + + def get_snapshot_info(self, snapshot_id, requestslib_kwargs=None): + + """GET /snapshots/{snapshot_id}""" + + url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) + + return self.request( + 'GET', url, + response_entity_type=self.response_models.VolumeSnapshotResponse, + requestslib_kwargs=requestslib_kwargs) + + def delete_snapshot(self, snapshot_id, requestslib_kwargs=None): + + """Delete /snapshots/{snapshot_id} """ + + url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) + return self.request( + 'DELETE', url, + response_entity_type=self.response_models.VolumeSnapshotResponse, + requestslib_kwargs=requestslib_kwargs) diff --git a/cloudcafe/blockstorage/volumes_api/v1/client.py b/cloudcafe/blockstorage/volumes_api/v1/client.py index 434d4352..61dd7de4 100644 --- a/cloudcafe/blockstorage/volumes_api/v1/client.py +++ b/cloudcafe/blockstorage/volumes_api/v1/client.py @@ -1,5 +1,5 @@ """ -Copyright 2013 Rackspace +Copyright 2015 Rackspace Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,36 +13,33 @@ 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 cafe.engine.http.client import AutoMarshallingHTTPClient - -from cloudcafe.blockstorage.volumes_api.v1.models.requests import ( - VolumeRequest, VolumeSnapshotRequest) - -from cloudcafe.blockstorage.volumes_api.v1.models.responses import ( - VolumeResponse, VolumeSnapshotResponse, VolumeTypeResponse, - VolumeListResponse, VolumeTypeListResponse, VolumeSnapshotListResponse) +from cloudcafe.blockstorage.volumes_api.common.client import BaseVolumesClient +from cloudcafe.blockstorage.volumes_api.v1.models import \ + requests as _request_models +from cloudcafe.blockstorage.volumes_api.v1.models import \ + responses as _response_models -class VolumesClient(AutoMarshallingHTTPClient): +class VolumesClient(BaseVolumesClient): + def __init__( self, url, auth_token, serialize_format=None, deserialize_format=None): super(VolumesClient, self).__init__( - serialize_format, deserialize_format) + url, auth_token, serialize_format, deserialize_format) - self.url = url - self.auth_token = auth_token - self.default_headers['X-Auth-Token'] = auth_token - self.default_headers['Content-Type'] = 'application/{0}'.format( - self.serialize_format) - self.default_headers['Accept'] = 'application/{0}'.format( - self.deserialize_format) + @property + def request_models(self): + return _request_models + + @property + def response_models(self): + return _response_models def create_volume( - self, size, volume_type, display_name=None, - display_description=None, name=None, description=None, + self, size, volume_type, name=None, description=None, + display_name=None, display_description=None, availability_zone=None, metadata=None, bootable=None, image_ref=None, snapshot_id=None, source_volid=None, requestslib_kwargs=None): @@ -54,7 +51,7 @@ class VolumesClient(AutoMarshallingHTTPClient): display_name = display_name or name display_description = display_description or description - volume_request_entity = VolumeRequest( + volume_request_entity = self.request_models.VolumeRequest( size=size, volume_type=volume_type, display_name=display_name, display_description=display_description, metadata=metadata, bootable=bootable, snapshot_id=snapshot_id, @@ -63,7 +60,7 @@ class VolumesClient(AutoMarshallingHTTPClient): return self.request( 'POST', url, - response_entity_type=VolumeResponse, + response_entity_type=self.response_models.VolumeResponse, request_entity=volume_request_entity, requestslib_kwargs=requestslib_kwargs) @@ -79,72 +76,16 @@ class VolumesClient(AutoMarshallingHTTPClient): display_name = display_name or name display_description = display_description or description - volume_request_entity = VolumeRequest( + volume_request_entity = self.request_models.VolumeRequest( display_name=display_name, display_description=display_description, metadata=metadata) return self.request( 'PUT', url, - response_entity_type=VolumeResponse, + response_entity_type=self.response_models.VolumeResponse, request_entity=volume_request_entity, requestslib_kwargs=requestslib_kwargs) - def list_all_volumes(self, requestslib_kwargs=None): - - """GET /volumes""" - - url = '{0}/volumes'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def list_all_volumes_info(self, requestslib_kwargs=None): - - """GET /volumes/detail""" - - url = '{0}/volumes/detail'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_volume_info(self, volume_id, requestslib_kwargs=None): - - """GET /volumes/{volume_id}""" - - url = '{0}/volumes/{1}'.format(self.url, volume_id) - return self.request( - 'GET', url, response_entity_type=VolumeResponse, - requestslib_kwargs=requestslib_kwargs) - - def delete_volume(self, volume_id, requestslib_kwargs=None): - - """DELETE /volumes/{volume_id}""" - - url = '{0}/volumes/{1}'.format(self.url, volume_id) - return self.request( - 'DELETE', url, response_entity_type=VolumeResponse, - requestslib_kwargs=requestslib_kwargs) - - # Volume Types API - def list_all_volume_types(self, requestslib_kwargs=None): - - """GET /types """ - - url = '{0}/types'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeTypeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_volume_type_info(self, volume_type_id, requestslib_kwargs=None): - - """GET /types/{volume_type_id}""" - - url = '{0}/types/{1}'.format(self.url, volume_type_id) - return self.request( - 'GET', url, response_entity_type=VolumeTypeResponse, - requestslib_kwargs=requestslib_kwargs) - - # Volume Snapshot API def create_snapshot( self, volume_id, display_name=None, display_description=None, name=None, description=None, force_create=False, @@ -157,52 +98,15 @@ class VolumesClient(AutoMarshallingHTTPClient): display_name = display_name or name display_description = display_description or description - volume_snapshot_request_entity = VolumeSnapshotRequest( - volume_id, - force=force_create, - display_name=display_name, - display_description=display_description) + volume_snapshot_request_entity = \ + self.request_models.VolumeSnapshotRequest( + volume_id, + force=force_create, + display_name=display_name, + display_description=display_description) return self.request( 'POST', url, - response_entity_type=VolumeSnapshotResponse, + response_entity_type=self.response_models.VolumeSnapshotResponse, request_entity=volume_snapshot_request_entity, requestslib_kwargs=requestslib_kwargs) - - def list_all_snapshots(self, requestslib_kwargs=None): - - """GET /snapshots""" - - url = '{0}/snapshots'.format(self.url) - - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotListResponse, - requestslib_kwargs=requestslib_kwargs) - - def list_all_snapshots_info(self, requestslib_kwargs=None): - - """GET /snapshots/detail""" - - url = '{0}/snapshots/detail'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_snapshot_info(self, snapshot_id, requestslib_kwargs=None): - - """GET /snapshots/{snapshot_id}""" - - url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) - - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotResponse, - requestslib_kwargs=requestslib_kwargs) - - def delete_snapshot(self, snapshot_id, requestslib_kwargs=None): - - """Delete /snapshots/{snapshot_id} """ - - url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) - return self.request( - 'DELETE', url, response_entity_type=VolumeSnapshotResponse, - requestslib_kwargs=requestslib_kwargs) diff --git a/cloudcafe/blockstorage/volumes_api/v1/models/requests.py b/cloudcafe/blockstorage/volumes_api/v1/models/requests.py index ec81599a..f5650911 100644 --- a/cloudcafe/blockstorage/volumes_api/v1/models/requests.py +++ b/cloudcafe/blockstorage/volumes_api/v1/models/requests.py @@ -128,3 +128,50 @@ class VolumeSnapshotRequest(CommonModelProperties, AutoMarshallingModel): "display_description": self.description, "force": str(self.force)} return self._set_xml_etree_element(element, snapshot_attrs) + + +class VolumeTypeCreateRequest(AutoMarshallingModel): + + def __init__(self, name, extra_specs=None): + super(VolumeTypeCreateRequest, self).__init__() + self.name = name + self.extra_specs = extra_specs + + def _obj_to_json(self): + return json.dumps(self._obj_to_json_dict()) + + def _obj_to_json_dict(self): + attrs = { + "name": self.name, + "extra_specs": self.extra_specs} + return dict(volume_type=self._remove_empty_values(attrs)) + + def _obj_to_xml(self): + return ElementTree.tostring(self._obj_to_xml_ele()) + + def _obj_to_xml_ele(self): + element = ElementTree.Element('volume_type') + attrs = {"name": self.name} + + if len(self.extra_specs.keys()) > 0: + extra_specs = ElementTree.Element('extra_specs') + for key in self.extra_specs.keys(): + spec = ElementTree.Element('extra_spec') + spec.set('key', key) + spec.text = self.extra_specs[key] + extra_specs.append(spec) + element.append(extra_specs) + + return self._set_xml_etree_element(element, attrs) + + +class VolumeTypeExtraSpecsUpdateRequest(AutoMarshallingModel): + def __init__(self, extra_specs=None): + super(VolumeTypeExtraSpecsUpdateRequest, self).__init__() + self.extra_specs = extra_specs + + def _obj_to_json(self): + return json.dumps(self._obj_to_json_dict()) + + def _obj_to_json_dict(self): + return dict(extra_specs=self._remove_empty_values(self.extra_specs)) diff --git a/cloudcafe/blockstorage/volumes_api/v2/client.py b/cloudcafe/blockstorage/volumes_api/v2/client.py index e6cd7d21..3df7ceff 100644 --- a/cloudcafe/blockstorage/volumes_api/v2/client.py +++ b/cloudcafe/blockstorage/volumes_api/v2/client.py @@ -1,5 +1,5 @@ """ -Copyright 2013 Rackspace +Copyright 2015 Rackspace Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. @@ -13,32 +13,29 @@ 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 cafe.engine.http.client import AutoMarshallingHTTPClient - -from cloudcafe.blockstorage.volumes_api.v2.models.requests import ( - VolumeRequest, VolumeSnapshotRequest) - -from cloudcafe.blockstorage.volumes_api.v2.models.responses import ( - VolumeResponse, VolumeSnapshotResponse, VolumeTypeResponse, - VolumeListResponse, VolumeTypeListResponse, VolumeSnapshotListResponse) +from cloudcafe.blockstorage.volumes_api.common.client import BaseVolumesClient +from cloudcafe.blockstorage.volumes_api.v2.models import \ + requests as _request_models +from cloudcafe.blockstorage.volumes_api.v2.models import \ + responses as _response_models -class VolumesClient(AutoMarshallingHTTPClient): +class VolumesClient(BaseVolumesClient): + def __init__( self, url, auth_token, serialize_format=None, deserialize_format=None): super(VolumesClient, self).__init__( - serialize_format, deserialize_format) + url, auth_token, serialize_format, deserialize_format) - self.url = url - self.auth_token = auth_token - self.default_headers['X-Auth-Token'] = auth_token - self.default_headers['Content-Type'] = 'application/{0}'.format( - self.serialize_format) - self.default_headers['Accept'] = 'application/{0}'.format( - self.deserialize_format) + @property + def request_models(self): + return _request_models + + @property + def response_models(self): + return _response_models def create_volume( self, size, volume_type, name=None, description=None, @@ -54,7 +51,7 @@ class VolumesClient(AutoMarshallingHTTPClient): name = name or display_name description = description or display_description - volume_request_entity = VolumeRequest( + volume_request_entity = self.request_models.VolumeRequest( size=size, volume_type=volume_type, name=name, image_ref=image_ref, description=description, availability_zone=availability_zone, metadata=metadata, bootable=bootable, snapshot_id=snapshot_id, @@ -62,7 +59,7 @@ class VolumesClient(AutoMarshallingHTTPClient): return self.request( 'POST', url, - response_entity_type=VolumeResponse, + response_entity_type=self.response_models.VolumeResponse, request_entity=volume_request_entity, requestslib_kwargs=requestslib_kwargs) @@ -78,71 +75,15 @@ class VolumesClient(AutoMarshallingHTTPClient): name = name or display_name description = description or display_description - volume_request_entity = VolumeRequest( + volume_request_entity = self.request_models.VolumeRequest( name=name, description=description, metadata=metadata) return self.request( 'PUT', url, - response_entity_type=VolumeResponse, + response_entity_type=self.response_models.VolumeResponse, request_entity=volume_request_entity, requestslib_kwargs=requestslib_kwargs) - def list_all_volumes(self, requestslib_kwargs=None): - - """GET /volumes""" - - url = '{0}/volumes'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def list_all_volumes_info(self, requestslib_kwargs=None): - - """GET /volumes/detail""" - - url = '{0}/volumes/detail'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_volume_info(self, volume_id, requestslib_kwargs=None): - - """GET /volumes/{volume_id}""" - - url = '{0}/volumes/{1}'.format(self.url, volume_id) - return self.request( - 'GET', url, response_entity_type=VolumeResponse, - requestslib_kwargs=requestslib_kwargs) - - def delete_volume(self, volume_id, requestslib_kwargs=None): - - """DELETE /volumes/{volume_id}""" - - url = '{0}/volumes/{1}'.format(self.url, volume_id) - return self.request( - 'DELETE', url, response_entity_type=VolumeResponse, - requestslib_kwargs=requestslib_kwargs) - - # Volume Types API - def list_all_volume_types(self, requestslib_kwargs=None): - - """GET /types """ - - url = '{0}/types'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeTypeListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_volume_type_info(self, volume_type_id, requestslib_kwargs=None): - - """GET /types/{volume_type_id}""" - - url = '{0}/types/{1}'.format(self.url, volume_type_id) - return self.request( - 'GET', url, response_entity_type=VolumeTypeResponse, - requestslib_kwargs=requestslib_kwargs) - - # Volume Snapshot API def create_snapshot( self, volume_id, name=None, description=None, display_name=None, display_description=None, @@ -155,52 +96,15 @@ class VolumesClient(AutoMarshallingHTTPClient): name = name or display_name description = description or display_description - volume_snapshot_request_entity = VolumeSnapshotRequest( - volume_id, - force=force_create, - name=name, - description=description) + volume_snapshot_request_entity = \ + self.request_models.VolumeSnapshotRequest( + volume_id, + force=force_create, + name=name, + description=description) return self.request( 'POST', url, - response_entity_type=VolumeSnapshotResponse, + response_entity_type=self.response_models.VolumeSnapshotResponse, request_entity=volume_snapshot_request_entity, requestslib_kwargs=requestslib_kwargs) - - def list_all_snapshots(self, requestslib_kwargs=None): - - """GET /snapshots""" - - url = '{0}/snapshots'.format(self.url) - - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotListResponse, - requestslib_kwargs=requestslib_kwargs) - - def list_all_snapshots_info(self, requestslib_kwargs=None): - - """GET /snapshots/detail""" - - url = '{0}/snapshots/detail'.format(self.url) - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotListResponse, - requestslib_kwargs=requestslib_kwargs) - - def get_snapshot_info(self, snapshot_id, requestslib_kwargs=None): - - """GET /snapshots/{snapshot_id}""" - - url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) - - return self.request( - 'GET', url, response_entity_type=VolumeSnapshotResponse, - requestslib_kwargs=requestslib_kwargs) - - def delete_snapshot(self, snapshot_id, requestslib_kwargs=None): - - """DELETE /snapshots/{snapshot_id}""" - - url = '{0}/snapshots/{1}'.format(self.url, snapshot_id) - return self.request( - 'DELETE', url, response_entity_type=VolumeSnapshotResponse, - requestslib_kwargs=requestslib_kwargs) diff --git a/cloudcafe/blockstorage/volumes_api/v2/models/requests.py b/cloudcafe/blockstorage/volumes_api/v2/models/requests.py index ceddbf34..3bc43d3c 100644 --- a/cloudcafe/blockstorage/volumes_api/v2/models/requests.py +++ b/cloudcafe/blockstorage/volumes_api/v2/models/requests.py @@ -127,3 +127,50 @@ class VolumeSnapshotRequest(CommonModelProperties, AutoMarshallingModel): "description": self.description, "force": str(self.force)} return self._set_xml_etree_element(element, snapshot_attrs) + + +class VolumeTypeCreateRequest(AutoMarshallingModel): + + def __init__(self, name, extra_specs=None): + super(VolumeTypeCreateRequest, self).__init__() + self.name = name + self.extra_specs = extra_specs + + def _obj_to_json(self): + return json.dumps(self._obj_to_json_dict()) + + def _obj_to_json_dict(self): + attrs = { + "name": self.name, + "extra_specs": self.extra_specs} + return dict(volume_type=self._remove_empty_values(attrs)) + + def _obj_to_xml(self): + return ElementTree.tostring(self._obj_to_xml_ele()) + + def _obj_to_xml_ele(self): + element = ElementTree.Element('volume_type') + attrs = {"name": self.name} + + if len(self.extra_specs.keys()) > 0: + extra_specs = ElementTree.Element('extra_specs') + for key in self.extra_specs.keys(): + spec = ElementTree.Element('extra_spec') + spec.set('key', key) + spec.text = self.extra_specs[key] + extra_specs.append(spec) + element.append(extra_specs) + + return self._set_xml_etree_element(element, attrs) + + +class VolumeTypeExtraSpecsUpdateRequest(AutoMarshallingModel): + def __init__(self, extra_specs=None): + super(VolumeTypeExtraSpecsUpdateRequest, self).__init__() + self.extra_specs = extra_specs + + def _obj_to_json(self): + return json.dumps(self._obj_to_json_dict()) + + def _obj_to_json_dict(self): + return dict(extra_specs=self._remove_empty_values(self.extra_specs))