Adding support for OpenNebula 5.0

This commit is contained in:
Adolfo Gómez García 2016-07-18 20:12:38 +02:00
parent 6827380e99
commit 1578c92a88
7 changed files with 265 additions and 93 deletions

View File

@ -39,7 +39,7 @@ from . import on
import pickle
import logging
__updated__ = '2016-02-26'
__updated__ = '2016-07-11'
logger = logging.getLogger(__name__)
@ -306,6 +306,7 @@ class LiveDeployment(UserDeployment):
return State.RUNNING
except Exception as e:
logger.exception('Got Exception')
return self.__error(e)
# Queue execution methods

View File

@ -40,7 +40,7 @@ from uds.core.ui import gui
import logging
__updated__ = '2016-04-21'
__updated__ = '2016-07-11'
logger = logging.getLogger(__name__)
@ -88,7 +88,7 @@ class LiveService(Service):
# : Types of deploys (services in cache and/or assigned to users)
deployedType = LiveDeployment
allowedProtocols = protocols.GENERIC + (protocols.SPICE,)
allowedProtocols = protocols.GENERIC
servicesTypeProvided = (serviceTypes.VDI,)
# Now the form part

View File

@ -48,7 +48,7 @@ import logging
import six
# Python bindings for OpenNebula
import oca
# import oca
__updated__ = '2016-07-11'
@ -129,8 +129,9 @@ class Provider(ServiceProvider):
@property
def api(self):
if self._api is None:
self._api = oca.Client('{}:{}'.format(self.username.value, self.password.value), self.endpoint)
self._api = on.OpenNebulaClient(self.username.value, self.password.value, self.endpoint)
logger.debug('Api: {}'.format(self._api))
return self._api
def resetApi(self):
@ -149,35 +150,13 @@ class Provider(ServiceProvider):
'''
try:
ver = self.api.version()
if ver < '4.1' or ver >= '5':
return [False, 'OpenNebula version is not supported (required version 4.x)']
if self.api.version[0] < '4':
return [False, 'OpenNebula version is not supported (required version 4.1 or newer)']
except Exception as e:
return [False, '{}'.format(e)]
return [True, _('Opennebula test connection passed')]
def getMachines(self, force=False):
'''
Obtains the list of machines inside OpenNebula.
Machines starting with UDS are filtered out
Args:
force: If true, force to update the cache, if false, tries to first
get data from cache and, if valid, return this.
Returns
An array of dictionaries, containing:
'name'
'id'
'cluster_id'
'''
vmpool = oca.VirtualMachinePool(self.api)
vmpool.info()
return vmpool
def getDatastores(self, datastoreType=0):
return on.storage.enumerateDatastores(self.api, datastoreType)
@ -204,12 +183,7 @@ class Provider(ServiceProvider):
Returns:
one of the on.VmState Values
'''
try:
vm = oca.VirtualMachine.new_with_id(self.api, int(machineId))
vm.info()
return vm.state
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
return on.vm.getMachineState(self.api, machineId)
def startMachine(self, machineId):

View File

