Initial Commit

This commit is contained in:
samu4924
2013-03-30 14:48:37 -05:00
commit 481102076c
125 changed files with 8560 additions and 0 deletions

View File

@@ -0,0 +1,16 @@
"""
Copyright 2013 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.
"""

View File

@@ -0,0 +1,30 @@
"""
Copyright 2013 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.
"""
from cloudcafe.common.models.configuration import ConfigSectionInterface
class BlockStorageConfig(ConfigSectionInterface):
SECTION_NAME = 'blockstorage'
@property
def identity_service_name(self):
return self.get('identity_service_name')
@property
def region(self):
return self.get('region')

View File

@@ -0,0 +1,26 @@
"""
Copyright 2013 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.
"""
from cafe.engine.behaviors import BaseBehavior, behavior
from cloudcafe.compute.servers_api.client import ServersClient
class ComputeBlockStorageBehaviors(BaseBehavior):
@behavior(ServersClient)
def attach_volume_to_server(*args, **kwargs):
pass

View File

@@ -0,0 +1,37 @@
"""
Copyright 2013 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.
"""
from cafe.engine.provider import BaseProvider
from cloudcafe.blockstorage.config import BlockStorageConfig
from cloudcafe.blockstorage.volumes_api.provider import VolumesProvider
class BlockStorageProvider(BaseProvider):
def get_volumes_provider(self):
# NEED TO IMPORT IDENTITY AND GET THESE THINGS
blockstorage_config = BlockStorageConfig()
blockstorage_service_name = blockstorage_config.identity_service_name
blockstorage_region = blockstorage_config.region
auth_token = '924ur802ur08j2f0984'
volumes_url = 'http://volumes_url'
tenant_id = '234234'
return VolumesProvider(volumes_url, auth_token, tenant_id)

View File

@@ -0,0 +1,16 @@
"""
Copyright 2013 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.
"""

View File

