You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@libcloud.apache.org by an...@apache.org on 2016/10/06 22:24:04 UTC

[1/4] libcloud git commit: Updated RunAbove doc to OVH

Repository: libcloud
Updated Branches:
  refs/heads/trunk 25c5ba962 -> cc9a01304


Updated RunAbove doc to OVH


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/a2cde6be
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/a2cde6be
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/a2cde6be

Branch: refs/heads/trunk
Commit: a2cde6beaa2538602662522610be52d567f165d4
Parents: 4f87f55
Author: ZuluPro <mo...@hotmail.com>
Authored: Wed Oct 5 17:01:01 2016 -0400
Committer: Anthony Shaw <an...@apache.org>
Committed: Fri Oct 7 09:23:34 2016 +1100

----------------------------------------------------------------------
 docs/_static/images/provider_logos/ovh.png      | Bin 0 -> 47729 bytes
 docs/_static/images/provider_logos/runabove.png | Bin 15052 -> 0 bytes
 docs/compute/drivers/ovh.rst                    |  81 +++++++++++++++++++
 docs/compute/drivers/runabove.rst               |  77 ------------------
 docs/examples/compute/ovh/attach_volume.py      |  11 +++
 docs/examples/compute/ovh/create_node.py        |  12 +++
 docs/examples/compute/runabove/attach_volume.py |  11 ---
 docs/examples/compute/runabove/create_node.py   |  12 ---
 8 files changed, 104 insertions(+), 100 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/docs/_static/images/provider_logos/ovh.png
