forked from apache/libcloud
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add Ninefold.com compute and load-balancer driver.
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
Showing
48 changed files
with
995 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
Oops, something went wrong.