Skip to content

Commit

Permalink
Add Ninefold.com compute and load-balancer driver.
Browse files Browse the repository at this point in the history
The patch has been contributed by Benno Rice <benno at jeamland dot net> and
it's part of LIBCLOUD-98.


git-svn-id: https://svn.apache.org/repos/asf/libcloud/trunk@1141506 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
Kami committed Jun 30, 2011
1 parent 38af38a commit 6761b76
Show file tree
Hide file tree
Showing 48 changed files with 995 additions and 2 deletions.
124 changes: 124 additions & 0 deletions libcloud/common/cloudstack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 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 base64
import hashlib
import hmac
import time
import urllib

try:
import json
except:
import simplejson as json

from libcloud.common.base import ConnectionUserAndKey, Response
from libcloud.common.types import MalformedResponseError

class CloudStackResponse(Response):
def parse_body(self):
try:
body = json.loads(self.body)
except:
raise MalformedResponseError(
"Failed to parse JSON",
body=self.body,
driver=self.connection.driver)
return body

parse_error = parse_body

class CloudStackConnection(ConnectionUserAndKey):
responseCls = CloudStackResponse

ASYNC_PENDING = 0
ASYNC_SUCCESS = 1
ASYNC_FAILURE = 2

def _make_signature(self, params):
signature = [(k.lower(), v) for k, v in params.items()]
signature.sort(key=lambda x: x[0])
signature = urllib.urlencode(signature)
signature = signature.lower().replace('+', '%20')
signature = hmac.new(self.key, msg=signature, digestmod=hashlib.sha1)
return base64.b64encode(signature.digest())

def add_default_params(self, params):
params['apiKey'] = self.user_id
params['response'] = 'json'

return params

def pre_connect_hook(self, params, headers):
params['signature'] = self._make_signature(params)

return params, headers

def _sync_request(self, command, **kwargs):
"""This method handles synchronous calls which are generally fast
information retrieval requests and thus return 'quickly'."""

kwargs['command'] = command
result = self.request(self.driver.path, params=kwargs)
command = command.lower() + 'response'
if command not in result.object:
raise MalformedResponseError(
"Unknown response format",
body=result.body,
driver=self.driver)
result = result.object[command]
return result

def _async_request(self, command, **kwargs):
"""This method handles asynchronous calls which are generally
requests for the system to do something and can thus take time.
In these cases the initial call will either fail fast and return
an error, or it can return a job ID. We then poll for the status
of the job ID which can either be pending, successful or failed."""

result = self._sync_request(command, **kwargs)
job_id = result['jobid']
success = True

while True:
result = self._sync_request('queryAsyncJobResult', jobid=job_id)
status = result.get('jobstatus', self.ASYNC_PENDING)
if status != self.ASYNC_PENDING:
break
time.sleep(self.driver.async_poll_frequency)

if result['jobstatus'] == self.ASYNC_FAILURE:
raise Exception(result)

return result['jobresult']

class CloudStackDriverMixIn(object):
host = None
path = None
async_poll_frequency = 1

connectionCls = CloudStackConnection

def __init__(self, key, secret=None, secure=True, host=None, port=None):
host = host or self.host
super(CloudStackDriverMixIn, self).__init__(key, secret, secure, host,
port)

def _sync_request(self, command, **kwargs):
return self.connection._sync_request(command, **kwargs)

def _async_request(self, command, **kwargs):
return self.connection._async_request(command, **kwargs)
267 changes: 267 additions & 0 deletions libcloud/compute/drivers/cloudstack.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,267 @@
# 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.

from libcloud.common.cloudstack import CloudStackConnection, \
CloudStackDriverMixIn
from libcloud.compute.base import Node, NodeDriver, NodeImage, NodeLocation, \
NodeSize
from libcloud.compute.types import DeploymentError, NodeState

class CloudStackNode(Node):
"Subclass of Node so we can expose our extension methods."

def ex_allocate_public_ip(self):
"Allocate a public IP and bind it to this node."
return self.driver.ex_allocate_public_ip(self)

def ex_release_public_ip(self, address):
"Release a public IP that this node holds."
return self.driver.ex_release_public_ip(self, address)

def ex_add_ip_forwarding_rule(self, address, protocol, start_port,
end_port=None):
"Add a NAT/firewall forwarding rule for a port or ports."
return self.driver.ex_add_ip_forwarding_rule(self, address, protocol,
start_port, end_port)

def ex_delete_ip_forwarding_rule(self, rule):
"Delete a NAT/firewall rule."
return self.driver.ex_delete_ip_forwarding_rule(self, rule)

class CloudStackAddress(object):
"A public IP address."

def __init__(self, node, id, address):
self.node = node
self.id = id
self.address = address

def release(self):
self.node.ex_release_public_ip(self)

def __str__(self):
return self.address

def __eq__(self, other):
return self.__class__ is other.__class__ and self.id == other.id

class CloudStackForwardingRule(object):
"A NAT/firewall forwarding rule."

def __init__(self, node, id, address, protocol, start_port, end_port=None):
self.node = node
self.id = id
self.address = address
self.protocol = protocol
self.start_port = start_port
self.end_port = end_port

def delete(self):
self.node.ex_delete_ip_forwarding_rule(self)

def __eq__(self, other):
return self.__class__ is other.__class__ and self.id == other.id