@ -38,12 +38,10 @@ import re
import logging
import six
from defusedxml import minidom
import xmlrpclib
from uds.core.util import xml2dict
# Python bindings for OpenNebula
import oca
__updated__ = '2016-02-25'
__updated__ = '2016-07-18'
logger = logging.getLogger(__name__)
@ -61,3 +59,218 @@ from . import template
from . import vm
from . import storage
# Decorator
def ensureConnected(fnc):
def inner(*args, **kwargs):
args[0].connect()
return fnc(*args, **kwargs)
return inner
# Result checker
def checkResult(lst, parseResult=True):
if lst[0] == False:
raise Exception('OpenNebula error {}: "{}"'.format(lst[2], lst[1]))
if parseResult:
return xml2dict.parse(lst[1])
else:
return lst[1]
def asList(element):
if isinstance(element, (tuple, list)):
return element
return (element,)
class OpenNebulaClient(object):
def __init__(self, username, password, endpoint):
self.username = username
self.password = password
self.endpoint = endpoint
self.connection = None
self.cachedVersion = None
@property
def sessionString(self):
return '{}:{}'.format(self.username, self.password)
@property
@ensureConnected
def version(self):
if self.cachedVersion is None:
# Retrieve Version & keep it
result = self.connection.one.system.version(self.sessionString)
self.cachedVersion = checkResult(result, parseResult=False).split('.')
return self.cachedVersion
def connect(self):
if self.connection is not None:
return
self.connection = xmlrpclib.ServerProxy(self.endpoint)
@ensureConnected
def enumStorage(self, storageType=0):
storageType = six.text_type(storageType) # Ensure it is an string
# Invoke datastore pools info, no parameters except connection string
result = self.connection.one.datastorepool.info(self.sessionString)
result = checkResult(result)
for ds in asList(result['DATASTORE_POOL']['DATASTORE']):
if ds['TYPE'] == storageType:
yield(ds['ID'], ds['NAME'], ds['TOTAL_MB'], ds['FREE_MB'])
@ensureConnected
def enumTemplates(self):
'''
Invoke templates pools info, with this parameters:
1.- Session string
2.- Filter flag - < = -3: Connected users resources - -2: All resources - -1: Connected users and his groups resources - > = 0: UID Users Resources
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
'''
result = self.connection.one.templatepool.info(self.sessionString, -3, -1, -1)
result = checkResult(result)
for ds in asList(result['VMTEMPLATE_POOL']['VMTEMPLATE']):
yield(ds['ID'], ds['NAME'], ds['TEMPLATE']['MEMORY'])
@ensureConnected
def enumImages(self):
'''
Invoke images pools info, with this parameters:
1.- Session string
2.- Filter flag - < = -3: Connected users resources - -2: All resources - -1: Connected users and his groups resources - > = 0: UID Users Resources
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
'''
result = self.connection.one.imagepool.info(self.sessionString, -3, -1, -1)
result = checkResult(result)
for ds in asList(result['IMAGE_POOL']['IMAGE']):
yield(ds['ID'], ds['NAME'])
@ensureConnected
def templateInfo(self, templateId, extraInfo=False):
'''
Returns a list
first element is a dictionary (built from XML)
second is original XML
'''
result = self.connection.one.template.info(self.sessionString, int(templateId), extraInfo)
res = checkResult(result)
return (res, result[1])
@ensureConnected
def instantiateTemplate(self, templateId, vmName, createHold=False, templateToMerge='', privatePersistent=False):
'''
Instantiates a template (compatible with open nebula 4 & 5)
1.- Session string
2.- ID Of the template to instantiate
3.- Name of the vm. If empty, open nebula will assign one
4.- False to create machine on pending (default), True to create it on hold
5.- A string containing an extra template to be merged with the one being instantiated. It can be empty. Syntax can be the usual attribute=value or XML.
6.- true to create a private persistent copy of the template plus any image defined in DISK, and instantiate that copy.
Note: This parameter is ignored on version 4, it is new for version 5.
'''
if self.version[0] == '4': # Version 4 has one less parameter than version 5
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge)
else:
result = self.connection.one.template.instantiate(self.sessionString, int(templateId), vmName, createHold, templateToMerge, privatePersistent)
return checkResult(result, parseResult=False)
@ensureConnected
def updateTemplate(self, templateId, template, updateType=0):
'''
Updates the template with the templateXml
1.- Session string
2.- Object ID (integer)
3.- The new template contents. Syntax can be the usual attribute=value or XML.
4.- Update type. 0 replace the whole template, 1 merge with the existing one
'''
result = self.connection.one.template.update(self.sessionString, int(templateId), template, int(updateType))
return checkResult(result, parseResult=False)
@ensureConnected
def cloneTemplate(self, templateId, name):
'''
Clones the template
'''
if self.version[0] == '4':
result = self.connection.one.template.clone(self.sessionString, int(templateId), name)
else:
result = self.connection.one.template.clone(self.sessionString, int(templateId), name, False) # This works as previous version clone
return checkResult(result, parseResult=False)
@ensureConnected
def deleteTemplate(self, templateId):
'''
'''
result = self.connection.one.template.delete(self.sessionString, int(templateId))
return checkResult(result, parseResult=False)
@ensureConnected
def cloneImage(self, srcId, name, datastoreId=-1):
'''
Clones the image.
'''
result = self.connection.one.image.clone(self.sessionString, int(srcId), name, int(datastoreId))
return checkResult(result, parseResult=False)
@ensureConnected
def deleteImage(self, imageId):
'''
Deletes an image
'''
result = self.connection.one.image.delete(self.sessionString, int(imageId))
return checkResult(result, parseResult=False)
@ensureConnected
def enumVMs(self):
'''
Invoke vm pools info, with this parameters:
1.- Session string
2.- Filter flag - < = -3: Connected users resources - -2: All resources - -1: Connected users and his groups resources - > = 0: UID Users Resources
3.- When the next parameter is >= -1 this is the Range start ID. Can be -1. For smaller values this is the offset used for pagination.
4.- For values >= -1 this is the Range end ID. Can be -1 to get until the last ID. For values < -1 this is the page size used for pagination.
5.- VM state to filter by. (-2 = any state including DONE, -1 = any state EXCEPT DONE)
'''
result = self.connection.one.vmpool.info(self.sessionString, -3, -1, -1, -1)
result = checkResult(result)
for ds in asList(result['VM_POOL']['VM']):
yield(ds['ID'], ds['NAME'])
@ensureConnected
def VMInfo(self, vmId):
'''
Returns a list
first element is a dictionary (built from XML)
second is original XML
'''
result = self.connection.one.vm.info(self.sessionString, int(vmId))
res = checkResult(result)
return (res, result[1])
@ensureConnected
def deleteVM(self, vmId):
'''
Deletes an vm
'''
if self.version[0] == '4':
result = self.VMAction(vmId, 'delete')
else:
# Version 5
result = self.VMAction(vmId, 'terminate-hard')
return checkResult(result, parseResult=False)
@ensureConnected
def getVMState(self, vmId):
'''
Returns the VM State
'''
result = self.connection.one.vm.info(self.sessionString, int(vmId))
return int(checkResult(result)['VM']['STATE'])
@ensureConnected
def VMAction(self, vmId, action):
result = self.connection.one.vm.action(self.sessionString, action, int(vmId))
return checkResult(result, parseResult=False)

