blob: def2335cae22be7b1b80f8cf6e15c7065b0b6162 [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.
"""
Slicehost Driver
"""
from libcloud.types import NodeState, Provider
from libcloud.base import ConnectionKey, Response, NodeDriver, Node
from libcloud.base import NodeSize, NodeImage, NodeLocation
import base64
import struct
import socket
from xml.etree import ElementTree as ET
from xml.parsers.expat import ExpatError
class SlicehostResponse(Response):
def parse_body(self):
if not self.body:
return None
return ET.XML(self.body)
def parse_error(self):
try:
object = ET.XML(self.body)
return "; ".join([ err.text
for err in
object.findall('error') ])
except ExpatError:
return self.body
class SlicehostConnection(ConnectionKey):
host = 'api.slicehost.com'
responseCls = SlicehostResponse
def add_default_headers(self, headers):
headers['Authorization'] = ('Basic %s'
% (base64.b64encode('%s:' % self.key)))
return headers
class SlicehostNodeDriver(NodeDriver):
connectionCls = SlicehostConnection
type = Provider.SLICEHOST
name = 'Slicehost'
NODE_STATE_MAP = { 'active': NodeState.RUNNING,
'build': NodeState.PENDING,
'reboot': NodeState.REBOOTING,
'hard_reboot': NodeState.REBOOTING,
'terminated': NodeState.TERMINATED }
def list_nodes(self):
return self._to_nodes(self.connection.request('/slices.xml').object)
def list_sizes(self, location=None):
return self._to_sizes(self.connection.request('/flavors.xml').object)
def list_images(self, location=None):
return self._to_images(self.connection.request('/images.xml').object)
def list_locations(self):
return [
NodeLocation(0, 'Slicehost St. Louis (STL-A)', 'US', self),
NodeLocation(0, 'Slicehost St. Louis (STL-B)', 'US', self),
NodeLocation(0, 'Slicehost Dallas-Fort Worth (DFW-1)', 'US', self)
]
def create_node(self, **kwargs):
name = kwargs['name']
image = kwargs['image']
size = kwargs['size']
uri = '/slices.xml'
# create a slice obj
root = ET.Element('slice')
el_name = ET.SubElement(root, 'name')
el_name.text = name
flavor_id = ET.SubElement(root, 'flavor-id')
flavor_id.text = str(size.id)
image_id = ET.SubElement(root, 'image-id')
image_id.text = str(image.id)
xml = ET.tostring(root)
node = self._to_nodes(
self.connection.request(
uri,
method='POST',
data=xml,
headers={'Content-Type': 'application/xml'}
).object
)[0]
return node
def reboot_node(self, node):
"""Reboot the node by passing in the node object"""
# 'hard' could bubble up as kwarg depending on how reboot_node
# turns out. Defaulting to soft reboot.
#hard = False
#reboot = self.api.hard_reboot if hard else self.api.reboot
#expected_status = 'hard_reboot' if hard else 'reboot'
uri = '/slices/%s/reboot.xml' % (node.id)
node = self._to_nodes(
self.connection.request(uri, method='PUT').object
)[0]
return node.state == NodeState.REBOOTING
def destroy_node(self, node):
"""Destroys the node
Requires 'Allow Slices to be deleted or rebuilt from the API' to be
ticked at https://manage.slicehost.com/api, otherwise returns::
<errors>
<error>You must enable slice deletes in the SliceManager</error>
<error>Permission denied</error>
</errors>
"""
uri = '/slices/%s/destroy.xml' % (node.id)
ret = self.connection.request(uri, method='PUT')
return True
def _to_nodes(self, object):
if object.tag == 'slice':
return [ self._to_node(object) ]
node_elements = object.findall('slice')
return [ self._to_node(el) for el in node_elements ]
def _to_node(self, element):
attrs = [ 'name', 'image-id', 'progress', 'id', 'bw-out', 'bw-in',
'flavor-id', 'status', 'ip-address' ]
node_attrs = {}
for attr in attrs:
node_attrs[attr] = element.findtext(attr)
# slicehost does not determine between public and private, so we
# have to figure it out
public_ip = element.findtext('ip-address')
private_ip = None
for addr in element.findall('addresses/address'):
ip = addr.text
try:
socket.inet_aton(ip)
except socket.error:
# not a valid ip
continue
if self._is_private_subnet(ip):
private_ip = ip
else:
public_ip = ip
try:
state = self.NODE_STATE_MAP[element.findtext('status')]
except:
state = NodeState.UNKNOWN
n = Node(id=element.findtext('id'),
name=element.findtext('name'),
state=state,
public_ip=[public_ip],
private_ip=[private_ip],
driver=self.connection.driver)
return n
def _to_sizes(self, object):
if object.tag == 'flavor':
return [ self._to_size(object) ]
elements = object.findall('flavor')
return [ self._to_size(el) for el in elements ]
def _to_size(self, element):
s = NodeSize(id=int(element.findtext('id')),
name=str(element.findtext('name')),
ram=int(element.findtext('ram')),
disk=None, # XXX: needs hardcode
bandwidth=None, # XXX: needs hardcode
price=float(element.findtext('price'))/(100*24*30),
driver=self.connection.driver)
return s
def _to_images(self, object):
if object.tag == 'image':
return [ self._to_image(object) ]
elements = object.findall('image')
return [ self._to_image(el) for el in elements ]
def _to_image(self, element):
i = NodeImage(id=int(element.findtext('id')),
name=str(element.findtext('name')),
driver=self.connection.driver)
return i
def _is_private_subnet(self, ip):
priv_subnets = [ {'subnet': '10.0.0.0', 'mask': '255.0.0.0'},
{'subnet': '172.16.0.0', 'mask': '172.16.0.0'},
{'subnet': '192.168.0.0', 'mask': '192.168.0.0'} ]
ip = struct.unpack('I',socket.inet_aton(ip))[0]
for network in priv_subnets:
subnet = struct.unpack('I',socket.inet_aton(network['subnet']))[0]
mask = struct.unpack('I',socket.inet_aton(network['mask']))[0]
if (ip & mask) == (subnet & mask):
return True
return False