@@ -0,0 +1,374 @@
"""
Copyright 2013 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.
"""
from time import time
from cafe.engine.behaviors import BaseBehavior, behavior
from cloudcafe.blockstorage.volumes_api.volumes_client import VolumesClient
from cloudcafe.blockstorage.volumes_api.config import VolumesAPIConfig
class BehaviorResponse(object):
'''An object to represent the result of behavior.
@ivar response: Last response returned from last client call
@ivar ok: Represents the success state of the behavior call
@type ok:C{bool}
@ivar entity: Data model created via behavior calls, if applicable
@TODO: This should probably be moved to the base behavior module,
or even into the engine's models
'''
def __init__(self):
self.response = None
self.ok = False
self.entity = None
class VolumesAPI_Behaviors(BaseBehavior):
def __init__(self, volumes_client=None):
self._client = volumes_client
self.config = VolumesAPIConfig()
@behavior(VolumesClient)
def wait_for_volume_status(
self, volume_id, expected_status, timeout, wait_period=None):
''' Waits for a specific status and returns a BehaviorResponse object
when that status is observed.
Note: Shouldn't be used for transient statuses like 'deleting'.
'''
wait_period = wait_period or self.config.volume_status_poll_frequency
behavior_response = BehaviorResponse()
end_time = time() + timeout
while time() < end_time:
resp = self._client.get_volume_info(volume_id=volume_id)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"get_volume_info() call failed with status_code {0} while "
"waiting for volume status".format(resp.status_code))
break
if resp.entity is None:
behavior_response.ok = False
self._log.error(
"get_volume_info() response body did not deserialize as "
"expected")
break
if resp.entity.status == expected_status:
behavior_response.ok = True
self._log.info('Volume status "{0}" observed'.format(
expected_status))
break
else:
behavior_response.ok = False
self._log.info(
"wait_for_volume_status() ran for {0} seconds and did not "
"observe the volume achieving the {1} status.".format(
timeout, expected_status))
return behavior_response
@behavior(VolumesClient)
def wait_for_snapshot_status(
self, snapshot_id, expected_status, timeout, wait_period=None):
''' Waits for a specific status and returns a BehaviorResponse object
when that status is observed.
Note: Shouldn't be used for transient statuses like 'deleting'.
'''
wait_period = wait_period or self.config.snapshot_status_poll_frequency
behavior_response = BehaviorResponse()
end_time = time() + timeout
while time() < end_time:
resp = self._client.get_snapshot_info(snapshot_id=snapshot_id)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"get_snapshot_info() call failed with status_code {0} "
"while waiting for snapshot status".format(
resp.status_code))
break
if resp.entity is None:
behavior_response.ok = False
self._log.error(
"get_snapshot_info() response body did not deserialize as "
"expected")
break
if resp.entity.status == expected_status:
behavior_response.ok = True
self._log.info('Snapshot status "{0}" observed'.format(
expected_status))
break
else:
behavior_response.ok = False
self._log.error(
"wait_for_snapshot_status() ran for {0} seconds and did not "
"observe the snapshot achieving the '{1}' status.".format(
timeout, expected_status))
return behavior_response
@behavior(VolumesClient)
def create_available_volume(
self, display_name, size, volume_type, display_description=None,
metadata=None, availability_zone=None, timeout=None,
wait_period=None):
expected_status = 'available'
metadata = metadata or {}
timeout = timeout or self.config.volume_create_timeout
behavior_response = BehaviorResponse()
self._log.info("create_available_volume() is creating a volume")
resp = self._client.create_volume(
display_name=display_name, size=size, volume_type=volume_type,
display_description=display_description, metadata=metadata,
availability_zone=availability_zone)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"create_available_volume() call failed with status_code {0} "
"while attempting to create a volume".format(resp.status_code))
if resp.entity is None:
behavior_response.ok = False
self._log.error(
"create_available_volume() response body did not deserialize "
"as expected")
#Bail on fail
if not behavior_response.ok:
return behavior_response
# Wait for expected_status on success
wait_resp = self.wait_for_volume_status(
resp.entity.id_, expected_status, timeout, wait_period)
if not wait_resp.ok:
behavior_response.ok = False
self._log.error(
"Something went wrong while create_available_volume() was "
"waiting for the volume to reach the '{0}' status")
else:
behavior_response.ok = True
return behavior_response
@behavior(VolumesClient)
def create_available_snapshot(
self, volume_id, display_name=None, display_description=None,
force_create='False', name=None, timeout=None, wait_period=None):
expected_status = 'available'
timeout = timeout or self.config.snapshot_create_timeout
behavior_response = BehaviorResponse()
self._log.info("create_available_snapshot() is creating a snapshot")
resp = self._client.create_snapshot(
volume_id, display_name=display_name,
display_description=display_description,
force_create=force_create, name=name)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"create_available_volume() call failed with status_code {0} "
"while attempting to create a volume".format(resp.status_code))
if resp.entity is None:
behavior_response.ok = False
self._log.error(
"create_available_volume() response body did not deserialize "
"as expected")
# Bail on fail
if not behavior_response.ok:
return behavior_response
# Wait for expected_status on success
wait_resp = self.wait_for_volume_status(
resp.entity.id_, expected_status, timeout, wait_period)
if not wait_resp.ok:
behavior_response.ok = False
self._log.error(
"Something went wrong while create_available_volume() was "
"waiting for the volume to reach the '{0}' status")
else:
behavior_response.ok = True
return behavior_response
@behavior(VolumesClient)
def list_volume_snapshots(self, volume_id):
behavior_response = BehaviorResponse()
# List all snapshots
resp = self._client.list_all_snapshots_info()
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"list_volume_snapshots() failed to get a list of all snapshots"
"due to a '{0}' response from list_all_snapshots_info()"
.format(resp.status_code))
return behavior_response
if resp.entity is None:
behavior_response.ok = False
self._log.error(
"list_all_snapshots_info() response body did not deserialize "
"as expected")
return behavior_response
# Expects an entity of type VolumeSnapshotList
volume_snapshots = [s for s in resp.entity if s.volume_id == volume_id]
behavior_response.entity = volume_snapshots
return behavior_response
@behavior(VolumesClient)
def delete_volume_confirmed(
self, volume_id, size=None, timeout=None, wait_period=None):
if size is not None:
if self.config.volume_delete_wait_per_gig is not None:
wait_per_gig = self.config.volume_snapshot_delete_wait_per_gig
timeout = timeout or size * wait_per_gig
if self.config.volume_delete_min_timeout is not None:
min_timeout = self.config.volume_snapshot_delete_min_timeout
timeout = timeout if timeout > min_timeout else min_timeout
if self.config.volume_delete_max_timeout is not None:
max_timeout = self.config.volume_snapshot_delete_max_timeout
timeout = timeout if timeout < max_timeout else max_timeout
end = time() + timeout
while time() < end:
#issue DELETE request on volume
behavior_response = BehaviorResponse()
resp = self._client.delete_volume(volume_id)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"delete_volume_confirmed() call to delete_volume() failed "
"with a '{0}'".format(resp.status_code))
return behavior_response
#Poll volume status to make sure it deleted properly
status_resp = self._client.get_volume_info(volume_id)
if status_resp.status_code == 404:
behavior_response.ok = True
self._log.info(
"Status request on volume {0} returned 404, volume delete"
"confirmed".format(volume_id))
break
if (not status_resp.ok) and (status_resp.status_code != 404):
behavior_response.ok = False
self._log.error(
"Status request on volume {0} failed with a {0}".format(
volume_id))
break
else:
behavior_response.ok = False
self._log.error(
"delete_volume_confirmed() was unable to verify the volume"
"delete withing the alloted {0} second timeout".format())
return behavior_response
@behavior(VolumesClient)
def delete_snapshot_confirmed(
self, snapshot_id, size=None, timeout=None, wait_period=None):
if size is not None:
if self.config.volume_snapshot_delete_wait_per_gig is not None:
wait_per_gig = self.config.volume_snapshot_delete_wait_per_gig
timeout = timeout or size * wait_per_gig
if self.config.volume_snapshot_delete_min_timeout is not None:
min_timeout = self.config.volume_snapshot_delete_min_timeout
timeout = timeout if timeout > min_timeout else min_timeout
if self.config.volume_snapshot_delete_max_timeout is not None:
max_timeout = self.config.volume_snapshot_delete_max_timeout
timeout = timeout if timeout < max_timeout else max_timeout
end = time() + timeout
while time() < end:
# issue DELETE request on volume snapshot
behavior_response = BehaviorResponse()
resp = self._client.delete_snapshot(snapshot_id)
behavior_response.response = resp
behavior_response.entity = resp.entity
if not resp.ok:
behavior_response.ok = False
self._log.error(
"delete_snapshot_confirmed() call to delete_snapshot()"
"failed with a '{0}'".format(resp.status_code))
return behavior_response
# Poll snapshot status to make sure it deleted properly
status_resp = self._client.get_snapshot_info(snapshot_id)
if status_resp.status_code == 404:
behavior_response.ok = True
self._log.info(
"Status request on snapshot {0} returned 404, snapshot"
"delete confirmed".format(snapshot_id))
break
if (not status_resp.ok) and (status_resp.status_code != 404):
behavior_response.ok = False
self._log.error(
"Status request on snapshot {0} failed with a {0}".format(
snapshot_id))
break
else:
behavior_response.ok = False
self._log.error(
"delete_snapshot_confirmed() was unable to verify the snapshot"
"delete withing the alloted {0} second timeout".format())
return behavior_response
@behavior(VolumesClient)
def delete_volume_with_snapshots_confirmed(self):
pass

