Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added RunAbove Driver #550

Closed
wants to merge 3 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Binary file added docs/_static/images/provider_logos/runabove.png
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
72 changes: 72 additions & 0 deletions docs/compute/drivers/runabove.rst
@@ -0,0 +1,72 @@
Cloudwatt 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:

* ``user_id`` - 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 instance
~~~~~~~~~~~~~~~

.. literalinclude:: /examples/compute/runabove/create_node.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/#/
12 changes: 12 additions & 0 deletions docs/examples/compute/runabove/create_node.py
@@ -0,0 +1,12 @@
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)
134 changes: 134 additions & 0 deletions libcloud/common/runabove.py
@@ -0,0 +1,134 @@
# 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.common.base import ConnectionUserAndKey, JsonResponse
from libcloud.httplib_ssl import LibcloudHTTPSConnection

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 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 = JsonResponse
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}' for valid it. After instantiate " \
"your driver with \"ex_consumer_key='{consumerKey}'\"."\
.format(**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().read()
json_response = json.loads(response)
httpcon.close()
return json_response

def get_timestamp(self):
if not self._timedelta:
action = API_ROOT + '/auth/time'
response = self.connection.request('GET', action, headers={})
timestamp = int(response)
self._time_delta = 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)