class CloudStackNodeDriver(CloudStackDriverMixIn, NodeDriver):
"""Driver for the CloudStack API.
@cvar host: The host where the API can be reached.
@cvar path: The path where the API can be reached.
@cvar async_poll_frequency: How often (in seconds) to poll for async
job completion.
@type async_poll_frequency: C{int}"""

api_name = 'cloudstack'

NODE_STATE_MAP = {
'Running': NodeState.RUNNING,
'Starting': NodeState.REBOOTING,
'Stopped': NodeState.TERMINATED,
'Stopping': NodeState.TERMINATED
}

def list_images(self, location=None):
args = {
'templatefilter': 'executable'
}
if location is not None:
args['zoneid'] = location.id
imgs = self._sync_request('listTemplates', **args)
images = []
for img in imgs['template']:
images.append(NodeImage(img['id'], img['name'], self, {
'hypervisor': img['hypervisor'],
'format': img['format'],
'os': img['ostypename'],
}))
return images

def list_locations(self):
locs = self._sync_request('listZones')
locations = []
for loc in locs['zone']:
locations.append(NodeLocation(loc['id'], loc['name'], 'AU', self))
return locations

def list_nodes(self):
vms = self._sync_request('listVirtualMachines')
addrs = self._sync_request('listPublicIpAddresses')

public_ips = {}
for addr in addrs['publicipaddress']:
if 'virtualmachineid' not in addr:
continue
vm_id = addr['virtualmachineid']
if vm_id not in public_ips:
public_ips[vm_id] = {}
public_ips[vm_id][addr['ipaddress']] = addr['id']

nodes = []

for vm in vms.get('virtualmachine', []):
node = CloudStackNode(
id=vm['id'],
name=vm.get('displayname', None),
state=self.NODE_STATE_MAP[vm['state']],
public_ip=public_ips.get(vm['id'], {}).keys(),
private_ip=[x['ipaddress'] for x in vm['nic']],
driver=self,
extra={
'zoneid': vm['zoneid'],
}
)

addrs = public_ips.get(vm['id'], {}).items()
addrs = [CloudStackAddress(node, v, k) for k, v in addrs]
node.extra['ip_addresses'] = addrs

rules = []
for addr in addrs:
result = self._sync_request('listIpForwardingRules')
for r in result.get('ipforwardingrule', []):
rule = CloudStackForwardingRule(node, r['id'], addr,
r['protocol'].upper(),
r['startport'],
r['endport'])
rules.append(rule)
node.extra['ip_forwarding_rules'] = rules

nodes.append(node)

return nodes

def list_sizes(self, location=None):
szs = self._sync_request('listServiceOfferings')
sizes = []
for sz in szs['serviceoffering']:
sizes.append(NodeSize(sz['id'], sz['name'], sz['memory'], 0, 0,
0, self))
return sizes

def create_node(self, name, size, image, location=None, **kwargs):
if location is None:
location = self.list_locations()[0]

networks = self._sync_request('listNetworks')
network_id = networks['network'][0]['id']

result = self._async_request('deployVirtualMachine',
name=name,
displayname=name,
serviceofferingid=size.id,
templateid=image.id,
zoneid=location.id,
networkids=network_id,
)

node = result['virtualmachine']

return Node(
id=node['id'],
name=node['displayname'],
state=self.NODE_STATE_MAP[node['state']],
public_ip=[],
private_ip=[x['ipaddress'] for x in node['nic']],
driver=self,
extra={
'zoneid': location.id,
'ip_addresses': [],
'forwarding_rules': [],
}
)

def destroy_node(self, node):
self._async_request('destroyVirtualMachine', id=node.id)
return True

def reboot_node(self, node):
self._async_request('rebootVirtualMachine', id=node.id)
return True

def ex_allocate_public_ip(self, node):
"Allocate a public IP and bind it to a node."

zoneid = node.extra['zoneid']
addr = self._async_request('associateIpAddress', zoneid=zoneid)
addr = addr['ipaddress']
result = self._sync_request('enableStaticNat', virtualmachineid=node.id,
ipaddressid=addr['id'])
if result.get('success', '').lower() != 'true':
return None

node.public_ip.append(addr['ipaddress'])
addr = CloudStackAddress(node, addr['id'], addr['ipaddress'])
node.extra['ip_addresses'].append(addr)
return addr

def ex_release_public_ip(self, node, address):
"Release a public IP."

node.extra['ip_addresses'].remove(address)
node.public_ip.remove(address.address)

self._async_request('disableStaticNat', ipaddressid=address.id)
self._async_request('disassociateIpAddress', id=address.id)
return True

def ex_add_ip_forwarding_rule(self, node, address, protocol,
start_port, end_port=None):
"Add a NAT/firewall forwarding rule."

protocol = protocol.upper()
if protocol not in ('TCP', 'UDP'):
return None

args = {
'ipaddressid': address.id,
'protocol': protocol,
'startport': int(start_port)
}
if end_port is not None:
args['endport'] = int(end_port)

result = self._async_request('createIpForwardingRule', **args)
result = result['ipforwardingrule']
rule = CloudStackForwardingRule(node, result['id'], address,
protocol, start_port, end_port)
node.extra['ip_forwarding_rules'].append(rule)
return rule

def ex_delete_ip_forwarding_rule(self, node, rule):
"Remove a NAT/firewall forwading rule."

node.extra['ip_forwarding_rules'].remove(rule)
self._async_request('deleteIpForwardingRule', id=rule.id)
return True
Loading

0 comments on commit 6761b76

Please sign in to comment.