You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by to...@apache.org on 2015/08/15 21:30:19 UTC
[1/2] libcloud git commit: Added Runabove volume management & fixed
many little things
Repository: libcloud
Updated Branches:
refs/heads/trunk abe832fc9 -> 59de189c4
Added Runabove volume management & fixed many little things
Close #561
Signed-off-by: Tomaz Muraus <to...@tomaz.me>
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/7bc2d29b
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/7bc2d29b
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/7bc2d29b
Branch: refs/heads/trunk
Commit: 7bc2d29bcd3a1d7810a621e611a1f9d61c173dd0
Parents: 9fefa0c
Author: ZuluPro <mo...@hotmail.com>
Authored: Sun Jul 26 04:16:37 2015 -0400
Committer: Tomaz Muraus <to...@tomaz.me>
Committed: Sat Aug 15 16:32:41 2015 +0200
----------------------------------------------------------------------
CHANGES.rst | 5 +
docs/compute/drivers/runabove.rst | 15 +-
docs/examples/compute/runabove/attach_volume.py | 11 +
libcloud/compute/drivers/runabove.py | 297 +++++++++++++++----
.../compute/fixtures/runabove/volume_get.json | 1 +
.../fixtures/runabove/volume_get_detail.json | 1 +
libcloud/test/compute/test_runabove.py | 53 ++++
7 files changed, 317 insertions(+), 66 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/CHANGES.rst
----------------------------------------------------------------------
diff --git a/CHANGES.rst b/CHANGES.rst
index d3741b4..dc1c025 100644
--- a/CHANGES.rst
+++ b/CHANGES.rst
@@ -12,6 +12,11 @@ Compute
(GITHUB-516)
[Syed Mushtaq Ahmed]
+- Add volume management methods and other various improvements and fixes in the
+ RunAbove driver.
+ (GITHUB-561)
+ [ZuluPro]
+
Changes with Apache Libcloud 0.18.0
-----------------------------------
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/docs/compute/drivers/runabove.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/runabove.rst b/docs/compute/drivers/runabove.rst
index adf1b9d..2b3c476 100644
--- a/docs/compute/drivers/runabove.rst
+++ b/docs/compute/drivers/runabove.rst
@@ -1,5 +1,5 @@
-Cloudwatt Compute Driver Documentation
-======================================
+RunAbove Compute Driver Documentation
+=====================================
`RunAbove`_ is a public cloud offer created by OVH Group with datacenters
in North America and Europe.
@@ -18,7 +18,7 @@ Instantiating a driver
When you instantiate a driver you need to pass the following arguments to the
driver constructor:
-* ``user_id`` - Application key
+* ``key`` - Application key
* ``secret`` - Application secret
* ``ex_consumer_key`` - Consumer key
@@ -55,11 +55,16 @@ Now you have and can use you credentials with Libcloud.
Examples
--------
-Create instance
-~~~~~~~~~~~~~~~
+Create node
+~~~~~~~~~~~
.. literalinclude:: /examples/compute/runabove/create_node.py
+Create and attach a volume to a node
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. literalinclude:: /examples/compute/runabove/attach_volume.py
+
API Docs
--------
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/docs/examples/compute/runabove/attach_volume.py
----------------------------------------------------------------------
diff --git a/docs/examples/compute/runabove/attach_volume.py b/docs/examples/compute/runabove/attach_volume.py
new file mode 100644
index 0000000..c4c0178
--- /dev/null
+++ b/docs/examples/compute/runabove/attach_volume.py
@@ -0,0 +1,11 @@
+from libcloud.compute.types import Provider
+from libcloud.compute.providers import get_driver
+
+RunAbove = get_driver(Provider.RUNABOVE)
+driver = RunAbove('yourAppKey', 'yourAppSecret', 'YourConsumerKey')
+
+location = [l for l in driver.list_locations() if l.id == 'SBG-1'][0]
+node = driver.list_nodes()[0]
+
+volume = driver.create_volume(size=10, location=location)
+driver.attach_volume(node=node, volume=volume)
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/libcloud/compute/drivers/runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/runabove.py b/libcloud/compute/drivers/runabove.py
index a3ff95b..f5fd348 100644
--- a/libcloud/compute/drivers/runabove.py
+++ b/libcloud/compute/drivers/runabove.py
@@ -12,11 +12,13 @@
# 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.
-
+"""
+RunAbove driver
+"""
from libcloud.common.runabove import API_ROOT, RunAboveConnection
from libcloud.compute.base import NodeDriver, NodeSize, Node, NodeLocation
-from libcloud.compute.base import NodeImage
-from libcloud.compute.types import Provider
+from libcloud.compute.base import NodeImage, StorageVolume
+from libcloud.compute.types import Provider, StorageVolumeState
from libcloud.compute.drivers.openstack import OpenStackNodeDriver
from libcloud.compute.drivers.openstack import OpenStackKeyPair
@@ -25,24 +27,9 @@ class RunAboveNodeDriver(NodeDriver):
"""
Libcloud driver for the RunAbove API
- Rough mapping of which is which:
-
- list_nodes linode.list
- reboot_node linode.reboot
- destroy_node linode.delete
- create_node linode.create, linode.update,
- linode.disk.createfromdistribution,
- linode.disk.create, linode.config.create,
- linode.ip.addprivate, linode.boot
- list_sizes avail.linodeplans
- list_images avail.distributions
- list_locations avail.datacenters
- list_volumes linode.disk.list
- destroy_volume linode.disk.delete
-
- For more information on the Linode API, be sure to read the reference:
+ For more information on the RunAbove API, read the official reference:
- http://www.linode.com/api/
+ https://api.runabove.com/console/
"""
type = Provider.RUNABOVE
name = "RunAbove"
@@ -52,13 +39,20 @@ class RunAboveNodeDriver(NodeDriver):
api_name = 'runabove'
NODE_STATE_MAP = OpenStackNodeDriver.NODE_STATE_MAP
+ VOLUME_STATE_MAP = OpenStackNodeDriver.VOLUME_STATE_MAP
def __init__(self, key, secret, ex_consumer_key=None):
"""
- Instantiate the driver with the given API key
+ Instantiate the driver with the given API credentials.
- :param key: the API key to use (required)
- :type key: ``str``
+ :param key: Your application key (required)
+ :type key: ``str``
+
+ :param secret: Your application secret (required)
+ :type secret: ``str``
+
+ :param ex_consumer_key: Your consumer key (required)
+ :type ex_consumer_key: ``str``
:rtype: ``None``
"""
@@ -68,14 +62,12 @@ class RunAboveNodeDriver(NodeDriver):
def list_nodes(self, location=None):
"""
- List all Linodes that the API key can access
+ List all nodes.
- This call will return all Linodes that the API key in use has access
- to.
- If a node is in this list, rebooting will work; however, creation and
- destruction are a separate grant.
+ :keyword location: Location (region) used as filter
+ :type location: :class:`NodeLocation`
- :return: List of node objects that the API key can access
+ :return: List of node objects
:rtype: ``list`` of :class:`Node`
"""
action = API_ROOT + '/instance'
@@ -86,20 +78,50 @@ class RunAboveNodeDriver(NodeDriver):
return self._to_nodes(response.object)
def ex_get_node(self, node_id):
+ """
+ Get a individual node.
+
+ :keyword node_id: Node's ID
+ :type node_id: ``str``
+
+ :return: Created node
+ :rtype : :class:`Node`
+ """
action = API_ROOT + '/instance/' + node_id
response = self.connection.request(action, method='GET')
return self._to_node(response.object)
- def create_node(self, **kwargs):
+ def create_node(self, name, image, size, location, ex_keyname=None):
+ """
+ Create a new node
+
+ :keyword name: Name of created node
+ :type name: ``str``
+
+ :keyword image: Image used for node
+ :type image: :class:`NodeImage`
+
+ :keyword size: Size (flavor) used for node
+ :type size: :class:`NodeSize`
+
+ :keyword location: Location (region) where to create node
+ :type location: :class:`NodeLocation`
+
+ :keyword ex_keyname: Name of SSH key used
+ :type ex_keyname: ``str``
+
+ :retunrs: Created node
+ :rtype : :class:`Node`
+ """
action = API_ROOT + '/instance'
data = {
- 'name': kwargs["name"],
- 'imageId': kwargs["image"].id,
- 'flavorId': kwargs["size"].id,
- 'region': kwargs["location"].id,
+ 'name': name,
+ 'imageId': image.id,
+ 'flavorId': size.id,
+ 'region': location.id,
}
- if kwargs.get('ex_keyname'):
- data['sshKeyName'] = kwargs['ex_keyname']
+ if ex_keyname:
+ data['sshKeyName'] = ex_keyname
response = self.connection.request(action, data=data, method='POST')
return self._to_node(response.object)
@@ -109,14 +131,6 @@ class RunAboveNodeDriver(NodeDriver):
return True
def list_sizes(self, location=None):
- """
- List available RunAbove flavors.
-
- :keyword location: the facility to retrieve plans in
- :type location: :class:`NodeLocation`
-
- :rtype: ``list`` of :class:`NodeSize`
- """
action = API_ROOT + '/flavor'
data = {}
if location:
@@ -125,24 +139,38 @@ class RunAboveNodeDriver(NodeDriver):
return self._to_sizes(response.object)
def ex_get_size(self, size_id):
+ """
+ Get an individual size (flavor).
+
+ :keyword size_id: Size's ID
+ :type size_id: ``str``
+
+ :return: Size
+ :rtype: :class:`NodeSize`
+ """
action = API_ROOT + '/flavor/' + size_id
response = self.connection.request(action)
return self._to_size(response.object)
- def list_images(self, location=None, size=None):
+ def list_images(self, location=None, ex_size=None):
"""
- List available Linux distributions
+ List available images
+
+ :keyword location: Location (region) used as filter
+ :type location: :class:`NodeLocation`
- Retrieve all Linux distributions that can be deployed to a Linode.
+ :keyword ex_size: Exclude images which are uncompatible with given size
+ :type ex_size: :class:`NodeImage`
- :rtype: ``list`` of :class:`NodeImage`
+ :return: List of images
+ :rtype : ``list`` of :class:`NodeImage`
"""
action = API_ROOT + '/image'
data = {}
if location:
data['region'] = location.id
- if size:
- data['flavorId'] = size.id
+ if ex_size:
+ data['flavorId'] = ex_size.id
response = self.connection.request(action, data=data)
return self._to_images(response.object)
@@ -152,18 +180,20 @@ class RunAboveNodeDriver(NodeDriver):
return self._to_image(response.object)
def list_locations(self):
- """
- List available facilities for deployment
-
- Retrieve all facilities that a Linode can be deployed in.
-
- :rtype: ``list`` of :class:`NodeLocation`
- """
action = API_ROOT + '/region'
data = self.connection.request(action)
return self._to_locations(data.object)
def list_key_pairs(self, location=None):
+ """
+ List available SSH public keys.
+
+ :keyword location: Location (region) used as filter
+ :type location: :class:`NodeLocation`
+
+ :return: Public keys
+ :rtype: ``list``of :class:`KeyPair`
+ """
action = API_ROOT + '/ssh'
data = {}
if location:
@@ -172,6 +202,18 @@ class RunAboveNodeDriver(NodeDriver):
return self._to_key_pairs(response.object)
def get_key_pair(self, name, location):
+ """
+ Get an individual SSH public key by its name and location.
+
+ :keyword name: SSH key name
+ :type name: str
+
+ :keyword location: Key's region
+ :type location: :class:`NodeLocation`
+
+ :return: Public key
+ :rtype: :class:`KeyPair`
+ """
action = API_ROOT + '/ssh/' + name
data = {'region': location.id}
response = self.connection.request(action, data=data)
@@ -179,7 +221,7 @@ class RunAboveNodeDriver(NodeDriver):
def import_key_pair_from_string(self, name, key_material, location):
"""
- Import a new public key.
+ Import a new public key from string.
:param name: Key pair name.
:type name: ``str``
@@ -188,7 +230,7 @@ class RunAboveNodeDriver(NodeDriver):
:type key_material: ``str``
:return: Imported key pair object.
- :rtype: :class:`.KeyPair`
+ :rtype: :class:`KeyPair`
"""
action = API_ROOT + '/ssh'
data = {'name': name, 'publicKey': key_material, 'region': location.id}
@@ -199,8 +241,11 @@ class RunAboveNodeDriver(NodeDriver):
"""
Delete an existing key pair.
- :param key_pair: Key pair object or ID.
- :type key_pair: :class.KeyPair` or ``int``
+ :param name: Key pair name.
+ :type name: ``str``
+
+ :keyword location: Key's region
+ :type location: :class:`NodeLocation`
:return: True of False based on success of Keypair deletion
:rtype: ``bool``
@@ -210,6 +255,136 @@ class RunAboveNodeDriver(NodeDriver):
self.connection.request(action, data=data, method='DELETE')
return True
+ def create_volume(self, size, location, name=None,
+ ex_volume_type='classic', ex_description=None):
+ """
+ Create a volume.
+
+ :param size: Size of volume to create (in GB).
+ :type size: ``int``
+
+ :param name: Name of volume to create
+ :type name: ``str``
+
+ :keyword location: Location to create the volume in
+ :type location: :class:`NodeLocation` or ``None``
+
+ :keyword ex_volume_type: ``'classic'`` or ``'high-speed'``
+ :type ex_volume_type: ``str``
+
+ :keyword ex_description: Optionnal description of volume
+ :type ex_description: str
+
+ :return: Storage Volume object
+ :rtype: :class:`StorageVolume`
+ """
+ action = API_ROOT + '/volume'
+ data = {
+ 'region': location.id,
+ 'size': str(size),
+ 'type': ex_volume_type,
+ }
+ if name:
+ data['name'] = name
+ if ex_description:
+ data['description'] = ex_description
+ response = self.connection.request(action, data=data, method='POST')
+ return self._to_volume(response.object)
+
+ def destroy_volume(self, volume):
+ action = API_ROOT + '/volume/' + volume.id
+ self.connection.request(action, method='DELETE')
+ return True
+
+ def list_volumes(self, location=None):
+ """
+ Return a list of volumes.
+
+ :keyword location: Location use for filter
+ :type location: :class:`NodeLocation` or ``None``
+
+ :return: A list of volume objects.
+ :rtype: ``list`` of :class:`StorageVolume`
+ """
+ action = API_ROOT + '/volume'
+ data = {}
+ if location:
+ data['region'] = location.id
+ response = self.connection.request(action, data=data)
+ return self._to_volumes(response.object)
+
+ def ex_get_volume(self, volume_id):
+ """
+ Return a Volume object based on a volume ID.
+
+ :param volume_id: The ID of the volume
+ :type volume_id: ``int``
+
+ :return: A StorageVolume object for the volume
+ :rtype: :class:`StorageVolume`
+ """
+ action = API_ROOT + '/volume/' + volume_id
+ response = self.connection.request(action)
+ return self._to_volume(response.object)
+
+ def attach_volume(self, node, volume, device=None):
+ """
+ Attach a volume to a node.
+
+ :param node: Node where to attach volume
+ :type node: :class:`Node`
+
+ :param volume: The ID of the volume
+ :type volume: :class:`StorageVolume`
+
+ :param device: Unsed parameter
+
+ :return: True or False representing operation successful
+ :rtype: ``bool``
+ """
+ action = '%s/volume/%s/attach' % (API_ROOT, volume.id)
+ data = {'instanceId': node.id}
+ self.connection.request(action, data=data, method='POST')
+ return True
+
+ def detach_volume(self, volume, ex_node=None):
+ """
+ Detach a volume to a node.
+
+ :param volume: The ID of the volume
+ :type volume: :class:`StorageVolume`
+
+ :param ex_node: Node to detach from (optionnal if volume is attached
+ to only one node)
+ :type ex_node: :class:`Node`
+
+ :return: True or False representing operation successful
+ :rtype: ``bool``
+
+ :raises: Exception: If ``ex_node`` is not provided and more than one
+ node is attached to the volume
+ """
+ action = '%s/volume/%s/detach' % (API_ROOT, volume.id)
+ if ex_node is None:
+ if len(volume.extra['attachedTo']) != 1:
+ err_msg = "Volume '%s' has more or less than one attached \
+ nodes, you must specify one."
+ raise Exception(err_msg)
+ ex_node = self.ex_get_node(volume.extra['attachedTo'][0])
+ data = {'instanceId': ex_node.id}
+ self.connection.request(action, data=data, method='POST')
+ return True
+
+ def _to_volume(self, obj):
+ extra = obj.copy()
+ extra.pop('id')
+ extra.pop('name')
+ extra.pop('size')
+ state = self.VOLUME_STATE_MAP.get(obj.pop('status', None),
+ StorageVolumeState.UNKNOWN)
+ return StorageVolume(id=obj['id'], name=obj['name'], size=obj['size'],
+ state=state, extra=extra, driver=self)
+
def _to_volumes(self, objs):
return [self._to_volume(obj) for obj in objs]
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/libcloud/test/compute/fixtures/runabove/volume_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/volume_get.json b/libcloud/test/compute/fixtures/runabove/volume_get.json
new file mode 100644
index 0000000..aa0b360
--- /dev/null
+++ b/libcloud/test/compute/fixtures/runabove/volume_get.json
@@ -0,0 +1 @@
+[{"id": "foo", "attachedTo": [], "created": "2015-08-09T15:13:59.459187Z", "name": "testvol", "description": "", "size": 10, "status": "creating", "region": "SBG-1", "type": "classic" }]
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/libcloud/test/compute/fixtures/runabove/volume_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/volume_get_detail.json b/libcloud/test/compute/fixtures/runabove/volume_get_detail.json
new file mode 100644
index 0000000..861cb4e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/runabove/volume_get_detail.json
@@ -0,0 +1 @@
+{"id": "foo", "attachedTo": [], "created": "2015-08-09T15:13:59.459187Z", "name": "testvol", "description": "", "size": 10, "status": "creating", "region": "SBG-1", "type": "classic" }
http://git-wip-us.apache.org/repos/asf/libcloud/blob/7bc2d29b/libcloud/test/compute/test_runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_runabove.py b/libcloud/test/compute/test_runabove.py
index 54e881e..a1538e0 100644
--- a/libcloud/test/compute/test_runabove.py
+++ b/libcloud/test/compute/test_runabove.py
@@ -80,6 +80,29 @@ class RunAboveMockHttp(BaseRunAboveMockHttp):
body = self.fixtures.load('instance_get_detail.json')
return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+ def _json_1_0_volume_get(self, method, url, body, headers):
+ body = self.fixtures.load('volume_get.json')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _json_1_0_volume_post(self, method, url, body, headers):
+ body = self.fixtures.load('volume_get_detail.json')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _json_1_0_volume_foo_get(self, method, url, body, headers):
+ body = self.fixtures.load('volume_get_detail.json')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _json_1_0_volume_foo_delete(self, method, url, body, headers):
+ return (httplib.OK, '', {}, httplib.responses[httplib.OK])
+
+ def _json_1_0_volume_foo_attach_post(self, method, url, body, headers):
+ body = self.fixtures.load('volume_get_detail.json')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+ def _json_1_0_volume_foo_detach_post(self, method, url, body, headers):
+ body = self.fixtures.load('volume_get_detail.json')
+ return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
@patch('libcloud.common.runabove.RunAboveConnection._timedelta', 42)
class RunAboveTests(unittest.TestCase):
@@ -144,5 +167,35 @@ class RunAboveTests(unittest.TestCase):
node = self.driver.list_nodes()[0]
self.driver.destroy_node(node)
+ def test_list_volumes(self):
+ volumes = self.driver.list_volumes()
+ self.assertTrue(len(volumes) > 0)
+
+ def test_get_volume(self):
+ volume = self.driver.ex_get_volume('foo')
+ self.assertEqual(volume.name, 'testvol')
+
+ def test_create_volume(self):
+ location = self.driver.list_locations()[0]
+ volume = self.driver.create_volume(size=10, name='testvol',
+ location=location)
+ self.assertEqual(volume.name, 'testvol')
+
+ def test_destroy_volume(self):
+ volume = self.driver.list_volumes()[0]
+ self.driver.destroy_volume(volume)
+
+ def test_attach_volume(self):
+ node = self.driver.list_nodes()[0]
+ volume = self.driver.ex_get_volume('foo')
+ response = self.driver.attach_volume(node=node, volume=volume)
+ self.assertTrue(response)
+
+ def test_detach_volume(self):
+ node = self.driver.list_nodes()[0]
+ volume = self.driver.ex_get_volume('foo')
+ response = self.driver.detach_volume(ex_node=node, volume=volume)
+ self.assertTrue(response)
+
if __name__ == '__main__':
sys.exit(unittest.main())
[2/2] libcloud git commit: Merge branch 'trunk' of
https://git-wip-us.apache.org/repos/asf/libcloud into trunk
Posted by to...@apache.org.
Merge branch 'trunk' of https://git-wip-us.apache.org/repos/asf/libcloud into trunk
Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/59de189c
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/59de189c
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/59de189c
Branch: refs/heads/trunk
Commit: 59de189c4f76f49d41a234e34fdb35da7a6c21fc
Parents: 7bc2d29 abe832f
Author: Tomaz Muraus <to...@tomaz.me>
Authored: Sat Aug 15 21:30:11 2015 +0200
Committer: Tomaz Muraus <to...@tomaz.me>
Committed: Sat Aug 15 21:30:11 2015 +0200
----------------------------------------------------------------------
doap_libcloud.rdf | 7 +++++++
1 file changed, 7 insertions(+)
----------------------------------------------------------------------