----------------------------------------------------------------------
diff --git a/docs/_static/images/provider_logos/ovh.png b/docs/_static/images/provider_logos/ovh.png
new file mode 100644
index 0000000..95a9c50
Binary files /dev/null and b/docs/_static/images/provider_logos/ovh.png differ

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/docs/_static/images/provider_logos/runabove.png
----------------------------------------------------------------------
diff --git a/docs/_static/images/provider_logos/runabove.png b/docs/_static/images/provider_logos/runabove.png
deleted file mode 100644
index c249888..0000000
Binary files a/docs/_static/images/provider_logos/runabove.png and /dev/null differ

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/docs/compute/drivers/ovh.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/ovh.rst b/docs/compute/drivers/ovh.rst
new file mode 100644
index 0000000..8633595
--- /dev/null
+++ b/docs/compute/drivers/ovh.rst
@@ -0,0 +1,81 @@
+OVH Compute Driver Documentation
+================================
+
+`OVH`_ is an Internet Service Provider providing dedicated servers, shared and
+cloud hosting, domain registration, and VOIP telephony services.
+
+.. figure:: /_static/images/provider_logos/ovh.png
+    :align: center
+    :width: 300
+    :target: https://www.ovh.com
+
+OVH driver uses a REST API, for more information about that, please refer to
+`API console`_.
+
+Instantiating a driver
+----------------------
+
+When you instantiate a driver you need to pass the following arguments to the
+driver constructor:
+
+* ``key`` - Application key
+* ``secret`` - Application secret
+* ``ex_project_id`` - Project ID
+* ``ex_consumer_key`` - Consumer key
+
+For get application key and secret, you must register an application
+at https://eu.api.ovh.com/createApp/. Next step, create a consumer key with
+following command: ::
+
+    curl -X POST \
+        -H 'X-Ra-Application: youApplicationKey' \
+        -H 'Content-Type: application/json' \
+        -d '{
+            "accessRules":
+                [
+                    {"method":"GET","path":"/*"},
+                    {"method":"POST","path":"/*"},
+                    {"method":"DELETE","path":"/*"},
+                    {"method":"PUT","path":"/*"},
+                ],
+                "redirection":"http://ovh.com"
+            }' \
+        https://eu.api.ovh.com/1.0/auth/credential
+
+This will answer a JSON like below with inside your Consumer Key and
+``validationUrl``. Follow this link for valid your key. ::
+
+    {
+      "validationUrl":"https://eu.api.ovh.com/auth/?credentialToken=fIDK6KCVHfEMuSTP3LV84D3CsHTq4T3BhOrmEEdd2hQ0CNcfVgGVWZRqIlolDJ3W",
+      "consumerKey":"y7epYeHCIqoO17BzBgxluvB4XLedpba9",
+      "state":"pendingValidation"
+    }
+
+
+Secondly, you must create a cloud project and retrieve its ID, from URL for
+example.
+
+Now you have and can use you credentials with Libcloud.
+
+Examples
+--------
+
+Create node
+~~~~~~~~~~~
+
+.. literalinclude:: /examples/compute/ovh/create_node.py
+
+Create and attach a volume to a node
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+.. literalinclude:: /examples/compute/ovh/attach_volume.py
+
+API Docs
+--------
+
+.. autoclass:: libcloud.compute.drivers.ovh.OvhNodeDriver
+    :members:
+    :inherited-members:
+
+.. _`OVH`: https://www.ovh.com
+.. _`API console`: https://api.ovh.com/console/#/

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/docs/compute/drivers/runabove.rst
----------------------------------------------------------------------
diff --git a/docs/compute/drivers/runabove.rst b/docs/compute/drivers/runabove.rst
deleted file mode 100644
index 2b3c476..0000000
--- a/docs/compute/drivers/runabove.rst
+++ /dev/null
@@ -1,77 +0,0 @@
-RunAbove Compute Driver Documentation
-=====================================
-
-`RunAbove`_ is a public cloud offer created by OVH Group with datacenters
-in North America and Europe.
-
-.. figure:: /_static/images/provider_logos/runabove.png
-    :align: center
-    :width: 300
-    :target: https://www.runabove.com/index.xml
-
-RunAbove driver uses the OVH/RunAbove API so for more information about
-that, please refer to `RunAbove knowledge base`_ page and `API console`_.
-
-Instantiating a driver
-----------------------
-
-When you instantiate a driver you need to pass the following arguments to the
-driver constructor:
-
-* ``key`` - Application key
-* ``secret`` - Application secret
-* ``ex_consumer_key`` - Consumer key
-
-For get application key and secret, you must first register an application
-at https://api.runabove.com/createApp/. Next step, create a consumer key with
-following command: ::
-
-    curl -X POST \
-        -H 'X-Ra-Application: youApplicationKey' \
-        -H 'Content-Type: application/json' \
-        -d '{
-            "accessRules":
-                [
-                    {"method":"GET","path":"/*"},
-                    {"method":"POST","path":"/*"},
-                    {"method":"DELETE","path":"/*"},
-                    {"method":"PUT","path":"/*"},
-                ],
-                "redirection":"http://runabove.com"
-            }' \
-        "https://api.runabove.com/1.0/auth/credential"
-
-This will answer a JSON like below with inside your Consumer Key and
-``validationUrl``. Follow this link for valid your key. ::
-
-    {
-      "validationUrl":"https://api.runabove.com/login/?credentialToken=fIDK6KCVHfEMuSTP3LV84D3CsHTq4T3BhOrmEEdd2hQ0CNcfVgGVWZRqIlolDJ3W",
-      "consumerKey":"y7epYeHCIqoO17BzBgxluvB4XLedpba9",
-      "state":"pendingValidation"
-    }
-
-Now you have and can use you credentials with Libcloud.
-
-Examples
---------
-
-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
---------
-
-.. autoclass:: libcloud.compute.drivers.runabove.RunAboveNodeDriver
-    :members:
-    :inherited-members:
-
-.. _`Runabove`: https://www.runabove.com/index.xml
-.. _`RunAbove knowledge base`: https://community.runabove.com/kb/
-.. _`API console`: https://api.runabove.com/console/#/

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/docs/examples/compute/ovh/attach_volume.py
----------------------------------------------------------------------
diff --git a/docs/examples/compute/ovh/attach_volume.py b/docs/examples/compute/ovh/attach_volume.py
new file mode 100644
index 0000000..a1cc02b
--- /dev/null
+++ b/docs/examples/compute/ovh/attach_volume.py
@@ -0,0 +1,11 @@
+from libcloud.compute.types import Provider
+from libcloud.compute.providers import get_driver
+
+Ovh = get_driver(Provider.OVH)
+driver = Ovh('yourAppKey', 'yourAppSecret', 'youProjectId', 'yourConsumerKey')
+
+location = [l for l in driver.list_locations() if l.id == 'SBG1'][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/a2cde6be/docs/examples/compute/ovh/create_node.py
----------------------------------------------------------------------
diff --git a/docs/examples/compute/ovh/create_node.py b/docs/examples/compute/ovh/create_node.py
new file mode 100644
index 0000000..80147b5
--- /dev/null
+++ b/docs/examples/compute/ovh/create_node.py
@@ -0,0 +1,12 @@
+from libcloud.compute.types import Provider
+from libcloud.compute.providers import get_driver
+
+Ovh = get_driver(Provider.OVH)
+driver = Ovh('yourAppKey', 'yourAppSecret', 'yourProjectId', 'yourConsumerKey')
+
+location = [l for l in driver.list_locations() if l.id == 'SBG1'][0]
+image = [i for i in driver.list_images() if 'Debian 8' == i.name][0]
+size = [s for s in driver.list_sizes() if s.name == 'vps-ssd-1'][0]
+
+node = driver.create_node(name='yournode', size=size, image=image,
+                          location=location)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/a2cde6be/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
deleted file mode 100644
index c4c0178..0000000
--- a/docs/examples/compute/runabove/attach_volume.py
+++ /dev/null
@@ -1,11 +0,0 @@
-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/a2cde6be/docs/examples/compute/runabove/create_node.py
----------------------------------------------------------------------
diff --git a/docs/examples/compute/runabove/create_node.py b/docs/examples/compute/runabove/create_node.py
deleted file mode 100644
index 7137ff4..0000000
--- a/docs/examples/compute/runabove/create_node.py
+++ /dev/null
@@ -1,12 +0,0 @@
-from libcloud.compute.types import Provider
-from libcloud.compute.providers import get_driver
-
-RunAbove = get_driver(Provider.RUNABOVE)
-driver = RunAbove('yourAppKey', 'yourAppSecret', 'YourConsumerKey')
-
-image = [i for i in driver.list_images() if 'Debian 8' == i.name][0]
-size = [s for s in driver.list_sizes() if s.name == 'ra.s'][0]
-location = [l for l in driver.list_locations() if l.id == 'SBG-1'][0]
-
-node = driver.create_node(name='yournode', size=size, image=image,
-                          location=location)


[2/4] libcloud git commit: Updated RunAbove to OVH

Posted by an...@apache.org.
Updated RunAbove to OVH


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/4f87f556
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/4f87f556
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/4f87f556

Branch: refs/heads/trunk
Commit: 4f87f556ef6f21976e25b77ca28beaea6f23a09f
Parents: d810096
Author: ZuluPro <mo...@hotmail.com>
Authored: Wed Oct 5 16:22:50 2016 -0400
Committer: Anthony Shaw <an...@apache.org>
Committed: Fri Oct 7 09:23:34 2016 +1100

----------------------------------------------------------------------
 libcloud/test/common/test_ovh.py                |   2 +-
 libcloud/test/common/test_runabove.py           |  29 ---
 .../compute/fixtures/ovh/auth_time_get.json     |   1 +
 .../test/compute/fixtures/ovh/flavor_get.json   |   1 +
 .../compute/fixtures/ovh/flavor_get_detail.json |   1 +
 .../test/compute/fixtures/ovh/image_get.json    |   1 +
 .../compute/fixtures/ovh/image_get_detail.json  |   1 +
 .../test/compute/fixtures/ovh/instance_get.json |   1 +
 .../fixtures/ovh/instance_get_detail.json       |   1 +
 .../compute/fixtures/ovh/instance_post.json     |   1 +
 .../test/compute/fixtures/ovh/region_get.json   |   1 +
 libcloud/test/compute/fixtures/ovh/ssh_get.json |   1 +
 .../compute/fixtures/ovh/ssh_get_detail.json    |   1 +
 .../test/compute/fixtures/ovh/volume_get.json   |   1 +
 .../compute/fixtures/ovh/volume_get_detail.json |   1 +
 .../fixtures/runabove/auth_time_get.json        |   1 -
 .../compute/fixtures/runabove/flavor_get.json   |   1 -
 .../fixtures/runabove/flavor_get_detail.json    |   1 -
 .../compute/fixtures/runabove/image_get.json    |   1 -
 .../fixtures/runabove/image_get_detail.json     |   1 -
 .../compute/fixtures/runabove/instance_get.json |   1 -
 .../fixtures/runabove/instance_get_detail.json  |   1 -
 .../fixtures/runabove/instance_post.json        |   1 -
 .../compute/fixtures/runabove/region_get.json   |   1 -
 .../test/compute/fixtures/runabove/ssh_get.json |   2 -
 .../fixtures/runabove/ssh_get_detail.json       |   1 -
 .../compute/fixtures/runabove/volume_get.json   |   1 -
 .../fixtures/runabove/volume_get_detail.json    |   1 -
 libcloud/test/compute/test_ovh.py               | 205 +++++++++++++++++++
 libcloud/test/compute/test_runabove.py          | 201 ------------------
 libcloud/test/secrets.py-dist                   |   2 +-
 31 files changed, 220 insertions(+), 246 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/common/test_ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/test/common/test_ovh.py b/libcloud/test/common/test_ovh.py
index 946f907..c1662d6 100644
--- a/libcloud/test/common/test_ovh.py
+++ b/libcloud/test/common/test_ovh.py
@@ -16,7 +16,7 @@
 import re
 from libcloud.test import MockHttp
 
-FORMAT_URL = re.compile(r'[./-]')
+FORMAT_URL = re.compile(r'[./?=-]')
 
 
 class BaseOvhMockHttp(MockHttp):

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/common/test_runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/test/common/test_runabove.py b/libcloud/test/common/test_runabove.py
deleted file mode 100644
index aead6c7..0000000
--- a/libcloud/test/common/test_runabove.py
+++ /dev/null
@@ -1,29 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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 re
-from libcloud.test import MockHttp
-
-FORMAT_URL = re.compile(r'[./-]')
-
-
-class BaseRunAboveMockHttp(MockHttp):
-
-    def _get_method_name(self, type, use_param, qs, path):
-        return "_json"
-
-    def _json(self, method, url, body, headers):
-        meth_name = '_json%s_%s' % (FORMAT_URL.sub('_', url), method.lower())
-        return getattr(self, meth_name)(method, url, body, headers)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/auth_time_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/auth_time_get.json b/libcloud/test/compute/fixtures/ovh/auth_time_get.json
new file mode 100644
index 0000000..f84eeec
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/auth_time_get.json
@@ -0,0 +1 @@
+1437075564

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/flavor_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/flavor_get.json b/libcloud/test/compute/fixtures/ovh/flavor_get.json
new file mode 100644
index 0000000..d81087d
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/flavor_get.json
@@ -0,0 +1 @@
+[{"id":"foo-id","disk":80,"name":"ovh.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG1","type":"ovh.ceph.eg","inboundBandwidth":511,"osType":"Linux"},{"id":"bar-id","disk":80,"name":"ovh.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG1","type":"ovh.ceph.eg", "outboundBandwidth": 511,"inboundBandwidth": 511,"osType":"Linux"}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/flavor_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/flavor_get_detail.json b/libcloud/test/compute/fixtures/ovh/flavor_get_detail.json
new file mode 100644
index 0000000..9695862
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/flavor_get_detail.json
@@ -0,0 +1 @@
+{"id":"foo-id","disk":80,"name":"ra.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG-1","type":"ra.s"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/image_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/image_get.json b/libcloud/test/compute/fixtures/ovh/image_get.json
new file mode 100644
index 0000000..aa552d9
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/image_get.json
@@ -0,0 +1 @@
+[{"id":"foo-id","name":"Debian 8","region":"SBG-1","visibility":"public","deprecated":false},{"id":"bar-id","name":"CentOs","region":"SBG-1","visibility":"public","deprecated":false}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/image_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/image_get_detail.json b/libcloud/test/compute/fixtures/ovh/image_get_detail.json
new file mode 100644
index 0000000..6048ed3
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/image_get_detail.json
@@ -0,0 +1 @@
+{"id":"foo-id","name":"Debian 8","region":"SBG-1","visibility":"public","deprecated":false}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/instance_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/instance_get.json b/libcloud/test/compute/fixtures/ovh/instance_get.json
new file mode 100644
index 0000000..61f28c8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/instance_get.json
@@ -0,0 +1 @@
+[{"id":"foo","name":"test_vm","ipAddresses":[{"networkId":"","version":4, "ip":"92.0.0.1","type":"public"}],"flavorId":"551dc104-4174-495a-af34-4aafe75f22ca","imageId":"1f3b49ad-3566-4838-93f8-b657a36b870f","sshKeyId":"6447567a6447746c655bad3d","status":"ACTIVE","created":"2015-05-29T11:20:48Z","region":"SBG1"}, {"id":"bar","name":"test_vm2","ipAddresses":[{"networkId":"","version":4, "ip":"92.0.0.2","type":"public"}],"flavorId":"551dc104-4174-495a-af34-4aafe75f22ca","imageId":"1f3b49ad-3566-4838-93f8-b657a36b870f","sshKeyId":"mykey","status":"ACTIVE","created":"2015-05-29T11:40:48Z","region":"SBG-1"}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/instance_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/instance_get_detail.json b/libcloud/test/compute/fixtures/ovh/instance_get_detail.json
new file mode 100644
index 0000000..b5c5df8
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/instance_get_detail.json
@@ -0,0 +1 @@
+{"status":"ACTIVE","region":"SBG1","name":"test_vm","image":{"visibility":"public","status":"active","region":"SBG1","name":"Ubuntu12.04","minDisk":0,"size":2.19921875,"creationDate":"2016-06-01T07:39:56Z","minRam":0,"user":"ubuntu","id":"3031ed24-8337-4b09-94b5-e51c54bec6c8","type":"linux"},"created":"2016-10-05T19:17:14Z","sshKey":{"fingerPrint":"7d:25:ec:f9:53:91:95:13:45:f9:73:a1:33:f0:00:00","regions":["SBG1"],"name":"testkey","id":"6447567a6447746c65513d3d","publicKey":"ssh-rsaAAAAB3NzaC1yc2EAAAADAQABAAACAQC80oMgRmfbUzNWk/H2vlUl2ISHjLwIUFz+b9jB2hCXoWLluyQN4dZqebusWwUUpAzzBpUcrPLYKyPcI0H9eAJaieK1QQX7gfaWLZnvHgm64i3lkDM5HbXqxd5ZL7FqEb8UbYqiR0AK05/JCvz0ux0Qb1oXMKbs+cwYuWhTgr10tumbkSyv8MZ2hamtJt3W176F3NJuNBXTKvzWmFF6AFpdUQWMgbo86SKRlnR8Q+3YiTaaPxEXSpD120QYaDFWpdvVL+IMS5ywRrFqrDoHFbsUEPEjsPHSd/B7hbjacE8dkz5BxHAGse2vNrPzcTnvw2ovj4+xQymqH7nCxv4pD++YYzeLyhyU93Zq5e9Wp72zSSfqE4X8qJsGw+zj7RZThN/imRPrg+z96ZCIpxcvOt1gsQNXsHzF3dfThssO9JR/SjyTaGcy0yn7oyhI324YtwsaNmBXo3FdZTTaJ8MRHkwQolBvhCY74
 uHX6cZW38UHh1+O92ExLq4JWu1dx4r9o9yrkA78kl8IoXfstxr4dpAZQIh27Z+wqbw8cpwjvkDvFr1kcrK7aifhezpihrboizhgoibnzbnefnoapfnzpiafzipanfipzanfpznaipfzapfneirnsboiseibreiosbrioebgipapigbezpiabgipezbgpiabeiprbgzbg6jfGQ=="},"monthlyBilling":null,"id":"cc350b4a-b04b-41d2-959d-1f8f388877a2","ipAddresses":[{"networkId":"","version":4,"ip":"92.0.0.1","type":"public"}],"flavor":{"outboundBandwidth":102,"disk":10,"region":"SBG1","name":"vps-ssd-1","inboundBandwidth":102,"id":"98c1e679-5f2c-4069-b4da-4a4f7179b758","vcpus":1,"type":"ovh.vps-ssd","osType":"linux","ram":2000}}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/instance_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/instance_post.json b/libcloud/test/compute/fixtures/ovh/instance_post.json
new file mode 100644
index 0000000..15a4075
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/instance_post.json
@@ -0,0 +1 @@
+{"instanceId":"df4e55cb-90e6-4f20-a604-7084f311f498","name":"testvm","ipv4":"","created":"2015-07-16T22:35:55Z","status":"BUILD","flavor":{"id":"551dc104-4174-495a-af34-4aafe75f22ca","disk":30,"name":"ra.intel.sb.l","ram":4096,"vcpus":1,"region":"SBG-1","type":"ra.sb"},"image":{"id":"1f3b49ad-3566-4838-93f8-b657a36b870f","name":"Debian 8","region":"SBG-1","visibility":"","deprecated":false},"sshKey":{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ydCgh5D0aWKSveYH9F/zuwyH+CGcu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTD9nAsx5UYSvdTo+axlbAAAAFQCFBEBKWuCabLfUQpAoiMQSNpCsFQAAAIAVHwW6+LyDPyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/Rn7ND1YrY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydW8jLiopI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42pej5ee5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQdX/Kqsc4n4twkb94jjoj7doiBjN7aRAr9f3JEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oA
 bc1eQg== amonthe@amonthe","name":"mykey","fingerPrint":"75:19:bf:48:dc:31:5b:91:b2:e5:ba:3b:fa:dc:ef:a7","region":"BHS-1"},"region":"SBG-1"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/region_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/region_get.json b/libcloud/test/compute/fixtures/ovh/region_get.json
new file mode 100644
index 0000000..7ed3f0b
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/region_get.json
@@ -0,0 +1 @@
+["SBG1","BHS1","GRA1"]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/ssh_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/ssh_get.json b/libcloud/test/compute/fixtures/ovh/ssh_get.json
new file mode 100644
index 0000000..3013560
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/ssh_get.json
@@ -0,0 +1 @@
+[{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ABCDE5D0aWKSveYH9F/zuwyABCDEu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTABCDEx5UYSvdTo+axlbAAAAFQCFBEABCDEabLfUQpAoiMQSNpCsFQAAAIAVHwW6ABCDEyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/RnABCDErY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydWABCDEpI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42ABCDEe5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQABCDEsc4n4twkb94jjoj7doiBjN7aRABCDEJEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oAbc1eQg== user@host","name":"mykey","id":"ham-id","regions":["SGB1"]}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/ssh_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/ssh_get_detail.json b/libcloud/test/compute/fixtures/ovh/ssh_get_detail.json
new file mode 100644
index 0000000..b7fa97e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/ssh_get_detail.json
@@ -0,0 +1 @@
+{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ABCDE5D0aWKSveYH9F/zuwyABCDEu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTABCDEx5UYSvdTo+axlbAAAAFQCFBEABCDEabLfUQpAoiMQSNpCsFQAAAIAVHwW6ABCDEyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/RnABCDErY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydWABCDEpI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42ABCDEe5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQABCDEsc4n4twkb94jjoj7doiBjN7aRABCDEJEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oAbc1eQg== user@host","name":"mykey","id":"ham-id","regions":["SGB1"]}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/ovh/volume_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/volume_get.json b/libcloud/test/compute/fixtures/ovh/volume_get.json
new file mode 100644
index 0000000..aa0b360
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/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/4f87f556/libcloud/test/compute/fixtures/ovh/volume_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/ovh/volume_get_detail.json b/libcloud/test/compute/fixtures/ovh/volume_get_detail.json
new file mode 100644
index 0000000..861cb4e
--- /dev/null
+++ b/libcloud/test/compute/fixtures/ovh/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/4f87f556/libcloud/test/compute/fixtures/runabove/auth_time_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/auth_time_get.json b/libcloud/test/compute/fixtures/runabove/auth_time_get.json
deleted file mode 100644
index f84eeec..0000000
--- a/libcloud/test/compute/fixtures/runabove/auth_time_get.json
+++ /dev/null
@@ -1 +0,0 @@
-1437075564

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/flavor_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/flavor_get.json b/libcloud/test/compute/fixtures/runabove/flavor_get.json
deleted file mode 100644
index 19ed785..0000000
--- a/libcloud/test/compute/fixtures/runabove/flavor_get.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"id":"foo-id","disk":80,"name":"ra.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG-1","type":"ra.s"},{"id":"bar-id","disk":80,"name":"ra.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG-1","type":"ra.s"}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/flavor_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/flavor_get_detail.json b/libcloud/test/compute/fixtures/runabove/flavor_get_detail.json
deleted file mode 100644
index 9695862..0000000
--- a/libcloud/test/compute/fixtures/runabove/flavor_get_detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{"id":"foo-id","disk":80,"name":"ra.intel.ha.l","ram":8192,"vcpus":2,"region":"SBG-1","type":"ra.s"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/image_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/image_get.json b/libcloud/test/compute/fixtures/runabove/image_get.json
deleted file mode 100644
index aa552d9..0000000
--- a/libcloud/test/compute/fixtures/runabove/image_get.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"id":"foo-id","name":"Debian 8","region":"SBG-1","visibility":"public","deprecated":false},{"id":"bar-id","name":"CentOs","region":"SBG-1","visibility":"public","deprecated":false}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/image_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/image_get_detail.json b/libcloud/test/compute/fixtures/runabove/image_get_detail.json
deleted file mode 100644
index 6048ed3..0000000
--- a/libcloud/test/compute/fixtures/runabove/image_get_detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{"id":"foo-id","name":"Debian 8","region":"SBG-1","visibility":"public","deprecated":false}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/instance_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/instance_get.json b/libcloud/test/compute/fixtures/runabove/instance_get.json
deleted file mode 100644
index 16f609e..0000000
--- a/libcloud/test/compute/fixtures/runabove/instance_get.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"instanceId":"foo","name":"test_vm","ip":"92.0.0.1","flavorId":"551dc104-4174-495a-af34-4aafe75f22ca","imageId":"1f3b49ad-3566-4838-93f8-b657a36b870f","keyName":"mykey","status":"ACTIVE","created":"2015-05-29T11:20:48Z","region":"SBG-1"}, {"instanceId":"bar","name":"test_vm2","ip":"92.0.0.2","flavorId":"551dc104-4174-495a-af34-4aafe75f22ca","imageId":"1f3b49ad-3566-4838-93f8-b657a36b870f","keyName":"mykey","status":"ACTIVE","created":"2015-05-29T11:40:48Z","region":"SBG-1"}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/instance_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/instance_get_detail.json b/libcloud/test/compute/fixtures/runabove/instance_get_detail.json
deleted file mode 100644
index 9f8ab5b..0000000
--- a/libcloud/test/compute/fixtures/runabove/instance_get_detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{"instanceId":"c8de03b5-16cb-4eed-aa57-cfb1800faf00","name":"testvm","ipv4":"92.0.0.1","created":"2015-05-29T11:20:48Z","status":"ACTIVE","flavor":{"id":"551dc104-4174-495a-af34-4aafe75f22ca","disk":30,"name":"ra.intel.sb.l","ram":4096,"vcpus":1,"region":"SBG-1","type":"ra.sb"},"image":{"id":"1f3b49ad-3566-4838-93f8-b657a36b870f","name":"Debian 8","region":"SBG-1","visibility":"","deprecated":false},"sshKey":{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoABCDEF8k1+qdqfq6RB/wYjXAqtoVABCDEFgh5D0aWKSveYH9F/zuwyH+CGcu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTD9nAsx5UYSvdTo+axlbAAAAFQCFBEBKWuCabLfUQpAoiMQSNpCsFQAAAIAVHwW6+LyDPyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/ABCDEFYrY63WC2vUAbmR0hrmoa3dS1Fw6ABCDEFs41z9xzLydW8jLiopI655LrDtnABCDEFYtr8idKq3j3IvmBfynvzhmb0r101agiMABCDEFjGQbRAbL42pej5ee5gAAAIAbdNorJ2iFrczABCDEF5Vjap+gqFGUYMwaHlta26WWf+ZHQdX/Kqsc4nABCDEF4jjoj7doiBjN7aRAr9f3JEDUboTatpdOzanU9gSrryCBykz5RK016ABCDEFxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5
 ovXlI25oAbc1eQg== user@host","name":"mykey","fingerPrint":"75:19:bf:48:dc:31:5b:91:b2:e5:ba:3b:f0:00:ba:ar","region":"SBG-1"},"region":"SBG-1"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/instance_post.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/instance_post.json b/libcloud/test/compute/fixtures/runabove/instance_post.json
deleted file mode 100644
index 15a4075..0000000
--- a/libcloud/test/compute/fixtures/runabove/instance_post.json
+++ /dev/null
@@ -1 +0,0 @@
-{"instanceId":"df4e55cb-90e6-4f20-a604-7084f311f498","name":"testvm","ipv4":"","created":"2015-07-16T22:35:55Z","status":"BUILD","flavor":{"id":"551dc104-4174-495a-af34-4aafe75f22ca","disk":30,"name":"ra.intel.sb.l","ram":4096,"vcpus":1,"region":"SBG-1","type":"ra.sb"},"image":{"id":"1f3b49ad-3566-4838-93f8-b657a36b870f","name":"Debian 8","region":"SBG-1","visibility":"","deprecated":false},"sshKey":{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ydCgh5D0aWKSveYH9F/zuwyH+CGcu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTD9nAsx5UYSvdTo+axlbAAAAFQCFBEBKWuCabLfUQpAoiMQSNpCsFQAAAIAVHwW6+LyDPyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/Rn7ND1YrY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydW8jLiopI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42pej5ee5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQdX/Kqsc4n4twkb94jjoj7doiBjN7aRAr9f3JEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oA
 bc1eQg== amonthe@amonthe","name":"mykey","fingerPrint":"75:19:bf:48:dc:31:5b:91:b2:e5:ba:3b:fa:dc:ef:a7","region":"BHS-1"},"region":"SBG-1"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/region_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/region_get.json b/libcloud/test/compute/fixtures/runabove/region_get.json
deleted file mode 100644
index 9ceda7f..0000000
--- a/libcloud/test/compute/fixtures/runabove/region_get.json
+++ /dev/null
@@ -1 +0,0 @@
-["SBG-1","BHS-1"]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/ssh_get.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/ssh_get.json b/libcloud/test/compute/fixtures/runabove/ssh_get.json
deleted file mode 100644
index 47dccfc..0000000
--- a/libcloud/test/compute/fixtures/runabove/ssh_get.json
+++ /dev/null
@@ -1,2 +0,0 @@
-
-[{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ABCDE5D0aWKSveYH9F/zuwyABCDEu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTABCDEx5UYSvdTo+axlbAAAAFQCFBEABCDEabLfUQpAoiMQSNpCsFQAAAIAVHwW6ABCDEyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/RnABCDErY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydWABCDEpI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42ABCDEe5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQABCDEsc4n4twkb94jjoj7doiBjN7aRABCDEJEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oAbc1eQg== user@host","name":"mykey","fingerPrint":"75:19:bf:48:dc:31:5b:91:b2:e5:ba:3b:f0:00:ba:ar","region":"SGB-1"}]

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/fixtures/runabove/ssh_get_detail.json
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/fixtures/runabove/ssh_get_detail.json b/libcloud/test/compute/fixtures/runabove/ssh_get_detail.json
deleted file mode 100644
index 7373992..0000000
--- a/libcloud/test/compute/fixtures/runabove/ssh_get_detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{"publicKey":"ssh-dss AAAAB3NzaC1kc3MAAACBALDA8LBVhtEDgns2AFtF4hO7BckyFWUyuwCUy1Frh8UZSkrhRtfNcrbir/qcoql5psB8k1+qdqfq6RB/wYjXAqtoVNF+ABCDE5D0aWKSveYH9F/zuwyABCDEu+fBLo/q6JAqnXziFluK9jcnu1vam3FvTABCDEx5UYSvdTo+axlbAAAAFQCFBEABCDEabLfUQpAoiMQSNpCsFQAAAIAVHwW6ABCDEyS/o6aV6icX0Gw8K9AG4zjj+OZWd0HxJP/RnABCDErY63WC2vUAbmR0hrmoa3dS1Fw6553n+Ks41z9xzLydWABCDEpI655LrDtnMEzDpIYtr8idKq3j3IvmBfynvzhmb0r101agiMdEKsUJjGQbRAbL42ABCDEe5gAAAIAbdNorJ2iFrcz8OyYi25Vjap+gqFGUYMwaHlta26WWf+ZHQABCDEsc4n4twkb94jjoj7doiBjN7aRABCDEJEDUboTatpdOzanU9gSrryCBykz5RK016wwOU8fxdmaar/U47qEKa4TYwmYyA5UH+KmbYmJExC5ovXlI25oAbc1eQg== user@host","name":"mykey","fingerPrint":"75:19:bf:48:dc:31:5b:91:b2:e5:ba:3b:f0:00:ba:ar","region":"SGB-1"}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/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
deleted file mode 100644
index aa0b360..0000000
--- a/libcloud/test/compute/fixtures/runabove/volume_get.json
+++ /dev/null
@@ -1 +0,0 @@
-[{"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/4f87f556/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
deleted file mode 100644
index 861cb4e..0000000
--- a/libcloud/test/compute/fixtures/runabove/volume_get_detail.json
+++ /dev/null
@@ -1 +0,0 @@
-{"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/4f87f556/libcloud/test/compute/test_ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_ovh.py b/libcloud/test/compute/test_ovh.py
new file mode 100644
index 0000000..8f30687
--- /dev/null
+++ b/libcloud/test/compute/test_ovh.py
@@ -0,0 +1,205 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 sys
+import unittest
+from mock import patch
+
+from libcloud.utils.py3 import httplib
+
+from libcloud.compute.drivers.ovh import OvhNodeDriver
+
+from libcloud.test.common.test_ovh import BaseOvhMockHttp
+from libcloud.test.secrets import OVH_PARAMS
+from libcloud.test.file_fixtures import ComputeFileFixtures
+
+
+class OvhMockHttp(BaseOvhMockHttp):
+    """Fixtures needed for tests related to rating model"""
+    fixtures = ComputeFileFixtures('ovh')
+
+    def _json_1_0_auth_time_get(self, method, url, body, headers):
+        body = self.fixtures.load('auth_time_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_region_get(self, method, url, body, headers):
+        body = self.fixtures.load('region_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_flavor_get(self, method, url, body, headers):
+        body = self.fixtures.load('flavor_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_flavor_region_SBG1_get(self, method, url, body, headers):
+        body = self.fixtures.load('flavor_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_flavor_foo_id_get(self, method, url, body, headers):
+        body = self.fixtures.load('flavor_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_image_get(self, method, url, body, headers):
+        body = self.fixtures.load('image_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_image_foo_id_get(self, method, url, body, headers):
+        body = self.fixtures.load('image_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_sshkey_region_SBG1_get(self, method, url, body, headers):
+        body = self.fixtures.load('ssh_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_sshkey_post(self, method, url, body, headers):
+        body = self.fixtures.load('ssh_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_ssh_mykey_get(self, method, url, body, headers):
+        body = self.fixtures.load('ssh_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_instance_get(self, method, url, body, headers):
+        body = self.fixtures.load('instance_get.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_instance_foo_get(self, method, url, body, headers):
+        body = self.fixtures.load('instance_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_instance_foo_delete(self, method, url, body, headers):
+        return (httplib.OK, '', {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_instance_post(self, method, url, body, headers):
+        body = self.fixtures.load('instance_get_detail.json')
+        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_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_cloud_project_project_id_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_cloud_project_project_id_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_cloud_project_project_id_volume_foo_delete(self, method, url, body, headers):
+        return (httplib.OK, '', {}, httplib.responses[httplib.OK])
+
+    def _json_1_0_cloud_project_project_id_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_cloud_project_project_id_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.ovh.OvhConnection._timedelta', 42)
+class OvhTests(unittest.TestCase):
+    def setUp(self):
+        OvhNodeDriver.connectionCls.conn_classes = (
+            OvhMockHttp, OvhMockHttp)
+        OvhMockHttp.type = None
+        self.driver = OvhNodeDriver(*OVH_PARAMS)
+
+    def test_list_locations(self):
+        images = self.driver.list_locations()
+        self.assertTrue(len(images) > 0)
+
+    def test_list_images(self):
+        images = self.driver.list_images()
+        self.assertTrue(len(images) > 0)
+
+    def test_get_image(self):
+        image = self.driver.get_image('foo-id')
+        self.assertEqual(image.id, 'foo-id')
+
+    def test_list_sizes(self):
+        sizes = self.driver.list_sizes()
+        self.assertTrue(len(sizes) > 0)
+
+    def test_get_size(self):
+        size = self.driver.ex_get_size('foo-id')
+        self.assertEqual(size.id, 'foo-id')
+
+    def test_list_key_pairs(self):
+        keys = self.driver.list_sizes()
+        self.assertTrue(len(keys) > 0)
+
+    def test_get_key_pair(self):
+        location = self.driver.list_locations()[0]
+        key = self.driver.get_key_pair('mykey', location)
+        self.assertEqual(key.name, 'mykey')
+
+    def test_import_key_pair_from_string(self):
+        location = self.driver.list_locations()[0]
+        key = self.driver.import_key_pair_from_string('mykey', 'material',
+                                                      location)
+        self.assertEqual(key.name, 'mykey')
+
+    def test_list_nodes(self):
+        nodes = self.driver.list_nodes()
+        self.assertTrue(len(nodes) > 0)
+
+    def test_get_node(self):
+        node = self.driver.ex_get_node('foo')
+        self.assertEqual(node.name, 'test_vm')
+
+    def test_create_node(self):
+        location = self.driver.list_locations()[0]
+        image = self.driver.list_sizes(location)[0]
+        size = self.driver.list_sizes(location)[0]
+        node = self.driver.create_node(name='test_vm', image=image, size=size,
+                                       location=location)
+        self.assertEqual(node.name, 'test_vm')
+
+    def test_destroy_node(self):
+        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())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/compute/test_runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/test/compute/test_runabove.py b/libcloud/test/compute/test_runabove.py
deleted file mode 100644
index a1538e0..0000000
--- a/libcloud/test/compute/test_runabove.py
+++ /dev/null
@@ -1,201 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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 sys
-import unittest
-from mock import patch
-
-from libcloud.utils.py3 import httplib
-
-from libcloud.compute.drivers.runabove import RunAboveNodeDriver
-
-from libcloud.test.common.test_runabove import BaseRunAboveMockHttp
-from libcloud.test.secrets import RUNABOVE_PARAMS
-from libcloud.test.file_fixtures import ComputeFileFixtures
-
-
-class RunAboveMockHttp(BaseRunAboveMockHttp):
-    """Fixtures needed for tests related to rating model"""
-    fixtures = ComputeFileFixtures('runabove')
-
-    def _json_1_0_auth_time_get(self, method, url, body, headers):
-        body = self.fixtures.load('auth_time_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_region_get(self, method, url, body, headers):
-        body = self.fixtures.load('region_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_flavor_foo_id_get(self, method, url, body, headers):
-        body = self.fixtures.load('flavor_get_detail.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_flavor_get(self, method, url, body, headers):
-        body = self.fixtures.load('flavor_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_image_get(self, method, url, body, headers):
-        body = self.fixtures.load('image_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_image_foo_id_get(self, method, url, body, headers):
-        body = self.fixtures.load('image_get_detail.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_ssh_get(self, method, url, body, headers):
-        body = self.fixtures.load('ssh_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_ssh_post(self, method, url, body, headers):
-        body = self.fixtures.load('ssh_get_detail.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_ssh_mykey_get(self, method, url, body, headers):
-        body = self.fixtures.load('ssh_get_detail.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_instance_get(self, method, url, body, headers):
-        body = self.fixtures.load('instance_get.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_instance_foo_get(self, method, url, body, headers):
-        body = self.fixtures.load('instance_get_detail.json')
-        return (httplib.OK, body, {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_instance_foo_delete(self, method, url, body, headers):
-        return (httplib.OK, '', {}, httplib.responses[httplib.OK])
-
-    def _json_1_0_instance_post(self, method, url, body, headers):
-        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):
-    def setUp(self):
-        RunAboveNodeDriver.connectionCls.conn_classes = (
-            RunAboveMockHttp, RunAboveMockHttp)
-        RunAboveMockHttp.type = None
-        self.driver = RunAboveNodeDriver(*RUNABOVE_PARAMS)
-
-    def test_list_locations(self):
-        images = self.driver.list_locations()
-        self.assertTrue(len(images) > 0)
-
-    def test_list_images(self):
-        images = self.driver.list_images()
-        self.assertTrue(len(images) > 0)
-
-    def test_get_image(self):
-        image = self.driver.get_image('foo-id')
-        self.assertEqual(image.id, 'foo-id')
-
-    def test_list_sizes(self):
-        sizes = self.driver.list_sizes()
-        self.assertTrue(len(sizes) > 0)
-
-    def test_get_size(self):
-        size = self.driver.ex_get_size('foo-id')
-        self.assertEqual(size.id, 'foo-id')
-
-    def test_list_key_pairs(self):
-        keys = self.driver.list_sizes()
-        self.assertTrue(len(keys) > 0)
-
-    def test_get_key_pair(self):
-        location = self.driver.list_locations()[0]
-        key = self.driver.get_key_pair('mykey', location)
-        self.assertEqual(key.name, 'mykey')
-
-    def test_import_key_pair_from_string(self):
-        location = self.driver.list_locations()[0]
-        key = self.driver.import_key_pair_from_string('mykey', 'material',
-                                                      location)
-        self.assertEqual(key.name, 'mykey')
-
-    def test_list_nodes(self):
-        nodes = self.driver.list_nodes()
-        self.assertTrue(len(nodes) > 0)
-
-    def test_get_node(self):
-        node = self.driver.ex_get_node('foo')
-        self.assertEqual(node.name, 'testvm')
-
-    def test_create_node(self):
-        location = self.driver.list_locations()[0]
-        image = self.driver.list_sizes(location)[0]
-        size = self.driver.list_sizes(location)[0]
-        node = self.driver.create_node(name='testvm', image=image, size=size,
-                                       location=location)
-        self.assertEqual(node.name, 'testvm')
-
-    def test_destroy_node(self):
-        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())

http://git-wip-us.apache.org/repos/asf/libcloud/blob/4f87f556/libcloud/test/secrets.py-dist
----------------------------------------------------------------------
diff --git a/libcloud/test/secrets.py-dist b/libcloud/test/secrets.py-dist
index f887947..5b17a86 100644
--- a/libcloud/test/secrets.py-dist
+++ b/libcloud/test/secrets.py-dist
@@ -32,7 +32,7 @@ OPENSTACK_PARAMS = ('user_name', 'api_key', False, 'host', 8774)
 OPENNEBULA_PARAMS = ('user', 'key')
 DIMENSIONDATA_PARAMS = ('user', 'password')
 OPSOURCE_PARAMS = ('user', 'password')
-RUNABOVE_PARAMS = ('application_key', 'application_secret', 'consumer_key')
+OVH_PARAMS = ('application_key', 'application_secret', 'project_id', 'consumer_key')
 RACKSPACE_PARAMS = ('user', 'key')
 RACKSPACE_NOVA_PARAMS = ('user_name', 'api_key', False, 'host', 8774)
 SLICEHOST_PARAMS = ('key',)


[3/4] libcloud git commit: Replace RunAbove code by OVH

Posted by an...@apache.org.
Replace RunAbove code by OVH


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/d8100961
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/d8100961
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/d8100961

Branch: refs/heads/trunk
Commit: d8100961f0ac5ff2051dcfe8e56ad5fce81a257d
Parents: 25c5ba9
Author: ZuluPro <mo...@hotmail.com>
Authored: Wed Oct 5 14:44:38 2016 -0400
Committer: Anthony Shaw <an...@apache.org>
Committed: Fri Oct 7 09:23:34 2016 +1100

----------------------------------------------------------------------
 libcloud/common/ovh.py               | 173 ++++++++++++
 libcloud/common/runabove.py          | 165 -----------
 libcloud/compute/drivers/ovh.py      | 455 ++++++++++++++++++++++++++++++
 libcloud/compute/drivers/runabove.py | 453 -----------------------------
 libcloud/compute/providers.py        |   4 +-
 libcloud/compute/types.py            |   2 +-
 libcloud/test/common/test_ovh.py     |  29 ++
 7 files changed, 660 insertions(+), 621 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/common/ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/common/ovh.py b/libcloud/common/ovh.py
new file mode 100644
index 0000000..3854e0b
--- /dev/null
+++ b/libcloud/common/ovh.py
@@ -0,0 +1,173 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 hashlib
+import time
+
+try:
+    import simplejson as json
+except ImportError:
+    import json
+
+from libcloud.utils.py3 import httplib
+from libcloud.utils.connection import get_response_object
+from libcloud.common.types import InvalidCredsError
+from libcloud.common.base import ConnectionUserAndKey, JsonResponse
+from libcloud.httplib_ssl import LibcloudHTTPSConnection
+
+__all__ = [
+    'OvhResponse',
+    'OvhConnection'
+]
+
+API_HOST = 'api.ovh.com'
+API_ROOT = '/1.0'
+LOCATIONS = {
+    'SBG1': {'id': 'SBG1', 'name': 'Strasbourg 1', 'country': 'FR'},
+    'BHS1': {'id': 'BHS1', 'name': 'Montreal 1', 'country': 'CA'},
+    'GRA1': {'id': 'GRA1', 'name': 'Gravelines 1', 'country': 'FR'}
+}
+DEFAULT_ACCESS_RULES = [
+    {'method': 'GET', 'path': '/*'},
+    {'method': 'POST', 'path': '/*'},
+    {'method': 'PUT', 'path': '/*'},
+    {'method': 'DELETE', 'path': '/*'},
+]
+
+
+class OvhException(Exception):
+    pass
+
+
+class OvhResponse(JsonResponse):
+    def parse_error(self):
+        response = super(OvhResponse, self).parse_body()
+        response = response or {}
+
+        if response.get('errorCode', None) == 'INVALID_SIGNATURE':
+            raise InvalidCredsError('Signature validation failed, probably '
+                                    'using invalid credentials')
+
+        return self.body
+
+
+class OvhConnection(ConnectionUserAndKey):
+    """
+    A connection to the Ovh API
+
+    Wraps SSL connections to the Ovh API, automagically injecting the
+    parameters that the API needs for each request.
+    """
+    host = API_HOST
+    request_path = API_ROOT
+    responseCls = OvhResponse
+    timestamp = None
+    ua = []
+    LOCATIONS = LOCATIONS
+    _timedelta = None
+
+    allow_insecure = True
+
+    def __init__(self, user_id, *args, **kwargs):
+        self.consumer_key = kwargs.pop('ex_consumer_key', None)
+        if self.consumer_key is None:
+            consumer_key_json = self.request_consumer_key(user_id)
+            msg = ("Your consumer key isn't validated, "
+                   "go to '%(validationUrl)s' for valid it. After instantiate "
+                   "your driver with \"ex_consumer_key='%(consumerKey)s'\"." %
+                   consumer_key_json)
+            raise OvhException(msg)
+        super(OvhConnection, self).__init__(user_id, *args, **kwargs)
+
+    def request_consumer_key(self, user_id):
+        action = self.request_path + '/auth/credential'
+        data = json.dumps({
+            'accessRules': DEFAULT_ACCESS_RULES,
+            'redirection': 'http://ovh.com',
+        })
+        headers = {
+            'Content-Type': 'application/json',
+            'X-Ovh-Application': user_id,
+        }
+        httpcon = LibcloudHTTPSConnection(self.host)
+        httpcon.request(method='POST', url=action, body=data, headers=headers)
+        response = httpcon.getresponse()
+
+        if response.status == httplib.UNAUTHORIZED:
+            raise InvalidCredsError()
+
+        body = response.read()
+        json_response = json.loads(body)
+        httpcon.close()
+        return json_response
+
+    def get_timestamp(self):
+        if not self._timedelta:
+            url = 'https://%s%s/auth/time' % (API_HOST, API_ROOT)
+            response = get_response_object(url=url, method='GET', headers={})
+            if not response or not response.body:
+                raise Exception('Failed to get current time from Ovh API')
+
+            timestamp = int(response.body)
+            self._timedelta = timestamp - int(time.time())
+        return int(time.time()) + self._timedelta
+
+    def make_signature(self, method, action, params, data, timestamp):
+        full_url = 'https://%s%s' % (API_HOST, action)
+        if params:
+            full_url += '?'
+            for key, value in params.items():
+                full_url += '%s=%s&' % (key, value)
+            else:
+                full_url = full_url[:-1]
+        sha1 = hashlib.sha1()
+        base_signature = "+".join([
+            self.key,
+            self.consumer_key,
+            method.upper(),
+            full_url,
+            data if data else '',
+            str(timestamp),
+        ])
+        sha1.update(base_signature.encode())
+        signature = '$1$' + sha1.hexdigest()
+        return signature
+
+    def add_default_params(self, params):
+        return params
+
+    def add_default_headers(self, headers):
+        headers.update({
+            'X-Ovh-Application': self.user_id,
+            'X-Ovh-Consumer': self.consumer_key,
+            'Content-type': 'application/json',
+        })
+        return headers
+
+    def request(self, action, params=None, data=None, headers=None,
+                method='GET', raw=False):
+        data = json.dumps(data) if data else None
+        timestamp = self.get_timestamp()
+        signature = self.make_signature(method, action, params, data,
+                                        timestamp)
+        headers = headers or {}
+        headers.update({
+            'X-Ovh-Timestamp': timestamp,
+            'X-Ovh-Signature': signature
+        })
+        return super(OvhConnection, self)\
+            .request(action, params=params, data=data, headers=headers,
+                     method=method, raw=raw)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/common/runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/common/runabove.py b/libcloud/common/runabove.py
deleted file mode 100644
index 0f08b59..0000000
--- a/libcloud/common/runabove.py
+++ /dev/null
@@ -1,165 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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 hashlib
-import time
-
-try:
-    import simplejson as json
-except ImportError:
-    import json
-
-from libcloud.utils.py3 import httplib
-from libcloud.utils.connection import get_response_object
-from libcloud.common.types import InvalidCredsError
-from libcloud.common.base import ConnectionUserAndKey, JsonResponse
-from libcloud.httplib_ssl import LibcloudHTTPSConnection
-
-__all__ = [
-    'RunAboveResponse',
-    'RunAboveConnection'
-]
-
-API_HOST = 'api.runabove.com'
-API_ROOT = '/1.0'
-LOCATIONS = {
-    'SBG-1': {'id': 'SBG-1', 'name': 'Strasbourg 1', 'country': 'FR'},
-    'BHS-1': {'id': 'BHS-1', 'name': 'Montreal 1', 'country': 'CA'}
-}
-DEFAULT_ACCESS_RULES = [
-    {'method': 'GET', 'path': '/*'},
-    {'method': 'POST', 'path': '/*'},
-    {'method': 'PUT', 'path': '/*'},
-    {'method': 'DELETE', 'path': '/*'},
-]
-
-
-class RunAboveException(Exception):
-    pass
-
-
-class RunAboveResponse(JsonResponse):
-    def parse_error(self):
-        response = super(RunAboveResponse, self).parse_body()
-        response = response or {}
-
-        if response.get('errorCode', None) == 'INVALID_SIGNATURE':
-            raise InvalidCredsError('Signature validation failed, probably '
-                                    'using invalid credentials')
-
-        return self.body
-
-
-class RunAboveConnection(ConnectionUserAndKey):
-    """
-    A connection to the RunAbove API
-
-    Wraps SSL connections to the RunAbove API, automagically injecting the
-    parameters that the API needs for each request.
-    """
-    host = API_HOST
-    request_path = API_ROOT
-    responseCls = RunAboveResponse
-    timestamp = None
-    ua = []
-    LOCATIONS = LOCATIONS
-    _timedelta = None
-
-    allow_insecure = True
-
-    def __init__(self, user_id, *args, **kwargs):
-        self.consumer_key = kwargs.pop('ex_consumer_key', None)
-        if self.consumer_key is None:
-            consumer_key_json = self.request_consumer_key(user_id)
-            msg = ("Your consumer key isn't validated, "
-                   "go to '%(validationUrl)s' for valid it. After instantiate "
-                   "your driver with \"ex_consumer_key='%(consumerKey)s'\"." %
-                   consumer_key_json)
-            raise RunAboveException(msg)
-        super(RunAboveConnection, self).__init__(user_id, *args, **kwargs)
-
-    def request_consumer_key(self, user_id):
-        action = self.request_path + '/auth/credential'
-        data = json.dumps({
-            'accessRules': DEFAULT_ACCESS_RULES,
-            'redirection': 'http://runabove.com',
-        })
-        headers = {
-            'Content-Type': 'application/json',
-            'X-Ra-Application': user_id,
-        }
-        httpcon = LibcloudHTTPSConnection(self.host)
-        httpcon.request(method='POST', url=action, body=data, headers=headers)
-        response = httpcon.getresponse()
-
-        if response.status == httplib.UNAUTHORIZED:
-            raise InvalidCredsError()
-
-        body = response.read()
-        json_response = json.loads(body)
-        httpcon.close()
-        return json_response
-
-    def get_timestamp(self):
-        if not self._timedelta:
-            url = 'https://%s/%s/auth/time' % (API_HOST, API_ROOT)
-            response = get_response_object(url=url, method='GET', headers={})
-            if not response or not response.body:
-                raise Exception('Failed to get current time from RunAbove API')
-
-            timestamp = int(response.body)
-            self._timedelta = timestamp - int(time.time())
-        return int(time.time()) + self._timedelta
-
-    def make_signature(self, method, action, data, timestamp):
-        full_url = 'https://%s%s' % (API_HOST, action)
-        sha1 = hashlib.sha1()
-        base_signature = "+".join([
-            self.key,
-            self.consumer_key,
-            method.upper(),
-            full_url,
-            data if data else '',
-            str(timestamp),
-        ])
-        sha1.update(base_signature.encode())
-        signature = '$1$' + sha1.hexdigest()
-        return signature
-
-    def add_default_params(self, params):
-        return params
-
-    def add_default_headers(self, headers):
-        headers.update({
-            'X-Ra-Application': self.user_id,
-            'X-Ra-Consumer': self.consumer_key,
-            'Content-type': 'application/json',
-        })
-        return headers
-
-    def request(self, action, params=None, data=None, headers=None,
-                method='GET', raw=False):
-        data = json.dumps(data) if data else None
-        timestamp = self.get_timestamp()
-        signature = self.make_signature(method, action, data, timestamp)
-        headers = headers or {}
-        headers.update({
-            'X-Ra-Timestamp': timestamp,
-            'X-Ra-Signature': signature
-        })
-        return super(RunAboveConnection, self)\
-            .request(action, params=params, data=data, headers=headers,
-                     method=method, raw=raw)

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/compute/drivers/ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ovh.py b/libcloud/compute/drivers/ovh.py
new file mode 100644
index 0000000..a103650
--- /dev/null
+++ b/libcloud/compute/drivers/ovh.py
@@ -0,0 +1,455 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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.
+"""
+Ovh driver
+"""
+from libcloud.common.ovh import API_ROOT, OvhConnection
+from libcloud.compute.base import NodeDriver, NodeSize, Node, NodeLocation
+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
+
+
+class OvhNodeDriver(NodeDriver):
+    """
+    Libcloud driver for the Ovh API
+
+    For more information on the Ovh API, read the official reference:
+
+        https://api.ovh.com/console/
+    """
+    type = Provider.OVH
+    name = "Ovh"
+    website = 'https://www.ovh.com/'
+    connectionCls = OvhConnection
+    features = {'create_node': ['ssh_key']}
+    api_name = 'ovh'
+
+    NODE_STATE_MAP = OpenStackNodeDriver.NODE_STATE_MAP
+    VOLUME_STATE_MAP = OpenStackNodeDriver.VOLUME_STATE_MAP
+
+    def __init__(self, key, secret, ex_project_id, ex_consumer_key=None):
+        """
+        Instantiate the driver with the given API credentials.
+
+        :param key: Your application key (required)
+        :type key: ``str``
+
+        :param secret: Your application secret (required)
+        :type secret: ``str``
+
+        :param ex_project_id: Your project ID
+        :type ex_project_id: ``str``
+
+        :param ex_consumer_key: Your consumer key (required)
+        :type ex_consumer_key: ``str``
+
+        :rtype: ``None``
+        """
+        self.datacenter = None
+        self.project_id = ex_project_id
+        self.consumer_key = ex_consumer_key
+        NodeDriver.__init__(self, key, secret, ex_consumer_key=ex_consumer_key)
+
+    def list_nodes(self, location=None):
+        """
+        List all nodes.
+
+        :keyword location: Location (region) used as filter
+        :type    location: :class:`NodeLocation`
+
+        :return: List of node objects
+        :rtype: ``list`` of :class:`Node`
+        """
+        action = '%s/cloud/project/%s/instance' % (API_ROOT, self.project_id)
+        data = {}
+        if location:
+            data['region'] = location.id
+        response = self.connection.request(action, data=data)
+        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 = '%s/cloud/project/%s/instance/%s' % (
+            API_ROOT, self.project_id, node_id)
+        response = self.connection.request(action, method='GET')
+        return self._to_node(response.object)
+
+    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``
+
+        :return: Created node
+        :rtype : :class:`Node`
+        """
+        action = '%s/cloud/project/%s/instance' % (API_ROOT, self.project_id)
+        data = {
+            'name': name,
+            'imageId': image.id,
+            'flavorId': size.id,
+            'region': location.id,
+        }
+        if ex_keyname:
+            key_id = self.get_key_pair(ex_keyname, location).extra['id']
+            data['sshKeyId'] = key_id
+        response = self.connection.request(action, data=data, method='POST')
+        return self._to_node(response.object)
+
+    def destroy_node(self, node):
+        action = '%s/cloud/project/%s/instance/%s' % (
+            API_ROOT, self.project_id, node.id)
+        self.connection.request(action, method='DELETE')
+        return True
+
+    def list_sizes(self, location=None):
+        action = '%s/cloud/project/%s/flavor' % (API_ROOT, self.project_id)
+        params = {}
+        if location:
+            params['region'] = location.id
+        response = self.connection.request(action, params=params)
+        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 = '%s/cloud/project/%s/flavor/%s' % (
+            API_ROOT, self.project_id, size_id)
+        response = self.connection.request(action)
+        return self._to_size(response.object)
+
+    def list_images(self, location=None, ex_size=None):
+        """
+        List available images
+
+        :keyword location: Location (region) used as filter
+        :type    location: :class:`NodeLocation`
+
+        :keyword ex_size: Exclude images which are uncompatible with given size
+        :type    ex_size: :class:`NodeImage`
+
+        :return: List of images
+        :rtype  : ``list`` of :class:`NodeImage`
+        """
+        action = '%s/cloud/project/%s/image' % (API_ROOT, self.project_id)
+        params = {}
+        if location:
+            params['region'] = location.id
+        if ex_size:
+            params['flavorId'] = ex_size.id
+        response = self.connection.request(action, params=params)
+        return self._to_images(response.object)
+
+    def get_image(self, image_id):
+        action = '%s/cloud/project/%s/image/%s' % (
+            API_ROOT, self.project_id, image_id)
+        response = self.connection.request(action)
+        return self._to_image(response.object)
+
+    def list_locations(self):
+        action = '%s/cloud/project/%s/region' % (API_ROOT, self.project_id)
+        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 = '%s/cloud/project/%s/sshkey' % (API_ROOT, self.project_id)
+        params = {}
+        if location:
+            params['region'] = location.id
+        response = self.connection.request(action, params=params)
+        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`
+        """
+        # Keys are indexed with ID
+        keys = [key for key in self.list_key_pairs(location)
+                if key.name == name]
+        if not keys:
+            raise Exception("No key named '%s'" % name)
+        return keys[0]
+
+    def import_key_pair_from_string(self, name, key_material, location):
+        """
+        Import a new public key from string.
+
+        :param name: Key pair name.
+        :type name: ``str``
+
+        :param key_material: Public key material.
+        :type key_material: ``str``
+
+        :return: Imported key pair object.
+        :rtype: :class:`KeyPair`
+        """
+        action = '%s/cloud/project/%s/sshkey' % (API_ROOT, self.project_id)
+        data = {'name': name, 'publicKey': key_material, 'region': location.id}
+        response = self.connection.request(action, data=data, method='POST')
+        return self._to_key_pair(response.object)
+
+    def delete_key_pair(self, name, location):
+        """
+        Delete an existing key pair.
+
+        :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``
+        """
+        action = '%s/cloud/project/%s/sshkey/%s' % (
+            API_ROOT, self.project_id, name)
+        params = {'name': name, 'region': location.id}
+        self.connection.request(action, params=params, 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 = '%s/cloud/project/%s/volume' % (API_ROOT, self.project_id)
+        data = {
+            'region': location.id,
+            'size': 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 = '%s/cloud/project/%s/volume/%s' % (
+            API_ROOT, self.project_id, 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 = '%s/cloud/project/%s/volume' % (API_ROOT, self.project_id)
+        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 = '%s/cloud/project/%s/volume/%s' % (
+            API_ROOT, self.project_id, 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/cloud/project/%s/volume/%s/attach' % (
+            API_ROOT, self.project_id, volume.id)
+        data = {'instanceId': node.id, 'volumeId': volume.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/cloud/project/%s/volume/%s/detach' % (
+            API_ROOT, self.project_id, 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]
+
+    def _to_location(self, obj):
+        location = self.connection.LOCATIONS[obj]
+        return NodeLocation(driver=self, **location)
+
+    def _to_locations(self, objs):
+        return [self._to_location(obj) for obj in objs]
+
+    def _to_node(self, obj):
+        extra = obj.copy()
+        if 'ipAddresses' in extra:
+            public_ips = [ip['ip'] for ip in extra['ipAddresses']]
+        del extra['id']
+        del extra['name']
+        return Node(id=obj['id'], name=obj['name'],
+                    state=self.NODE_STATE_MAP[obj['status']],
+                    public_ips=public_ips, private_ips=[], driver=self,
+                    extra=extra)
+
+    def _to_nodes(self, objs):
+        return [self._to_node(obj) for obj in objs]
+
+    def _to_size(self, obj):
+        extra = {'vcpus': obj['vcpus'], 'type': obj['type'],
+                 'region': obj['region']}
+        return NodeSize(id=obj['id'], name=obj['name'], ram=obj['ram'],
+                        disk=obj['disk'], bandwidth=None, price=None,
+                        driver=self, extra=extra)
+
+    def _to_sizes(self, objs):
+        return [self._to_size(obj) for obj in objs]
+
+    def _to_image(self, obj):
+        extra = {'region': obj['region'], 'visibility': obj['visibility']}
+        return NodeImage(id=obj['id'], name=obj['name'], driver=self,
+                         extra=extra)
+
+    def _to_images(self, objs):
+        return [self._to_image(obj) for obj in objs]
+
+    def _to_key_pair(self, obj):
+        extra = {'regions': obj['regions'], 'id': obj['id']}
+        return OpenStackKeyPair(name=obj['name'], public_key=obj['publicKey'],
+                                driver=self, fingerprint=None, extra=extra)
+
+    def _to_key_pairs(self, objs):
+        return [self._to_key_pair(obj) for obj in objs]
+
+    def _ex_connection_class_kwargs(self):
+        return {'ex_consumer_key': self.consumer_key}

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/compute/drivers/runabove.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/runabove.py b/libcloud/compute/drivers/runabove.py
deleted file mode 100644
index 72a45c6..0000000
--- a/libcloud/compute/drivers/runabove.py
+++ /dev/null
@@ -1,453 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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.
-"""
-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, StorageVolume
-from libcloud.compute.types import Provider, StorageVolumeState
-from libcloud.compute.drivers.openstack import OpenStackNodeDriver
-from libcloud.compute.drivers.openstack import OpenStackKeyPair
-
-
-class RunAboveNodeDriver(NodeDriver):
-    """
-    Libcloud driver for the RunAbove API
-
-    For more information on the RunAbove API, read the official reference:
-
-        https://api.runabove.com/console/
-    """
-    type = Provider.RUNABOVE
-    name = "RunAbove"
-    website = 'https://www.runabove.com/'
-    connectionCls = RunAboveConnection
-    features = {'create_node': ['ssh_key']}
-    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 credentials.
-
-        :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``
-        """
-        self.datacenter = None
-        self.consumer_key = ex_consumer_key
-        NodeDriver.__init__(self, key, secret, ex_consumer_key=ex_consumer_key)
-
-    def list_nodes(self, location=None):
-        """
-        List all nodes.
-
-        :keyword location: Location (region) used as filter
-        :type    location: :class:`NodeLocation`
-
-        :return: List of node objects
-        :rtype: ``list`` of :class:`Node`
-        """
-        action = API_ROOT + '/instance'
-        data = {}
-        if location:
-            data['region'] = location.id
-        response = self.connection.request(action, data=data)
-        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, 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``
-
-        :return: Created node
-        :rtype : :class:`Node`
-        """
-        action = API_ROOT + '/instance'
-        data = {
-            'name': name,
-            'imageId': image.id,
-            'flavorId': size.id,
-            'region': location.id,
-        }
-        if ex_keyname:
-            data['sshKeyName'] = ex_keyname
-        response = self.connection.request(action, data=data, method='POST')
-        return self._to_node(response.object)
-
-    def destroy_node(self, node):
-        action = API_ROOT + '/instance/' + node.id
-        self.connection.request(action, method='DELETE')
-        return True
-
-    def list_sizes(self, location=None):
-        action = API_ROOT + '/flavor'
-        data = {}
-        if location:
-            data['region'] = location.id
-        response = self.connection.request(action, data=data)
-        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, ex_size=None):
-        """
-        List available images
-
-        :keyword location: Location (region) used as filter
-        :type    location: :class:`NodeLocation`
-
-        :keyword ex_size: Exclude images which are uncompatible with given size
-        :type    ex_size: :class:`NodeImage`
-
-        :return: List of images
-        :rtype  : ``list`` of :class:`NodeImage`
-        """
-        action = API_ROOT + '/image'
-        data = {}
-        if location:
-            data['region'] = location.id
-        if ex_size:
-            data['flavorId'] = ex_size.id
-        response = self.connection.request(action, data=data)
-        return self._to_images(response.object)
-
-    def get_image(self, image_id):
-        action = API_ROOT + '/image/' + image_id
-        response = self.connection.request(action)
-        return self._to_image(response.object)
-
-    def list_locations(self):
-        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:
-            data['region'] = location.id
-        response = self.connection.request(action, data=data)
-        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)
-        return self._to_key_pair(response.object)
-
-    def import_key_pair_from_string(self, name, key_material, location):
-        """
-        Import a new public key from string.
-
-        :param name: Key pair name.
-        :type name: ``str``
-
-        :param key_material: Public key material.
-        :type key_material: ``str``
-
-        :return: Imported key pair object.
-        :rtype: :class:`KeyPair`
-        """
-        action = API_ROOT + '/ssh'
-        data = {'name': name, 'publicKey': key_material, 'region': location.id}
-        response = self.connection.request(action, data=data, method='POST')
-        return self._to_key_pair(response.object)
-
-    def delete_key_pair(self, name, location):
-        """
-        Delete an existing key pair.
-
-        :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``
-        """
-        action = API_ROOT + '/ssh/' + name
-        data = {'name': name, 'region': location.id}
-        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]
-
-    def _to_location(self, obj):
-        location = self.connection.LOCATIONS[obj]
-        return NodeLocation(driver=self, **location)
-
-    def _to_locations(self, objs):
-        return [self._to_location(obj) for obj in objs]
-
-    def _to_node(self, obj):
-        extra = obj.copy()
-        if 'flavorId' in extra:
-            public_ips = [obj.pop('ip')]
-        else:
-            ip = extra.pop('ipv4')
-            public_ips = [ip] if ip else []
-        del extra['instanceId']
-        del extra['name']
-        return Node(id=obj['instanceId'], name=obj['name'],
-                    state=self.NODE_STATE_MAP[obj['status']],
-                    public_ips=public_ips, private_ips=[], driver=self,
-                    extra=extra)
-
-    def _to_nodes(self, objs):
-        return [self._to_node(obj) for obj in objs]
-
-    def _to_size(self, obj):
-        extra = {'vcpus': obj['vcpus'], 'type': obj['type'],
-                 'region': obj['region']}
-        return NodeSize(id=obj['id'], name=obj['name'], ram=obj['ram'],
-                        disk=obj['disk'], bandwidth=None, price=None,
-                        driver=self, extra=extra)
-
-    def _to_sizes(self, objs):
-        return [self._to_size(obj) for obj in objs]
-
-    def _to_image(self, obj):
-        extra = {'region': obj['region'], 'visibility': obj['visibility'],
-                 'deprecated': obj['deprecated']}
-        return NodeImage(id=obj['id'], name=obj['name'], driver=self,
-                         extra=extra)
-
-    def _to_images(self, objs):
-        return [self._to_image(obj) for obj in objs]
-
-    def _to_key_pair(self, obj):
-        extra = {'region': obj['region']}
-        return OpenStackKeyPair(name=obj['name'], public_key=obj['publicKey'],
-                                driver=self, fingerprint=obj['fingerPrint'],
-                                extra=extra)
-
-    def _to_key_pairs(self, objs):
-        return [self._to_key_pair(obj) for obj in objs]
-
-    def _ex_connection_class_kwargs(self):
-        return {'ex_consumer_key': self.consumer_key}
-
-    def _add_required_headers(self, headers, method, action, data, timestamp):
-        timestamp = self.connection.get_timestamp()
-        signature = self.connection.make_signature(method, action, data,
-                                                   str(timestamp))
-        headers.update({
-            'X-Ra-Timestamp': timestamp,
-            'X-Ra-Signature': signature
-        })

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/compute/providers.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/providers.py b/libcloud/compute/providers.py
index 568c886..459335e 100644
--- a/libcloud/compute/providers.py
+++ b/libcloud/compute/providers.py
@@ -124,8 +124,8 @@ DRIVERS = {
     ('libcloud.compute.drivers.packet', 'PacketNodeDriver'),
     Provider.ONAPP:
     ('libcloud.compute.drivers.onapp', 'OnAppNodeDriver'),
-    Provider.RUNABOVE:
-    ('libcloud.compute.drivers.runabove', 'RunAboveNodeDriver'),
+    Provider.OVH:
+    ('libcloud.compute.drivers.ovh', 'OvhNodeDriver'),
     Provider.INTERNETSOLUTIONS:
     ('libcloud.compute.drivers.internetsolutions',
      'InternetSolutionsNodeDriver'),

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/compute/types.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index 740b688..e66f1e7 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -144,12 +144,12 @@ class Provider(Type):
     OPSOURCE = 'opsource'
     OUTSCALE_INC = 'outscale_inc'
     OUTSCALE_SAS = 'outscale_sas'
+    OVH = 'ovh'
     PACKET = 'packet'
     PROFIT_BRICKS = 'profitbricks'
     RACKSPACE = 'rackspace'
     RACKSPACE_FIRST_GEN = 'rackspace_first_gen'
     RIMUHOSTING = 'rimuhosting'
-    RUNABOVE = 'runabove'
     SERVERLOVE = 'serverlove'
     SKALICLOUD = 'skalicloud'
     SOFTLAYER = 'softlayer'

http://git-wip-us.apache.org/repos/asf/libcloud/blob/d8100961/libcloud/test/common/test_ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/test/common/test_ovh.py b/libcloud/test/common/test_ovh.py
new file mode 100644
index 0000000..946f907
--- /dev/null
+++ b/libcloud/test/common/test_ovh.py
@@ -0,0 +1,29 @@
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You 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 re
+from libcloud.test import MockHttp
+
+FORMAT_URL = re.compile(r'[./-]')
+
+
+class BaseOvhMockHttp(MockHttp):
+
+    def _get_method_name(self, type, use_param, qs, path):
+        return "_json"
+
+    def _json(self, method, url, body, headers):
+        meth_name = '_json%s_%s' % (FORMAT_URL.sub('_', url), method.lower())
+        return getattr(self, meth_name)(method, url, body, headers)


[4/4] libcloud git commit: Small OVH fixes Closes #891

Posted by an...@apache.org.
Small OVH fixes
Closes #891


Project: http://git-wip-us.apache.org/repos/asf/libcloud/repo
Commit: http://git-wip-us.apache.org/repos/asf/libcloud/commit/cc9a0130
Tree: http://git-wip-us.apache.org/repos/asf/libcloud/tree/cc9a0130
Diff: http://git-wip-us.apache.org/repos/asf/libcloud/diff/cc9a0130

Branch: refs/heads/trunk
Commit: cc9a01304b7e7725b84553c41e5efcd160ca4dba
Parents: a2cde6b
Author: ZuluPro <mo...@hotmail.com>
Authored: Wed Oct 5 18:28:36 2016 -0400
Committer: Anthony Shaw <an...@apache.org>
Committed: Fri Oct 7 09:23:41 2016 +1100

----------------------------------------------------------------------
 libcloud/compute/deprecated.py  |  5 +++
 libcloud/compute/drivers/ovh.py | 65 +++++++++++++-----------------------
 libcloud/compute/types.py       |  1 +
 3 files changed, 30 insertions(+), 41 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/libcloud/blob/cc9a0130/libcloud/compute/deprecated.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/deprecated.py b/libcloud/compute/deprecated.py
index d68e47f..dbaffa0 100644
--- a/libcloud/compute/deprecated.py
+++ b/libcloud/compute/deprecated.py
@@ -46,5 +46,10 @@ DEPRECATED_DRIVERS = {
         'reason': 'The CloudFrames Provider is no longer supported',
         'url': 'http://libcloud.apache.org/blog/2016/02/16/new-drivers-'
                'deprecated-drivers.html'
+    },
+    Provider.RUNABOVE: {
+        'reason': 'The RunAbove compute is no longer supported. '
+                  'Use the OVH one instead.',
+        'url': 'https://www.runabove.com/cloud-instance.xml'
     }
 }

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cc9a0130/libcloud/compute/drivers/ovh.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/drivers/ovh.py b/libcloud/compute/drivers/ovh.py
index a103650..3043835 100644
--- a/libcloud/compute/drivers/ovh.py
+++ b/libcloud/compute/drivers/ovh.py
@@ -64,6 +64,10 @@ class OvhNodeDriver(NodeDriver):
         self.consumer_key = ex_consumer_key
         NodeDriver.__init__(self, key, secret, ex_consumer_key=ex_consumer_key)
 
+    def _get_project_action(self, suffix):
+        base_url = '%s/cloud/project/%s/' % (API_ROOT, self.project_id)
+        return base_url + suffix
+
     def list_nodes(self, location=None):
         """
         List all nodes.
@@ -74,7 +78,7 @@ class OvhNodeDriver(NodeDriver):
         :return: List of node objects
         :rtype: ``list`` of :class:`Node`
         """
-        action = '%s/cloud/project/%s/instance' % (API_ROOT, self.project_id)
+        action = self._get_project_action('instance')
         data = {}
         if location:
             data['region'] = location.id
@@ -91,8 +95,7 @@ class OvhNodeDriver(NodeDriver):
         :return: Created node
         :rtype  : :class:`Node`
         """
-        action = '%s/cloud/project/%s/instance/%s' % (
-            API_ROOT, self.project_id, node_id)
+        action = self._get_project_action('instance/%s' % node_id)
         response = self.connection.request(action, method='GET')
         return self._to_node(response.object)
 
@@ -118,7 +121,7 @@ class OvhNodeDriver(NodeDriver):
         :return: Created node
         :rtype : :class:`Node`
         """
-        action = '%s/cloud/project/%s/instance' % (API_ROOT, self.project_id)
+        action = self._get_project_action('instance')
         data = {
             'name': name,
             'imageId': image.id,
@@ -132,13 +135,12 @@ class OvhNodeDriver(NodeDriver):
         return self._to_node(response.object)
 
     def destroy_node(self, node):
-        action = '%s/cloud/project/%s/instance/%s' % (
-            API_ROOT, self.project_id, node.id)
+        action = self._get_project_action('instance/%s' % node.id)
         self.connection.request(action, method='DELETE')
         return True
 
     def list_sizes(self, location=None):
-        action = '%s/cloud/project/%s/flavor' % (API_ROOT, self.project_id)
+        action = self._get_project_action('flavor')
         params = {}
         if location:
             params['region'] = location.id
@@ -155,8 +157,7 @@ class OvhNodeDriver(NodeDriver):
         :return: Size
         :rtype: :class:`NodeSize`
         """
-        action = '%s/cloud/project/%s/flavor/%s' % (
-            API_ROOT, self.project_id, size_id)
+        action = self._get_project_action('flavor/%s' % size_id)
         response = self.connection.request(action)
         return self._to_size(response.object)
 
@@ -173,7 +174,7 @@ class OvhNodeDriver(NodeDriver):
         :return: List of images
         :rtype  : ``list`` of :class:`NodeImage`
         """
-        action = '%s/cloud/project/%s/image' % (API_ROOT, self.project_id)
+        action = self._get_project_action('image')
         params = {}
         if location:
             params['region'] = location.id
@@ -183,13 +184,12 @@ class OvhNodeDriver(NodeDriver):
         return self._to_images(response.object)
 
     def get_image(self, image_id):
-        action = '%s/cloud/project/%s/image/%s' % (
-            API_ROOT, self.project_id, image_id)
+        action = self._get_project_action('image/%s' % image_id)
         response = self.connection.request(action)
         return self._to_image(response.object)
 
     def list_locations(self):
-        action = '%s/cloud/project/%s/region' % (API_ROOT, self.project_id)
+        action = self._get_project_action('region')
         data = self.connection.request(action)
         return self._to_locations(data.object)
 
@@ -203,7 +203,7 @@ class OvhNodeDriver(NodeDriver):
         :return: Public keys
         :rtype: ``list``of :class:`KeyPair`
         """
-        action = '%s/cloud/project/%s/sshkey' % (API_ROOT, self.project_id)
+        action = self._get_project_action('sshkey')
         params = {}
         if location:
             params['region'] = location.id
@@ -243,27 +243,14 @@ class OvhNodeDriver(NodeDriver):
         :return: Imported key pair object.
         :rtype: :class:`KeyPair`
         """
-        action = '%s/cloud/project/%s/sshkey' % (API_ROOT, self.project_id)
+        action = self._get_project_action('sshkey')
         data = {'name': name, 'publicKey': key_material, 'region': location.id}
         response = self.connection.request(action, data=data, method='POST')
         return self._to_key_pair(response.object)
 
-    def delete_key_pair(self, name, location):
-        """
-        Delete an existing key pair.
-
-        :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``
-        """
-        action = '%s/cloud/project/%s/sshkey/%s' % (
-            API_ROOT, self.project_id, name)
-        params = {'name': name, 'region': location.id}
+    def delete_key_pair(self, key_pair):
+        action = self._get_project_action('sshkey/%s' % key_pair.extra['id'])
+        params = {'keyId': key_pair.extra['id']}
         self.connection.request(action, params=params, method='DELETE')
         return True
 
@@ -290,7 +277,7 @@ class OvhNodeDriver(NodeDriver):
         :return:  Storage Volume object
         :rtype:   :class:`StorageVolume`
         """
-        action = '%s/cloud/project/%s/volume' % (API_ROOT, self.project_id)
+        action = self._get_project_action('volume')
         data = {
             'region': location.id,
             'size': size,
@@ -304,8 +291,7 @@ class OvhNodeDriver(NodeDriver):
         return self._to_volume(response.object)
 
     def destroy_volume(self, volume):
-        action = '%s/cloud/project/%s/volume/%s' % (
-            API_ROOT, self.project_id, volume.id)
+        action = self._get_project_action('volume/%s' % volume.id)
         self.connection.request(action, method='DELETE')
         return True
 
@@ -319,7 +305,7 @@ class OvhNodeDriver(NodeDriver):
         :return: A list of volume objects.
         :rtype: ``list`` of :class:`StorageVolume`
         """
-        action = '%s/cloud/project/%s/volume' % (API_ROOT, self.project_id)
+        action = self._get_project_action('volume')
         data = {}
         if location:
             data['region'] = location.id
@@ -336,8 +322,7 @@ class OvhNodeDriver(NodeDriver):
         :return:  A StorageVolume object for the volume
         :rtype:   :class:`StorageVolume`
         """
-        action = '%s/cloud/project/%s/volume/%s' % (
-            API_ROOT, self.project_id, volume_id)
+        action = self._get_project_action('volume/%s' % volume_id)
         response = self.connection.request(action)
         return self._to_volume(response.object)
 
@@ -356,8 +341,7 @@ class OvhNodeDriver(NodeDriver):
         :return: True or False representing operation successful
         :rtype:   ``bool``
         """
-        action = '%s/cloud/project/%s/volume/%s/attach' % (
-            API_ROOT, self.project_id, volume.id)
+        action = self._get_project_action('volume/%s/attach' % volume.id)
         data = {'instanceId': node.id, 'volumeId': volume.id}
         self.connection.request(action, data=data, method='POST')
         return True
@@ -379,8 +363,7 @@ class OvhNodeDriver(NodeDriver):
         :raises: Exception: If ``ex_node`` is not provided and more than one
                             node is attached to the volume
         """
-        action = '%s/cloud/project/%s/volume/%s/detach' % (
-            API_ROOT, self.project_id, volume.id)
+        action = self._get_project_action('volume/%s/detach' % volume.id)
         if ex_node is None:
             if len(volume.extra['attachedTo']) != 1:
                 err_msg = "Volume '%s' has more or less than one attached" \

http://git-wip-us.apache.org/repos/asf/libcloud/blob/cc9a0130/libcloud/compute/types.py
----------------------------------------------------------------------
diff --git a/libcloud/compute/types.py b/libcloud/compute/types.py
index e66f1e7..e34cb2f 100644
--- a/libcloud/compute/types.py
+++ b/libcloud/compute/types.py
@@ -150,6 +150,7 @@ class Provider(Type):
     RACKSPACE = 'rackspace'
     RACKSPACE_FIRST_GEN = 'rackspace_first_gen'
     RIMUHOSTING = 'rimuhosting'
+    RUNABOVE = 'runabove'
     SERVERLOVE = 'serverlove'
     SKALICLOUD = 'skalicloud'
     SOFTLAYER = 'softlayer'