Mini Shell
# 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.
"""
Enomaly ECP driver
"""
import time
import os
import socket
import binascii
from libcloud.utils.py3 import httplib
from libcloud.utils.py3 import b
from libcloud.utils.py3 import base64_encode_string
# 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 simplejson as json
except ImportError:
import json
from libcloud.common.base import Response, ConnectionUserAndKey
from libcloud.compute.base import NodeDriver, NodeSize, NodeLocation
from libcloud.compute.base import NodeImage, Node
from libcloud.compute.types import Provider, NodeState, InvalidCredsError
from libcloud.utils.networking import is_private_subnet
# Defaults
API_HOST = ''
API_PORT = (80, 443)
class ECPResponse(Response):
def success(self):
if self.status == httplib.OK or self.status == httplib.CREATED:
try:
j_body = json.loads(self.body)
except ValueError:
self.error = "JSON response cannot be decoded."
return False
if j_body['errno'] == 0:
return True
else:
self.error = "ECP error: %s" % j_body['message']
return False
elif self.status == httplib.UNAUTHORIZED:
raise InvalidCredsError()
else:
self.error = "HTTP Error Code: %s" % self.status
return False
def parse_error(self):
return self.error
# Interpret the json responses - no error checking required
def parse_body(self):
return json.loads(self.body)
def getheaders(self):
return self.headers
class ECPConnection(ConnectionUserAndKey):
"""
Connection class for the Enomaly ECP driver
"""
responseCls = ECPResponse
host = API_HOST
port = API_PORT
def add_default_headers(self, headers):
# Authentication
username = self.user_id
password = self.key
base64string = base64_encode_string(
b('%s:%s' % (username, password)))[:-1]
authheader = "Basic %s" % base64string
headers['Authorization'] = authheader
return headers
def _encode_multipart_formdata(self, fields):
"""
Based on Wade Leftwich's function:
http://code.activestate.com/recipes/146306/
"""
# use a random boundary that does not appear in the fields
boundary = ''
while boundary in ''.join(fields):
boundary = binascii.hexlify(os.urandom(16)).decode('utf-8')
L = []
for i in fields:
L.append('--' + boundary)
L.append('Content-Disposition: form-data; name="%s"' % i)
L.append('')
L.append(fields[i])
L.append('--' + boundary + '--')
L.append('')
body = '\r\n'.join(L)
content_type = 'multipart/form-data; boundary=%s' % boundary
header = {'Content-Type': content_type}
return header, body
class ECPNodeDriver(NodeDriver):
"""
Enomaly ECP node driver
"""
name = "Enomaly Elastic Computing Platform"
website = 'http://www.enomaly.com/'
type = Provider.ECP
connectionCls = ECPConnection
def list_nodes(self):
"""
Returns a list of all running Nodes
:rtype: ``list`` of :class:`Node`
"""
# Make the call
res = self.connection.request('/rest/hosting/vm/list').parse_body()
# Put together a list of node objects
nodes = []
for vm in res['vms']:
node = self._to_node(vm)
if node is not None:
nodes.append(node)
# And return it
return nodes
def _to_node(self, vm):
"""
Turns a (json) dictionary into a Node object.
This returns only running VMs.
"""
# Check state
if not vm['state'] == "running":
return None
# IPs
iplist = [interface['ip'] for interface in vm['interfaces'] if
interface['ip'] != '127.0.0.1']
public_ips = []
private_ips = []
for ip in iplist:
try:
socket.inet_aton(ip)
except socket.error:
# not a valid ip
continue
if is_private_subnet(ip):
private_ips.append(ip)
else:
public_ips.append(ip)
# Create the node object
n = Node(
id=vm['uuid'],
name=vm['name'],
state=NodeState.RUNNING,
public_ips=public_ips,
private_ips=private_ips,
driver=self,
)
return n
def reboot_node(self, node):
"""
Shuts down a VM and then starts it again.
@inherits: :class:`NodeDriver.reboot_node`
"""
# Turn the VM off
# Black magic to make the POST requests work
d = self.connection._encode_multipart_formdata({'action': 'stop'})
self.connection.request(
'/rest/hosting/vm/%s' % node.id,
method='POST',
headers=d[0],
data=d[1]
).parse_body()
node.state = NodeState.REBOOTING
# Wait for it to turn off and then continue (to turn it on again)
while node.state == NodeState.REBOOTING:
# Check if it's off.
response = self.connection.request(
'/rest/hosting/vm/%s' % node.id
).parse_body()
if response['vm']['state'] == 'off':
node.state = NodeState.TERMINATED
else:
time.sleep(5)
# Turn the VM back on.
# Black magic to make the POST requests work
d = self.connection._encode_multipart_formdata({'action': 'start'})
self.connection.request(
'/rest/hosting/vm/%s' % node.id,
method='POST',
headers=d[0],
data=d[1]
).parse_body()
node.state = NodeState.RUNNING
return True
def destroy_node(self, node):
"""
Shuts down and deletes a VM.
@inherits: :class:`NodeDriver.destroy_node`
"""
# Shut down first
# Black magic to make the POST requests work
d = self.connection._encode_multipart_formdata({'action': 'stop'})
self.connection.request(
'/rest/hosting/vm/%s' % node.id,
method='POST',
headers=d[0],
data=d[1]
).parse_body()
# Ensure there was no application level error
node.state = NodeState.PENDING
# Wait for the VM to turn off before continuing
while node.state == NodeState.PENDING:
# Check if it's off.
response = self.connection.request(
'/rest/hosting/vm/%s' % node.id
).parse_body()
if response['vm']['state'] == 'off':
node.state = NodeState.TERMINATED
else:
time.sleep(5)
# Delete the VM
# Black magic to make the POST requests work
d = self.connection._encode_multipart_formdata({'action': 'delete'})
self.connection.request(
'/rest/hosting/vm/%s' % (node.id),
method='POST',
headers=d[0],
data=d[1]
).parse_body()
return True
def list_images(self, location=None):
"""
Returns a list of all package templates aka appliances aka images.
@inherits: :class:`NodeDriver.list_images`
"""
# Make the call
response = self.connection.request(
'/rest/hosting/ptemplate/list').parse_body()
# Turn the response into an array of NodeImage objects
images = []
for ptemplate in response['packages']:
images.append(NodeImage(
id=ptemplate['uuid'],
name='%s: %s' % (ptemplate['name'], ptemplate['description']),
driver=self,)
)
return images
def list_sizes(self, location=None):
"""
Returns a list of all hardware templates
@inherits: :class:`NodeDriver.list_sizes`
"""
# Make the call
response = self.connection.request(
'/rest/hosting/htemplate/list').parse_body()
# Turn the response into an array of NodeSize objects
sizes = []
for htemplate in response['templates']:
sizes.append(NodeSize(
id=htemplate['uuid'],
name=htemplate['name'],
ram=htemplate['memory'],
disk=0, # Disk is independent of hardware template.
bandwidth=0, # There is no way to keep track of bandwidth.
price=0, # The billing system is external.
driver=self,)
)
return sizes
def list_locations(self):
"""
This feature does not exist in ECP. Returns hard coded dummy location.
:rtype: ``list`` of :class:`NodeLocation`
"""
return [NodeLocation(id=1,
name="Cloud",
country='',
driver=self),
]
def create_node(self, name, size, image):
"""
Creates a virtual machine.
:keyword name: String with a name for this new node (required)
:type name: ``str``
:keyword size: The size of resources allocated to this node .
(required)
:type size: :class:`NodeSize`
:keyword image: OS Image to boot on node. (required)
:type image: :class:`NodeImage`
:rtype: :class:`Node`
"""
# Find out what network to put the VM on.
res = self.connection.request(
'/rest/hosting/network/list').parse_body()
# Use the first / default network because there is no way to specific
# which one
network = res['networks'][0]['uuid']
# Prepare to make the VM
data = {
'name': str(name),
'package': str(image.id),
'hardware': str(size.id),
'network_uuid': str(network),
'disk': ''
}
# Black magic to make the POST requests work
d = self.connection._encode_multipart_formdata(data)
response = self.connection.request(
'/rest/hosting/vm/',
method='PUT',
headers=d[0],
data=d[1]
).parse_body()
# Create a node object and return it.
n = Node(
id=response['machine_id'],
name=data['name'],
state=NodeState.PENDING,
public_ips=[],
private_ips=[],
driver=self,
)
return n
Zerion Mini Shell 1.0