View File

@@ -0,0 +1,201 @@
"""
Copyright 2013 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.
"""
from cafe.engine.clients.rest import AutoMarshallingRestClient
from cloudcafe.blockstorage.volumes_api.models.requests.volumes_api import \
Volume as VolumeRequest, VolumeSnapshot as VolumeSnapshotRequest\
from cloudcafe.blockstorage.volumes_api.models.responses.volumes_api import \
Volume as VolumeResponse, VolumeSnapshot as VolumeSnapshotResponse,\
VolumeType, VolumeList, VolumeTypeList, VolumeSnapshotList
class VolumesClient(AutoMarshallingRestClient):
def __init__(
self, url, auth_token, tenant_id, serialize_format=None,
deserialize_format=None):
super(VolumesClient, 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/%s' % \
self.serialize_format
self.default_headers['Accept'] = 'application/%s' % \
self.deserialize_format
def create_volume(
self, display_name, size, volume_type, availability_zone=None,
metadata={}, display_description='', snapshot_id=None,
requestslib_kwargs=None):
'''POST v1/{tenant_id}/volumes'''
url = '{0}/volumes'.format(self.url)
volume_request_entity = VolumeRequest(
display_name=display_name,
size=size,
volume_type=volume_type,
display_description=display_description,
metadata=metadata,
availability_zone=availability_zone,
snapshot_id=snapshot_id)
return self.request(
'POST', url, response_entity_type=VolumeResponse,
request_entity=volume_request_entity,
requestslib_kwargs=requestslib_kwargs)
def create_volume_from_snapshot(
self, snapshot_id, size, display_name='', volume_type=None,
availability_zone=None, display_description='', metadata={},
requestslib_kwargs=None):
'''POST v1/{tenant_id}/volumes'''
url = '{0}/volumes'.format(self.url)
volume_request_entity = VolumeRequest(
display_name=display_name,
size=size,
volume_type=volume_type,
display_description=display_description,
metadata=metadata,
availability_zone=availability_zone,
snapshot_id=snapshot_id)
return self.request(
'POST', url, response_entity_type=VolumeResponse,
request_entity=volume_request_entity,
requestslib_kwargs=requestslib_kwargs)
def list_all_volumes(self, requestslib_kwargs=None):
'''GET v1/{tenant_id}/volumes'''
url = '{0}/volumes'.format(self.url)
return self.request(
'GET', url, response_entity_type=VolumeList,
requestslib_kwargs=requestslib_kwargs)
def list_all_volumes_info(self, requestslib_kwargs=None):
'''GET v1/{tenant_id}/volumes/detail'''
url = '{0}/volumes/detail'.format(self.url)
return self.request(
'GET', url, response_entity_type=VolumeList,
requestslib_kwargs=requestslib_kwargs)
def get_volume_info(self, volume_id, requestslib_kwargs=None):
'''GET v1/{tenant_id}/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 v1/{tenant_id}/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 v1/{tenant_id}/types '''
url = '{0}/types'.format(self.url)
return self.request(
'GET', url, response_entity_type=VolumeTypeList,
requestslib_kwargs=requestslib_kwargs)
def get_volume_type_info(self, volume_type_id, requestslib_kwargs=None):
'''GET v1/{tenant_id}/types/{volume_type_id}'''
url = '{0}/types/{1}'.format(self.url, volume_type_id)
return self.request(
'GET', url, response_entity_type=VolumeType,
requestslib_kwargs=requestslib_kwargs)
#Volume Snapshot API
def create_snapshot(
self, volume_id, display_name=None, display_description=None,
force_create=False, name=None, requestslib_kwargs=None):
'''POST v1/{tenant_id}/snapshots'''
url = '{0}/snapshots'.format(self.url)
volume_snapshot_request_entity = VolumeSnapshotRequest(
volume_id,
force=force_create,
display_name=display_name,
name=name,
display_description=display_description)
return self.request(
'POST', url, response_entity_type=VolumeSnapshotResponse,
request_entity=volume_snapshot_request_entity,
requestslib_kwargs=requestslib_kwargs)
def list_all_snapshots(self, requestslib_kwargs=None):
'''GET v1/{tenant_id}/snapshots'''
url = '{0}/snapshots'.format(self.url)
return self.request(
'GET', url, response_entity_type=VolumeSnapshotList,
requestslib_kwargs=requestslib_kwargs)
def list_all_snapshots_info(self, requestslib_kwargs=None):
'''GET v1/{tenant_id}/snapshots/detail'''
url = '{0}/snapshots/detail'.format(self.url)
return self.request(
'GET', url, response_entity_type=VolumeSnapshotList,
requestslib_kwargs=requestslib_kwargs)
def get_snapshot_info(self, snapshot_id, requestslib_kwargs=None):
'''GET v1/{tenant_id}/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 v1/{tenant_id}/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)

View File

@@ -0,0 +1,75 @@
"""
Copyright 2013 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.
"""
from cafe.engine.models.configuration import BaseConfigSectionInterface
class VolumesAPIConfig(BaseConfigSectionInterface):
SECTION_NAME = 'volumes_api'
@property
def serialize_format(self):
return self.get("serialize_format")
@property
def deserialize_format(self):
return self.get("deserialize_format")
@property
def max_volume_size(self):
return self.get("max_volume_size", default='1024')
@property
def min_volume_size(self):
return self.get("min_volume_size", default='1')
@property
def volume_create_timeout(self):
return self.get("volume_create_timeout", default='10')
@property
def volume_status_poll_frequency(self):
return self.get("volume_status_poll_frequency", default='30')
@property
def volume_delete_wait_per_gig(self):
return self.get("volume_delete_wait_per_gig", default='30')
@property
def snapshot_create_timeout(self):
return self.get("snapshot_create_timeout", default='10')
@property
def snapshot_status_poll_frequency(self):
return self.get("snapshot_status_poll_frequency", default='30')
@property
def volume_snapshot_delete_min_timeout(self):
"""Absolute lower limit on calculated volume snapshot delete timeouts
"""
return self.get("volume_delete_wait_per_gig", default=None)
@property
def volume_snapshot_delete_max_timeout(self):
"""Absolute upper limit on calculated volume snapshot delete timeouts
"""
return self.get("volume_delete_wait_per_gig", default=None)
@property
def volume_snapshot_delete_wait_per_gig(self):
"""If set, volume snapshot delete behaviors can estimate the time
it will take a particular volume to delete given it's size"""
return self.get("volume_delete_wait_per_gig", default=None)

View File

@@ -0,0 +1,16 @@
"""
Copyright 2013 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.
"""

View File

@@ -0,0 +1,16 @@
"""
Copyright 2013 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.
"""

View File

@@ -0,0 +1,121 @@
"""
Copyright 2013 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 json
from xml.etree import ElementTree
from cafe.engine.models.base import AutoMarshallingModel
class Volume(AutoMarshallingModel):
def __init__(
self, display_name=None, size=None, volume_type=None,
display_description=None, metadata=None, availability_zone=None,
snapshot_id=None, attachments=None, xmlns=None):
self.display_name = display_name
self.display_description = display_description
self.size = size
self.volume_type = volume_type
self.metadata = metadata or {}
self.availability_zone = availability_zone
self.snapshot_id = snapshot_id
def _obj_to_json(self):
return json.dumps(self._obj_to_json_dict())
def _obj_to_json_dict(self):
sub_dict = {}
sub_dict["display_name"] = self.display_name
sub_dict["display_description"] = self.display_description
sub_dict["size"] = self.size
sub_dict["volume_type"] = self.volume_type
sub_dict["metadata"] = self.metadata
sub_dict["availability_zone"] = self.availability_zone
sub_dict['snapshot_id'] = self.snapshot_id
root_dict = {}
root_dict["volume"] = self._remove_empty_values(sub_dict)
return root_dict
def _obj_to_xml_ele(self):
element = ElementTree.Element('volume')
attrs = {}
attrs["xmlns"] = self.xmlns
attrs["display_name"] = self.display_name
attrs["display_description"] = self.display_description
attrs["size"] = str(self.size)
attrs["volume_type"] = self.volume_type
attrs["availability_zone"] = self.availability_zone
element = self._set_xml_etree_element(element, attrs)
if len(self.metadata.keys()) > 0:
metadata_element = ElementTree.Element('metadata')
for key in self.metadata.keys():
meta_element = ElementTree.Element('meta')
meta_element.set('key', key)
meta_element.text = self.metadata[key]
metadata_element.append(meta_element)
element.append(metadata_element)
return element
def _obj_to_xml(self):
return ElementTree.tostring(self._obj_to_xml_ele())
class VolumeSnapshot(AutoMarshallingModel):
def __init__(
self, volume_id, force=True, display_name=None, name=None,
display_description=None):
self.force = force
self.display_name = display_name
self.volume_id = volume_id
self.name = name
self.display_description = display_description
def _obj_to_json(self):
return json.dumps(self._obj_to_json_dict())
def _obj_to_json_dict(self):
attrs = {}
attrs["snapshot"] = {}
sub_attrs = {}
sub_attrs["volume_id"] = self.volume_id
sub_attrs["force"] = self.force
sub_attrs["display_name"] = self.display_name
sub_attrs["display_description"] = self.display_description
attrs["snapshot"] = self._remove_empty_values({}, sub_attrs)
return 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('snapshot')
attrs = {}
attrs["xmlns"] = self.xmlns
attrs["volume_id"] = self.volume_id
attrs["force"] = str(self.force)
attrs["display_name"] = self.display_name
attrs["display_description"] = self.display_description
element = self._set_xml_etree_element(element, attrs)
return element

View File

@@ -0,0 +1,16 @@
"""
Copyright 2013 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.
"""

View File

@@ -0,0 +1,265 @@
"""
Copyright 2013 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 json
from xml.etree import ElementTree
from cafe.engine.models.base import \
AutoMarshallingModel, AutoMarshallingListModel
class Volume(AutoMarshallingModel):
TAG = 'volume'
'''@TODO Make sub data model for attachments element'''
def __init__(
self, id_=None, display_name=None, size=None, volume_type=None,
display_description=None, metadata=None, availability_zone=None,
snapshot_id=None, attachments=None, created_at=None, status=None,
xmlns=None):
#Common attributes
self.id_ = id_
self.display_name = display_name
self.display_description = display_description
self.size = size
self.volume_type = volume_type
self.metadata = metadata or {}
self.availability_zone = availability_zone
self.snapshot_id = snapshot_id
self.attachments = attachments
self.created_at = created_at
self.status = status
self.xmlns = xmlns
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_dict = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_dict)
@classmethod
def _json_dict_to_obj(cls, json_dict):
return Volume(
id_=json_dict.get('id'),
display_name=json_dict.get('display_name'),
size=json_dict.get('size'),
volume_type=json_dict.get('volume_type'),
display_description=json_dict.get('display_description'),
metadata=json_dict.get('metadata'),
availability_zone=json_dict.get('availability_zone'),
snapshot_id=json_dict.get('snapshot_id'),
attachments=json_dict.get('attachments'),
created_at=json_dict.get('created_at'),
status=json_dict.get('status'))
#Response Deserializers
@classmethod
def _xml_to_obj(cls, serialized_str):
element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(element)
@classmethod
def _xml_ele_to_obj(cls, element):
return Volume(
id_=element.get('id'),
display_name=element.get('display_name'),
size=element.get('size'),
volume_type=element.get('volume_type'),
display_description=element.get('display_description'),
metadata=element.get('metadata'),
availability_zone=element.get('availability_zone'),
snapshot_id=element.get('snapshot_id'),
attachments=element.get('attachments'),
created_at=element.get('created_at'),
status=element.get('status'))
class VolumeList(AutoMarshallingListModel):
TAG = 'volumes'
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_dict_list = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_dict_list)
@classmethod
def _json_dict_to_obj(cls, json_dict):
volume_list = VolumeList()
for volume_dict in json_dict:
volume_list.append(Volume._json_dict_to_obj(volume_dict))
return volume_list
@classmethod
def _xml_to_obj(cls, serialized_str):
volume_list_element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(volume_list_element)
@classmethod
def _xml_ele_to_obj(cls, xml_etree_element):
volume_list = VolumeList()
for volume_element in xml_etree_element:
volume_list.append(Volume._xml_ele_to_obj(volume_element))
return volume_list
class VolumeSnapshot(AutoMarshallingModel):
TAG = 'snapshot'
def __init__(self, id_=None, volume_id=None, display_name=None,
display_description=None, status=None,
size=None, created_at=None, name=None):
self.id_ = id_
self.volume_id = volume_id
self.display_name = display_name
self.display_description = display_description
self.status = status
self.size = size
self.created_at = created_at
self.name = name
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_snap_dict = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_snap_dict)
@classmethod
def _json_dict_to_obj(cls, json_dict):
return VolumeSnapshot(
id_=json_dict.get('id'),
volume_id=json_dict.get('volume_id'),
display_name=json_dict.get('display_name'),
display_description=json_dict.get('display_description'),
status=json_dict.get('status'),
size=json_dict.get('size'),
created_at=json_dict.get('created_at'),
name=json_dict.get('name'))
@classmethod
def _xml_to_obj(cls, serialized_str):
volume_snap_element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(volume_snap_element)
@classmethod
def _xml_ele_to_obj(cls, xml_etree_element):
return VolumeSnapshot(
id_=xml_etree_element.get('id'),
volume_id=xml_etree_element.get('volume_id'),
display_name=xml_etree_element.get('display_name'),
display_description=xml_etree_element.get('display_description'),
status=xml_etree_element.get('status'),
size=xml_etree_element.get('size'),
created_at=xml_etree_element.get('created_at'),
name=xml_etree_element.get('name'))
class VolumeSnapshotList(AutoMarshallingListModel):
TAG = 'snapshots'
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_snap_dict_list = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_snap_dict_list)
@classmethod
def _json_dict_to_obj(cls, json_dict):
volume_snap_list = VolumeSnapshotList()
for volume_snap_dict in json_dict:
volume_snap_list.append(VolumeSnapshot._json_dict_to_obj(
volume_snap_dict))
return volume_snap_list
@classmethod
def _xml_to_obj(cls, serialized_str):
volume_snap_list_element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(volume_snap_list_element)
@classmethod
def _xml_ele_to_obj(cls, xml_etree_element):
volume_snap_list = VolumeSnapshotList()
for volume_snap_element in xml_etree_element:
volume_snap_list.append(VolumeSnapshot._xml_ele_to_obj(
volume_snap_element))
return volume_snap_list
class VolumeType(AutoMarshallingModel):
TAG = 'volume_type'
def __init__(self, id_=None, name=None, extra_specs=None):
self.id_ = id_
self.name = name
self.extra_specs = extra_specs
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_type_dict = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_type_dict)
@classmethod
def _json_dict_to_obj(cls, json_dict):
return VolumeType(
id_=json_dict.get('id_'),
name=json_dict.get('name'),
extra_specs=json_dict.get('extra_specs'))
@classmethod
def _xml_to_obj(cls, serialized_str):
volume_type_element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(volume_type_element)
@classmethod
def _xml_ele_to_obj(cls, xml_etree_element):
return VolumeType(
id_=xml_etree_element.get('id_'),
name=xml_etree_element.get('name'),
extra_specs=xml_etree_element.get('extra_specs'))
class VolumeTypeList(AutoMarshallingListModel):
TAG = 'volume_types'
@classmethod
def _json_to_obj(cls, serialized_str):
json_dict = json.loads(serialized_str)
volume_type_dict_list = json_dict.get(cls.TAG)
return cls._json_dict_to_obj(volume_type_dict_list)
@classmethod
def _json_dict_to_obj(cls, json_dict):
volume_type_list = VolumeTypeList()
for volume_type_dict in json_dict:
volume_type_list.append(VolumeType._json_dict_to_obj(
volume_type_dict))
return volume_type_list
@classmethod
def _xml_to_obj(cls, serialized_str):
volume_type_list_element = ElementTree.fromstring(serialized_str)
return cls._xml_ele_to_obj(volume_type_list_element)
@classmethod
def _xml_ele_to_obj(cls, xml_etree_element):
volume_type_list = VolumeTypeList()
for volume_type_element in xml_etree_element:
volume_type_list.append(VolumeType._xml_ele_to_obj(
volume_type_element))
return volume_type_list

View File

@@ -0,0 +1,35 @@
"""
Copyright 2013 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.
"""
from cafe.engine.provider import BaseProvider
from cloudcafe.blockstorage.volumes_api.config import VolumesAPIConfig
from cloudcafe.blockstorage.volumes_api.behaviors import VolumesBehaviors
from cloudcafe.blockstorage.volumes_api.client import VolumesClient
class VolumesProvider(BaseProvider):
def __init__(self, url, auth_token, tenant_id):
volumes_config = VolumesAPIConfig()
serialize_format = volumes_config.serialize_format
deserialize_format = volumes_config.deserialize_format
self.client = VolumesClient(
url, auth_token, tenant_id, serialize_format=serialize_format,
deserialize_format=deserialize_format)
self.behaviors = VolumesBehaviors(self.client)