796 lines
31 KiB
Python
796 lines
31 KiB
Python
# 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.
|
|
"""
|
|
Abiquo Compute Driver
|
|
|
|
The driver implements the compute Abiquo functionality for the Abiquo API.
|
|
This version is compatible with the following versions of Abiquo:
|
|
|
|
* Abiquo 3.4 (http://wiki.abiquo.com/display/ABI34/The+Abiquo+API)
|
|
"""
|
|
|
|
from libcloud.utils.py3 import ET
|
|
from libcloud.compute.base import NodeDriver, NodeSize
|
|
from libcloud.compute.types import Provider, LibcloudError
|
|
from libcloud.common.abiquo import (AbiquoConnection, get_href,
|
|
AbiquoResponse)
|
|
from libcloud.compute.base import NodeLocation, NodeImage, Node
|
|
from libcloud.utils.py3 import tostring
|
|
|
|
|
|
class AbiquoNodeDriver(NodeDriver):
|
|
"""
|
|
Implements the :class:`NodeDriver`'s for the Abiquo Compute Provider
|
|
"""
|
|
|
|
type = Provider.ABIQUO
|
|
name = 'Abiquo'
|
|
website = 'http://www.abiquo.com/'
|
|
connectionCls = AbiquoConnection
|
|
timeout = 2000 # some images take a lot of time!
|
|
|
|
# Media Types
|
|
NODES_MIME_TYPE = 'application/vnd.abiquo.virtualmachines+xml'
|
|
NODE_MIME_TYPE = 'application/vnd.abiquo.virtualmachine+xml'
|
|
VAPPS_MIME_TYPE = 'application/vnd.abiquo.virtualappliances+xml'
|
|
VAPP_MIME_TYPE = 'application/vnd.abiquo.virtualappliance+xml'
|
|
VM_TASK_MIME_TYPE = 'application/vnd.abiquo.virtualmachinetask+xml'
|
|
USER_MIME_TYPE = 'application/vnd.abiquo.user+xml'
|
|
ENT_MIME_TYPE = 'application/vnd.abiquo.enterprise+xml'
|
|
VDCS_MIME_TYPE = 'application/vnd.abiquo.virtualdatacenters+xml'
|
|
VDC_MIME_TYPE = 'application/vnd.abiquo.virtualdatacenter+xml'
|
|
DCS_MIME_TYPE = 'application/vnd.abiquo.datacenters+xml'
|
|
VMTPLS_MIME_TYPE = 'application/vnd.abiquo.virtualmachinetemplates+xml'
|
|
VMTPL_MIME_TYPE = 'application/vnd.abiquo.virtualmachinetemplate+xml'
|
|
NICS_MIME_TYPE = 'application/vnd.abiquo.nics+xml'
|
|
DCRS_MIME_TYPE = 'application/vnd.abiquo.datacenterrepositories+xml'
|
|
DCR_MIME_TYPE = 'application/vnd.abiquo.datacenterrepository+xml'
|
|
AR_MIME_TYPE = 'application/vnd.abiquo.acceptedrequest+xml'
|
|
|
|
# Others constants
|
|
GIGABYTE = 1073741824
|
|
|
|
def __init__(self, user_id, secret, endpoint, **kwargs):
|
|
"""
|
|
Initializes Abiquo Driver
|
|
|
|
Initializes the :class:`NodeDriver` object and populate the cache.
|
|
|
|
:param user_id: identifier of Abiquo user (required)
|
|
:type user_id: ``str``
|
|
:param secret: password of the Abiquo user (required)
|
|
:type secret: ``str``
|
|
:param endpoint: Abiquo API endpoint (required)
|
|
:type endpoint: ``str`` that can be parsed as URL
|
|
"""
|
|
self.endpoint = endpoint
|
|
super(AbiquoNodeDriver, self).__init__(key=user_id, secret=secret,
|
|
secure=False, host=None,
|
|
port=None, **kwargs)
|
|
self.ex_populate_cache()
|
|
|
|
def create_node(self, image, name=None, size=None, location=None,
|
|
ex_group_name=None):
|
|
"""
|
|
Create a new node instance in Abiquo
|
|
|
|
All the :class:`Node`s need to be defined inside a VirtualAppliance
|
|
(called :class:`NodeGroup` here). If there is no group name defined,
|
|
'libcloud' name will be used instead.
|
|
|
|
This method wraps these Abiquo actions:
|
|
|
|
1. Create a group if it does not exist.
|
|
2. Register a new node in the group.
|
|
3. Deploy the node and boot it.
|
|
4. Retrieves it again to get schedule-time attributes (such as ips
|
|
and vnc ports).
|
|
|
|
The rest of the driver methods has been created in a way that, if any
|
|
of these actions fail, the user can not reach an inconsistent state
|
|
|
|
:keyword name: The name for this new node (required)
|
|
:type name: ``str``
|
|
|
|
:keyword size: The size of resources allocated to this node.
|
|
:type size: :class:`NodeSize`
|
|
|
|
:keyword image: OS Image to boot on node. (required)
|
|
:type image: :class:`NodeImage`
|
|
|
|
:keyword location: Which data center to create a node in. If empty,
|
|
undefined behavior will be selected. (optional)
|
|
:type location: :class:`NodeLocation`
|
|
|
|
:keyword ex_group_name: Which group this node belongs to. If empty,
|
|
it will be created into 'libcloud' group. If
|
|
it does not found any group in the target
|
|
location (random location if you have not
|
|
set the parameter), then it will create a
|
|
new group with this name.
|
|
:type ex_group_name: c{str}
|
|
|
|
:return: The newly created node.
|
|
:rtype: :class:`Node`
|
|
"""
|
|
# Define the location
|
|
# To be clear:
|
|
# 'xml_loc' is the xml element we navigate into (we need links)
|
|
# 'loc' is the :class:`NodeLocation` entity
|
|
xml_loc, loc = self._define_create_node_location(image=image,
|
|
location=location)
|
|
|
|
# Define the Group
|
|
group = self._define_create_node_group(xml_loc, loc, ex_group_name)
|
|
|
|
# Register the Node
|
|
vm = self._define_create_node_node(group, name=name, size=size,
|
|
image=image)
|
|
|
|
# Execute the 'create' in hypervisor action
|
|
self._deploy_remote(vm)
|
|
|
|
# Retrieve it again, to get some schedule-time defined values
|
|
edit_vm = get_href(vm, 'edit')
|
|
headers = {'Accept': self.NODE_MIME_TYPE}
|
|
vm = self.connection.request(edit_vm, headers=headers).object
|
|
return self._to_node(vm, self)
|
|
|
|
def destroy_node(self, node):
|
|
"""
|
|
Destroy a node
|
|
|
|
Depending on the provider, this may destroy all data associated with
|
|
the node, including backups.
|
|
|
|
:param node: The node to be destroyed
|
|
:type node: :class:`Node`
|
|
|
|
:return: True if the destroy was successful, otherwise False
|
|
:rtype: ``bool``
|
|
"""
|
|
|
|
# Refresh node state
|
|
headers = {'Accept': self.NODE_MIME_TYPE}
|
|
e_vm = self.connection.request(node.extra['uri_id'],
|
|
headers=headers).object
|
|
|
|
state = e_vm.findtext('state')
|
|
|
|
if state in ['ALLOCATED', 'CONFIGURED', 'LOCKED', 'UNKNOWN']:
|
|
raise LibcloudError('Invalid Node state', self)
|
|
|
|
if state != 'NOT_ALLOCATED':
|
|
# prepare the element that forces the undeploy
|
|
vm_task = ET.Element('virtualmachinetask')
|
|
force_undeploy = ET.SubElement(vm_task, 'forceUndeploy')
|
|
force_undeploy.text = 'True'
|
|
# Set the URI
|
|
destroy_uri = node.extra['uri_id'] + '/action/undeploy'
|
|
# Prepare the headers
|
|
headers = {'Accept': self.AR_MIME_TYPE,
|
|
'Content-type': self.VM_TASK_MIME_TYPE}
|
|
res = self.connection.async_request(action=destroy_uri,
|
|
method='POST',
|
|
data=tostring(vm_task),
|
|
headers=headers)
|
|
|
|
# pylint: disable=maybe-no-member
|
|
if state == 'NOT_ALLOCATED' or res.async_success():
|
|
# pylint: enable=maybe-no-member
|
|
self.connection.request(action=node.extra['uri_id'],
|
|
method='DELETE')
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def ex_run_node(self, node):
|
|
"""
|
|
Runs a node
|
|
|
|
Here there is a bit difference between Abiquo states and libcloud
|
|
states, so this method is created to have better compatibility. In
|
|
libcloud, if the node is not running, then it does not exist (avoiding
|
|
UNKNOWN and temporal states). In Abiquo, you can define a node, and
|
|
then deploy it.
|
|
|
|
If the node is in :class:`NodeState.TERMINATED` libcloud's state and in
|
|
'NOT_DEPLOYED' Abiquo state, there is a way to run and recover it
|
|
for libcloud using this method. There is no way to reach this state
|
|
if you are using only libcloud, but you may have used another Abiquo
|
|
client and now you want to recover your node to be used by libcloud.
|
|
|
|
:param node: The node to run
|
|
:type node: :class:`Node`
|
|
|
|
:return: The node itself, but with the new state
|
|
:rtype: :class:`Node`
|
|
"""
|
|
# Refresh node state
|
|
e_vm = self.connection.request(node.extra['uri_id']).object
|
|
state = e_vm.findtext('state')
|
|
|
|
if state != 'NOT_ALLOCATED':
|
|
raise LibcloudError('Invalid Node state', self)
|
|
|
|
# --------------------------------------------------------
|
|
# Deploy the Node
|
|
# --------------------------------------------------------
|
|
self._deploy_remote(e_vm)
|
|
|
|
# --------------------------------------------------------
|
|
# Retrieve it again, to get some schedule-defined
|
|
# values.
|
|
# --------------------------------------------------------
|
|
edit_vm = get_href(e_vm, 'edit')
|
|
headers = {'Accept': self.NODE_MIME_TYPE}
|
|
e_vm = self.connection.request(edit_vm, headers=headers).object
|
|
return self._to_node(e_vm, self)
|
|
|
|
def ex_populate_cache(self):
|
|
"""
|
|
Populate the cache.
|
|
|
|
For each connection, it is good to store some objects that will be
|
|
useful for further requests, such as the 'user' and the 'enterprise'
|
|
objects.
|
|
|
|
Executes the 'login' resource after setting the connection parameters
|
|
and, if the execution is successful, it sets the 'user' object into
|
|
cache. After that, it also requests for the 'enterprise' and
|
|
'locations' data.
|
|
|
|
List of locations should remain the same for a single libcloud
|
|
connection. However, this method is public and you are able to
|
|
refresh the list of locations any time.
|
|
"""
|
|
|
|
user_headers = {'Accept': self.USER_MIME_TYPE}
|
|
user = self.connection.request('/login', headers=user_headers).object
|
|
self.connection.cache['user'] = user
|
|
e_ent = get_href(self.connection.cache['user'],
|
|
'enterprise')
|
|
ent_headers = {'Accept': self.ENT_MIME_TYPE}
|
|
ent = self.connection.request(e_ent, headers=ent_headers).object
|
|
self.connection.cache['enterprise'] = ent
|
|
|
|
vdcs_headers = {'Accept': self.VDCS_MIME_TYPE}
|
|
uri_vdcs = '/cloud/virtualdatacenters'
|
|
e_vdcs = self.connection.request(uri_vdcs, headers=vdcs_headers).object
|
|
|
|
params = {"idEnterprise": self._get_enterprise_id()}
|
|
|
|
dcs_headers = {'Accept': self.DCS_MIME_TYPE}
|
|
e_dcs = self.connection.request('/admin/datacenters',
|
|
headers=dcs_headers,
|
|
params=params).object
|
|
dc_dict = {}
|
|
for dc in e_dcs.findall('datacenter'):
|
|
key = get_href(dc, 'self')
|
|
dc_dict[key] = dc
|
|
|
|
# Populate locations name cache
|
|
self.connection.cache['locations'] = {}
|
|
for e_vdc in e_vdcs.findall('virtualDatacenter'):
|
|
loc = get_href(e_vdc, 'location')
|
|
if loc is not None:
|
|
self.connection.cache['locations'][loc] = get_href(e_vdc,
|
|
'edit')
|
|
|
|
def ex_create_group(self, name, location=None):
|
|
"""
|
|
Create an empty group.
|
|
|
|
You can specify the location as well.
|
|
|
|
:param group: name of the group (required)
|
|
:type group: ``str``
|
|
|
|
:param location: location were to create the group
|
|
:type location: :class:`NodeLocation`
|
|
|
|
:returns: the created group
|
|
:rtype: :class:`NodeGroup`
|
|
"""
|
|
# prepare the element
|
|
vapp = ET.Element('virtualAppliance')
|
|
vapp_name = ET.SubElement(vapp, 'name')
|
|
vapp_name.text = name
|
|
|
|
if location is None:
|
|
location = self.list_locations()[0]
|
|
elif location not in self.list_locations():
|
|
raise LibcloudError('Location does not exist')
|
|
|
|
link_vdc = self.connection.cache['locations'][location]
|
|
hdr_vdc = {'Accept': self.VDC_MIME_TYPE}
|
|
e_vdc = self.connection.request(link_vdc, headers=hdr_vdc).object
|
|
|
|
creation_link = get_href(e_vdc, 'virtualappliances')
|
|
headers = {'Accept': self.VAPP_MIME_TYPE,
|
|
'Content-type': self.VAPP_MIME_TYPE}
|
|
vapp = self.connection.request(creation_link, data=tostring(vapp),
|
|
headers=headers, method='POST').object
|
|
|
|
uri_vapp = get_href(vapp, 'edit')
|
|
|
|
return NodeGroup(self, vapp.findtext('name'),
|
|
uri=uri_vapp)
|
|
|
|
def ex_destroy_group(self, group):
|
|
"""
|
|
Destroy a group.
|
|
|
|
Be careful! Destroying a group means destroying all the :class:`Node`
|
|
instances there and the group itself!
|
|
|
|
If there is currently any action over any :class:`Node` of the
|
|
:class:`NodeGroup`, then the method will raise an exception.
|
|
|
|
:param name: The group (required)
|
|
:type name: :class:`NodeGroup`
|
|
|
|
:return: If the group was destroyed successfully
|
|
:rtype: ``bool``
|
|
"""
|
|
# Refresh group state
|
|
e_group = self.connection.request(group.uri).object
|
|
state = e_group.findtext('state')
|
|
|
|
if state not in ['NOT_DEPLOYED', 'DEPLOYED']:
|
|
error = 'Can not destroy group because of current state'
|
|
raise LibcloudError(error, self)
|
|
|
|
if state == 'DEPLOYED':
|
|
# prepare the element that forces the undeploy
|
|
vm_task = ET.Element('virtualmachinetask')
|
|
force_undeploy = ET.SubElement(vm_task, 'forceUndeploy')
|
|
force_undeploy.text = 'True'
|
|
|
|
# Set the URI
|
|
undeploy_uri = group.uri + '/action/undeploy'
|
|
|
|
# Prepare the headers
|
|
headers = {'Accept': self.AR_MIME_TYPE,
|
|
'Content-type': self.VM_TASK_MIME_TYPE}
|
|
res = self.connection.async_request(action=undeploy_uri,
|
|
method='POST',
|
|
data=tostring(vm_task),
|
|
headers=headers)
|
|
|
|
# pylint: disable=maybe-no-member
|
|
if state == 'NOT_DEPLOYED' or res.async_success():
|
|
# pylint: enable=maybe-no-member
|
|
# The node is no longer deployed. Unregister it.
|
|
self.connection.request(action=group.uri,
|
|
method='DELETE')
|
|
return True
|
|
else:
|
|
return False
|
|
|
|
def ex_list_groups(self, location=None):
|
|
"""
|
|
List all groups.
|
|
|
|
:param location: filter the groups by location (optional)
|
|
:type location: a :class:`NodeLocation` instance.
|
|
|
|
:return: the list of :class:`NodeGroup`
|
|
"""
|
|
groups = []
|
|
for vdc in self._get_locations(location):
|
|
link_vdc = self.connection.cache['locations'][vdc]
|
|
hdr_vdc = {'Accept': self.VDC_MIME_TYPE}
|
|
e_vdc = self.connection.request(link_vdc, headers=hdr_vdc).object
|
|
apps_link = get_href(e_vdc, 'virtualappliances')
|
|
hdr_vapps = {'Accept': self.VAPPS_MIME_TYPE}
|
|
vapps = self.connection.request(apps_link,
|
|
headers=hdr_vapps).object
|
|
for vapp in vapps.findall('virtualAppliance'):
|
|
nodes = []
|
|
vms_link = get_href(vapp, 'virtualmachines')
|
|
headers = {'Accept': self.NODES_MIME_TYPE}
|
|
vms = self.connection.request(vms_link, headers=headers).object
|
|
for vm in vms.findall('virtualMachine'):
|
|
nodes.append(self._to_node(vm, self))
|
|
group = NodeGroup(self, vapp.findtext('name'),
|
|
nodes, get_href(vapp, 'edit'))
|
|
groups.append(group)
|
|
|
|
return groups
|
|
|
|
def list_images(self, location=None):
|
|
"""
|
|
List images on Abiquo Repositories
|
|
|
|
:keyword location: The location to list images for.
|
|
:type location: :class:`NodeLocation`
|
|
|
|
:return: list of node image objects
|
|
:rtype: ``list`` of :class:`NodeImage`
|
|
"""
|
|
enterprise_id = self._get_enterprise_id()
|
|
uri = '/admin/enterprises/%s/datacenterrepositories/' % (enterprise_id)
|
|
repos_hdr = {'Accept': self.DCRS_MIME_TYPE}
|
|
repos = self.connection.request(uri, headers=repos_hdr).object
|
|
|
|
images = []
|
|
for repo in repos.findall('datacenterRepository'):
|
|
# filter by location. Skips when the name of the location
|
|
# is different from the 'datacenterRepository' element
|
|
for vdc in self._get_locations(location):
|
|
# Check if the virtual datacenter belongs to this repo
|
|
link_vdc = self.connection.cache['locations'][vdc]
|
|
hdr_vdc = {'Accept': self.VDC_MIME_TYPE}
|
|
e_vdc = self.connection.request(link_vdc,
|
|
headers=hdr_vdc).object
|
|
dc_link_vdc = get_href(e_vdc, 'location')
|
|
dc_link_repo = get_href(repo, 'datacenter')
|
|
|
|
if dc_link_vdc.split("/")[-1] == dc_link_repo.split("/")[-1]:
|
|
# Filter the template in case we don't have it yet
|
|
url_templates = get_href(repo, 'virtualmachinetemplates')
|
|
hypervisor_type = e_vdc.findtext('hypervisorType')
|
|
params = {'hypervisorTypeName': hypervisor_type}
|
|
headers = {'Accept': self.VMTPLS_MIME_TYPE}
|
|
templates = self.connection.request(url_templates, params,
|
|
headers=headers).object
|
|
for templ in templates.findall('virtualMachineTemplate'):
|
|
# Avoid duplicated templates
|
|
id_template = templ.findtext('id')
|
|
ids = [image.id for image in images]
|
|
if id_template not in ids:
|
|
images.append(self._to_nodeimage(templ, self,
|
|
get_href(repo,
|
|
'edit')))
|
|
|
|
return images
|
|
|
|
def list_locations(self):
|
|
"""
|
|
Return list of locations where the user has access to.
|
|
|
|
:return: the list of :class:`NodeLocation` available for the current
|
|
user
|
|
:rtype: ``list`` of :class:`NodeLocation`
|
|
"""
|
|
return list(self.connection.cache['locations'].keys())
|
|
|
|
def list_nodes(self, location=None):
|
|
"""
|
|
List all nodes.
|
|
|
|
:param location: Filter the groups by location (optional)
|
|
:type location: a :class:`NodeLocation` instance.
|
|
|
|
:return: List of node objects
|
|
:rtype: ``list`` of :class:`Node`
|
|
"""
|
|
nodes = []
|
|
|
|
for group in self.ex_list_groups(location):
|
|
nodes.extend(group.nodes)
|
|
|
|
return nodes
|
|
|
|
def list_sizes(self, location=None):
|
|
"""
|
|
List sizes on a provider.
|
|
|
|
Abiquo does not work with sizes. However, this method
|
|
returns a list of predefined ones (copied from :class:`DummyNodeDriver`
|
|
but without price neither bandwidth) to help the users to create their
|
|
own.
|
|
|
|
If you call the method :class:`AbiquoNodeDriver.create_node` with the
|
|
size informed, it will just override the 'ram' value of the 'image'
|
|
template. So it is no too much usefull work with sizes...
|
|
|
|
:return: The list of sizes
|
|
:rtype: ``list`` of :class:`NodeSizes`
|
|
"""
|
|
return [
|
|
NodeSize(id=1,
|
|
name='Small',
|
|
ram=128,
|
|
disk=4,
|
|
bandwidth=None,
|
|
price=None,
|
|
driver=self),
|
|
NodeSize(id=2,
|
|
name='Medium',
|
|
ram=512,
|
|
disk=16,
|
|
bandwidth=None,
|
|
price=None,
|
|
driver=self),
|
|
NodeSize(id=3,
|
|
name='Big',
|
|
ram=4096,
|
|
disk=32,
|
|
bandwidth=None,
|
|
price=None,
|
|
driver=self),
|
|
NodeSize(id=4,
|
|
name="XXL Big",
|
|
ram=4096 * 2,
|
|
disk=32 * 4,
|
|
bandwidth=None,
|
|
price=None,
|
|
driver=self)
|
|
]
|
|
|
|
def reboot_node(self, node):
|
|
"""
|
|
Reboot a node.
|
|
|
|
:param node: The node to be rebooted
|
|
:type node: :class:`Node`
|
|
|
|
:return: True if the reboot was successful, otherwise False
|
|
:rtype: ``bool``
|
|
"""
|
|
reboot_uri = node.extra['uri_id'] + '/action/reset'
|
|
reboot_hdr = {'Accept': self.AR_MIME_TYPE}
|
|
res = self.connection.async_request(action=reboot_uri,
|
|
method='POST', headers=reboot_hdr)
|
|
return res.async_success() # pylint: disable=maybe-no-member
|
|
|
|
# -------------------------
|
|
# Extenstion methods
|
|
# -------------------------
|
|
|
|
def _ex_connection_class_kwargs(self):
|
|
"""
|
|
Set the endpoint as an extra :class:`AbiquoConnection` argument.
|
|
|
|
According to Connection code, the "url" argument should be
|
|
parsed properly to connection.
|
|
|
|
:return: ``dict`` of :class:`AbiquoConnection` input arguments
|
|
"""
|
|
|
|
return {'url': self.endpoint}
|
|
|
|
def _deploy_remote(self, e_vm):
|
|
"""
|
|
Asynchronous call to create the node.
|
|
"""
|
|
# --------------------------------------------------------
|
|
# Deploy the Node
|
|
# --------------------------------------------------------
|
|
# prepare the element that forces the deploy
|
|
vm_task = ET.Element('virtualmachinetask')
|
|
force_deploy = ET.SubElement(vm_task, 'forceEnterpriseSoftLimits')
|
|
force_deploy.text = 'True'
|
|
|
|
# Prepare the headers
|
|
headers = {'Accept': self.AR_MIME_TYPE,
|
|
'Content-type': self.VM_TASK_MIME_TYPE}
|
|
link_deploy = get_href(e_vm, 'deploy')
|
|
res = self.connection.async_request(action=link_deploy, method='POST',
|
|
data=tostring(vm_task),
|
|
headers=headers)
|
|
if not res.async_success(): # pylint: disable=maybe-no-member
|
|
raise LibcloudError('Could not run the node', self)
|
|
|
|
def _to_location(self, vdc, dc, driver):
|
|
"""
|
|
Generates the :class:`NodeLocation` class.
|
|
"""
|
|
identifier = vdc.findtext('id')
|
|
name = vdc.findtext('name')
|
|
country = dc.findtext('name')
|
|
return NodeLocation(identifier, name, country, driver)
|
|
|
|
def _to_node(self, vm, driver):
|
|
"""
|
|
Generates the :class:`Node` class.
|
|
"""
|
|
identifier = vm.findtext('id')
|
|
name = vm.findtext('label')
|
|
state = AbiquoResponse.NODE_STATE_MAP[vm.findtext('state')]
|
|
|
|
link_image = get_href(vm, 'virtualmachinetemplate')
|
|
link_hdr = {'Accept': self.VMTPL_MIME_TYPE}
|
|
image_element = self.connection.request(link_image,
|
|
headers=link_hdr).object
|
|
repo_link = get_href(image_element, 'datacenterrepository')
|
|
image = self._to_nodeimage(image_element, self, repo_link)
|
|
|
|
# Fill the 'ips' data
|
|
private_ips = []
|
|
public_ips = []
|
|
nics_hdr = {'Accept': self.NICS_MIME_TYPE}
|
|
nics_element = self.connection.request(get_href(vm, 'nics'),
|
|
headers=nics_hdr).object
|
|
for nic in nics_element.findall('nic'):
|
|
ip = nic.findtext('ip')
|
|
for link in nic.findall('link'):
|
|
rel = link.attrib['rel']
|
|
if rel == 'privatenetwork':
|
|
private_ips.append(ip)
|
|
elif rel in ['publicnetwork', 'externalnetwork',
|
|
'unmanagednetwork']:
|
|
public_ips.append(ip)
|
|
|
|
extra = {'uri_id': get_href(vm, 'edit')}
|
|
|
|
if vm.find('vdrpIp') is not None:
|
|
extra['vdrp_ip'] = vm.findtext('vdrpIP')
|
|
extra['vdrp_port'] = vm.findtext('vdrpPort')
|
|
|
|
return Node(identifier, name, state, public_ips, private_ips,
|
|
driver, image=image, extra=extra)
|
|
|
|
def _to_nodeimage(self, template, driver, repo):
|
|
"""
|
|
Generates the :class:`NodeImage` class.
|
|
"""
|
|
identifier = template.findtext('id')
|
|
name = template.findtext('name')
|
|
url = get_href(template, 'edit')
|
|
hdreqd = template.findtext('hdRequired')
|
|
extra = {'repo': repo, 'url': url, 'hdrequired': hdreqd}
|
|
return NodeImage(identifier, name, driver, extra)
|
|
|
|
def _get_locations(self, location=None):
|
|
"""
|
|
Returns the locations as a generator.
|
|
"""
|
|
if location is not None:
|
|
yield location
|
|
else:
|
|
for loc in self.list_locations():
|
|
yield loc
|
|
|
|
def _get_enterprise_id(self):
|
|
"""
|
|
Returns the identifier of the logged user's enterprise.
|
|
"""
|
|
return self.connection.cache['enterprise'].findtext('id')
|
|
|
|
def _define_create_node_location(self, image, location):
|
|
"""
|
|
Search for a location where to create the node.
|
|
|
|
Based on 'create_node' **kwargs argument, decide in which
|
|
location will be created.
|
|
"""
|
|
# First, get image location
|
|
if not image:
|
|
error = "'image' parameter is mandatory"
|
|
raise LibcloudError(error, self)
|
|
|
|
# Get the location argument
|
|
if location:
|
|
if location not in self.list_locations():
|
|
raise LibcloudError('Location does not exist')
|
|
|
|
# Check if the image is compatible with any of the locations or
|
|
# the input location
|
|
loc = None
|
|
target_loc = None
|
|
for candidate_loc in self._get_locations(location):
|
|
link_vdc = self.connection.cache['locations'][candidate_loc]
|
|
hdr_vdc = {'Accept': self.VDC_MIME_TYPE}
|
|
e_vdc = self.connection.request(link_vdc, headers=hdr_vdc).object
|
|
for img in self.list_images(candidate_loc):
|
|
if img.id == image.id:
|
|
loc = e_vdc
|
|
target_loc = candidate_loc
|
|
break
|
|
|
|
if loc is None:
|
|
error = 'The image can not be used in any location'
|
|
raise LibcloudError(error, self)
|
|
|
|
return loc, target_loc
|
|
|
|
def _define_create_node_group(self, xml_loc, loc, group_name=None):
|
|
"""
|
|
Search for a group where to create the node.
|
|
|
|
If we can not find any group, create it into argument 'location'
|
|
"""
|
|
if not group_name:
|
|
group_name = NodeGroup.DEFAULT_GROUP_NAME
|
|
|
|
# We search if the group is already defined into the location
|
|
groups_link = get_href(xml_loc, 'virtualappliances')
|
|
groups_hdr = {'Accept': self.VAPPS_MIME_TYPE}
|
|
vapps_element = self.connection.request(groups_link,
|
|
headers=groups_hdr).object
|
|
target_group = None
|
|
for vapp in vapps_element.findall('virtualAppliance'):
|
|
if vapp.findtext('name') == group_name:
|
|
uri_vapp = get_href(vapp, 'edit')
|
|
return NodeGroup(self, vapp.findtext('name'), uri=uri_vapp)
|
|
|
|
# target group not found: create it. Since it is an extension of
|
|
# the basic 'libcloud' functionality, we try to be as flexible as
|
|
# possible.
|
|
if target_group is None:
|
|
return self.ex_create_group(group_name, loc)
|
|
|
|
def _define_create_node_node(self, group, name=None, size=None,
|
|
image=None):
|
|
"""
|
|
Defines the node before to create.
|
|
|
|
In Abiquo, you first need to 'register' or 'define' the node in
|
|
the API before to create it into the target hypervisor.
|
|
"""
|
|
vm = ET.Element('virtualMachine')
|
|
if name:
|
|
vmname = ET.SubElement(vm, 'label')
|
|
vmname.text = name
|
|
attrib = {'type': self.VMTPL_MIME_TYPE,
|
|
'rel': 'virtualmachinetemplate',
|
|
'href': image.extra['url']}
|
|
ET.SubElement(vm, 'link', attrib=attrib)
|
|
headers = {'Accept': self.NODE_MIME_TYPE,
|
|
'Content-type': self.NODE_MIME_TYPE}
|
|
|
|
if size:
|
|
# Override the 'NodeSize' data
|
|
ram = ET.SubElement(vm, 'ram')
|
|
ram.text = str(size.ram)
|
|
|
|
# Create the virtual machine
|
|
nodes_link = group.uri + '/virtualmachines'
|
|
vm = self.connection.request(nodes_link, data=tostring(vm),
|
|
headers=headers, method='POST').object
|
|
edit_vm = get_href(vm, 'edit')
|
|
headers = {'Accept': self.NODE_MIME_TYPE}
|
|
|
|
return self.connection.request(edit_vm, headers=headers).object
|
|
|
|
|
|
class NodeGroup(object):
|
|
"""
|
|
Group of virtual machines that can be managed together
|
|
|
|
All :class:`Node`s in Abiquo must be defined inside a Virtual Appliance.
|
|
We offer a way to handle virtual appliances (called NodeGroup to
|
|
maintain some kind of name conventions here) inside the
|
|
:class:`AbiquoNodeDriver` without breaking compatibility of the rest of
|
|
libcloud API.
|
|
|
|
If the user does not want to handle groups, all the virtual machines
|
|
will be created inside a group named 'libcloud'
|
|
"""
|
|
DEFAULT_GROUP_NAME = 'libcloud'
|
|
|
|
def __init__(self, driver, name=DEFAULT_GROUP_NAME, nodes=[], uri=''):
|
|
"""
|
|
Initialize a new group object.
|
|
"""
|
|
self.driver = driver
|
|
self.name = name
|
|
self.nodes = nodes
|
|
self.uri = uri
|
|
|
|
def __repr__(self):
|
|
return (('<NodeGroup: name=%s, nodes=[%s] >')
|
|
% (self.name, ",".join(map(str, self.nodes))))
|
|
|
|
def destroy(self):
|
|
"""
|
|
Destroys the group delegating the execution to
|
|
:class:`AbiquoNodeDriver`.
|
|
"""
|
|
return self.driver.ex_destroy_group(self)
|