View File

@ -32,11 +32,9 @@
'''
import logging
import six
import oca
__updated__ = '2016-02-09'
__updated__ = '2016-07-11'
logger = logging.getLogger(__name__)
@ -44,9 +42,4 @@ def enumerateDatastores(api, datastoreType=0):
'''
0 seems to be images datastore
'''
datastores = oca.DatastorePool(api)
datastores.info()
for ds in datastores:
if ds.type == datastoreType:
yield (ds.id, ds.name)
return api.enumStorage(datastoreType)

View File

@ -33,26 +33,21 @@
import logging
import six
import oca
from defusedxml import minidom
# Python bindings for OpenNebula
from .common import sanitizeName
__updated__ = '2016-02-09'
__updated__ = '2016-07-18'
logger = logging.getLogger(__name__)
def getTemplates(api, force=False):
logger.debug('Api: {}'.format(api))
templatesPool = oca.VmTemplatePool(api)
templatesPool.info()
for t in templatesPool:
if t.name[:4] != 'UDSP':
yield (t.id, t.name)
for t in api.enumTemplates():
if t[1][:4] != 'UDSP': # 0 = id, 1 = name
yield t
def create(api, fromTemplateId, name, toDataStore):
'''
@ -71,15 +66,14 @@ def create(api, fromTemplateId, name, toDataStore):
'''
try:
# First, we clone the themplate itself
templateId = api.call('template.clone', int(fromTemplateId), name)
# templateId = api.call('template.clone', int(fromTemplateId), name)
templateId = api.cloneTemplate(fromTemplateId, name)
# Now copy cloned images if possible
try:
imgs = oca.ImagePool(api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
imgs = dict(((i[1], i[0]) for i in api.enumImages()))
info = api.call('template.info', templateId)
info = api.templateInfo(templateId)[1]
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
@ -102,14 +96,15 @@ def create(api, fromTemplateId, name, toDataStore):
# Now clone the image
imgName = sanitizeName(name + ' DSK ' + six.text_type(counter))
newId = api.call('image.clone', int(imgId), imgName, int(toDataStore))
newId = api.cloneImage(imgId, imgName, toDataStore) # api.call('image.clone', int(imgId), imgName, int(toDataStore))
if fromId is True:
node.data = six.text_type(newId)
else:
node.data = imgName
# Now update the clone
api.call('template.update', templateId, template.toxml())
# api.call('template.update', templateId, template.toxml())
api.updateTemplate(templateId, template.toxml())
except Exception:
logger.exception('Exception cloning image')
@ -128,11 +123,9 @@ def remove(api, templateId):
# First, remove Images (wont be possible if there is any images already in use, but will try)
# Now copy cloned images if possible
try:
imgs = oca.ImagePool(api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
imgs = dict(((i[1], i[0]) for i in api.enumImages()))
info = api.call('template.info', int(templateId))
info = api.templateInfo(templateId)[1]
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
@ -148,12 +141,12 @@ def remove(api, templateId):
logger.debug('Found {} for cloning'.format(imgId))
# Now delete the image
api.call('image.delete', int(imgId))
api.deleteImage(imgId) # api.call('image.delete', int(imgId))
except:
logger.exception('Exception cloning image')
api.call('template.delete', int(templateId))
api.deleteTemplate(templateId) # api.call('template.delete', int(templateId))
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
@ -165,13 +158,9 @@ def deployFrom(api, templateId, name):
name: Name (sanitized) of the machine
comments: Comments for machine
templateId: Id of the template to deploy from
clusterId: Id of the cluster to deploy to
displayType: 'vnc' or 'spice'. Display to use ad OpenNebula admin interface
memoryMB: Memory requested for machine, in MB
guaranteedMB: Minimum memory guaranteed for this machine
Returns:
Id of the machine being created form template
'''
vmId = api.call('template.instantiate', int(templateId), name, False, '')
vmId = api.instantiateTemplate(templateId, name, False, '', False) # api.call('template.instantiate', int(templateId), name, False, '')
return six.text_type(vmId)

