Initial Commit
This commit is contained in:
commit
481102076c
30
.gitignore
vendored
Normal file
30
.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
||||
*.py[co]
|
||||
|
||||
# Packages
|
||||
*.egg
|
||||
*.egg-info
|
||||
dist
|
||||
build
|
||||
eggs
|
||||
parts
|
||||
var
|
||||
sdist
|
||||
develop-eggs
|
||||
.installed.cfg
|
||||
|
||||
# Installer logs
|
||||
pip-log.txt
|
||||
|
||||
# Unit test / coverage reports
|
||||
.coverage
|
||||
.tox
|
||||
|
||||
#Translations
|
||||
*.mo
|
||||
|
||||
#Mr Developer
|
||||
.mr.developer.cfg
|
||||
|
||||
# IDE Project Files
|
||||
*.project
|
||||
*.pydev*
|
0
HISTORY.rst
Normal file
0
HISTORY.rst
Normal file
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
||||
# 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.
|
79
README.md
Normal file
79
README.md
Normal file
@ -0,0 +1,79 @@
|
||||
CloudCAFE, An CAFE Implementation for OpenStack
|
||||
================================
|
||||
<pre>
|
||||
_ _ _
|
||||
( ` )_
|
||||
( ) `) _
|
||||
(____(__.___`)__)
|
||||
|
||||
( (
|
||||
) )
|
||||
.........
|
||||
| |___
|
||||
| |_ |
|
||||
| :-) |_| |
|
||||
| |___|
|
||||
|_______|
|
||||
=== CloudCAFE ===
|
||||
= An Open CAFE Implementation =
|
||||
</pre>
|
||||
|
||||
CloudCAFE is an implementation of the [Open CAFE Framework](https://github.com/stackforge) specifically designed to test deployed
|
||||
versions of [OpenStack](http://http://www.openstack.org/). It is built using the [Open CAFE Core](https://github.com/stackforge).
|
||||
|
||||
Supported Operating Systems
|
||||
---------------------------
|
||||
CloudCAFE has been developed primarily in Linux and MAC environments, however, it supports installation and
|
||||
execution on Windows
|
||||
|
||||
Installation
|
||||
------------
|
||||
CloudCAFE can be [installed with pip](https://pypi.python.org/pypi/pip) from the git repository after it is cloned to a local machine.
|
||||
|
||||
* First follow the README instructions to install [Open CAFE Core](https://github.com/stackforge)
|
||||
* Clone this repository to your local machine
|
||||
* CD to the root directory in your cloned repository.
|
||||
* Run "pip install . --upgrade" and pip will auto install all other dependencies.
|
||||
|
||||
Configuration
|
||||
--------------
|
||||
CloudCAFE works in tandem with the [Open CAFE Core](https://github.com/stackforge) cafe-runner. This installation of CloudCAFE includes a reference
|
||||
configuration for each of the CloudCAFE supported OpenStack products. Configurations will be installed to: <USER_HOME>/.cloudcafe/configs/<PRODUCT>
|
||||
|
||||
To use CloudCAFE you **will need to create/install your own configurations** based on the reference configs pointing to your deployment of OpenStack.
|
||||
|
||||
At this stage you will have the Open CAFE Core engine and the CloudCAFE Framework implementation. From this point you are ready to:
|
||||
1) Write entirely new tests using the CloudCAFE Framework
|
||||
or
|
||||
2) Install the [CloudRoast Test Repository](https://github.com/stackforge), an Open Source body of OpenStack automated tests written with CloudCAFE
|
||||
that can be executed or extended.
|
||||
|
||||
Logging
|
||||
-------
|
||||
CloudCAFE leverages the logging capabilities of the CAFE Core engine. If tests are executed with the built-in cafe-runner, runtime logs will be output
|
||||
to <USER_HOME>/.cloudcafe/logs/<PRODUCT>/<CONFIGURATION>/<TIME_STAMP>. In addition, tests built from the built-in CAFE unittest driver will generate
|
||||
csv statistics files in <USER_HOME>/.cloudcafe/logs/<PRODUCT>/<CONFIGURATION>/statistics for each and ever execution of each and every test case that
|
||||
provides metrics of execution over time for elapsed time, pass/fail rates, etc...
|
||||
|
||||
Basic CloudCAFE Package Anatomy
|
||||
-------------------------------
|
||||
Below is a short description of the top level CloudCAFE Packages.
|
||||
|
||||
##cloudcafe
|
||||
This is the root package for all things CloudCAFE.
|
||||
|
||||
##common
|
||||
Contains modules that extend the CAFE Core engine specific to OpenStack. This is the primary namespace for tools, data generators, common
|
||||
reporting classes, etc...
|
||||
|
||||
##identity
|
||||
OpenStack Identity Service plug-in based on CAFE Core extensions.
|
||||
|
||||
##compute
|
||||
OpenStack Compute plug-in based on CAFE Core extensions.
|
||||
|
||||
##blockstorage
|
||||
OpenStack Block Storage plug-in based on CAFE Core extensions.
|
||||
|
||||
##objectstorage
|
||||
OpenStack Object Storage plug-in based on CAFE Core extensions.
|
16
__init__.py
Normal file
16
__init__.py
Normal 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.
|
||||
"""
|
||||
|
22
cloudcafe/__init__.py
Normal file
22
cloudcafe/__init__.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
__title__ = 'cloudcafe'
|
||||
__version__ = '0.0.1'
|
||||
#__build__ = 0x010100
|
||||
__author__ = 'Rackspace Cloud QE'
|
||||
__license__ = 'Internal Only'
|
||||
__copyright__ = 'Copyright 2013 Rackspace Inc.'
|
16
cloudcafe/blockstorage/__init__.py
Normal file
16
cloudcafe/blockstorage/__init__.py
Normal 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.
|
||||
"""
|
||||
|
30
cloudcafe/blockstorage/config.py
Normal file
30
cloudcafe/blockstorage/config.py
Normal 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')
|
26
cloudcafe/blockstorage/integration/compute_behaviors.py
Normal file
26
cloudcafe/blockstorage/integration/compute_behaviors.py
Normal 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
|
37
cloudcafe/blockstorage/provider.py
Normal file
37
cloudcafe/blockstorage/provider.py
Normal 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)
|
||||
|
||||
|
16
cloudcafe/blockstorage/volumes_api/__init__.py
Normal file
16
cloudcafe/blockstorage/volumes_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
374
cloudcafe/blockstorage/volumes_api/behaviors.py
Normal file
374
cloudcafe/blockstorage/volumes_api/behaviors.py
Normal 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
|
201
cloudcafe/blockstorage/volumes_api/client.py
Normal file
201
cloudcafe/blockstorage/volumes_api/client.py
Normal 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)
|
||||
|
75
cloudcafe/blockstorage/volumes_api/config.py
Normal file
75
cloudcafe/blockstorage/volumes_api/config.py
Normal 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)
|
16
cloudcafe/blockstorage/volumes_api/models/__init__.py
Normal file
16
cloudcafe/blockstorage/volumes_api/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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
|
@ -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.
|
||||
"""
|
||||
|
@ -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
|
35
cloudcafe/blockstorage/volumes_api/provider.py
Normal file
35
cloudcafe/blockstorage/volumes_api/provider.py
Normal 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)
|
16
cloudcafe/common/__init__.py
Normal file
16
cloudcafe/common/__init__.py
Normal 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.
|
||||
"""
|
||||
|
25
cloudcafe/common/constants.py
Normal file
25
cloudcafe/common/constants.py
Normal file
@ -0,0 +1,25 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class InstanceClientConstants:
|
||||
LAST_REBOOT_TIME_FORMAT = '%Y-%m-%d %H:%M'
|
||||
LAST_REBOOT_TIME_FORMAT_GENTOO = '%b %d %H:%M %Y'
|
||||
LINUX_OS_FAMILY = 'linux'
|
||||
PING_IPV4_COMMAND_LINUX = 'ping -c 3 '
|
||||
PING_IPV6_COMMAND_LINUX = 'ping6 -c 3 '
|
||||
PING_IPV4_COMMAND_WINDOWS = 'ping '
|
||||
PING_IPV6_COMMAND_WINDOWS = 'ping6 '
|
||||
PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\%.*loss'
|
5
cloudcafe/common/generators/__init__.py
Normal file
5
cloudcafe/common/generators/__init__.py
Normal file
@ -0,0 +1,5 @@
|
||||
'''
|
||||
@summary: Classes and Utilities for adapters that provide low level connectivity to various resources
|
||||
@note: Most often consumed by a L{cafe.engine.clients} or L{cafe.common.reporting}
|
||||
@note: Should not be used directly by a test case or process
|
||||
'''
|
0
cloudcafe/common/generators/identity/__init__.py
Normal file
0
cloudcafe/common/generators/identity/__init__.py
Normal file
19
cloudcafe/common/generators/identity/password.py
Normal file
19
cloudcafe/common/generators/identity/password.py
Normal file
@ -0,0 +1,19 @@
|
||||
from datetime import datetime
|
||||
|
||||
from cafe.common.generators.base import BaseDataGenerator
|
||||
|
||||
class PasswordGenerator(BaseDataGenerator):
|
||||
def __init__(self):
|
||||
self.test_records = []
|
||||
stamp = datetime.now().microsecond
|
||||
cluster_name = "auth_functional_%s" %stamp
|
||||
self.test_records.append({"false_password":'00000000',
|
||||
"false_username":'@1234567'})
|
||||
self.test_records.append({"false_password":'',
|
||||
"false_username":''})
|
||||
self.test_records.append({"false_password":'Pass1',
|
||||
"false_username":'@'})
|
||||
self.test_records.append({"false_password":'!@#$%^&*()',
|
||||
"false_username":' 1Afarsf'})
|
||||
self.test_records.append({"false_password":'102102101031013010311031',
|
||||
"false_username":'Ricardo0000000000000!'})
|
16
cloudcafe/common/models/__init__.py
Normal file
16
cloudcafe/common/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
48
cloudcafe/common/models/configuration.py
Normal file
48
cloudcafe/common/models/configuration.py
Normal file
@ -0,0 +1,48 @@
|
||||
"""
|
||||
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 os
|
||||
from cafe.engine.models.data_interfaces import\
|
||||
BaseConfigSectionInterface, ConfigEnvironmentVariableError
|
||||
|
||||
_TEST_CONFIG_FILE_ENV_VAR = 'OSTNG_CONFIG_FILE'
|
||||
|
||||
|
||||
class ConfigSectionInterface(BaseConfigSectionInterface):
|
||||
def __init__(self, config_file_path=None, section_name=None):
|
||||
section_name = (section_name or
|
||||
getattr(self, 'SECTION_NAME', None) or
|
||||
getattr(self, 'CONFIG_SECTION_NAME', None))
|
||||
|
||||
config_file_path = config_file_path or self.default_config_file
|
||||
|
||||
super(ConfigSectionInterface, self).__init__(config_file_path, section_name)
|
||||
|
||||
@property
|
||||
def default_config_file(self):
|
||||
test_config_file_path = None
|
||||
try:
|
||||
test_config_file_path = os.environ[_TEST_CONFIG_FILE_ENV_VAR]
|
||||
except KeyError:
|
||||
msg = "'{0}' environment variable was not set.".format(
|
||||
_TEST_CONFIG_FILE_ENV_VAR)
|
||||
raise ConfigEnvironmentVariableError(msg)
|
||||
except Exception as exception:
|
||||
print ("Unexpected exception when attempting to access '{1}'"
|
||||
" environment variable.".format(_TEST_CONFIG_FILE_ENV_VAR))
|
||||
raise exception
|
||||
|
||||
return test_config_file_path
|
44
cloudcafe/common/resources.py
Normal file
44
cloudcafe/common/resources.py
Normal file
@ -0,0 +1,44 @@
|
||||
|
||||
|
||||
class Resource:
|
||||
"""
|
||||
@summary: Keeps details of a resource like server or image and how to delete it.
|
||||
"""
|
||||
|
||||
def __init__(self, resource_id, delete_function):
|
||||
self.resource_id = resource_id
|
||||
self.delete_function = delete_function
|
||||
|
||||
def delete(self):
|
||||
"""
|
||||
@summary: Deletes the resource
|
||||
"""
|
||||
self.delete_function(self.resource_id)
|
||||
|
||||
|
||||
class ResourcePool:
|
||||
"""
|
||||
@summary: Pool of resources to be tracked for deletion.
|
||||
"""
|
||||
def __init__(self):
|
||||
self.resources = []
|
||||
|
||||
def add(self, resource_id, delete_function):
|
||||
"""
|
||||
@summary: Adds a resource to the resource pool
|
||||
@param resource_id: Unique identifier of resource
|
||||
@type resource_id: string
|
||||
@param delete_function: The function to be called to delete a server
|
||||
@type delete_function: Function Pointer
|
||||
"""
|
||||
self.resources.append(Resource(resource_id, delete_function))
|
||||
|
||||
def release(self):
|
||||
"""
|
||||
@summary: Delete all the resources in the Resource Pool
|
||||
"""
|
||||
for resource in self.resources:
|
||||
try:
|
||||
resource.delete()
|
||||
except:
|
||||
pass
|
16
cloudcafe/compute/__init__.py
Normal file
16
cloudcafe/compute/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/behaviors.py
Normal file
16
cloudcafe/compute/behaviors.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/common/__init__.py
Normal file
16
cloudcafe/compute/common/__init__.py
Normal 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.
|
||||
"""
|
||||
|
41
cloudcafe/compute/common/constants.py
Normal file
41
cloudcafe/compute/common/constants.py
Normal file
@ -0,0 +1,41 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class Constants:
|
||||
LAST_REBOOT_TIME_FORMAT = '%Y-%m-%d %H:%M'
|
||||
LAST_REBOOT_TIME_FORMAT_GENTOO = '%b %d %H:%M %Y'
|
||||
LINUX_OS_FAMILY = 'linux'
|
||||
PING_IPV4_COMMAND_LINUX = 'ping -c 3 '
|
||||
PING_IPV6_COMMAND_LINUX = 'ping6 -c 3 '
|
||||
PING_IPV4_COMMAND_WINDOWS = 'ping '
|
||||
PING_IPV6_COMMAND_WINDOWS = 'ping6 '
|
||||
PING_PACKET_LOSS_REGEX = '(\d{1,3})\.?\d*\%.*loss'
|
||||
XML_API_NAMESPACE = 'http://docs.openstack.org/compute/api/v1.1'
|
||||
XML_API_DISK_CONFIG_NAMESPACE = 'http://docs.openstack.org/compute/ext/disk_config/api/v1.1'
|
||||
XML_API_EXTENDED_STATUS_NAMESPACE = 'http://docs.openstack.org/compute/ext/extended_status/api/v1.1'
|
||||
XML_API_ATOM_NAMESPACE = 'http://www.w3.org/2005/Atom'
|
||||
XML_API_RESCUE = 'http://docs.openstack.org/compute/ext/rescue/api/v1.1'
|
||||
XML_API_UNRESCUE = 'http://docs.rackspacecloud.com/servers/api/v1.1'
|
||||
DATETIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
||||
DATETIME_6AM_FORMAT = "%Y-%m-%d 06:00:00"
|
||||
DATETIME_0AM_FORMAT = "%Y-%m-%d 00:00:00"
|
||||
XML_HEADER = "<?xml version='1.0' encoding='UTF-8'?>"
|
||||
SERVICE_TYPE = 'cloudServersOpenStack'
|
||||
|
||||
|
||||
class HTTPResponseCodes(object):
|
||||
NOT_FOUND = 404
|
||||
SERVER_ERROR = 500
|
133
cloudcafe/compute/common/datagen.py
Normal file
133
cloudcafe/compute/common/datagen.py
Normal file
@ -0,0 +1,133 @@
|
||||
"""
|
||||
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 uuid import uuid4
|
||||
import random
|
||||
from math import pow
|
||||
import time
|
||||
|
||||
SOURCE_RANDOM = '/dev/urandom'
|
||||
SOURCE_ZEROS = '/dev/zero'
|
||||
TEMP_LOCATION = '/tmp'
|
||||
|
||||
#Binary prefixes
|
||||
#IEE_MAGNITUDE = int(pow(2,10))
|
||||
EXACT_BYTE = 8
|
||||
EXACT_KIBIBYTE = int(pow(2,10))
|
||||
EXACT_MEBIBYTE = int(pow(2,20))
|
||||
EXACT_GIBIBYTE = int(pow(2,30))
|
||||
EXACT_TEBIBYTE = int(pow(2,40))
|
||||
|
||||
#Decimal prefixes
|
||||
#SI_MAGNITURE = int(pow(10,3))
|
||||
|
||||
EXACT_KILOBYTE = int(pow(10,3))
|
||||
EXACT_MEGABYTE = int(pow(10,6))
|
||||
EXACT_GIGABYTE = int(pow(10,9))
|
||||
EXACT_TERABYTE = int(pow(10,12))
|
||||
|
||||
|
||||
def timestamp_string(prefix=None, suffix=None, decimal_precision=6):
|
||||
'''
|
||||
Return a unix timestamp surrounded by any defined prefixes and suffixes
|
||||
Decimal precision is full (6) by default.
|
||||
'''
|
||||
t = str('%f' % time.time())
|
||||
int_seconds, dec_seconds = t.split('.')
|
||||
for x in range(6 - decimal_precision):
|
||||
dec_seconds=dec_seconds[:-1]
|
||||
|
||||
int_seconds = str(int_seconds)
|
||||
dec_seconds = str(dec_seconds)
|
||||
prefix = prefix or ''
|
||||
suffix = suffix or ''
|
||||
final = None
|
||||
if len(dec_seconds) > 0:
|
||||
final = '%s%s%s' % ( prefix, int_seconds, suffix)
|
||||
else:
|
||||
final = '%s%s.%s%s' % ( prefix, int_seconds, dec_seconds, suffix)
|
||||
|
||||
return final
|
||||
|
||||
|
||||
def random_string(prefix=None, suffix=None, size=8):
|
||||
'''
|
||||
Return exactly size bytes worth of base_text as a string
|
||||
surrounded by any defined pre or suf-fixes
|
||||
'''
|
||||
|
||||
base_text = str(uuid4()).replace('-','0')
|
||||
|
||||
if size <= 0:
|
||||
return '%s%s' % (prefix, suffix)
|
||||
|
||||
extra = size % len(base_text)
|
||||
body = ''
|
||||
|
||||
if extra == 0:
|
||||
body = base_text * size
|
||||
|
||||
if extra == size:
|
||||
body = base_text[:size]
|
||||
|
||||
if (extra > 0) and (extra < size):
|
||||
body = (size / len(base_text)) * base_text + base_text[:extra]
|
||||
|
||||
body = str(prefix) + str(body) if prefix is not None else body
|
||||
body = str(body) + str(suffix) if suffix is not None else body
|
||||
return body
|
||||
|
||||
def random_ip(pattern=None):
|
||||
'''Takes a pattern as a string in the format of #.#.#.# where a # is an
|
||||
integer, and a can be substituded with an * to produce a random octet.
|
||||
pattern = 127.0.0.* would return a random string between 127.0.0.1 and
|
||||
127.0.0.254'''
|
||||
if pattern is None:
|
||||
pattern = '*.*.*.*'
|
||||
num_asterisks = 0
|
||||
for c in pattern:
|
||||
if c == '*':
|
||||
num_asterisks += 1
|
||||
rand_list = [random.randint(1, 255) for i in range(0, num_asterisks)]
|
||||
for item in rand_list:
|
||||
pattern = pattern.replace('*', str(item), 1)
|
||||
return pattern
|
||||
|
||||
def random_cidr(ip_pattern=None, mask=None, min_mask=0, max_mask=30):
|
||||
'''Gets a random cidr using the random_ip function in this module. If mask
|
||||
is None then a random mask between 0 and 30 inclusive will be assigned.'''
|
||||
if mask is None:
|
||||
mask = random.randint(min_mask, max_mask)
|
||||
ip = random_ip(ip_pattern)
|
||||
return ''.join([ip, '/', str(mask)])
|
||||
|
||||
def random_int(min_int, max_int):
|
||||
return random.randint(min_int, max_int)
|
||||
|
||||
def rand_name(name='test'):
|
||||
return name + str(random.randint(99999, 1000000))
|
||||
|
||||
def random_item_in_list(selection_list):
|
||||
return random.choice(selection_list)
|
||||
|
||||
def bytes_to_gb(val):
|
||||
return float(val) / 1073741824
|
||||
|
||||
def gb_to_bytes(val):
|
||||
return int(val * 1073741824)
|
||||
|
||||
def bytes_to_mb(val):
|
||||
return float(val) / 1024
|
73
cloudcafe/compute/common/equality_tools.py
Normal file
73
cloudcafe/compute/common/equality_tools.py
Normal file
@ -0,0 +1,73 @@
|
||||
"""
|
||||
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 datetime import timedelta
|
||||
|
||||
class EqualityTools:
|
||||
|
||||
@classmethod
|
||||
def are_not_equal(expected, actual):
|
||||
return expected != None and expected != actual
|
||||
|
||||
@classmethod
|
||||
def are_lists_equal(expected, actual):
|
||||
if expected is None and actual is None:
|
||||
return True
|
||||
if expected is None or actual is None:
|
||||
return False
|
||||
if len(expected) != len(actual):
|
||||
return False
|
||||
for i in range(len(expected)):
|
||||
if not expected[i].equals(actual[i]):
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def sanitized_dict(self, dict_to_sanitize={}, keys_not_to_include=[]):
|
||||
#make a shallow copy
|
||||
sanitized_dict = dict_to_sanitize.copy()
|
||||
for key in keys_not_to_include:
|
||||
try:
|
||||
del sanitized_dict[str(key)]
|
||||
except:
|
||||
continue
|
||||
return sanitized_dict
|
||||
|
||||
@classmethod
|
||||
def are_objects_equal(cls, expected_object, actual_object, keys_to_exclude=[]):
|
||||
|
||||
if(expected_object is None and actual_object is None):
|
||||
return True
|
||||
|
||||
if(expected_object is None or actual_object is None):
|
||||
return False
|
||||
|
||||
for key, expected_value in expected_object.__dict__.items():
|
||||
if key not in keys_to_exclude and expected_value != actual_object.__dict__[key]:
|
||||
return False
|
||||
return True
|
||||
|
||||
@classmethod
|
||||
def are_sizes_equal(cls, size1, size2, leeway):
|
||||
return abs(size1 - size2) <= leeway
|
||||
|
||||
@classmethod
|
||||
def is_true(cls, value):
|
||||
return value is not None and (str(value) == '1' or str.lower(value) == 'true')
|
||||
|
||||
@classmethod
|
||||
def are_datetimes_equal(cls, datetime1, datetime2, leeway=timedelta(seconds=0)):
|
||||
return abs(datetime1 - datetime2) <= leeway
|
121
cloudcafe/compute/common/exception_handler.py
Normal file
121
cloudcafe/compute/common/exception_handler.py
Normal 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 xml.etree.ElementTree as ET
|
||||
import json
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
import cloudcafe.compute.common.exceptions as exceptions
|
||||
|
||||
|
||||
ns = "http://docs.openstack.org/compute/api/v1.1"
|
||||
|
||||
|
||||
class ExceptionHandler:
|
||||
|
||||
error_codes_list = [400, 401, 403, 404, 405, 409, 413, 415, 500, 501, 503]
|
||||
|
||||
def check_for_errors(self, resp):
|
||||
|
||||
if resp.status_code not in self.error_codes_list:
|
||||
return
|
||||
|
||||
resp_body_dict = None
|
||||
if resp.text != "":
|
||||
resp_body_dict, type = self._parse_resp_body(resp.text)
|
||||
|
||||
if resp.status_code == 400 and type == 'html':
|
||||
raise exceptions.BadRequest(resp_body_dict['400 Bad Request']['message'])
|
||||
|
||||
if resp.status_code == 400:
|
||||
raise exceptions.BadRequest(resp_body_dict['badRequest']['message'])
|
||||
|
||||
if resp.status_code == 401:
|
||||
raise exceptions.Unauthorized()
|
||||
|
||||
if resp.status_code == 413:
|
||||
if 'overLimit' in resp_body_dict:
|
||||
message = resp_body_dict['overLimit']['message']
|
||||
else:
|
||||
message = 'Rate or absolute limit exceeded'
|
||||
raise exceptions.OverLimit(message)
|
||||
|
||||
if resp.status_code == 500 and type == 'html':
|
||||
raise exceptions.InternalServerError()
|
||||
|
||||
if (resp.status_code == 500) and (resp_body_dict == None):
|
||||
raise exceptions.ComputeFault(resp.reason)
|
||||
|
||||
if resp.status_code in (500, 501):
|
||||
message = ''
|
||||
if 'computeFault' in resp_body_dict:
|
||||
message = resp_body_dict['computeFault']['message']
|
||||
if 'cloudServersFault' in resp_body_dict:
|
||||
message = resp_body_dict['cloudServersFault']['message']
|
||||
if 'x-compute-request-id' in resp_body_dict:
|
||||
message += ' x-compute-request-id ' + resp_body_dict['x-compute-request-id']
|
||||
raise exceptions.ComputeFault(message)
|
||||
|
||||
if resp.status_code == 404:
|
||||
raise exceptions.ItemNotFound()
|
||||
|
||||
if resp.status_code == 409:
|
||||
message = ''
|
||||
if 'conflictingRequest' in resp_body_dict:
|
||||
message = resp_body_dict['conflictingRequest']['message']
|
||||
if 'inProgress' in resp_body_dict:
|
||||
message = resp_body_dict['inProgress']['message']
|
||||
raise exceptions.ActionInProgress(message)
|
||||
|
||||
if resp.status_code == 405:
|
||||
raise exceptions.BadMethod()
|
||||
|
||||
if resp.status_code == 403:
|
||||
raise exceptions.Forbidden()
|
||||
|
||||
if resp.status_code == 503:
|
||||
raise exceptions.ServiceUnavailable()
|
||||
|
||||
if resp.status_code == 415:
|
||||
raise exceptions.BadMediaType()
|
||||
|
||||
def _parse_resp_body(self, resp_body):
|
||||
#Try parsing as JSON
|
||||
|
||||
try:
|
||||
body = json.loads(resp_body)
|
||||
type = 'json'
|
||||
return body, type
|
||||
except:
|
||||
#Not JSON
|
||||
pass
|
||||
|
||||
#Try parsing as XML
|
||||
try:
|
||||
element = ET.fromstring(resp_body)
|
||||
# Handle the case where the API returns the exception in HTML
|
||||
AutoMarshallingModel._remove_namespace(element, ns)
|
||||
type = 'xml'
|
||||
return {element.tag: {'message': element.find('message').text}}, type
|
||||
except:
|
||||
#Not XML Either
|
||||
pass
|
||||
|
||||
#Parse as HTML
|
||||
finally:
|
||||
split_resp = resp_body.split("\n\n")
|
||||
type = 'html'
|
||||
return {split_resp[0]: {'message': split_resp[1]}}, type
|
184
cloudcafe/compute/common/exceptions.py
Normal file
184
cloudcafe/compute/common/exceptions.py
Normal file
@ -0,0 +1,184 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class TimeoutException(Exception):
|
||||
""" Exception on timeout """
|
||||
def __init__(self, message='Request timed out'):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BuildErrorException(Exception):
|
||||
""" Exception on server build """
|
||||
def __init__(self, message='Build Error'):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class DeleteException(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BadRequest(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BadRequestHtml(Exception):
|
||||
def __init__(self):
|
||||
self.message = '400 - Bad Request.'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class OverLimit(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class ComputeFault(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class ItemNotFound(Exception):
|
||||
def __init__(self):
|
||||
self.message = '404 - Not found.'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BadMethod(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = "405 - Bad Method."
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class Unauthorized(Exception):
|
||||
def __init__(self):
|
||||
self.message = "401 - Unauthorized."
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class Forbidden(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = "403 - Forbidden Operation"
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class ActionInProgress(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BuildInProgress(Exception):
|
||||
def __init__(self):
|
||||
self.message = "409 - Action failed. Entity is currently building."
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class ServiceUnavailable(Exception):
|
||||
def __init__(self):
|
||||
self.message = "503 - The service is currently unavailable."
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class FileNotFoundException(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class SshConnectionException(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = message
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class ServerUnreachable(Exception):
|
||||
def __init__(self, address):
|
||||
self.message = 'Could not reach the server at %s.' % address
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class InvalidJSON(Exception):
|
||||
def __init__(self, message, expected_response):
|
||||
self.message = 'Unexpected JSON response. Parsing of the following JSON failed ' + message + '. Expected response of type ' + expected_response
|
||||
|
||||
|
||||
class AuthenticationTimeoutException(Exception):
|
||||
def __init__(self, server_id=None):
|
||||
if server_id is None:
|
||||
self.message = 'Authentication to the desired failed due to timing out.'
|
||||
else:
|
||||
self.message = 'Authentication to server ' + server_id + ' failed due to timing out.'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class BadMediaType(Exception):
|
||||
def __init__(self, message):
|
||||
self.message = '415 - Bad media type.'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
||||
|
||||
|
||||
class InternalServerError(Exception):
|
||||
def __init__(self):
|
||||
self.message = '500 - Internal Server Error.'
|
||||
|
||||
def __str__(self):
|
||||
return repr(self.message)
|
16
cloudcafe/compute/common/models/__init__.py
Normal file
16
cloudcafe/compute/common/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
29
cloudcafe/compute/common/models/file_details.py
Normal file
29
cloudcafe/compute/common/models/file_details.py
Normal file
@ -0,0 +1,29 @@
|
||||
"""
|
||||
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.compute.common.equality_tools import EqualityTools
|
||||
|
||||
class FileDetails:
|
||||
"""
|
||||
@summary: Represents File details
|
||||
"""
|
||||
def __init__(self, absolute_permissions, content, name):
|
||||
self.absolute_permissions = absolute_permissions
|
||||
self.content = content
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
return EqualityTools.are_objects_equal(self, other)
|
127
cloudcafe/compute/common/models/link.py
Normal file
127
cloudcafe/compute/common/models/link.py
Normal file
@ -0,0 +1,127 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
|
||||
|
||||
class Links(AutoMarshallingModel):
|
||||
"""
|
||||
@summary: Represents links (url) in the system
|
||||
"""
|
||||
ROOT_TAG = 'links'
|
||||
|
||||
def __init__(self, links_list):
|
||||
super(Links, self).__init__()
|
||||
self.links = {}
|
||||
if links_list is not None:
|
||||
for link in links_list:
|
||||
self.links[link['rel']] = link['href']
|
||||
for key_name in self.links:
|
||||
setattr(self, key_name, self.links[key_name])
|
||||
|
||||
@classmethod
|
||||
def _xml_to_object(cls, serialized_str):
|
||||
"""
|
||||
@summary: Initializes the object from xml response
|
||||
@param objectified_links: links details
|
||||
@type objectified_links: objectify.Element
|
||||
"""
|
||||
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
cls._remove_namespace(element, Constants.XML_API_ATOM_NAMESPACE)
|
||||
|
||||
return cls._xml_ele_to_obj(element)
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Links instance.'''
|
||||
links = []
|
||||
'''
|
||||
When we serialize a flavor object to XML, we generate an additional
|
||||
tag <links> for the links which is the parent to the <link> element.
|
||||
Hence we need to loop twice to get to the dictionary of links
|
||||
<links>
|
||||
<link/>
|
||||
...
|
||||
</links>
|
||||
'''
|
||||
for child_element in element._children:
|
||||
if child_element.tag[29:] == 'link':
|
||||
links.append(child_element.attrib)
|
||||
if element.findall('link'):
|
||||
for link in element.findall('link'):
|
||||
links.append(link.attrib)
|
||||
return Links(links)
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, list_of_links):
|
||||
"""
|
||||
@summary: Initializes the object from json response
|
||||
@param list_of_links: links details
|
||||
@type list_of_links: list
|
||||
"""
|
||||
return Links(list_of_links)
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in __dict__:
|
||||
values.append("%s: %s" % (prop, __dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of links based on the json
|
||||
serialized_str passed in.'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
if 'links' in json_dict.keys():
|
||||
links_list = json_dict['links']
|
||||
return Links(links_list)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Links object to compare with
|
||||
@type other: Links
|
||||
@return: True if Links objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
if(self is None and other is None):
|
||||
return True
|
||||
|
||||
if(self is None or other is None):
|
||||
return False
|
||||
|
||||
for key in self.links:
|
||||
#Alternate links are random, equality is impossible..ignoring it
|
||||
if key != 'alternate' and \
|
||||
self.links[key] != other.links[key]:
|
||||
return False
|
||||
return True
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Links object to compare with
|
||||
@type other: Links
|
||||
@return: True if Links objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
214
cloudcafe/compute/common/models/metadata.py
Normal file
214
cloudcafe/compute/common/models/metadata.py
Normal file
@ -0,0 +1,214 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
from cloudcafe.compute.common.equality_tools import EqualityTools
|
||||
|
||||
class MetadataItem(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: MetadataItem Request/Response Object for Server/Image
|
||||
'''
|
||||
ROOT_TAG = 'meta'
|
||||
|
||||
def __init__(self, metadata_dict):
|
||||
for key, value in metadata_dict.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element('meta')
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
for key, value in (self._obj_to_dict(meta_obj=self)).items():
|
||||
element.set('key', key)
|
||||
element.text = value
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
@classmethod
|
||||
def _obj_to_dict(self, meta_obj):
|
||||
meta = {}
|
||||
for name in dir(meta_obj):
|
||||
value = getattr(meta_obj, name)
|
||||
if not name.startswith('_') and not name.startswith('RO') and not name.startswith('deser') and not name.startswith('sele') and not name.startswith('seria'):
|
||||
meta[name] = value
|
||||
return meta
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
"""
|
||||
@summary: Initializes the object from xml response
|
||||
@param objectified_links: metadata item
|
||||
@type objectified_links: objectify.Element
|
||||
"""
|
||||
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
metadata_item = cls._xml_ele_to_obj(element)
|
||||
return metadata_item
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to metadata instance.'''
|
||||
metadata_dict = {}
|
||||
metadata_dict[(element.attrib).get('key')] = element.text
|
||||
return MetadataItem(metadata_dict)
|
||||
|
||||
@classmethod
|
||||
def _dict_to_object(cls, metadata_dict):
|
||||
"""
|
||||
@summary: Initializes the object from json response
|
||||
@param metadata_dict: metadata items
|
||||
@type metadata_dict: dictionary
|
||||
"""
|
||||
return MetadataItem(metadata_dict)
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of metadata item based on the json
|
||||
serialized_str passed in.'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
if 'meta' in json_dict.keys():
|
||||
metadata_dict = json_dict['meta']
|
||||
return MetadataItem(metadata_dict)
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in __dict__:
|
||||
values.append("%s: %s" % (prop, __dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
|
||||
class Metadata(AutoMarshallingModel):
|
||||
ROOT_TAG = 'metadata'
|
||||
|
||||
'''
|
||||
@summary: Metadata Request Object for Server/Image
|
||||
'''
|
||||
def __init__(self, metadata_dict):
|
||||
for key, value in metadata_dict.items():
|
||||
setattr(self, key, value)
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element('metadata')
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
for name in dir(self):
|
||||
value = getattr(self, name)
|
||||
if not name.startswith('_') and not name.startswith('RO') and not name.startswith('deser') and not name.startswith('sele') and not name.startswith('seria'):
|
||||
element.append(self._dict_to_xml(key=name, value=value))
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
@classmethod
|
||||
def _dict_to_xml(self, key, value):
|
||||
meta_element = ET.Element('meta')
|
||||
meta_element.set('key', key)
|
||||
meta_element.text = value
|
||||
return meta_element
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
"""
|
||||
@summary: Initializes the object from xml response
|
||||
@param objectified_links: metadata details
|
||||
@type objectified_links: objectify.Element
|
||||
"""
|
||||
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
metadata = cls._xml_ele_to_obj(element)
|
||||
return metadata
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to metadata instance.'''
|
||||
meta_dict = {}
|
||||
entity = element
|
||||
if entity.find('metadata') is not None:
|
||||
meta_list = entity.find("metadata").findall('meta')
|
||||
for each in meta_list:
|
||||
meta_dict[each.attrib['key']] = each.text
|
||||
return Metadata(meta_dict)
|
||||
if entity.tag == 'metadata':
|
||||
meta_list = entity.findall('meta')
|
||||
for each in meta_list:
|
||||
meta_dict[each.attrib['key']] = each.text
|
||||
return Metadata(meta_dict)
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, metadata_dict):
|
||||
"""
|
||||
@summary: Initializes the object from json response
|
||||
@param metadata_dict: metadata details
|
||||
@type metadata_dict: dictionary
|
||||
"""
|
||||
return Metadata(metadata_dict)
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of metadata based on the json
|
||||
serialized_str passed in.'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
if 'metadata' in json_dict.keys():
|
||||
metadata_dict = json_dict['metadata']
|
||||
return Metadata(metadata_dict)
|
||||
|
||||
@classmethod
|
||||
def _obj_to_dict(self, meta_obj):
|
||||
meta = {}
|
||||
for name in dir(meta_obj):
|
||||
value = getattr(meta_obj, name)
|
||||
if not name.startswith('_') and not name.startswith('RO') and not name.startswith('deser') and not name.startswith('sele') and not name.startswith('seria'):
|
||||
meta[name] = value
|
||||
return meta
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in __dict__:
|
||||
values.append("%s: %s" % (prop, __dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Links object to compare with
|
||||
@type other: Links
|
||||
@return: True if Links objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Links object to compare with
|
||||
@type other: Links
|
||||
@return: True if Links objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
96
cloudcafe/compute/common/models/partition.py
Normal file
96
cloudcafe/compute/common/models/partition.py
Normal file
@ -0,0 +1,96 @@
|
||||
"""
|
||||
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.compute.common.equality_tools import EqualityTools
|
||||
|
||||
|
||||
class Partition:
|
||||
"""
|
||||
@summary: Represents a Disk Partition
|
||||
"""
|
||||
def __init__(self, name, size, type):
|
||||
self.name = name
|
||||
self.size = size
|
||||
self.type = type
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Partition object to compare with
|
||||
@type other: Partition
|
||||
@return: True if Partition objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Partition object to compare with
|
||||
@type other: Partition
|
||||
@return: True if Partition objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
@summary: Return string representation of Partition
|
||||
@return: String representation of Partition
|
||||
@rtype: string
|
||||
"""
|
||||
return "Partition Name : %s, Size: %s, Type : %s" % (self.name, self.size, self.type)
|
||||
|
||||
|
||||
class DiskSize:
|
||||
"""
|
||||
@summary: Represents a Disk Size
|
||||
"""
|
||||
|
||||
def __init__(self, value, unit, leeway_for_disk_size=2):
|
||||
self.value = float(value)
|
||||
self.unit = unit
|
||||
self.leeway_for_disk_size = leeway_for_disk_size
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: DiskSize object to compare with
|
||||
@type other: DiskSize
|
||||
@return: True if DiskSize objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return self.unit == other.unit and EqualityTools.are_sizes_equal(
|
||||
self.value, other.value,
|
||||
self.leeway_for_disk_size)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: DiskSize object to compare with
|
||||
@type other: DiskSize
|
||||
@return: True if DiskSize objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
"""
|
||||
@summary: Return string representation of DiskSize
|
||||
@return: String representation of DiskSize
|
||||
@rtype: string
|
||||
"""
|
||||
return "Disk Size : %s %s" % (self.value, self.unit)
|
95
cloudcafe/compute/common/types.py
Normal file
95
cloudcafe/compute/common/types.py
Normal file
@ -0,0 +1,95 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class NovaServerStatusTypes(object):
|
||||
'''
|
||||
@summary: Types dictating an individual Server Status
|
||||
@cvar ACTIVE: Server is active and available
|
||||
@type ACTIVE: C{str}
|
||||
@cvar BUILD: Server is being built
|
||||
@type BUILD: C{str}
|
||||
@cvar ERROR: Server is in error
|
||||
@type ERROR: C{str}
|
||||
@note: This is essentially an Enumerated Type
|
||||
'''
|
||||
ACTIVE = "ACTIVE"
|
||||
BUILD = "BUILD"
|
||||
REBUILD = "REBUILD"
|
||||
ERROR = "ERROR"
|
||||
DELETING = "DELETING"
|
||||
DELETED = "DELETED"
|
||||
RESCUE = "RESCUE"
|
||||
PREP_RESCUE = "PREP_RESCUE"
|
||||
INVALID_OPTION = "INVALID_OPTION"
|
||||
RESIZE = "RESIZE"
|
||||
VERIFY_RESIZE = "VERIFY_RESIZE"
|
||||
|
||||
|
||||
class NovaImageStatusTypes(object):
|
||||
'''
|
||||
@summary: Types dictating an individual Server Status
|
||||
@cvar ACTIVE: Server is active and available
|
||||
@type ACTIVE: C{str}
|
||||
@cvar BUILD: Server is being built
|
||||
@type BUILD: C{str}
|
||||
@cvar ERROR: Server is in error
|
||||
@type ERROR: C{str}
|
||||
@note: This is essentially an Enumerated Type
|
||||
'''
|
||||
ACTIVE = "ACTIVE"
|
||||
SAVING = "SAVING"
|
||||
ERROR = "ERROR"
|
||||
DELETED = "DELETED"
|
||||
UNKNOWN = "UNKNOWN"
|
||||
|
||||
|
||||
class NovaServerRebootTypes(object):
|
||||
'''
|
||||
@summary: Types dictating server reboot types
|
||||
@cvar HARD: Hard reboot
|
||||
@type HARD: C{str}
|
||||
@cvar SOFT: Soft reboot
|
||||
@type SOFT: C{str}
|
||||
@note: This is essentially an Enumerated Type
|
||||
'''
|
||||
HARD = "HARD"
|
||||
SOFT = "SOFT"
|
||||
|
||||
|
||||
class NovaVolumeStatusTypes(object):
|
||||
'''
|
||||
@summary: Types dictating an individual Volume Status
|
||||
@cvar AVAILABLE: Volume is active and available
|
||||
@type AVAILABLE: C{str}
|
||||
@cvar CREATING: Volume is being created
|
||||
@type CREATING: C{str}
|
||||
@cvar ERROR: Volume is in error
|
||||
@type ERROR: C{str}
|
||||
@cvar DELETING: Volume is being deleted
|
||||
@type DELETING: C{str}
|
||||
@cvar ERROR_DELETING: Volume is in error while being deleted
|
||||
@type ERROR_DELETING: C{str}
|
||||
@cvar IN_USE: Volume is active and available
|
||||
@type IN_USE: C{str}
|
||||
@note: This is essentially an Enumerated Type
|
||||
'''
|
||||
AVAILABLE = "available"
|
||||
ATTACHING = "attaching"
|
||||
CREATING = "creating"
|
||||
DELETING = "deleting"
|
||||
ERROR = "error"
|
||||
ERROR_DELETING = "error_deleting"
|
||||
IN_USE = "in-use"
|
30
cloudcafe/compute/config.py
Normal file
30
cloudcafe/compute/config.py
Normal 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 ComputeConfig(ConfigSectionInterface):
|
||||
|
||||
SECTION_NAME = 'compute'
|
||||
|
||||
@property
|
||||
def region(self):
|
||||
return self.get("region")
|
||||
|
||||
@property
|
||||
def compute_endpoint_name(self):
|
||||
return self.get("compute_endpoint_name")
|
16
cloudcafe/compute/extensions/__init__.py
Normal file
16
cloudcafe/compute/extensions/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/extensions_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/extensions_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/floating_ips_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/floating_ips_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/keypairs_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/keypairs_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/rescue_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/rescue_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/security_groups_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/security_groups_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/extensions/volumes_api/__init__.py
Normal file
16
cloudcafe/compute/extensions/volumes_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
15
cloudcafe/compute/flavors_api/__init__.py
Normal file
15
cloudcafe/compute/flavors_api/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
115
cloudcafe/compute/flavors_api/client.py
Normal file
115
cloudcafe/compute/flavors_api/client.py
Normal file
@ -0,0 +1,115 @@
|
||||
"""
|
||||
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 urlparse import urlparse
|
||||
|
||||
from cafe.engine.clients.rest import AutoMarshallingRestClient
|
||||
from cloudcafe.compute.flavors_api.models.flavor import Flavor, FlavorMin
|
||||
|
||||
|
||||
class FlavorsClient(AutoMarshallingRestClient):
|
||||
|
||||
def __init__(self, url, auth_token, serialize_format=None,
|
||||
deserialize_format=None):
|
||||
"""
|
||||
@param url: Base URL for the compute service
|
||||
@type url: String
|
||||
@param auth_token: Auth token to be used for all requests
|
||||
@type auth_token: String
|
||||
@param serialize_format: Format for serializing requests
|
||||
@type serialize_format: String
|
||||
@param deserialize_format: Format for de-serializing responses
|
||||
@type deserialize_format: String
|
||||
"""
|
||||
super(FlavorsClient, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
self.auth_token = auth_token
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
ct = ''.join(['application/', self.serialize_format])
|
||||
accept = ''.join(['application/', self.deserialize_format])
|
||||
self.default_headers['Content-Type'] = ct
|
||||
self.default_headers['Accept'] = accept
|
||||
self.url = url
|
||||
|
||||
def list_flavors(self, min_disk=None, min_ram=None, marker=None,
|
||||
limit=None, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Returns a list of flavors
|
||||
@param min_disk: min Disk in GB, to filter by minimum Disk size in MB
|
||||
@type min_disk:int
|
||||
@param min_ram: min ram in GB, to filter by minimum RAM size in MB
|
||||
@type min_Disk:int
|
||||
@param marker: ID of last item in previous list (paginated collections)
|
||||
@type marker:C{str}
|
||||
@param limit: Sets page size
|
||||
@type limit: int
|
||||
@return: List of flavors filtered by params on success
|
||||
@rtype: C{list}
|
||||
'''
|
||||
|
||||
url = '%s/flavors' % (self.url)
|
||||
|
||||
params = {'minDisk': min_disk, 'minRam': min_ram, 'marker': marker,
|
||||
'limit': limit}
|
||||
flavor_response = self.request('GET', url, params=params,
|
||||
response_entity_type=FlavorMin,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return flavor_response
|
||||
|
||||
def list_flavors_with_detail(self, min_disk=None, min_ram=None,
|
||||
marker=None, limit=None,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Returns details from a list of flavors
|
||||
@param min_disk: min Disk in GB, to filter by minimum Disk size in MB
|
||||
@type min_disk:int
|
||||
@param min_ram: min ram in GB, to filter by minimum RAM size in MB
|
||||
@type min_Disk:int
|
||||
@param marker: ID of last item in previous list (paginated collections)
|
||||
@type marker:C{str}
|
||||
@param limit: Sets page size
|
||||
@type limit: int
|
||||
@return: Detail List of flavors filtered by params on success
|
||||
@rtype: C{list}
|
||||
'''
|
||||
|
||||
url = '%s/flavors/detail' % (self.url)
|
||||
|
||||
params = {'minDisk': min_disk, 'minRam': min_ram, 'marker': marker,
|
||||
'limit': limit}
|
||||
flavor_response = self.request('GET', url, params=params,
|
||||
response_entity_type=Flavor,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return flavor_response
|
||||
|
||||
def get_flavor_details(self, flavor_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Returns a dict of details for given filter
|
||||
@param flavor_id: if of flavor for which details are required
|
||||
@type flavor_id:C{str}
|
||||
@return: Details of filter with filter id in the param on success
|
||||
@rtype: C{dict}
|
||||
'''
|
||||
|
||||
url_new = str(flavor_id)
|
||||
url_scheme = urlparse(url_new).scheme
|
||||
url = url_new if url_scheme \
|
||||
else '%s/flavors/%s' % (self.url, flavor_id)
|
||||
|
||||
flavor_response = self.request('GET', url, requestslib_kwargs,
|
||||
response_entity_type=Flavor,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return flavor_response
|
32
cloudcafe/compute/flavors_api/config.py
Normal file
32
cloudcafe/compute/flavors_api/config.py
Normal file
@ -0,0 +1,32 @@
|
||||
"""
|
||||
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 FlavorsConfig(ConfigSectionInterface):
|
||||
|
||||
SECTION_NAME = 'flavors'
|
||||
|
||||
@property
|
||||
def primary_flavor(self):
|
||||
"""Default flavor to be used when building servers in compute tests"""
|
||||
return self.get("primary_flavor")
|
||||
|
||||
@property
|
||||
def secondary_flavor(self):
|
||||
"""Alternate flavor to be used in compute tests"""
|
||||
return self.get("secondary_flavor")
|
15
cloudcafe/compute/flavors_api/models/__init__.py
Normal file
15
cloudcafe/compute/flavors_api/models/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
185
cloudcafe/compute/flavors_api/models/flavor.py
Normal file
185
cloudcafe/compute/flavors_api/models/flavor.py
Normal file
@ -0,0 +1,185 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.equality_tools import EqualityTools
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
from cloudcafe.compute.common.models.link import Links
|
||||
|
||||
|
||||
class Flavor(AutoMarshallingModel):
|
||||
|
||||
def __init__(self, id=None, name=None, ram=None, disk=None, vcpus=None,
|
||||
swap=None, rxtx_factor=None, links=None):
|
||||
'''An object that represents a flavor.
|
||||
'''
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.ram = ram
|
||||
self.disk = disk
|
||||
self.vcpus = vcpus
|
||||
self.links = links
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in self.__dict__:
|
||||
values.append("%s: %s" % (prop, self.__dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of a Flavor based on the json serialized_str
|
||||
passed in.'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
|
||||
if 'flavor' in json_dict.keys():
|
||||
flavor = cls._dict_to_obj(json_dict['flavor'])
|
||||
return flavor
|
||||
|
||||
if 'flavors' in json_dict.keys():
|
||||
flavors = []
|
||||
for flavor_dict in json_dict['flavors']:
|
||||
flavor = cls._dict_to_obj(flavor_dict)
|
||||
flavors.append(flavor)
|
||||
return flavors
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, flavor_dict):
|
||||
'''Helper method to turn dictionary into Server instance.'''
|
||||
flavor = Flavor(id=flavor_dict.get('id'),
|
||||
name=flavor_dict.get('name'),
|
||||
ram=flavor_dict.get('ram'),
|
||||
disk=flavor_dict.get('disk'),
|
||||
vcpus=flavor_dict.get('vcpus'))
|
||||
flavor.links = Links._dict_to_obj(flavor_dict['links'])
|
||||
return flavor
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of a Flavor based on the xml serialized_str
|
||||
passed in.'''
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_xml_etree_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
cls._remove_xml_etree_namespace(element,
|
||||
Constants.XML_API_ATOM_NAMESPACE)
|
||||
|
||||
if element.tag == 'flavor':
|
||||
flavor = cls._xml_ele_to_obj(element)
|
||||
return flavor
|
||||
|
||||
if element.tag == 'flavors':
|
||||
flavors = []
|
||||
for flavor in element.findall('flavor'):
|
||||
flavor = cls._xml_ele_to_obj(flavor)
|
||||
flavors.append(flavor)
|
||||
return flavors
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Flavor instance.'''
|
||||
flavor_dict = element.attrib
|
||||
if 'vcpus' in flavor_dict:
|
||||
flavor_dict['vcpus'] = (flavor_dict.get('vcpus') and
|
||||
int(flavor_dict.get('vcpus')))
|
||||
if 'disk' in flavor_dict:
|
||||
flavor_dict['disk'] = (flavor_dict.get('disk') and
|
||||
int(flavor_dict.get('disk')))
|
||||
if 'rxtx_factor' in flavor_dict:
|
||||
flavor_dict['rxtx_factor'] = flavor_dict.get('rxtx_factor') \
|
||||
and float(flavor_dict.get('rxtx_factor'))
|
||||
if 'ram' in flavor_dict:
|
||||
flavor_dict['ram'] = flavor_dict.get('ram') \
|
||||
and int(flavor_dict.get('ram'))
|
||||
if 'swap' in flavor_dict:
|
||||
flavor_dict['swap'] = flavor_dict.get('swap') \
|
||||
and int(flavor_dict.get('swap'))
|
||||
|
||||
links = Links._xml_ele_to_obj(element)
|
||||
flavor = Flavor(flavor_dict.get('id'), flavor_dict.get('name'),
|
||||
flavor_dict.get('ram'), flavor_dict.get('disk'),
|
||||
flavor_dict.get('vcpus'), flavor_dict.get('swap'),
|
||||
flavor_dict.get('rxtx_factor'), links)
|
||||
return flavor
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Flavor object to compare with
|
||||
@type other: Flavor
|
||||
@return: True if Flavor objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other, ['links'])
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Flavor object to compare with
|
||||
@type other: Flavor
|
||||
@return: True if Flavor objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
|
||||
class FlavorMin(Flavor):
|
||||
"""
|
||||
@summary: Represents minimum details of a flavor
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
'''Flavor Min has only id, name and links '''
|
||||
for keys, values in kwargs.items():
|
||||
setattr(self, keys, values)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: FlavorMin object to compare with
|
||||
@type other: FlavorMin
|
||||
@return: True if FlavorMin objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other, ['links'])
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: FlavorMin object to compare with
|
||||
@type other: FlavorMin
|
||||
@return: True if FlavorMin objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Server instance.'''
|
||||
flavor_dict = element.attrib
|
||||
flavor_min = FlavorMin(id=flavor_dict.get('id'),
|
||||
name=flavor_dict.get('name'))
|
||||
flavor_min.links = Links._xml_ele_to_obj(element)
|
||||
return flavor_min
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, flavor_dict):
|
||||
'''Helper method to turn dictionary into Server instance.'''
|
||||
flavor_min = FlavorMin(id=flavor_dict.get('id'),
|
||||
name=flavor_dict.get('name'))
|
||||
flavor_min.links = Links._dict_to_obj(flavor_dict['links'])
|
||||
return flavor_min
|
15
cloudcafe/compute/images_api/__init__.py
Normal file
15
cloudcafe/compute/images_api/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
88
cloudcafe/compute/images_api/behaviors.py
Normal file
88
cloudcafe/compute/images_api/behaviors.py
Normal file
@ -0,0 +1,88 @@
|
||||
"""
|
||||
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 time
|
||||
|
||||
from cloudcafe.compute.common.types import NovaImageStatusTypes as ImageStates
|
||||
from cloudcafe.compute.common.exceptions import ItemNotFound, \
|
||||
TimeoutException, BuildErrorException
|
||||
|
||||
|
||||
class ImageBehaviors(object):
|
||||
|
||||
def __init__(self, images_client, config):
|
||||
|
||||
self.config = config
|
||||
self.images_client = images_client
|
||||
|
||||
|
||||
def wait_for_image_status(self, image_id, desired_status):
|
||||
'''Polls image image_id details until status_to_wait_for is met.'''
|
||||
image_response = self.images_client.get_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.image_status_interval
|
||||
while (image_obj.status.lower() != desired_status.lower() and
|
||||
time_waited < self.config.snapshot_timeout):
|
||||
image_response = self.images_client.get_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
|
||||
if image_obj.status.lower() is ImageStates.ERROR.lower():
|
||||
message = 'Snapshot failed. Image with uuid {0} entered ERROR status.'
|
||||
raise BuildErrorException(message.format(image_id))
|
||||
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
return image_response
|
||||
|
||||
def wait_for_image_resp_code(self, image_id, response_code):
|
||||
'''Polls image resp for the specified status code.'''
|
||||
|
||||
image_response = self.images_client.get_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.image_status_interval
|
||||
while (image_response.status_code != response_code and
|
||||
image_obj.status.lower() != ImageStates.ERROR.lower() and
|
||||
time_waited < self.config.snapshot_timeout):
|
||||
image_response = self.images_client.get_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
return image_response
|
||||
|
||||
def wait_for_image_to_be_deleted(self, image_id):
|
||||
'''Waits for the image to be deleted. '''
|
||||
|
||||
image_response = self.images_client.delete_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.image_status_interval
|
||||
|
||||
try:
|
||||
while (True):
|
||||
image_response = self.images_client.get_image(image_id)
|
||||
image_obj = image_response.entity
|
||||
if time_waited > self.config.snapshot_timeout:
|
||||
raise TimeoutException("Timed out while deleting image id: %s" % image_id)
|
||||
if image_obj.status.lower() == ImageStates.DELETED.lower():
|
||||
return
|
||||
if image_obj.status.lower() != ImageStates.ERROR.lower():
|
||||
raise BuildErrorException("Image entered Error state while deleting, Image id : %s" % image_id)
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
except ItemNotFound:
|
||||
pass
|
255
cloudcafe/compute/images_api/client.py
Normal file
255
cloudcafe/compute/images_api/client.py
Normal file
@ -0,0 +1,255 @@
|
||||
"""
|
||||
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 urlparse import urlparse
|
||||
|
||||
from cafe.engine.clients.rest import AutoMarshallingRestClient
|
||||
from cloudcafe.compute.common.models.metadata import Metadata
|
||||
from cloudcafe.compute.common.models.metadata import MetadataItem
|
||||
from cloudcafe.compute.images_api.models.image import Image, ImageMin
|
||||
|
||||
|
||||
class ImagesClient(AutoMarshallingRestClient):
|
||||
|
||||
'''
|
||||
Client for Image API
|
||||
'''
|
||||
|
||||
def __init__(self, url, auth_token, serialize_format, deserialize_format):
|
||||
"""
|
||||
@param url: Base URL for the compute service
|
||||
@type url: String
|
||||
@param auth_token: Auth token to be used for all requests
|
||||
@type auth_token: String
|
||||
@param serialize_format: Format for serializing requests
|
||||
@type serialize_format: String
|
||||
@param deserialize_format: Format for de-serializing responses
|
||||
@type deserialize_format: String
|
||||
"""
|
||||
super(ImagesClient, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
self.auth_token = auth_token
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
ct = ''.join(['application/', self.serialize_format])
|
||||
accept = ''.join(['application/', self.deserialize_format])
|
||||
self.default_headers['Content-Type'] = ct
|
||||
self.default_headers['Accept'] = accept
|
||||
self.url = url
|
||||
|
||||
def list_images(self, server_ref=None, image_name=None, status=None,
|
||||
image_type=None, marker=None, changes_since=None, limit=None,
|
||||
requestslib_kwargs=None):
|
||||
|
||||
'''
|
||||
@summary: Lists IDs, names, and links for all available images.
|
||||
@param server_ref: Server id or Url to server
|
||||
@type server_ref: String
|
||||
@param image_name: Image Name
|
||||
@type image_name: String
|
||||
@param status: Image Status
|
||||
@type status: String
|
||||
@param image_type:BASE|SERVER
|
||||
@type image_type:String
|
||||
@param changes_since: changed since the changes-since time
|
||||
@type changes_since: DateTime
|
||||
@param marker: The ID of the last item in the previous list
|
||||
@type marker: String
|
||||
@param limit:Sets the page size.
|
||||
@type limit:int
|
||||
@return: lists all images visible by the account filtered by the params
|
||||
@rtype: Response with Image List as response.entity
|
||||
'''
|
||||
|
||||
url = '%s/images' % (self.url)
|
||||
|
||||
params = {'server': server_ref, 'name': image_name,
|
||||
'status': status, 'type': image_type, 'marker': marker,
|
||||
'changes-since': changes_since, 'limit': limit}
|
||||
return self.request('GET', url, params=params,
|
||||
response_entity_type=ImageMin,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def list_images_with_detail(self, server_ref=None, image_name=None,
|
||||
status=None, image_type=None, marker=None,
|
||||
changes_since=None, limit=None,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: List all details for all available images.
|
||||
@param server_ref: Server id or Url to server
|
||||
@type server_ref: String
|
||||
@param image_name: Image Name
|
||||
@type image_name: String
|
||||
@param status: Image Status
|
||||
@type status: String
|
||||
@param type:BASE|SERVER
|
||||
@type type:String
|
||||
@param changes_since: changed since the changes-since time
|
||||
@type changes_since: DateTime
|
||||
@param marker: The ID of the last item in the previous list
|
||||
@type marker: String
|
||||
@param limit:Sets the page size.
|
||||
@type limit:int
|
||||
@return: lists all images visible by the account filtered by the params
|
||||
@rtype: Response with Image List as response.entity
|
||||
'''
|
||||
|
||||
url = '%s/images/detail' % (self.url)
|
||||
|
||||
params = {'server': server_ref, 'name': image_name,
|
||||
'status': status, 'type': image_type, 'marker': marker,
|
||||
'changes-since': changes_since, 'limit': limit}
|
||||
return self.request('GET', url, params=params,
|
||||
response_entity_type=Image,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def get_image(self, image_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Lists details of the specified image.
|
||||
@param image_id: Image id
|
||||
@type image_id: String
|
||||
@return: Details of specified Image. BUT no server_id in image details
|
||||
@rtype: Response with Image as response.entity
|
||||
'''
|
||||
|
||||
url_new = str(image_id)
|
||||
url_scheme = urlparse(url_new).scheme
|
||||
url = url_new if url_scheme else '%s/images/%s' % (self.url, image_id)
|
||||
|
||||
return self.request('GET', url, response_entity_type=Image,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def delete_image(self, image_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Deletes the specified image.
|
||||
@param image_id: Image id
|
||||
@type image_id: String
|
||||
@return: Response code 204 if successful
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
url = '%s/images/%s' % (self.url, image_id)
|
||||
return self.request('DELETE', url,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def list_image_metadata(self, image_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Returns metadata associated with an image
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@return: Metadata associated with an image on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata' % (self.url, image_id)
|
||||
image_response = self.request('GET', url,
|
||||
response_entity_type=Metadata,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
||||
|
||||
def set_image_metadata(self, image_id, metadata, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets metadata for the specified image
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@param metadata: Metadata to be set for an image
|
||||
@type metadata: dictionary
|
||||
@return: Metadata associated with an image on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata' % (self.url, image_id)
|
||||
request_object = Metadata(metadata)
|
||||
image_response = self.request('PUT', url,
|
||||
response_entity_type=Metadata,
|
||||
request_entity=request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
||||
|
||||
def update_image_metadata(self, image_id, metadata,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Updates metadata items for the specified image
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@param metadata: Metadata to be updated for an image
|
||||
@type metadata: dictionary
|
||||
@return: Metadata associated with an image on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata' % (self.url, image_id)
|
||||
request_object = Metadata(metadata)
|
||||
image_response = self.request('POST', url,
|
||||
response_entity_type=Metadata,
|
||||
request_entity=request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
||||
|
||||
def get_image_metadata_item(self, image_id, key, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Retrieves a single metadata item by key
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@param key: Key for which metadata item needs to be retrieved
|
||||
@type key: String
|
||||
@return: Metadata Item for a key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata/%s' % (self.url, image_id, key)
|
||||
image_response = self.request('GET', url,
|
||||
response_entity_type=MetadataItem,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
||||
|
||||
def set_image_metadata_item(self, image_id, key, value,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets a metadata item for a specified image
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@param key: Key for which metadata item needs to be set
|
||||
@type key: String
|
||||
@param key: Value which the metadata key needs to be set to
|
||||
@type key: String
|
||||
@return: Metadata Item for the key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata/%s' % (self.url, image_id, key)
|
||||
metadata_item = MetadataItem({key: value})
|
||||
image_response = self.request('PUT', url,
|
||||
response_entity_type=MetadataItem,
|
||||
request_entity=metadata_item,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
||||
|
||||
def delete_image_metadata_item(self, image_id, key,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets a metadata item for a specified image
|
||||
@param image_id: Image ID
|
||||
@type image_id:String
|
||||
@param key: Key for which metadata item needs to be set
|
||||
@type key: String
|
||||
@return: Metadata Item for the key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/images/%s/metadata/%s' % (self.url, image_id, key)
|
||||
image_response = self.request('DELETE', url,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return image_response
|
42
cloudcafe/compute/images_api/config.py
Normal file
42
cloudcafe/compute/images_api/config.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""
|
||||
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 ImagesConfig(ConfigSectionInterface):
|
||||
|
||||
SECTION_NAME = 'images'
|
||||
|
||||
@property
|
||||
def primary_image(self):
|
||||
"""Default image to be used when building servers in compute tests"""
|
||||
return self.get("primary_image")
|
||||
|
||||
@property
|
||||
def secondary_image(self):
|
||||
"""Alternate image to be used in compute tests"""
|
||||
return self.get("secondary_image")
|
||||
|
||||
@property
|
||||
def image_status_interval(self):
|
||||
"""Amount of time to wait between polling the status of an image"""
|
||||
return int(self.get("image_status_interval"))
|
||||
|
||||
@property
|
||||
def snapshot_timeout(self):
|
||||
"""Length of time to wait before giving up on reaching a status"""
|
||||
return int(self.get("snapshot_timeout"))
|
15
cloudcafe/compute/images_api/models/__init__.py
Normal file
15
cloudcafe/compute/images_api/models/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
203
cloudcafe/compute/images_api/models/image.py
Normal file
203
cloudcafe/compute/images_api/models/image.py
Normal file
@ -0,0 +1,203 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.models.link import Links
|
||||
from cloudcafe.compute.common.equality_tools import EqualityTools
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
from cloudcafe.compute.common.models.metadata import Metadata
|
||||
|
||||
|
||||
class Image(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'image'
|
||||
|
||||
def __init__(self, diskConfig, id, name, status, updated, created,
|
||||
minDisk, minRam, progress, links=None, metadata=None,
|
||||
server=None):
|
||||
self.diskConfig = diskConfig
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.status = status
|
||||
self.updated = updated
|
||||
self.created = created
|
||||
self.minDisk = minDisk
|
||||
self.minRam = minRam
|
||||
self.progress = progress
|
||||
self.links = links
|
||||
self.metadata = metadata
|
||||
self.server = server
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Image object to compare with
|
||||
@type other: Image
|
||||
@return: True if Image objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Image object to compare with
|
||||
@type other: Image
|
||||
@return: True if Image objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in __dict__:
|
||||
values.append("%s: %s" % (prop, __dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
json_dict = json.loads(serialized_str)
|
||||
if 'image' in json_dict.keys():
|
||||
image = cls._dict_to_obj(json_dict['image'])
|
||||
return image
|
||||
|
||||
if 'images' in json_dict.keys():
|
||||
images = []
|
||||
for image_dict in json_dict['images']:
|
||||
images.append(cls._dict_to_obj(image_dict))
|
||||
return images
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, json_dict):
|
||||
image = Image(json_dict.get('OS-DCF:diskConfig'), json_dict.get('id'),
|
||||
json_dict.get('name'), json_dict.get('status'),
|
||||
json_dict.get('updated'), json_dict.get('created'),
|
||||
json_dict.get('minDisk'), json_dict.get('minRam'),
|
||||
json_dict.get('progress'))
|
||||
if 'links' in json_dict:
|
||||
image.links = Links._dict_to_obj(json_dict['links'])
|
||||
if 'metadata' in json_dict:
|
||||
image.metadata = Metadata._dict_to_obj(json_dict['metadata'])
|
||||
if 'server' in json_dict:
|
||||
from cloudcafe.compute.servers_api.models.servers import ServerMin
|
||||
image.server = ServerMin._dict_to_obj(json_dict['server'])
|
||||
return image
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of a Image based on the xml serialized_str
|
||||
passed in.'''
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_xml_etree_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
cls._remove_xml_etree_namespace(element,
|
||||
Constants.XML_API_ATOM_NAMESPACE)
|
||||
cls._set_clean_xml_etree_attrs(element.attrib,
|
||||
Constants.XML_API_DISK_CONFIG_NAMESPACE)
|
||||
|
||||
if element.tag == 'image':
|
||||
image = cls._xml_ele_to_obj(element)
|
||||
return image
|
||||
|
||||
if element.tag == 'images':
|
||||
images = []
|
||||
for image in element.findall('image'):
|
||||
image = cls._xml_ele_to_obj(image)
|
||||
images.append(image)
|
||||
return images
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
image_dict = element.attrib
|
||||
if 'minDisk' in image_dict:
|
||||
image_dict['minDisk'] = image_dict.get('minDisk') \
|
||||
and int(image_dict.get('minDisk'))
|
||||
if 'progress' in image_dict:
|
||||
image_dict['progress'] = image_dict.get('progress') \
|
||||
and int(image_dict.get('progress'))
|
||||
if 'minRam' in image_dict:
|
||||
image_dict['minRam'] = image_dict.get('minRam') \
|
||||
and int(image_dict.get('minRam'))
|
||||
|
||||
links = None
|
||||
metadata = None
|
||||
server = None
|
||||
|
||||
if element.find('link') is not None:
|
||||
links = Links._xml_ele_to_obj(element)
|
||||
if element.find('metadata') is not None:
|
||||
metadata = Metadata._xml_ele_to_obj(element)
|
||||
if element.find('server') is not None:
|
||||
'''To prevent circular import issue import just in time'''
|
||||
from cloudcafe.compute.servers_api.models.servers import ServerMin
|
||||
server = ServerMin._xml_ele_to_obj(element)
|
||||
|
||||
image = Image(image_dict.get('diskConfig'),
|
||||
image_dict.get('id'), image_dict.get('name'),
|
||||
image_dict.get('status'), image_dict.get('updated'),
|
||||
image_dict.get('created'), image_dict.get('minDisk'),
|
||||
image_dict.get('minRam'), image_dict.get('progress'),
|
||||
links, metadata, server)
|
||||
|
||||
return image
|
||||
|
||||
|
||||
class ImageMin(Image):
|
||||
"""
|
||||
@summary: Represents minimum details of a image
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
'''Image min should only have id, name and links '''
|
||||
for keys, values in kwargs.items():
|
||||
setattr(self, keys, values)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: ImageMin object to compare with
|
||||
@type other: ImageMin
|
||||
@return: True if ImageMin objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: ImageMin object to compare with
|
||||
@type other: ImageMin
|
||||
@return: True if ImageMin objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Image instance.'''
|
||||
cls._remove_xml_etree_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
image_dict = element.attrib
|
||||
image_min = ImageMin(**image_dict)
|
||||
image_min.links = Links._xml_ele_to_obj(element)
|
||||
return image_min
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, json_dict):
|
||||
image_min = ImageMin(**json_dict)
|
||||
if 'links' in json_dict:
|
||||
image_min.links = Links(image_min.links)
|
||||
return image_min
|
15
cloudcafe/compute/limits_api/__init__.py
Normal file
15
cloudcafe/compute/limits_api/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
133
cloudcafe/compute/limits_api/client.py
Normal file
133
cloudcafe/compute/limits_api/client.py
Normal file
@ -0,0 +1,133 @@
|
||||
"""
|
||||
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.compute.limits_api.models.limit import Limits
|
||||
|
||||
|
||||
class LimitsClient(AutoMarshallingRestClient):
|
||||
|
||||
def __init__(self, url, auth_token, serialize_format=None,
|
||||
deserialize_format=None):
|
||||
"""
|
||||
@param url: Base URL for the compute service
|
||||
@type url: String
|
||||
@param auth_token: Auth token to be used for all requests
|
||||
@type auth_token: String
|
||||
@param serialize_format: Format for serializing requests
|
||||
@type serialize_format: String
|
||||
@param deserialize_format: Format for de-serializing responses
|
||||
@type deserialize_format: String
|
||||
"""
|
||||
|
||||
super(LimitsClient, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
self.auth_token = auth_token
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
ct = ''.join(['application/', self.serialize_format])
|
||||
accept = ''.join(['application/', self.deserialize_format])
|
||||
self.default_headers['Content-Type'] = ct
|
||||
self.default_headers['Accept'] = accept
|
||||
self.url = url
|
||||
|
||||
def get_limits(self, requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Returns limits.
|
||||
@param requestslib_kwargs: Overrides any default values injected by
|
||||
the framework
|
||||
@type requestslib_kwargs:dict
|
||||
@return: limit_response
|
||||
@rtype: Limits Response Domain Object
|
||||
"""
|
||||
url = '%s/limits' % (self.url)
|
||||
limit_response = self.request('GET', url,
|
||||
response_entity_type=Limits,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return limit_response
|
||||
|
||||
def _get_absolute_limits_property(self, limits_property=None):
|
||||
"""
|
||||
@summary: Returns the value of the specified key from the
|
||||
absolute_limits dictionary
|
||||
@param requestslib_kwargs: Overrides any default values injected by
|
||||
the framework
|
||||
@type requestslib_kwargs:dict
|
||||
"""
|
||||
if property is None:
|
||||
return None
|
||||
limits_response = self.get_limits()
|
||||
absolute_limits = vars(limits_response.entity).get('absolute')
|
||||
if absolute_limits is not None:
|
||||
return absolute_limits.get(limits_property)
|
||||
else:
|
||||
return None
|
||||
|
||||
def get_max_server_meta(self):
|
||||
"""
|
||||
@summary: Returns maximum number of metadata allowed for a server
|
||||
@return: Maximum number of server meta data
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxServerMeta')
|
||||
|
||||
def get_max_image_meta(self):
|
||||
"""
|
||||
@summary: Returns maximum number of metadata allowed for an Image.
|
||||
@return: Maximum number of image meta data
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxImageMeta')
|
||||
|
||||
def get_personality_file_limit(self):
|
||||
"""
|
||||
@summary: Returns maximum number of personality files allowed for a
|
||||
server
|
||||
@return: Maximum number of personality files.
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxPersonality')
|
||||
|
||||
def get_personality_file_size_limit(self):
|
||||
"""
|
||||
@summary: Returns the maximum size of a personality file.
|
||||
@return: Maximum size of a personality file.
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxPersonalitySize')
|
||||
|
||||
def get_max_total_instances(self):
|
||||
"""
|
||||
@summary: Returns maximum number of server allowed for a user
|
||||
@return: Maximum number of server
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxTotalInstances')
|
||||
|
||||
def get_max_total_RAM_size(self):
|
||||
"""
|
||||
@summary: Returns maximum RAM size to create servers for a user
|
||||
@return: Maximum RAM size
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('maxTotalRAMSize')
|
||||
|
||||
def get_total_RAM_used(self):
|
||||
"""
|
||||
@summary: Returns total RAM used by a user
|
||||
@return: total RAM used
|
||||
@rtype: Integer
|
||||
"""
|
||||
return self._get_absolute_limits_property('totalRAMUsed')
|
15
cloudcafe/compute/limits_api/models/__init__.py
Normal file
15
cloudcafe/compute/limits_api/models/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
132
cloudcafe/compute/limits_api/models/limit.py
Normal file
132
cloudcafe/compute/limits_api/models/limit.py
Normal file
@ -0,0 +1,132 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.equality_tools import EqualityTools
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
|
||||
|
||||
class Limits(AutoMarshallingModel):
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
'''An object that represents Limits.
|
||||
Keyword arguments:
|
||||
'''
|
||||
super(Limits, self).__init__(**kwargs)
|
||||
for keys, values in kwargs.items():
|
||||
setattr(self, keys, values)
|
||||
|
||||
def __repr__(self):
|
||||
values = []
|
||||
for prop in self.__dict__:
|
||||
values.append("%s: %s" % (prop, self.__dict__[prop]))
|
||||
return '[' + ', '.join(values) + ']'
|
||||
|
||||
def _obj_to_json(self):
|
||||
'''
|
||||
Automatically assigns any value that is an int or a str and is not
|
||||
None to a dictionary. Then returns the str representation of that dict.
|
||||
'''
|
||||
'''TODO: Implement serialization of lists, dictionaries, and objects'''
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
'''need to code'''
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''Returns an instance of a Limits based on the json serialized_str
|
||||
passed in.'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
ret = None
|
||||
if 'limits' in json_dict.keys():
|
||||
ret = Limits(**(json_dict.get('limits')))
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
'''Returns a Limits Response Object based on xml serialized str'''
|
||||
limits = {}
|
||||
rate_list = []
|
||||
absolute_dict = {}
|
||||
#Removing namespaces
|
||||
root = ET.fromstring(serialized_str)
|
||||
cls._remove_namespace(root,
|
||||
'http://docs.openstack.org/common/api/v1.0')
|
||||
cls._remove_namespace(root,
|
||||
'http://docs.openstack.org/common/api/v1.1')
|
||||
#Rates Limits
|
||||
rate_element_list = root.find('rates').findall('rate')
|
||||
for rate_element in rate_element_list:
|
||||
limit_element_list = rate_element.findall('limit')
|
||||
limit_list = []
|
||||
rate_dict = {}
|
||||
for limit in limit_element_list:
|
||||
limit_dict = {}
|
||||
attrib = limit.attrib
|
||||
for key in attrib.keys():
|
||||
limit_dict[key] = attrib.get(key)
|
||||
limit_list.append(limit_dict)
|
||||
rate_dict['limit'] = limit_list
|
||||
|
||||
attrib = rate_element.attrib
|
||||
for key in attrib.keys():
|
||||
rate_dict[key] = attrib.get(key)
|
||||
|
||||
rate_list.append(rate_dict)
|
||||
#Absolute Limits
|
||||
absolute_list = root.find('absolute')
|
||||
cls._remove_namespace(absolute_list, Constants.XML_API_ATOM_NAMESPACE)
|
||||
used = 'http://docs.openstack.org/compute/ext/used_limits/api/v1.1'
|
||||
cls._remove_namespace(absolute_list, used)
|
||||
for element in absolute_list.findall('limit'):
|
||||
attrib = element.attrib
|
||||
absolute_dict[attrib.get('name')] = int(attrib.get('value'))
|
||||
|
||||
limits['absolute'] = absolute_dict
|
||||
limits['rate'] = rate_list
|
||||
return Limits(**limits)
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, limits_dict):
|
||||
'''Helper method to turn dictionary into Limits instance.'''
|
||||
limits = Limits(**limits_dict)
|
||||
return limits
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Flavor object to compare with
|
||||
@type other: Flavor
|
||||
@return: True if Flavor objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Flavor object to compare with
|
||||
@type other: Flavor
|
||||
@return: True if Flavor objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
15
cloudcafe/compute/servers_api/__init__.py
Normal file
15
cloudcafe/compute/servers_api/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
203
cloudcafe/compute/servers_api/behaviors.py
Normal file
203
cloudcafe/compute/servers_api/behaviors.py
Normal file
@ -0,0 +1,203 @@
|
||||
"""
|
||||
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 time
|
||||
|
||||
from cloudcafe.compute.common.types import NovaServerStatusTypes as ServerStates
|
||||
from cloudcafe.compute.common.datagen import rand_name
|
||||
from cloudcafe.compute.common.exceptions import ItemNotFound, \
|
||||
TimeoutException, BuildErrorException
|
||||
|
||||
|
||||
class ServerBehaviors(object):
|
||||
|
||||
def __init__(self, servers_client, servers_config,
|
||||
images_config, flavors_config):
|
||||
|
||||
self.config = servers_config
|
||||
self.servers_client = servers_client
|
||||
self.images_config = images_config
|
||||
self.flavors_config = flavors_config
|
||||
|
||||
def create_active_server(self, name=None, image_ref=None, flavor_ref=None,
|
||||
personality=None, metadata=None, accessIPv4=None,
|
||||
accessIPv6=None, disk_config=None, networks=None):
|
||||
'''
|
||||
@summary:Creates a server and waits for server to reach active status
|
||||
@param name: The name of the server.
|
||||
@type name: String
|
||||
@param image_ref: The reference to the image used to build the server.
|
||||
@type image_ref: String
|
||||
@param flavor_ref: The flavor used to build the server.
|
||||
@type flavor_ref: String
|
||||
@param metadata: A dictionary of values to be used as metadata.
|
||||
@type metadata: Dictionary. The limit is 5 key/values.
|
||||
@param personality: A list of dictionaries for files to be
|
||||
injected into the server.
|
||||
@type personality: List
|
||||
@param accessIPv4: IPv4 address for the server.
|
||||
@type accessIPv4: String
|
||||
@param accessIPv6: IPv6 address for the server.
|
||||
@type accessIPv6: String
|
||||
@param disk_config: MANUAL/AUTO/None
|
||||
@type disk_config: String
|
||||
@return: Response Object containing response code and
|
||||
the server domain object
|
||||
@rtype: Request Response Object
|
||||
'''
|
||||
|
||||
if name is None:
|
||||
name = rand_name('testserver')
|
||||
if image_ref is None:
|
||||
image_ref = self.images_config.primary_image
|
||||
if flavor_ref is None:
|
||||
flavor_ref = self.flavors_config.primary_flavor
|
||||
|
||||
resp = self.servers_client.create_server(name, image_ref,
|
||||
flavor_ref,
|
||||
personality=personality,
|
||||
metadata=metadata,
|
||||
accessIPv4=accessIPv4,
|
||||
accessIPv6=accessIPv6,
|
||||
disk_config=disk_config,
|
||||
networks=networks)
|
||||
server_obj = resp.entity
|
||||
resp = self.wait_for_server_status(server_obj.id,
|
||||
ServerStates.ACTIVE)
|
||||
# Add the password from the create request into the final response
|
||||
resp.entity.admin_pass = server_obj.admin_pass
|
||||
return resp
|
||||
|
||||
def wait_for_server_status(self, server_id, desired_status, timeout=None):
|
||||
"""Polls server until the desired status is reached"""
|
||||
if desired_status == ServerStates.DELETED:
|
||||
return self.wait_for_server_to_be_deleted(server_id)
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.server_status_interval
|
||||
timeout = timeout or self.config.server_build_timeout
|
||||
while (server_obj.status.lower() != desired_status.lower() and
|
||||
server_obj.status.lower() != ServerStates.ERROR.lower() and
|
||||
time_waited <= timeout):
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
if time_waited > timeout:
|
||||
raise TimeoutException
|
||||
if server_obj.status.lower() == ServerStates.ERROR.lower():
|
||||
raise BuildErrorException(
|
||||
'Build failed. Server with uuid %s entered ERROR status.' %
|
||||
(server_id))
|
||||
return server_response
|
||||
|
||||
def wait_for_server_error_status(self, server_id, desired_status,
|
||||
timeout=None):
|
||||
"""Polls a server until the desired status is reached"""
|
||||
|
||||
if desired_status == ServerStates.DELETED:
|
||||
return self.wait_for_server_to_be_deleted(server_id)
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.server_status_interval
|
||||
timeout = timeout or self.config.compute_api.server_status_timeout
|
||||
while (server_obj.status.lower() != desired_status.lower()
|
||||
and time_waited <= timeout * 10):
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
return server_response
|
||||
|
||||
def wait_for_server_status_from_error(self, server_id, desired_status,
|
||||
timeout=None):
|
||||
if desired_status == ServerStates.DELETED:
|
||||
return self.wait_for_server_to_be_deleted(server_id)
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time_waited = 0
|
||||
interval_time = self.config.compute_api.build_interval
|
||||
timeout = timeout or self.config.compute_api.server_status_timeout
|
||||
while (server_obj.status.lower() != desired_status.lower()
|
||||
and time_waited <= timeout):
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
if time_waited > timeout:
|
||||
raise TimeoutException(server_obj.status, server_obj.status,
|
||||
id=server_obj.id)
|
||||
return server_response
|
||||
|
||||
def wait_for_server_to_be_deleted(self, server_id):
|
||||
time_waited = 0
|
||||
interval_time = self.config.server_status_interval
|
||||
try:
|
||||
while (True):
|
||||
server_response = self.servers_client.get_server(server_id)
|
||||
server_obj = server_response.entity
|
||||
if time_waited > self.config.server_build_timeout:
|
||||
raise TimeoutException(
|
||||
"Timed out while deleting server id: %s" % server_id)
|
||||
if server_obj.status.lower() != ServerStates.ERROR.lower():
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
continue
|
||||
if server_obj.status.lower() != ServerStates.ERROR.lower():
|
||||
raise BuildErrorException(
|
||||
"Server entered Error state while deleting, \
|
||||
server id : %s" % server_id)
|
||||
time.sleep(interval_time)
|
||||
time_waited += interval_time
|
||||
except ItemNotFound:
|
||||
pass
|
||||
|
||||
def resize_and_await(self, server_id, new_flavor):
|
||||
resp = self.servers_client.resize(server_id, new_flavor)
|
||||
assert resp.status_code is 202
|
||||
resized_server = self.wait_for_server_status(
|
||||
server_id, ServerStates.VERIFY_RESIZE)
|
||||
return resized_server.entity
|
||||
|
||||
def resize_and_confirm(self, server_id, new_flavor):
|
||||
self.resize_and_await(server_id, new_flavor)
|
||||
resp = self.servers_client.confirm_resize(server_id)
|
||||
assert resp.status_code is 204
|
||||
resized_server = self.wait_for_server_status(server_id,
|
||||
ServerStates.ACTIVE)
|
||||
return resized_server.entity
|
||||
|
||||
def resize_and_revert(self, server_id, new_flavor):
|
||||
self.resize_and_await(server_id, new_flavor)
|
||||
resp = self.servers_client.revert_resize(server_id)
|
||||
assert resp.status_code is 202
|
||||
resized_server = self.wait_for_server_status(server_id,
|
||||
ServerStates.ACTIVE)
|
||||
return resized_server.entity
|
||||
|
||||
def reboot_and_await(self, server_id, reboot_type):
|
||||
resp = self.servers_client.reboot(server_id, reboot_type)
|
||||
assert resp.status_code is 202
|
||||
self.wait_for_server_status(server_id,
|
||||
ServerStates.ACTIVE)
|
||||
|
||||
def change_password_and_await(self, server_id, new_password):
|
||||
resp = self.servers_client.change_password(server_id, new_password)
|
||||
assert resp.status_code is 202
|
||||
self.wait_for_server_status(server_id,
|
||||
ServerStates.ACTIVE)
|
634
cloudcafe/compute/servers_api/client.py
Normal file
634
cloudcafe/compute/servers_api/client.py
Normal file
@ -0,0 +1,634 @@
|
||||
"""
|
||||
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 urlparse import urlparse
|
||||
|
||||
from cafe.engine.clients.rest import AutoMarshallingRestClient
|
||||
from cloudcafe.compute.common.models.metadata import Metadata
|
||||
from cloudcafe.compute.common.models.metadata import MetadataItem
|
||||
from cloudcafe.compute.servers_api.models.servers import Server
|
||||
from cloudcafe.compute.servers_api.models.servers import Addresses
|
||||
from cloudcafe.compute.servers_api.models.requests import CreateServer
|
||||
from cloudcafe.compute.servers_api.models.requests import UpdateServer
|
||||
from cloudcafe.compute.servers_api.models.requests import ChangePassword, \
|
||||
ConfirmResize, Resize, Reboot, MigrateServer, Lock, Unlock, \
|
||||
Start, Stop, Suspend, Resume, Pause, Unpause, CreateImage
|
||||
|
||||
|
||||
class ServersClient(AutoMarshallingRestClient):
|
||||
|
||||
def __init__(self, url, auth_token, serialize_format=None,
|
||||
deserialize_format=None):
|
||||
"""
|
||||
@param url: Base URL for the compute service
|
||||
@type url: String
|
||||
@param auth_token: Auth token to be used for all requests
|
||||
@type auth_token: String
|
||||
@param serialize_format: Format for serializing requests
|
||||
@type serialize_format: String
|
||||
@param deserialize_format: Format for de-serializing responses
|
||||
@type deserialize_format: String
|
||||
"""
|
||||
super(ServersClient, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
self.auth_token = auth_token
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
ct = ''.join(['application/', self.serialize_format])
|
||||
accept = ''.join(['application/', self.deserialize_format])
|
||||
self.default_headers['Content-Type'] = ct
|
||||
self.default_headers['Accept'] = accept
|
||||
self.url = url
|
||||
|
||||
def list_servers(self, name=None, image=None, flavor=None,
|
||||
status=None, marker=None, limit=None, changes_since=None,
|
||||
requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Lists all servers with minimal details. Additionally,
|
||||
can filter results by params. Maps to /servers
|
||||
@param image: Image id to filter by
|
||||
@type image: String
|
||||
@param flavor: Flavor id to filter by
|
||||
@type flavor: String
|
||||
@param name: Server name to filter by
|
||||
@type name: String
|
||||
@param status: Server status to filter by
|
||||
@type status: String
|
||||
@param marker: Server id to be used as a marker for the next list
|
||||
@type marker: String
|
||||
@param limit: The maximum number of results to return
|
||||
@type limit: Int
|
||||
@param changes-since: Will only return servers where the updated time
|
||||
is later than the changes-since parameter.
|
||||
@return: server_response
|
||||
@rtype: Response
|
||||
"""
|
||||
|
||||
params = {'image': image, 'flavor': flavor, 'name': name,
|
||||
'status': status, 'marker': marker,
|
||||
'limit': limit, 'changes-since': changes_since}
|
||||
url = '%s/servers' % (self.url)
|
||||
server_response = self.request('GET', url, params=params,
|
||||
response_entity_type=Server,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def list_servers_with_detail(self, image=None, flavor=None, name=None,
|
||||
status=None, marker=None,
|
||||
limit=None, changes_since=None,
|
||||
requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Lists all servers with full details. Additionally,
|
||||
can filter results by params. Maps to /servers/detail
|
||||
@param image: Image id to filter by
|
||||
@type image: String
|
||||
@param flavor: Flavor id to filter by
|
||||
@type flavor: String
|
||||
@param name: Server name to filter by
|
||||
@type name: String
|
||||
@param status: Server status to filter by
|
||||
@type status: String
|
||||
@param marker: Server id to be used as a marker for the next list
|
||||
@type marker: String
|
||||
@param limit: The maximum number of results to return
|
||||
@type limit: Int
|
||||
@param changes-since: Will only return servers where the updated time
|
||||
is later than the changes-since parameter.
|
||||
@return: server_response
|
||||
@rtype: Response
|
||||
"""
|
||||
|
||||
params = {'image': image, 'flavor': flavor, 'name': name,
|
||||
'status': status, 'marker': marker, 'limit': limit,
|
||||
'changes-since': changes_since}
|
||||
url = '%s/servers/detail' % (self.url)
|
||||
server_response = self.request('GET', url, params=params,
|
||||
response_entity_type=Server,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def get_server(self, server_id, requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Retrieves the details of the specified server
|
||||
@param server_id: The id of an existing server
|
||||
@type server_id: String
|
||||
@return: server_response
|
||||
@rtype: Response
|
||||
"""
|
||||
|
||||
self.server_id = server_id
|
||||
url_new = str(server_id)
|
||||
url_scheme = urlparse(url_new).scheme
|
||||
url = url_new if url_scheme else '%s/servers/%s' % (self.url,
|
||||
self.server_id)
|
||||
server_response = self.request('GET', url,
|
||||
response_entity_type=Server,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def delete_server(self, server_id, requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Deletes the specified server
|
||||
@param server_id: The id of a server
|
||||
@type server_id: String
|
||||
@return: server_response
|
||||
@rtype: Response
|
||||
"""
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s' % (self.url, self.server_id)
|
||||
server_response = self.request('DELETE', url,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def create_server(self, name, image_ref, flavor_ref, personality=None,
|
||||
metadata=None, accessIPv4=None, accessIPv6=None,
|
||||
disk_config=None, networks=None, admin_pass=None,
|
||||
requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Creates an instance of a server given the
|
||||
provided parameters
|
||||
@param name: Name of the server
|
||||
@type name: String
|
||||
@param image_ref: Identifier for the image used to build the server
|
||||
@type image_ref: String
|
||||
@param flavor_ref: Identifier for the flavor used to build the server
|
||||
@type flavor_ref: String
|
||||
@param metadata: A dictionary of values to be used as server metadata
|
||||
@type meta: Dictionary
|
||||
@param personality: A list of dictionaries for files to be
|
||||
injected into the server.
|
||||
@type personality: List
|
||||
@param accessIPv4: IPv4 address for the server.
|
||||
@type accessIPv4: String
|
||||
@param accessIPv6: IPv6 address for the server.
|
||||
@type accessIPv6: String
|
||||
@param disk_config: MANUAL/AUTO/None
|
||||
@type disk_config: String
|
||||
@return: Response Object containing response code and
|
||||
the server domain object
|
||||
@rtype: Response Object
|
||||
"""
|
||||
|
||||
server_request_object = CreateServer(name=name, flavorRef=flavor_ref,
|
||||
imageRef=image_ref,
|
||||
personality=personality,
|
||||
metadata=metadata,
|
||||
accessIPv4=accessIPv4,
|
||||
accessIPv6=accessIPv6,
|
||||
diskConfig=disk_config,
|
||||
networks=networks,
|
||||
adminPass=admin_pass)
|
||||
|
||||
url = '%s/servers' % (self.url)
|
||||
server_response = self.request('POST', url,
|
||||
response_entity_type=Server,
|
||||
request_entity=server_request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def update_server(self, server_id, name=None, metadata=None,
|
||||
accessIPv4=None, accessIPv6=None,
|
||||
requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Updates the properties of an existing server.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param name: The name of the server.
|
||||
@type name: String
|
||||
@param meta: A dictionary of values to be used as metadata.
|
||||
@type meta: Dictionary. The limit is 5 key/values.
|
||||
@param ipv4: IPv4 address for the server.
|
||||
@type ipv4: String
|
||||
@param ipv6: IPv6 address for the server.
|
||||
@type ipv6: String
|
||||
@return: The response code and the updated Server .
|
||||
@rtype: Integer(Response code) and Object(Server)
|
||||
"""
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s' % (self.url, self.server_id)
|
||||
request = UpdateServer(name=name, metadata=metadata,
|
||||
accessIPv4=accessIPv4, accessIPv6=accessIPv6)
|
||||
resp = self.request('PUT', url,
|
||||
response_entity_type=Server,
|
||||
request_entity=request,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def list_addresses(self, server_id, requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Lists all addresses for a server.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@return: Response code and the Addresses
|
||||
@rtype: Integer(Response code) and Object(Addresses)
|
||||
"""
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/ips' % (self.url, self.server_id)
|
||||
resp = self.request('GET', url,
|
||||
response_entity_type=Addresses,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def list_addresses_by_network(self, server_id, network_id,
|
||||
requestslib_kwargs=None):
|
||||
"""
|
||||
@summary: Lists all addresses of a specific network type for a server.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param network_id: The ID of a network.
|
||||
@type network_id: String
|
||||
@return: Response code and the Addresses by network.
|
||||
@rtype: Integer(Response code) and Object(Addresses)
|
||||
"""
|
||||
|
||||
self.server_id = server_id
|
||||
self.network_id = network_id
|
||||
url = '%s/servers/%s/ips/%s' % (self.url, self.server_id,
|
||||
self.network_id)
|
||||
server_response = self.request('GET', url,
|
||||
response_entity_type=Addresses,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def change_password(self, server_id, password, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Changes the root password for the server.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param password: The new password.
|
||||
@type password: String.
|
||||
@return: Response Object containing response code and the empty
|
||||
body on success
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=ChangePassword(password),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def reboot(self, server_id, reboot_type, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Reboots the server - soft/hard based on reboot_type.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param reboot_type: Soft or Hard.
|
||||
@type reboot_type: String.
|
||||
@return: Response Object containing response code and the empty body
|
||||
after the server reboot is applied
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Reboot(reboot_type),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def rebuild(self, server_id, imageRef, name=None,
|
||||
flavorRef=None, adminPass=None,
|
||||
diskConfig=None, metadata=None,
|
||||
personality=None, accessIPv4=None, accessIPv6=None,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Rebuilds the server
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param name: The new name for the server
|
||||
@type name: String
|
||||
@param imageRef:The image ID.
|
||||
@type imageRef: String
|
||||
@param flavorRef:The flavor ID.
|
||||
@type flavorRef: String
|
||||
@param adminPass:The administrator password
|
||||
@type adminPass: String
|
||||
@param diskConfig:The disk configuration value, which is AUTO or MANUAL
|
||||
@type diskConfig: String(AUTO/MANUAL)
|
||||
@param metadata:A metadata key and value pair.
|
||||
@type metadata: Dictionary
|
||||
@param personality:The file path and file contents
|
||||
@type personality: String
|
||||
@param accessIPV4:The IP version 4 address.
|
||||
@type accessIPV4: String
|
||||
@param accessIPV6:The IP version 6 address
|
||||
@type accessIPV6: String
|
||||
@return: Response Object containing response code and
|
||||
the server domain object
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
rebuild_request_object = Rebuild(name=name, imageRef=imageRef,
|
||||
flavorRef=flavorRef,
|
||||
adminPass=adminPass,
|
||||
diskConfig=diskConfig,
|
||||
metadata=metadata,
|
||||
personality=personality,
|
||||
accessIPv4=accessIPv4,
|
||||
accessIPv6=accessIPv6)
|
||||
|
||||
resp = self.request('POST', url,
|
||||
response_entity_type=Server,
|
||||
request_entity=rebuild_request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def resize(self, server_id, flavorRef, diskConfig=None,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Resizes the server to specified flavorRef.
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param flavorRef: The flavor id.
|
||||
@type flavorRef: String.
|
||||
@return: Response Object containing response code and
|
||||
the empty body after the server resize is applied
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resize_request_object = Resize(flavorRef, diskConfig)
|
||||
|
||||
resp = self.request('POST', url,
|
||||
request_entity=resize_request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def confirm_resize(self, server_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Confirms resize of server
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@return: Response Object containing response code and the empty
|
||||
body after the server resize is applied
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
confirm_resize_request_object = ConfirmResize()
|
||||
resp = self.request('POST', url,
|
||||
request_entity=confirm_resize_request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def revert_resize(self, server_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Reverts resize of the server
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@return: Response Object containing response code and the empty body
|
||||
after the server resize is applied
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=RevertResize(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def migrate_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=MigrateServer(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def live_migrate_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=MigrateServer(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def lock_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Lock(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def unlock_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Unlock(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def stop_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Stop(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def start_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Start(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def suspend_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Suspend(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def resume_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Resume(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def pause_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Pause(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def unpause_server(self, server_id, requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=Unpause(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def reset_state(self, server_id, reset_state='error',
|
||||
requestslib_kwargs=None):
|
||||
self.server_id = server_id
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=MigrateServer(),
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def create_image(self, server_id, name=None, metadata=None,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Creates snapshot of the server
|
||||
@param server_id: The id of an existing server.
|
||||
@type server_id: String
|
||||
@param: metadata: A metadata key and value pair.
|
||||
@type: Metadata Object
|
||||
@return: Response Object containing response code and the empty body
|
||||
after the server resize is applied
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
if name is None:
|
||||
name = 'new_image'
|
||||
self.server_id = server_id
|
||||
if name is None:
|
||||
name = rand_name("TestImage")
|
||||
url = '%s/servers/%s/action' % (self.url, self.server_id)
|
||||
create_image_request_object = CreateImage(name, metadata)
|
||||
resp = self.request('POST', url,
|
||||
request_entity=create_image_request_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def list_server_metadata(self, server_id, requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Returns metadata associated with an server
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@return: Metadata associated with an server on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata' % (self.url, server_id)
|
||||
server_response = self.request('GET', url,
|
||||
response_entity_type=Metadata,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def set_server_metadata(self, server_id, metadata,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets metadata for the specified server
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@param metadata: Metadata to be set for an server
|
||||
@type metadata: dictionary
|
||||
@return: Metadata associated with an server on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata' % (self.url, server_id)
|
||||
request_metadata_object = Metadata(metadata)
|
||||
server_response = self.request('PUT', url,
|
||||
response_entity_type=Metadata,
|
||||
request_entity=request_metadata_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def update_server_metadata(self, server_id, metadata,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Updates metadata items for the specified server
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@param metadata: Metadata to be updated for an server
|
||||
@type metadata: dictionary
|
||||
@return: Metadata associated with an server on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata' % (self.url, server_id)
|
||||
request_metadata_object = Metadata(metadata)
|
||||
resp = self.request('POST', url,
|
||||
response_entity_type=Metadata,
|
||||
request_entity=request_metadata_object,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return resp
|
||||
|
||||
def get_server_metadata_item(self, server_id, key,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Retrieves a single metadata item by key
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@param key: Key for which metadata item needs to be retrieved
|
||||
@type key: String
|
||||
@return: Metadata Item for a key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata/%s' % (self.url, server_id, key)
|
||||
server_response = self.request('GET', url,
|
||||
response_entity_type=MetadataItem,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def set_server_metadata_item(self, server_id, key, value,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets a metadata item for a specified server
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@param key: Key for which metadata item needs to be set
|
||||
@type key: String
|
||||
@return: Metadata Item for the key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata/%s' % (self.url, server_id, key)
|
||||
request = MetadataItem({key: value})
|
||||
server_response = self.request('PUT', url,
|
||||
response_entity_type=MetadataItem,
|
||||
request_entity=request,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
||||
|
||||
def delete_server_metadata_item(self, server_id, key,
|
||||
requestslib_kwargs=None):
|
||||
'''
|
||||
@summary: Sets a metadata item for a specified server
|
||||
@param server_id: server ID
|
||||
@type server_id:String
|
||||
@param key: Key for which metadata item needs to be set
|
||||
@type key: String
|
||||
@return: Metadata Item for the key on success
|
||||
@rtype: Response object with metadata dictionary as entity
|
||||
'''
|
||||
|
||||
url = '%s/servers/%s/metadata/%s' % (self.url, server_id, key)
|
||||
server_response = self.request('DELETE', url,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return server_response
|
64
cloudcafe/compute/servers_api/config.py
Normal file
64
cloudcafe/compute/servers_api/config.py
Normal file
@ -0,0 +1,64 @@
|
||||
"""
|
||||
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 ServersConfig(ConfigSectionInterface):
|
||||
|
||||
SECTION_NAME = 'servers'
|
||||
|
||||
@property
|
||||
def server_status_interval(self):
|
||||
"""Amount of time to wait between polling the status of a server"""
|
||||
return int(self.get("server_status_interval"))
|
||||
|
||||
@property
|
||||
def server_build_timeout(self):
|
||||
"""
|
||||
Length of time to wait before timing out on a server reaching
|
||||
the ACTIVE state
|
||||
"""
|
||||
return int(self.get("server_build_timeout"))
|
||||
|
||||
@property
|
||||
def server_resize_timeout(self):
|
||||
"""
|
||||
Length of time to wait before timing out on a server reaching
|
||||
the VERIFY_RESIZE state
|
||||
"""
|
||||
return int(self.get("server_resize_timeout"))
|
||||
|
||||
@property
|
||||
def network_for_ssh(self):
|
||||
"""
|
||||
Name of network to be used for remote connections
|
||||
(ie. public, private)
|
||||
"""
|
||||
return self.get("network_for_ssh")
|
||||
|
||||
@property
|
||||
def ip_address_version_for_ssh(self):
|
||||
"""
|
||||
IP address version to be used for remote connections
|
||||
(ie. 4, 6)
|
||||
"""
|
||||
return self.get("ip_address_version_for_ssh")
|
||||
|
||||
@property
|
||||
def instance_disk_path(self):
|
||||
"""Primary disk path of instances under test"""
|
||||
return self.get("instance_disk_path")
|
15
cloudcafe/compute/servers_api/models/__init__.py
Normal file
15
cloudcafe/compute/servers_api/models/__init__.py
Normal file
@ -0,0 +1,15 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
596
cloudcafe/compute/servers_api/models/requests.py
Normal file
596
cloudcafe/compute/servers_api/models/requests.py
Normal file
@ -0,0 +1,596 @@
|
||||
"""
|
||||
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
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
from cloudcafe.compute.common.models.metadata import Metadata
|
||||
|
||||
|
||||
class CreateServer(AutoMarshallingModel):
|
||||
ROOT_TAG = 'server'
|
||||
|
||||
def __init__(self, name, imageRef, flavorRef, adminPass=None,
|
||||
diskConfig=None, metadata=None, personality=None,
|
||||
accessIPv4=None, accessIPv6=None, networks=None):
|
||||
|
||||
super(CreateServer, self).__init__()
|
||||
self.name = name
|
||||
self.imageRef = imageRef
|
||||
self.flavorRef = flavorRef
|
||||
self.diskConfig = diskConfig
|
||||
self.adminPass = adminPass
|
||||
self.metadata = metadata
|
||||
self.personality = personality
|
||||
self.accessIPv4 = accessIPv4
|
||||
self.accessIPv6 = accessIPv6
|
||||
self.networks = networks
|
||||
|
||||
def _obj_to_json(self):
|
||||
body = {}
|
||||
body['name'] = self.name
|
||||
body['imageRef'] = self.imageRef
|
||||
body['flavorRef'] = self.flavorRef
|
||||
|
||||
if self.diskConfig is not None:
|
||||
body['OS-DCF:diskConfig'] = self.diskConfig
|
||||
if self.adminPass is not None:
|
||||
body['adminPass'] = self.adminPass
|
||||
if self.metadata is not None:
|
||||
body['metadata'] = self.metadata
|
||||
if self.accessIPv4 is not None:
|
||||
body['accessIPv4'] = self.accessIPv4
|
||||
if self.accessIPv6 is not None:
|
||||
body['accessIPv6'] = self.accessIPv6
|
||||
if self.personality is not None:
|
||||
body['personality'] = self.personality
|
||||
if self.networks is not None:
|
||||
body['networks'] = self.networks
|
||||
|
||||
return json.dumps({self.ROOT_TAG: body})
|
||||
|
||||
def _obj_to_xml(self):
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
xml = Constants.XML_HEADER
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('name', self.name)
|
||||
element.set('imageRef', self.imageRef)
|
||||
element.set('flavorRef', self.flavorRef)
|
||||
if self.adminPass is not None:
|
||||
element.set('adminPass', self.adminPass)
|
||||
if self.diskConfig is not None:
|
||||
element.set('xmlns:OS-DCF',
|
||||
Constants.XML_API_DISK_CONFIG_NAMESPACE)
|
||||
element.set('OS-DCF:diskConfig', self.diskConfig)
|
||||
if self.metadata is not None:
|
||||
meta_ele = ET.Element('metadata')
|
||||
for key, value in self.metadata.items():
|
||||
meta_ele.append(Metadata._dict_to_xml(key, value))
|
||||
element.append(meta_ele)
|
||||
if self.networks is not None:
|
||||
networks_ele = ET.Element('networks')
|
||||
for network_id in self.networks:
|
||||
network = ET.Element('network')
|
||||
network.set('uuid', network_id['uuid'])
|
||||
networks_ele.append(network)
|
||||
element.append(networks_ele)
|
||||
if self.personality is not None:
|
||||
personality_ele = ET.Element('personality')
|
||||
personality_ele.append(Personality._obj_to_xml(self.personality))
|
||||
element.append(personality_ele)
|
||||
if self.accessIPv4 is not None:
|
||||
element.set('accessIPv4', self.accessIPv4)
|
||||
if self.accessIPv6 is not None:
|
||||
element.set('accessIPv6', self.accessIPv6)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class UpdateServer(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'server'
|
||||
|
||||
def __init__(self, name=None, metadata=None,
|
||||
accessIPv4=None, accessIPv6=None):
|
||||
self.name = name
|
||||
self.metadata = metadata
|
||||
self.accessIPv4 = accessIPv4
|
||||
self.accessIPv6 = accessIPv6
|
||||
|
||||
def _obj_to_json(self):
|
||||
return json.dumps(self._auto_to_dict())
|
||||
|
||||
def _obj_to_xml(self):
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
xml = Constants.XML_HEADER
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
if self.name is not None:
|
||||
element.set('name', self.name)
|
||||
if self.metadata is not None:
|
||||
meta_ele = ET.Element('metadata')
|
||||
for key, value in self.metadata.items():
|
||||
meta_ele.append(Metadata._dict_to_xml(key, value))
|
||||
element.append(meta_ele)
|
||||
if self.accessIPv4 is not None:
|
||||
element.set('accessIPv4', self.accessIPv4)
|
||||
if self.accessIPv6 is not None:
|
||||
element.set('accessIPv6', self.accessIPv6)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Reboot(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'reboot'
|
||||
|
||||
def __init__(self, reboot_type):
|
||||
self.type = reboot_type
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('type', self.type)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Personality(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Personality Request Object for Server
|
||||
'''
|
||||
ROOT_TAG = 'personality'
|
||||
|
||||
def __init__(self, type):
|
||||
self.type = type
|
||||
|
||||
@classmethod
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
@classmethod
|
||||
def _obj_to_xml(self, list_dicts):
|
||||
for pers_dict in list_dicts:
|
||||
pers_element = ET.Element('file')
|
||||
pers_element.set('path', pers_dict.get('path'))
|
||||
pers_element.text = pers_dict.get('contents')
|
||||
return pers_element
|
||||
|
||||
|
||||
class Rebuild(CreateServer):
|
||||
'''
|
||||
@summary: Rebuild Request Object for Server
|
||||
'''
|
||||
|
||||
ROOT_TAG = 'rebuild'
|
||||
|
||||
def __init__(self, name, imageRef, flavorRef, adminPass, diskConfig=None,
|
||||
metadata=None, personality=None, accessIPv4=None,
|
||||
accessIPv6=None):
|
||||
super(Rebuild, self).__init__(name=name, imageRef=imageRef,
|
||||
flavorRef=flavorRef, adminPass=adminPass,
|
||||
diskConfig=diskConfig, metadata=metadata,
|
||||
personality=personality,
|
||||
accessIPv4=accessIPv4,
|
||||
accessIPv6=accessIPv6)
|
||||
|
||||
|
||||
class Resize(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Resize Request Object for Server
|
||||
'''
|
||||
ROOT_TAG = 'resize'
|
||||
|
||||
def __init__(self, flavorRef, diskConfig=None):
|
||||
self.flavorRef = flavorRef
|
||||
self.diskConfig = diskConfig
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('flavorRef', self.flavorRef)
|
||||
if self.diskConfig is not None:
|
||||
element.set('xmlns:OS-DCF', Constants.XML_API_ATOM_NAMESPACE)
|
||||
element.set('OS-DCF:diskConfig', self.diskConfig)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class ResetState(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Reset State Request Object for Server
|
||||
'''
|
||||
ROOT_TAG = 'os-resetState'
|
||||
|
||||
def __init__(self, state):
|
||||
self.state = state
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('state', self.state)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class ConfirmResize(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Confirm Resize Request Object for Server
|
||||
'''
|
||||
ROOT_TAG = 'confirmResize'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
# element = self._auto_to_xml()
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class RevertResize(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Revert Resize Request Object for Server
|
||||
|
||||
'''
|
||||
ROOT_TAG = 'revertResize'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
# element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class MigrateServer(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Migrate Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'migrate'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class ConfirmServerMigration(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Confirm Server Migration Request Object
|
||||
'''
|
||||
ROOT_TAG = 'confirmResize'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Lock(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Lock Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'lock'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Unlock(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Unlock Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'unlock'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Start(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Start Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'os-start'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Stop(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Stop Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'os-stop'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Suspend(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Suspend Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'suspend'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Resume(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Resume Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'resume'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Pause(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Pause Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'pause'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class Unpause(AutoMarshallingModel):
|
||||
'''
|
||||
@summary: Unpause Server Request Object
|
||||
'''
|
||||
ROOT_TAG = 'unpause'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = self._auto_to_xml()
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class RescueMode(AutoMarshallingModel):
|
||||
'''
|
||||
Rescue Server Action Request Object
|
||||
'''
|
||||
ROOT_TAG = 'rescue'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_RESCUE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class ExitRescueMode(AutoMarshallingModel):
|
||||
'''
|
||||
Exit Rescue Action Request Object
|
||||
'''
|
||||
ROOT_TAG = 'unrescue'
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_UNRESCUE)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class CreateImage(AutoMarshallingModel):
|
||||
'''
|
||||
Create Image Server Action Request Object
|
||||
'''
|
||||
ROOT_TAG = 'createImage'
|
||||
|
||||
def __init__(self, name, metadata=None):
|
||||
self.name = name
|
||||
self.metadata = metadata
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('xmlns:atom', Constants.XML_API_ATOM_NAMESPACE)
|
||||
element.set('name', self.name)
|
||||
if self.metadata is not None:
|
||||
meta_ele = ET.Element('metadata')
|
||||
for key, value in self.metadata.items():
|
||||
meta_ele.append(Metadata._dict_to_xml(key, value))
|
||||
element.append(meta_ele)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class ChangePassword(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'changePassword'
|
||||
|
||||
def __init__(self, adminPassword):
|
||||
self.adminPass = adminPassword
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
xml = Constants.XML_HEADER
|
||||
element = ET.Element(self.ROOT_TAG)
|
||||
element.set('xmlns', Constants.XML_API_NAMESPACE)
|
||||
element.set('adminPass', self.adminPass)
|
||||
xml += ET.tostring(element)
|
||||
return xml
|
||||
|
||||
|
||||
class AddFixedIP(AutoMarshallingModel):
|
||||
'''
|
||||
Add Fixed IP Action Request Object
|
||||
'''
|
||||
ROOT_TAG = 'addFixedIp'
|
||||
|
||||
def __init__(self, networkId):
|
||||
self.networkId = networkId
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
#TODO: Implement when xml is known
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class RemoveFixedIP(AutoMarshallingModel):
|
||||
'''
|
||||
Remove Fixed IP Action Request Object
|
||||
'''
|
||||
ROOT_TAG = 'removeFixedIp'
|
||||
|
||||
def __init__(self, networkId):
|
||||
self.networkId = networkId
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = self._auto_to_dict()
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
#TODO: Implement when xml is known
|
||||
raise NotImplementedError
|
407
cloudcafe/compute/servers_api/models/servers.py
Normal file
407
cloudcafe/compute/servers_api/models/servers.py
Normal file
@ -0,0 +1,407 @@
|
||||
"""
|
||||
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
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
|
||||
from cafe.engine.models.base import BaseModel
|
||||
from cafe.engine.models.base import AutoMarshallingModel
|
||||
from cloudcafe.compute.common.models.link import Links
|
||||
from cloudcafe.compute.flavors_api.models.flavor import Flavor, FlavorMin
|
||||
from cloudcafe.compute.images_api.models.image import Image, ImageMin
|
||||
from cloudcafe.compute.common.equality_tools import EqualityTools
|
||||
from cloudcafe.compute.common.constants import Constants
|
||||
from cloudcafe.compute.common.models.metadata import Metadata
|
||||
|
||||
|
||||
class Server(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'server'
|
||||
|
||||
def __init__(self, id, diskConfig, power_state, progress, task_state,
|
||||
vm_state, name, tenantId, status, updated, created, hostId,
|
||||
user_id, accessIPv4, accessIPv6, addresses, flavor, image,
|
||||
links, metadata, admin_pass):
|
||||
self.diskConfig = diskConfig
|
||||
try:
|
||||
self.power_state = int(power_state)
|
||||
except TypeError:
|
||||
self.power_state = 0
|
||||
self.progress = progress
|
||||
self.task_state = task_state
|
||||
self.vm_state = vm_state
|
||||
self.name = name
|
||||
self.id = id
|
||||
self.tenant_id = tenantId
|
||||
self.status = status
|
||||
self.updated = updated
|
||||
self.created = created
|
||||
self.host_id = hostId
|
||||
self.user_id = user_id
|
||||
self.accessIPv4 = accessIPv4
|
||||
self.accessIPv6 = accessIPv6
|
||||
self.addresses = addresses
|
||||
self.flavor = flavor
|
||||
self.image = image
|
||||
self.links = links
|
||||
self.metadata = metadata
|
||||
self.admin_pass = admin_pass
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''
|
||||
Returns an instance of a Server based on the json serialized_str
|
||||
passed in
|
||||
'''
|
||||
ret = None
|
||||
json_dict = json.loads(serialized_str)
|
||||
if 'server' in json_dict.keys():
|
||||
ret = cls._dict_to_obj(json_dict['server'])
|
||||
if 'servers' in json_dict.keys():
|
||||
ret = []
|
||||
for server in json_dict['servers']:
|
||||
s = cls._dict_to_obj(server)
|
||||
ret.append(s)
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
'''
|
||||
Returns an instance of a Server based on the xml serialized_str
|
||||
passed in
|
||||
'''
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_xml_etree_namespace(
|
||||
element, Constants.XML_API_NAMESPACE)
|
||||
cls._remove_xml_etree_namespace(
|
||||
element, Constants.XML_API_EXTENDED_STATUS_NAMESPACE)
|
||||
cls._remove_xml_etree_namespace(
|
||||
element, Constants.XML_API_DISK_CONFIG_NAMESPACE)
|
||||
cls._remove_xml_etree_namespace(
|
||||
element, Constants.XML_API_ATOM_NAMESPACE)
|
||||
if element.tag == 'server':
|
||||
ret = cls._xml_ele_to_obj(element)
|
||||
if element.tag == 'servers':
|
||||
ret = []
|
||||
for server in element.findall('server'):
|
||||
s = cls._xml_ele_to_obj(server)
|
||||
ret.append(s)
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Server instance.'''
|
||||
server_dict = element.attrib
|
||||
|
||||
addresses = None
|
||||
flavor = None
|
||||
image = None
|
||||
links = None
|
||||
metadata = None
|
||||
|
||||
links = Links._xml_ele_to_obj(element)
|
||||
if element.find('addresses') is not None:
|
||||
addresses = Addresses._xml_ele_to_obj(element.find('addresses'))
|
||||
if element.find('flavor') is not None:
|
||||
flavor = Flavor._xml_ele_to_obj(element.find('flavor'))
|
||||
if element.find('image') is not None:
|
||||
image = Image._xml_ele_to_obj(element.find('image'))
|
||||
if element.find('metadata') is not None:
|
||||
metadata = Metadata._xml_ele_to_obj(element)
|
||||
|
||||
if 'progress' in server_dict:
|
||||
progress = server_dict.get('progress') \
|
||||
and int(server_dict.get('progress'))
|
||||
if 'tenantId' in server_dict:
|
||||
tenant_id = server_dict.get('tenantId')
|
||||
if 'userId' in server_dict:
|
||||
user_id = server_dict.get('userId')
|
||||
|
||||
server = Server(server_dict['id'], server_dict['diskConfig'],
|
||||
server_dict['power_state'], progress,
|
||||
server_dict['task_state'],
|
||||
server_dict['vm_state'],
|
||||
server_dict['name'], tenant_id,
|
||||
server_dict['status'], server_dict['updated'],
|
||||
server_dict['created'], server_dict['hostId'],
|
||||
user_id, server_dict['accessIPv4'],
|
||||
server_dict['accessIPv6'], addresses, flavor,
|
||||
image, links, metadata)
|
||||
return server
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, server_dict):
|
||||
'''Helper method to turn dictionary into Server instance.'''
|
||||
|
||||
addresses = None
|
||||
flavor = None
|
||||
image = None
|
||||
links = None
|
||||
metadata = None
|
||||
|
||||
if 'links' in server_dict:
|
||||
links = Links._dict_to_obj(server_dict['links'])
|
||||
if 'addresses' in server_dict:
|
||||
addresses = Addresses._dict_to_obj(server_dict['addresses'])
|
||||
if 'flavor' in server_dict:
|
||||
flavor = FlavorMin._dict_to_obj(server_dict['flavor'])
|
||||
if 'image' in server_dict:
|
||||
image = ImageMin._dict_to_obj(server_dict['image'])
|
||||
if 'metadata' in server_dict:
|
||||
metadata = Metadata._dict_to_obj(server_dict['metadata'])
|
||||
|
||||
server = Server(
|
||||
server_dict['id'], server_dict.get('OS-DCF:diskConfig'),
|
||||
server_dict.get('OS-EXT-STS:power_state'),
|
||||
server_dict.get('progress', 0),
|
||||
server_dict.get('OS-EXT-STS:task_state'),
|
||||
server_dict.get('OS-EXT-STS:vm_state'),
|
||||
server_dict.get('name'), server_dict.get('tenant_id'),
|
||||
server_dict.get('status'), server_dict.get('updated'),
|
||||
server_dict.get('created'), server_dict.get('hostId'),
|
||||
server_dict.get('user_id'), server_dict.get('accessIPv4'),
|
||||
server_dict.get('accessIPv6'), addresses, flavor,
|
||||
image, links, metadata, server_dict.get('adminPass'))
|
||||
|
||||
for each in server_dict:
|
||||
if each.startswith("{"):
|
||||
newkey = re.split("}", each)[1]
|
||||
setattr(server, newkey, server_dict[each])
|
||||
return server
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: Server object to compare with
|
||||
@type other: Server
|
||||
@return: True if Server objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other,
|
||||
['adminPass', 'updated',
|
||||
'progress'])
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default not-equals
|
||||
@param other: Server object to compare with
|
||||
@type other: Server
|
||||
@return: True if Server objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
def min_details(self):
|
||||
"""
|
||||
@summary: Get the Minimum details of server
|
||||
@return: Minimum details of server
|
||||
@rtype: ServerMin
|
||||
"""
|
||||
return ServerMin(name=self.name, id=self.id, links=self.links)
|
||||
|
||||
|
||||
class ServerMin(Server):
|
||||
"""
|
||||
@summary: Represents minimum details of a server
|
||||
"""
|
||||
def __init__(self, **kwargs):
|
||||
for keys, values in kwargs.items():
|
||||
setattr(self, keys, values)
|
||||
|
||||
def __eq__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: ServerMin object to compare with
|
||||
@type other: ServerMin
|
||||
@return: True if ServerMin objects are equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return EqualityTools.are_objects_equal(self, other)
|
||||
|
||||
def __ne__(self, other):
|
||||
"""
|
||||
@summary: Overrides the default equals
|
||||
@param other: ServerMin object to compare with
|
||||
@type other: ServerMin
|
||||
@return: True if ServerMin objects are not equal, False otherwise
|
||||
@rtype: bool
|
||||
"""
|
||||
return not self == other
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
'''Helper method to turn ElementTree instance to Server instance.'''
|
||||
if element.find('server') is not None:
|
||||
element = element.find('server')
|
||||
server_dict = element.attrib
|
||||
servermin = ServerMin(**server_dict)
|
||||
servermin.links = Links._xml_ele_to_obj(element)
|
||||
return servermin
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, server_dict):
|
||||
'''Helper method to turn dictionary into Server instance.'''
|
||||
servermin = ServerMin(**server_dict)
|
||||
if hasattr(servermin, 'links'):
|
||||
servermin.links = Links._dict_to_obj(servermin.links)
|
||||
'''
|
||||
Parse for those keys which have the namespace prefixed,
|
||||
strip the namespace out
|
||||
and take only the actual values such as diskConfig,
|
||||
power_state and assign to server obj
|
||||
'''
|
||||
for each in server_dict:
|
||||
if each.startswith("{"):
|
||||
newkey = re.split("}", each)[1]
|
||||
setattr(servermin, newkey, server_dict[each])
|
||||
|
||||
return servermin
|
||||
|
||||
|
||||
#New Version
|
||||
class Addresses(AutoMarshallingModel):
|
||||
|
||||
ROOT_TAG = 'addresses'
|
||||
|
||||
class _NetworkAddressesList(BaseModel):
|
||||
|
||||
def __init__(self):
|
||||
super(Addresses._NetworkAddressesList, self).__init__()
|
||||
self.addresses = []
|
||||
|
||||
def __repr__(self):
|
||||
ret = ''
|
||||
for a in self.addresses:
|
||||
ret = ret + 'Address:\n\t%s' % str(a)
|
||||
return ret
|
||||
|
||||
def append(self, addr_obj):
|
||||
self.addresses.append(addr_obj)
|
||||
|
||||
@property
|
||||
def ipv4(self):
|
||||
for addr in self.addresses:
|
||||
if str(addr.version) == '4':
|
||||
return str(addr.addr)
|
||||
return None
|
||||
|
||||
@property
|
||||
def ipv6(self):
|
||||
for addr in self.addresses:
|
||||
if str(addr.version) == '6':
|
||||
return str(addr.addr)
|
||||
return None
|
||||
|
||||
@property
|
||||
def count(self):
|
||||
return len(self.addresses)
|
||||
|
||||
class _AddrObj(BaseModel):
|
||||
|
||||
def __init__(self, version=None, addr=None):
|
||||
super(Addresses._AddrObj, self).__init__()
|
||||
self.version = version
|
||||
self.addr = addr
|
||||
|
||||
def __repr__(self):
|
||||
ret = ''
|
||||
ret = ret + 'version: %s' % str(self.version)
|
||||
ret = ret + 'addr: %s' % str(self.addr)
|
||||
return ret
|
||||
|
||||
def __init__(self, addr_dict):
|
||||
super(Addresses, self).__init__()
|
||||
|
||||
#Preset properties that should be expected, if not always populated
|
||||
self.public = None
|
||||
self.private = None
|
||||
|
||||
if len(addr_dict) > 1:
|
||||
''' adddress_type is PUBLIC/PRIVATE '''
|
||||
for address_type in addr_dict:
|
||||
''' address_list is list of address dictionaries'''
|
||||
address_list = addr_dict[address_type]
|
||||
''' init a network object with empty addresses list '''
|
||||
network = self._NetworkAddressesList()
|
||||
for address in address_list:
|
||||
addrobj = self._AddrObj(
|
||||
version=int(address.get('version')),
|
||||
addr=address.get('addr'))
|
||||
network.addresses.append(addrobj)
|
||||
setattr(self, address_type, network)
|
||||
# Validation in case we have nested addresses in addresses
|
||||
else:
|
||||
big_addr_dict = addr_dict
|
||||
if big_addr_dict.get('addresses') is not None:
|
||||
addr_dict = big_addr_dict.get('addresses')
|
||||
for address_type in addr_dict:
|
||||
''' address_list is list of address dictionaries'''
|
||||
address_list = addr_dict[address_type]
|
||||
''' init a network object with empty addresses list '''
|
||||
network = self._NetworkAddressesList()
|
||||
for address in address_list:
|
||||
addrobj = self._AddrObj(version=address.get('version'),
|
||||
addr=address.get('addr'))
|
||||
network.addresses.append(addrobj)
|
||||
setattr(self, address_type, network)
|
||||
|
||||
def get_by_name(self, label):
|
||||
try:
|
||||
ret = getattr(self, label)
|
||||
except AttributeError:
|
||||
ret = None
|
||||
return ret
|
||||
|
||||
def __repr__(self):
|
||||
ret = '\n'
|
||||
ret = ret + '\npublic:\n\t\t%s' % str(self.public)
|
||||
ret = ret + '\nprivate:\n\t\t%s' % str(self.private)
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
json_dict = json.loads(serialized_str)
|
||||
return Addresses(json_dict)
|
||||
|
||||
@classmethod
|
||||
def _dict_to_obj(cls, serialized_str):
|
||||
return Addresses(serialized_str)
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
element = ET.fromstring(serialized_str)
|
||||
cls._remove_xml_etree_namespace(element, Constants.XML_API_NAMESPACE)
|
||||
return cls._xml_ele_to_obj(element)
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, element):
|
||||
addresses = {}
|
||||
if element.tag != 'network':
|
||||
networks = element.findall('network')
|
||||
for network in networks:
|
||||
network_id = network.attrib.get('id')
|
||||
addresses[network_id] = []
|
||||
for ip in network:
|
||||
addresses[network_id].append(ip.attrib)
|
||||
else:
|
||||
networks = element
|
||||
network_id = networks.attrib.get('id')
|
||||
addresses[network_id] = []
|
||||
for ip in networks:
|
||||
addresses[network_id].append(ip.attrib)
|
||||
|
||||
return Addresses(addresses)
|
16
cloudcafe/compute/volume_attachments_api/__init__.py
Normal file
16
cloudcafe/compute/volume_attachments_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/compute/volume_attachments_api/models/__init__.py
Normal file
16
cloudcafe/compute/volume_attachments_api/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
@ -0,0 +1,38 @@
|
||||
"""
|
||||
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 cafe.engine.models.base import AutoMarshallingModel
|
||||
|
||||
|
||||
class VolumeAttachmentRequest(AutoMarshallingModel):
|
||||
def __init__(self, volume_id=None, device=None):
|
||||
self.id = None
|
||||
self.server_id = None
|
||||
self.volume_id = volume_id
|
||||
self.device = device
|
||||
|
||||
def _obj_to_json(self):
|
||||
return json.dumps(self._obj_to_json_ele())
|
||||
|
||||
def _obj_to_json_ele(self):
|
||||
sub_body = {"volumeId": self.volume_id}
|
||||
sub_body["device"] = self.device
|
||||
sub_body = self._set_clean_json_dict_attrs(sub_body)
|
||||
body = {"volumeAttachment": sub_body}
|
||||
body = self._set_clean_json_dict_attrs(body)
|
||||
return json.dumps(body)
|
@ -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.
|
||||
"""
|
||||
|
@ -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.
|
||||
"""
|
||||
|
||||
import json
|
||||
|
||||
from cafe.engine.models.base import \
|
||||
AutoMarshallingModel, AutoMarshallingListModel
|
||||
|
||||
|
||||
class VolumeAttachment(AutoMarshallingModel):
|
||||
def __init__(self, id_=None, volume_id=None, server_id=None, device=None):
|
||||
self.id_ = None
|
||||
self.server_id = None
|
||||
self.volume_id = volume_id
|
||||
self.device = device
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
json_dict = json.loads(serialized_str)
|
||||
return VolumeAttachment(
|
||||
id_=json_dict.get('id'),
|
||||
volume_id=json_dict.get('volumeId'),
|
||||
server_id=json_dict.get('serverId'),
|
||||
device=json_dict.get('device'))
|
||||
|
||||
|
||||
class VolumeAttachmentListResponse(AutoMarshallingListModel):
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
'''
|
||||
Handles both the single and list version of the Volume
|
||||
call, obviating the need for separate domain objects for "Volumes"
|
||||
and "Lists of Volumes" responses.
|
||||
Returns a list-like VolumeAttachmentListResponse
|
||||
of VolumeAttachment objects, even if there is only one volume
|
||||
attachment present.
|
||||
'''
|
||||
json_dict = json.loads(serialized_str)
|
||||
|
||||
is_list = True if json_dict.get('volumeAttachments', None) else False
|
||||
|
||||
va_list = VolumeAttachmentListResponse()
|
||||
if is_list:
|
||||
for volume_attachment in json_dict.get('volumeAttachments'):
|
||||
va = VolumeAttachment(
|
||||
id_=volume_attachment.get('id'),
|
||||
volume_id=volume_attachment.get('volumeId'),
|
||||
server_id=volume_attachment.get('serverId'),
|
||||
device=volume_attachment.get('device'))
|
||||
va_list.append(va)
|
||||
else:
|
||||
volume_attachment = json_dict.get('volumeAttachment')
|
||||
va_list.append(
|
||||
VolumeAttachment(
|
||||
id_=volume_attachment.get('id'),
|
||||
volume_id=volume_attachment.get('volumeId'),
|
||||
server_id=volume_attachment.get('serverId'),
|
||||
device=volume_attachment.get('device')))
|
||||
va_list.append(va)
|
||||
return va_list
|
||||
|
@ -0,0 +1,96 @@
|
||||
"""
|
||||
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.compute.volume_attachments_api.models.requests.volume_attachments \
|
||||
import VolumeAttachmentRequest
|
||||
|
||||
from cloudcafe.compute.volume_attachments_api.models.responses.volume_attachments \
|
||||
import VolumeAttachmentListResponse
|
||||
|
||||
|
||||
class VolumeAttachmentsAPIClient(AutoMarshallingRestClient):
|
||||
|
||||
def __init__(self, url, auth_token, tenant_id, serialize_format=None,
|
||||
deserialize_format=None):
|
||||
|
||||
super(VolumeAttachmentsAPIClient, self).__init__(
|
||||
serialize_format, deserialize_format)
|
||||
|
||||
self.url = url
|
||||
self.auth_token = auth_token
|
||||
self.tenant_id = tenant_id
|
||||
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)
|
||||
|
||||
def attach_volume(self, server_id, volume_id, device=None,
|
||||
requestslib_kwargs=None):
|
||||
'''POST
|
||||
v2/{tenant_id}/servers/{server_id}/os-volume_attachments
|
||||
'''
|
||||
|
||||
url = '{0}/servers/{1}/os-volume_attachments'.format(
|
||||
self.url, server_id)
|
||||
va = VolumeAttachmentRequest(volume_id, device)
|
||||
return self.request(
|
||||
'POST', url, response_entity_type=VolumeAttachmentListResponse,
|
||||
request_entity=va, requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def delete_volume_attachment(self, attachment_id, server_id,
|
||||
requestslib_kwargs=None):
|
||||
'''DELETE
|
||||
v2/servers/{server_id}/os-volume_attachments/{attachment_id}
|
||||
'''
|
||||
|
||||
url = '{0}/servers/{1}/os-volume_attachments/{2}'.format(
|
||||
self.url, server_id, attachment_id)
|
||||
|
||||
params = {
|
||||
'tenant_id': self.tenant_id, 'server_id': server_id,
|
||||
'attachment_id': attachment_id}
|
||||
|
||||
return self.request(
|
||||
'DELETE', url, params=params,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def get_server_volume_attachments(self, server_id,
|
||||
requestslib_kwargs=None):
|
||||
'''GET
|
||||
v2/servers/{server_id}/os-volume_attachments/
|
||||
'''
|
||||
|
||||
url = '{0}/servers/{1}/os-volume_attachments'.format(
|
||||
self.url, server_id)
|
||||
|
||||
params = {'tenant_id': self.tenant_id, 'server_id': server_id}
|
||||
|
||||
return self.request(
|
||||
'GET', url, params=params, requestslib_kwargs=requestslib_kwargs)
|
||||
|
||||
def get_volume_attachment_details(self, attachment_id, server_id,
|
||||
requestslib_kwargs=None):
|
||||
url = '{0}/servers/{1}/os-volume_attachments/{2}'.format(
|
||||
self.url, server_id, attachment_id)
|
||||
|
||||
params = {'tenant_id': self.tenant_id,
|
||||
'server_id': server_id,
|
||||
'attachment_id': attachment_id}
|
||||
|
||||
return self.request(
|
||||
'GET', url, params=params, requestslib_kwargs=requestslib_kwargs)
|
16
cloudcafe/identity/__init__.py
Normal file
16
cloudcafe/identity/__init__.py
Normal 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.
|
||||
"""
|
||||
|
22
cloudcafe/identity/v2_0/__init__.py
Normal file
22
cloudcafe/identity/v2_0/__init__.py
Normal file
@ -0,0 +1,22 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
'''
|
||||
@summary: Classes and Utilities used to manage common Domain Level Objects
|
||||
@note: Primarily used to pass data between L{ccengine.providers}, L{ccengine.clients} and L{testrepo} Tests
|
||||
@note: Also used to perform JSON or XML Marshaling and Validation
|
||||
@note: Consumed by L{ccengine.providers}, L{ccengine.clients}, L{ccengine.common.connectors} and L{testrepo} Tests
|
||||
'''
|
16
cloudcafe/identity/v2_0/provider.py
Normal file
16
cloudcafe/identity/v2_0/provider.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/identity/v2_0/tenants_api/__init__.py
Normal file
16
cloudcafe/identity/v2_0/tenants_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/identity/v2_0/tenants_api/models/__init__.py
Normal file
16
cloudcafe/identity/v2_0/tenants_api/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
16
cloudcafe/identity/v2_0/tokens_api/__init__.py
Normal file
16
cloudcafe/identity/v2_0/tokens_api/__init__.py
Normal 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.
|
||||
"""
|
||||
|
43
cloudcafe/identity/v2_0/tokens_api/behaviors.py
Normal file
43
cloudcafe/identity/v2_0/tokens_api/behaviors.py
Normal file
@ -0,0 +1,43 @@
|
||||
"""
|
||||
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.identity.v2_0.tokens_api.client import TokenAPI_Client
|
||||
from cloudcafe.identity.v2_0.tokens_api.config import TokenAPI_Config
|
||||
|
||||
|
||||
class TokenAPI_Behaviors(BaseBehavior):
|
||||
|
||||
def __init__(self, identity_user_api_client=None):
|
||||
self._client = identity_user_api_client
|
||||
self.config = TokenAPI_Config()
|
||||
|
||||
@behavior(TokenAPI_Client)
|
||||
def get_access_data(self, username=None, password=None,
|
||||
tenant_name=None):
|
||||
|
||||
username = username or self.config.username
|
||||
password = password or self.config.password
|
||||
tenant_name = tenant_name or self.config.tenant_name
|
||||
|
||||
access_data = None
|
||||
if username is not None and password is not None:
|
||||
response = self._client.authenticate(
|
||||
username=username, password=password,
|
||||
tenant_name=tenant_name)
|
||||
access_data = response.entity
|
||||
|
||||
return access_data
|
91
cloudcafe/identity/v2_0/tokens_api/client.py
Normal file
91
cloudcafe/identity/v2_0/tokens_api/client.py
Normal file
@ -0,0 +1,91 @@
|
||||
"""
|
||||
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.identity.v2_0.tokens_api.models.requests.auth import \
|
||||
Auth as AuthRequest
|
||||
from cloudcafe.identity.v2_0.tokens_api.models.responses.access import \
|
||||
Access as AuthResponse
|
||||
from cloudcafe.identity.v2_0.tokens_api.models.requests.credentials import \
|
||||
PasswordCredentials
|
||||
|
||||
_version = 'v2.0'
|
||||
_tokens = 'tokens'
|
||||
|
||||
|
||||
class BaseTokenAPI_Client(AutoMarshallingRestClient):
|
||||
|
||||
def __init__(self, serialize_format, deserialize_format=None):
|
||||
super(BaseTokenAPI_Client, self).__init__(serialize_format,
|
||||
deserialize_format)
|
||||
|
||||
@property
|
||||
def token(self):
|
||||
return self.default_headers.get('X-Auth-Token')
|
||||
|
||||
@token.setter
|
||||
def token(self, token):
|
||||
self.default_headers['X-Auth-Token'] = token
|
||||
|
||||
@token.deleter
|
||||
def token(self):
|
||||
del self.default_headers['X-Auth-Token']
|
||||
|
||||
|
||||
class TokenAPI_Client(BaseTokenAPI_Client):
|
||||
def __init__(self, url, serialize_format, deserialize_format=None,
|
||||
auth_token=None):
|
||||
|
||||
super(TokenAPI_Client, self).__init__(
|
||||
serialize_format, deserialize_format)
|
||||
self.base_url = '{0}/{1}'.format(url, _version)
|
||||
self.default_headers['Content-Type'] = 'application/{0}'.format(
|
||||
serialize_format)
|
||||
self.default_headers['Accept'] = 'application/{0}'.format(
|
||||
serialize_format)
|
||||
|
||||
if auth_token is not None:
|
||||
self.default_headers['X-Auth-Token'] = auth_token
|
||||
|
||||
def authenticate(self, username, password, tenant_name,
|
||||
requestslib_kwargs=None):
|
||||
|
||||
'''
|
||||
@summary: Creates authentication using Username and password.
|
||||
@param username: The username of the customer.
|
||||
@type name: String
|
||||
@param password: The user password.
|
||||
@type password: String
|
||||
@return: Response Object containing auth response
|
||||
@rtype: Response Object
|
||||
'''
|
||||
|
||||
'''
|
||||
POST
|
||||
v2.0/tokens
|
||||
'''
|
||||
credentials = PasswordCredentials(
|
||||
username=username,
|
||||
password=password)
|
||||
auth_request_entity = AuthRequest(credentials=credentials,
|
||||
tenant_name=tenant_name)
|
||||
|
||||
url = '{0}/{1}'.format(self.base_url, _tokens)
|
||||
response = self.post(url, response_entity_type=AuthResponse,
|
||||
request_entity=auth_request_entity,
|
||||
requestslib_kwargs=requestslib_kwargs)
|
||||
return response
|
46
cloudcafe/identity/v2_0/tokens_api/config.py
Normal file
46
cloudcafe/identity/v2_0/tokens_api/config.py
Normal file
@ -0,0 +1,46 @@
|
||||
"""
|
||||
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 TokenAPI_Config(ConfigSectionInterface):
|
||||
|
||||
SECTION_NAME = 'token_api'
|
||||
|
||||
@property
|
||||
def serialize_format(self):
|
||||
return self.get("serialize_format")
|
||||
|
||||
@property
|
||||
def deserialize_format(self):
|
||||
return self.get("deserialize_format")
|
||||
|
||||
@property
|
||||
def authentication_endpoint(self):
|
||||
return self.get("authentication_endpoint")
|
||||
|
||||
@property
|
||||
def username(self):
|
||||
return self.get("username")
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self.get_raw("password")
|
||||
|
||||
@property
|
||||
def tenant_name(self):
|
||||
return self.get("tenant_name")
|
16
cloudcafe/identity/v2_0/tokens_api/models/__init__.py
Normal file
16
cloudcafe/identity/v2_0/tokens_api/models/__init__.py
Normal 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.
|
||||
"""
|
||||
|
42
cloudcafe/identity/v2_0/tokens_api/models/base.py
Normal file
42
cloudcafe/identity/v2_0/tokens_api/models/base.py
Normal file
@ -0,0 +1,42 @@
|
||||
"""
|
||||
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.base import \
|
||||
AutoMarshallingModel, AutoMarshallingListModel
|
||||
|
||||
from cloudcafe.identity.v2_0.tokens_api.models.constants import V2_0Constants
|
||||
|
||||
|
||||
class BaseIdentityModel(AutoMarshallingModel):
|
||||
|
||||
@classmethod
|
||||
def _remove_identity_xml_namespaces(cls, element):
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_OS_KSADM)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_RAX_KSKEY)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_OS_KSEC2)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_RAX_KSQA)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_RAX_AUTH)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_RAX_KSGRP)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_OPENSTACK_COMMON)
|
||||
cls._remove_namespace(element, V2_0Constants.XML_NS_ATOM)
|
||||
|
||||
|
||||
class BaseIdentityListModel(AutoMarshallingListModel):
|
||||
|
||||
@classmethod
|
||||
def _remove_identity_xml_namespaces(cls, element):
|
||||
BaseIdentityListModel._remove_identity_xml_namespaces(element)
|
33
cloudcafe/identity/v2_0/tokens_api/models/constants.py
Normal file
33
cloudcafe/identity/v2_0/tokens_api/models/constants.py
Normal file
@ -0,0 +1,33 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
class V2_0Constants(object):
|
||||
XML_NS = 'http://docs.openstack.org/identity/api/v2.0'
|
||||
XML_NS_OPENSTACK_COMMON = 'http://docs.openstack.org/common/api/v1.0'
|
||||
XML_NS_XSI = 'http://www.w3.org/2001/XMLSchema-instance'
|
||||
XML_NS_OS_KSADM = \
|
||||
'http://docs.openstack.org/identity/api/ext/OS-KSADM/v1.0'
|
||||
XML_NS_OS_KSEC2 = \
|
||||
'http://docs.openstack.org/identity/api/ext/OS-KSEC2/v1.0'
|
||||
XML_NS_RAX_KSQA = \
|
||||
'http://docs.rackspace.com/identity/api/ext/RAX-KSQA/v1.0'
|
||||
XML_NS_RAX_KSKEY = \
|
||||
'http://docs.rackspace.com/identity/api/ext/RAX-KSKEY/v1.0'
|
||||
XML_NS_RAX_AUTH = \
|
||||
'http://docs.rackspace.com/identity/api/ext/RAX-AUTH/v1.0'
|
||||
XML_NS_RAX_KSGRP = \
|
||||
'http://docs.rackspace.com/identity/api/ext/RAX-KSGRP/v1.0'
|
||||
XML_NS_ATOM = 'http://www.w3.org/2005/Atom'
|
@ -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.
|
||||
"""
|
||||
|
84
cloudcafe/identity/v2_0/tokens_api/models/requests/auth.py
Normal file
84
cloudcafe/identity/v2_0/tokens_api/models/requests/auth.py
Normal file
@ -0,0 +1,84 @@
|
||||
"""
|
||||
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 cloudcafe.identity.v2_0.tokens_api.models.base import BaseIdentityModel
|
||||
from cloudcafe.identity.v2_0.tokens_api.models.requests.credentials import \
|
||||
PasswordCredentials
|
||||
|
||||
|
||||
class Auth(BaseIdentityModel):
|
||||
|
||||
ROOT_TAG = 'auth'
|
||||
|
||||
def __init__(self, credentials=None, tenant_name=None, token=None):
|
||||
self.passwordCredentials = credentials
|
||||
self.token = token
|
||||
self.tenant_name = tenant_name
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = {}
|
||||
if self.passwordCredentials is not None:
|
||||
ret[PasswordCredentials.ROOT_TAG] = \
|
||||
self.passwordCredentials._obj_to_dict()
|
||||
if self.token is not None:
|
||||
ret[Token.ROOT_TAG] = self.token._obj_to_dict()
|
||||
if self.tenant_name is not None:
|
||||
ret['tenantName'] = self.tenant_name
|
||||
ret = {self.ROOT_TAG: ret}
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_xml(self):
|
||||
ele = self._obj_to_xml_ele()
|
||||
#ele.set('xmlns:xsi', V2_0Constants.XML_NS_XSI)
|
||||
#ele.set('xmlns', V2_0Constants.XML_NS)
|
||||
return ElementTree.tostring(ele)
|
||||
|
||||
def _obj_to_xml_ele(self):
|
||||
element = ElementTree.Element(self.ROOT_TAG)
|
||||
if self.passwordCredentials is not None:
|
||||
element.append(self.passwordCredentials._obj_to_xml_ele())
|
||||
if self.token is not None:
|
||||
element.append(self.token._obj_to_xml_ele())
|
||||
if self.tenant_name is not None:
|
||||
element.set('tenantName', self.tenant_name)
|
||||
return element
|
||||
|
||||
|
||||
class Token(BaseIdentityModel):
|
||||
|
||||
ROOT_TAG = 'token'
|
||||
|
||||
def __init__(self, id=None):
|
||||
super(Token, self).__init__()
|
||||
self.id = id
|
||||
|
||||
def _obj_to_dict(self):
|
||||
ret = {}
|
||||
if self.id is not None:
|
||||
ret['id'] = self.id
|
||||
return ret
|
||||
|
||||
def _obj_to_xml(self):
|
||||
return ElementTree.tostring(self._obj_to_xml_ele())
|
||||
|
||||
def _obj_to_xml_ele(self):
|
||||
element = ElementTree.Element(self.ROOT_TAG)
|
||||
if self.id is not None:
|
||||
element.set('id', self.id)
|
||||
return element
|
@ -0,0 +1,59 @@
|
||||
"""
|
||||
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 cloudcafe.identity.v2_0.tokens_api.models.base import \
|
||||
BaseIdentityModel, V2_0Constants
|
||||
|
||||
|
||||
class PasswordCredentials(BaseIdentityModel):
|
||||
|
||||
ROOT_TAG = 'passwordCredentials'
|
||||
|
||||
def __init__(self, username=None, password=None):
|
||||
|
||||
super(PasswordCredentials, self).__init__()
|
||||
self.username = username
|
||||
self.password = password
|
||||
|
||||
def _obj_to_json(self):
|
||||
ret = {self.ROOT_TAG: self._obj_to_dict()}
|
||||
return json.dumps(ret)
|
||||
|
||||
def _obj_to_dict(self):
|
||||
ret = {}
|
||||
if self.username is not None:
|
||||
ret['username'] = self.username
|
||||
if self.password is not None:
|
||||
ret['password'] = self.password
|
||||
return ret
|
||||
|
||||
def _obj_to_xml(self):
|
||||
element = self._obj_to_xml_ele()
|
||||
element.set('xmlns', V2_0Constants.XML_NS)
|
||||
element.set('xmlns:xsi', V2_0Constants.XML_NS_XSI)
|
||||
return ElementTree.tostring(element)
|
||||
|
||||
def _obj_to_xml_ele(self):
|
||||
element = ElementTree.Element(self.ROOT_TAG)
|
||||
if self.username is not None:
|
||||
element.set('username', self.username)
|
||||
if self.password is not None:
|
||||
element.set('password', self.password)
|
||||
return element
|
||||
|
100
cloudcafe/identity/v2_0/tokens_api/models/requests/role.py
Normal file
100
cloudcafe/identity/v2_0/tokens_api/models/requests/role.py
Normal file
@ -0,0 +1,100 @@
|
||||
"""
|
||||
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 cloudcafe.identity.v2_0.tokens_api.models.base import \
|
||||
BaseIdentityModel, BaseIdentityListModel
|
||||
|
||||
|
||||
class Roles(BaseIdentityListModel):
|
||||
|
||||
ROOT_TAG = 'roles'
|
||||
|
||||
def __init__(self, roles=None):
|
||||
'''An object that represents an users response object.
|
||||
Keyword arguments:
|
||||
'''
|
||||
super(Roles, self).__init__()
|
||||
self.extend(roles)
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
json_dict = json.loads(serialized_str)
|
||||
return cls._list_to_obj(json_dict.get(cls.ROOT_TAG))
|
||||
|
||||
@classmethod
|
||||
def _list_to_obj(cls, list_):
|
||||
ret = {cls.ROOT_TAG: [Role(**role) for role in list_]}
|
||||
return Roles(**ret)
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
element = ElementTree.fromstring(serialized_str)
|
||||
cls._remove_identity_xml_namespaces(element)
|
||||
if element.tag != cls.ROOT_TAG:
|
||||
return None
|
||||
return cls._xml_list_to_obj(element.findall(Role.ROOT_TAG))
|
||||
|
||||
@classmethod
|
||||
def _xml_list_to_obj(cls, xml_list):
|
||||
kwargs = {cls.ROOT_TAG: [Role._xml_ele_to_obj(role)
|
||||
for role in xml_list]}
|
||||
return Roles(**kwargs)
|
||||
|
||||
|
||||
class Role(BaseIdentityModel):
|
||||
|
||||
ROOT_TAG = 'role'
|
||||
|
||||
def __init__(self, id=None, name=None, description=None, serviceId=None,
|
||||
tenantId=None, propagate=None, weight=None):
|
||||
super(Role, self).__init__()
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.serviceId = serviceId
|
||||
self.tenantId = tenantId
|
||||
self.weight = weight
|
||||
self.propagate = propagate
|
||||
|
||||
@classmethod
|
||||
def _json_to_obj(cls, serialized_str):
|
||||
json_dict = json.loads(serialized_str)
|
||||
json_dict['role']['propagate'] = json_dict['role'].pop('RAX-AUTH:propagate')
|
||||
json_dict['role']['weight'] = json_dict['role'].pop('RAX-AUTH:Weight')
|
||||
return Role(**json_dict.get(cls.ROOT_TAG))
|
||||
|
||||
@classmethod
|
||||
def _xml_to_obj(cls, serialized_str):
|
||||
element = ElementTree.fromstring(serialized_str)
|
||||
cls._remove_identity_xml_namespaces(element)
|
||||
if element.tag != cls.ROOT_TAG:
|
||||
return None
|
||||
return cls._xml_ele_to_obj(element)
|
||||
|
||||
@classmethod
|
||||
def _xml_ele_to_obj(cls, xml_ele):
|
||||
kwargs = {'name': xml_ele.get('name'),
|
||||
'description': xml_ele.get('description'),
|
||||
'serviceId': xml_ele.get('serviceId'),
|
||||
'tenantId': xml_ele.get('tenantId')}
|
||||
try:
|
||||
kwargs['id'] = int(xml_ele.get('id'))
|
||||
except (ValueError, TypeError):
|
||||
kwargs['id'] = xml_ele.get('id')
|
||||
return Role(**kwargs)
|
@ -0,0 +1,22 @@
|
||||
"""
|
||||
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.
|
||||
"""
|
||||
|
||||
'''
|
||||
@summary: Classes and Utilities used to manage common Domain Level Objects
|
||||
@note: Primarily used to pass data between L{ccengine.providers}, L{ccengine.clients} and L{testrepo} Tests
|
||||
@note: Also used to perform JSON or XML Marshaling and Validation
|
||||
@note: Consumed by L{ccengine.providers}, L{ccengine.clients}, L{ccengine.common.connectors} and L{testrepo} Tests
|
||||
'''
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user