blob: 0aebd7eefa86e419a7bfdc8cab8918432627bfee [file] [log] [blame]
# 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.
# libcloud.org 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.
# Copyright 2009 RedRata Ltd
"""
RimuHosting Driver
"""
from libcloud.types import Provider, NodeState, InvalidCredsException
from libcloud.base import ConnectionKey, Response, NodeAuthPassword
from libcloud.base import NodeDriver, NodeSize, Node, NodeLocation
from libcloud.base import NodeImage
# JSON is included in the standard library starting with Python 2.6. For 2.5
# and 2.4, there's a simplejson egg at: http://pypi.python.org/pypi/simplejson
try: import json
except: import simplejson as json
# Defaults
API_CONTEXT = '/r'
API_HOST = 'api.rimuhosting.com'
API_PORT = (80,443)
API_SECURE = True
class RimuHostingException(Exception):
def __str__(self):
return self.args[0]
def __repr__(self):
return "<RimuHostingException '%s'>" % (self.args[0])
class RimuHostingResponse(Response):
def __init__(self, response):
self.body = response.read()
self.status = response.status
self.headers = dict(response.getheaders())
self.error = response.reason
if self.success():
self.object = self.parse_body()
def success(self):
if self.status == 403:
raise InvalidCredsException()
return True
def parse_body(self):
try:
js = json.loads(self.body)
if js[js.keys()[0]]['response_type'] == "ERROR":
raise RimuHostingException(
js[js.keys()[0]]['human_readable_message']
)
return js[js.keys()[0]]
except ValueError:
raise RimuHostingException('Could not parse body: %s'
% (self.body))
except KeyError:
raise RimuHostingException('Could not parse body: %s'
% (self.body))
class RimuHostingConnection(ConnectionKey):
api_context = API_CONTEXT
host = API_HOST
port = API_PORT
responseCls = RimuHostingResponse
def __init__(self, key, secure=True):
# override __init__ so that we can set secure of False for testing
ConnectionKey.__init__(self,key,secure)
def add_default_headers(self, headers):
# We want JSON back from the server. Could be application/xml
# (but JSON is better).
headers['Accept'] = 'application/json'
# Must encode all data as json, or override this header.
headers['Content-Type'] = 'application/json'
headers['Authorization'] = 'rimuhosting apikey=%s' % (self.key)
return headers;
def request(self, action, params={}, data='', headers={}, method='GET'):
# Override this method to prepend the api_context
return ConnectionKey.request(self, self.api_context + action,
params, data, headers, method)
class RimuHostingNodeDriver(NodeDriver):
type = Provider.RIMUHOSTING
name = 'RimuHosting'
connectionCls = RimuHostingConnection
def __init__(self, key, host=API_HOST, port=API_PORT,
api_context=API_CONTEXT, secure=API_SECURE):
# Pass in some extra vars so that
self.key = key
self.secure = secure
self.connection = self.connectionCls(key ,secure)
self.connection.host = host
self.connection.api_context = api_context
self.connection.port = port
self.connection.driver = self
self.connection.connect()
def _order_uri(self, node,resource):
# Returns the order uri with its resourse appended.
return "/orders/%s/%s" % (node.id,resource)
# TODO: Get the node state.
def _to_node(self, order):
n = Node(id=order['slug'],
name=order['domain_name'],
state=NodeState.RUNNING,
public_ip=(
[order['allocated_ips']['primary_ip']]
+ order['allocated_ips']['secondary_ips']
),
private_ip=[],
driver=self.connection.driver,
extra={'order_oid': order['order_oid']})
return n
def _to_size(self,plan):
return NodeSize(
id=plan['pricing_plan_code'],
name=plan['pricing_plan_description'],
ram=plan['minimum_memory_mb'],
disk=plan['minimum_disk_gb'],
bandwidth=plan['minimum_data_transfer_allowance_gb'],
price=plan['monthly_recurring_amt']['amt_usd'],
driver=self.connection.driver
)
def _to_image(self,image):
return NodeImage(id=image['distro_code'],
name=image['distro_description'],
driver=self.connection.driver)
def list_sizes(self, location=None):
# Returns a list of sizes (aka plans)
# Get plans. Note this is really just for libcloud.
# We are happy with any size.
if location == None:
location = '';
else:
location = ";dc_location=%s" % (location.id)
res = self.connection.request('/pricing-plans;server-type=VPS%s' % (location)).object
return map(lambda x : self._to_size(x), res['pricing_plan_infos'])
def list_nodes(self):
# Returns a list of Nodes
# Will only include active ones.
res = self.connection.request('/orders;include_inactive=N').object
return map(lambda x : self._to_node(x), res['about_orders'])
def list_images(self, location=None):
# Get all base images.
# TODO: add other image sources. (Such as a backup of a VPS)
# All Images are available for use at all locations
res = self.connection.request('/distributions').object
return map(lambda x : self._to_image(x), res['distro_infos'])
def reboot_node(self, node):
# Reboot
# PUT the state of RESTARTING to restart a VPS.
# All data is encoded as JSON
data = {'reboot_request':{'running_state':'RESTARTING'}}
uri = self._order_uri(node,'vps/running-state')
self.connection.request(uri,data=json.dumps(data),method='PUT')
# XXX check that the response was actually successful
return True
def destroy_node(self, node):
# Shutdown a VPS.
uri = self._order_uri(node,'vps')
self.connection.request(uri,method='DELETE')
# XXX check that the response was actually successful
return True
def create_node(self, **kwargs):
# Creates a RimuHosting instance
#
# name Must be a FQDN. e.g example.com.
# image NodeImage from list_images
# size NodeSize from list_sizes
#
# Keyword arguements supported:
#
# billing_oid If not set, a billing method is automatically
# picked.
#
# host_server_oid The host server to set the VPS up on.
# vps_order_oid_to_clone Clone another VPS to use as the image
# for the new VPS.
#
# num_ips = 1 Number of IPs to allocate. Defaults to 1.
# extra_ip_reason Reason for needing the extra IPS.
#
# memory_mb Memory to allocate to the VPS.
# disk_space_mb Diskspace to allocate to the VPS.
# Defaults to 4096 (4GB).
# disk_space_2_mb Secondary disk size allocation.
# Disabled by default.
#
# pricing_plan_code Plan from list_sizes
#
# control_panel Control panel to install on the VPS.
#
#
# Note we don't do much error checking in this because we
# expect the API to error out if there is a problem.
name = kwargs['name']
image = kwargs['image']
size = kwargs['size']
data = {
'instantiation_options':{
'domain_name': name, 'distro': image.id
},
'pricing_plan_code': size.id,
}
if kwargs.has_key('control_panel'):
data['instantiation_options']['control_panel'] = kwargs['control_panel']
if kwargs.has_key('auth'):
auth = kwargs['auth']
if not isinstance(auth, NodeAuthPassword):
raise ValueError('auth must be of NodeAuthPassword type')
data['instantiation_options']['password'] = auth.password
if kwargs.has_key('billing_oid'):
#TODO check for valid oid.
data['billing_oid'] = kwargs['billing_oid']
if kwargs.has_key('host_server_oid'):
data['host_server_oid'] = kwargs['host_server_oid']
if kwargs.has_key('vps_order_oid_to_clone'):
data['vps_order_oid_to_clone'] = kwargs['vps_order_oid_to_clone']
if kwargs.has_key('num_ips') and int(kwargs['num_ips']) > 1:
if not kwargs.has_key('extra_ip_reason'):
raise RimuHostingException('Need an reason for having an extra IP')
else:
if not data.has_key('ip_request'):
data['ip_request'] = {}
data['ip_request']['num_ips'] = int(kwargs['num_ips'])
data['ip_request']['extra_ip_reason'] = kwargs['extra_ip_reason']
if kwargs.has_key('memory_mb'):
if not data.has_key('vps_parameters'):
data['vps_parameters'] = {}
data['vps_parameters']['memory_mb'] = kwargs['memory_mb']
if kwargs.has_key('disk_space_mb'):
if not data.has_key('vps_parameters'):
data['vps_parameters'] = {}
data['vps_parameters']['disk_space_mb'] = kwargs['disk_space_mb']
if kwargs.has_key('disk_space_2_mb'):
if not data.has_key('vps_parameters'):
data['vps_parameters'] = {}
data['vps_parameters']['disk_space_2_mb'] = kwargs['disk_space_2_mb']
res = self.connection.request(
'/orders/new-vps',
method='POST',
data=json.dumps({"new-vps":data})
).object
node = self._to_node(res['about_order'])
node.extra['password'] = res['new_order_request']['instantiation_options']['password']
return node
def list_locations(self):
return [
NodeLocation('DCAUCKLAND', "RimuHosting Auckland", 'NZ', self),
NodeLocation('DCDALLAS', "RimuHosting Dallas", 'US', self),
NodeLocation('DCLONDON', "RimuHosting London", 'GB', self),
NodeLocation('DCSYDNEY', "RimuHosting Sydney", 'AU', self),
]
features = {"create_node": ["password"]}