View File

@ -33,7 +33,7 @@
import logging
import six
import oca
# import oca
from defusedxml import minidom
# Python bindings for OpenNebula
@ -56,9 +56,10 @@ def getMachineState(api, machineId):
one of the on.VmState Values
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.info()
return vm.state
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
# vm.info()
# return vm.state
return api.getVMState(machineId)
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
@ -77,8 +78,9 @@ def startMachine(api, machineId):
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.resume()
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
# vm.resume()
api.VMAction(machineId, 'resume')
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
@ -92,8 +94,9 @@ def stopMachine(api, machineId):
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.poweroff_hard()
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
# vm.poweroff_hard()
api.VMAction(machineId, 'poweroff-hard')
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
@ -119,13 +122,14 @@ def removeMachine(api, machineId):
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.delete()
# vm = oca.VirtualMachine.new_with_id(api, int(machineId))
# vm.delete()
api.deleteVM(machineId)
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
def enumerateMachines(self):
def enumerateMachines(api):
'''
Obtains the list of machines inside OpenNebula.
Machines starting with UDS are filtered out
@ -140,18 +144,15 @@ def enumerateMachines(self):
'id'
'cluster_id'
'''
vmpool = oca.VirtualMachinePool(self.api)
vmpool.info()
for vm in vmpool:
yield (vm.id, vm.name)
return api.enumVMs()
def getNetInfo(api, machineId, networkId=None):
'''
Changes the mac address of first nic of the machine to the one specified
'''
md = minidom.parseString(api.call('vm.info', int(machineId)))
# md = minidom.parseString(api.call('vm.info', int(machineId)))
md = minidom.parseString(api.VMInfo(machineId)[1])
node = md
for nic in md.getElementsByTagName('NIC'):
@ -159,6 +160,7 @@ def getNetInfo(api, machineId, networkId=None):
if networkId is None or int(netId) == int(networkId):
node = nic
break
logger.debug(node.toxml())
# Default, returns first MAC found (or raise an exception if there is no MAC)
try: