1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-24 02:04:09 +03:00

Another big step forward for OpenNebula provider

This commit is contained in:
Adolfo Gómez García 2016-02-09 09:45:36 +01:00
parent de4aef3a5c
commit fd6b0c9458
8 changed files with 588 additions and 302 deletions

View File

@ -34,15 +34,17 @@ from uds.core.services import UserDeployment
from uds.core.util.State import State
from uds.core.util import log
from . import on
import pickle
import logging
__updated__ = '2016-01-28'
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry, opChangeMac = range(10)
opCreate, opStart, opSuspend, opRemove, opWait, opError, opFinish, opRetry = range(8)
NO_MORE_NAMES = 'NO-NAME-ERROR'
@ -139,8 +141,6 @@ class LiveDeployment(UserDeployment):
The get method of a mac generator takes one param, that is the mac range
to use to get an unused mac.
'''
if self._mac == '':
self._mac = self.macGenerator().get(self.service().getMacRange())
return self._mac
def getIp(self):
@ -173,12 +173,10 @@ class LiveDeployment(UserDeployment):
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
if state == on.VmState.UNKNOWN:
return self.__error('Machine is not available anymore')
if state not in ('up', 'powering_up', 'restoring_state'):
self._queue = [opStart, opFinish]
return self.__executeQueue()
self.service().startMachine()
self.cache().put('ready', '1')
return State.FINISHED
@ -217,24 +215,23 @@ class LiveDeployment(UserDeployment):
def __initQueueForDeploy(self, forLevel2=False):
if forLevel2 is False:
self._queue = [opCreate, opChangeMac, opStart, opFinish]
self._queue = [opCreate, opStart, opFinish]
else:
self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish]
self._queue = [opCreate, opStart, opWait, opSuspend, opFinish]
def __checkMachineState(self, chkState):
logger.debug('Checking that state of machine {} ({}) is {}'.format(self._vmid, self._name, chkState))
state = self.service().getMachineState(self._vmid)
# If we want to check an state and machine does not exists (except in case that we whant to check this)
if state == 'unknown' and chkState != 'unknown':
if state == on.VmState.UNKNOWN:
return self.__error('Machine not found')
ret = State.RUNNING
if type(chkState) is list:
for cks in chkState:
if state == cks:
ret = State.FINISHED
break
if state in chkState:
ret = State.FINISHED
else:
if state == chkState:
ret = State.FINISHED
@ -270,11 +267,9 @@ class LiveDeployment(UserDeployment):
logger.debug('Setting error state, reason: {0}'.format(reason))
self.doLog(log.ERROR, reason)
if self._vmid != '': # Powers off
if self._vmid != '': # Powers off & delete it
try:
state = self.service().getMachineState(self._vmid)
if state in ('up', 'suspended'):
self.service().stopMachine(self._vmid)
self.service().removeMachine(self._vmid)
except:
logger.debug('Can\t set machine state to stopped')
@ -296,11 +291,9 @@ class LiveDeployment(UserDeployment):
opCreate: self.__create,
opRetry: self.__retry,
opStart: self.__startMachine,
opStop: self.__stopMachine,
opSuspend: self.__suspendMachine,
opWait: self.__wait,
opRemove: self.__remove,
opChangeMac: self.__changeMac
}
try:
@ -341,123 +334,62 @@ class LiveDeployment(UserDeployment):
if name == NO_MORE_NAMES:
raise Exception('No more names available for this service. (Increase digits for this service to fix)')
name = self.service().sanitizeVmName(name) # oVirt don't let us to create machines with more than 15 chars!!!
comments = 'UDS Linked clone'
name = self.service().sanitizeVmName(name) # OpenNebula don't let us to create machines with more than 15 chars!!!
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
self._vmid = self.service().deployFromTemplate(name, templateId)
if self._vmid is None:
raise Exception('Can\'t create machine')
# Get IP & MAC (early stage)
self._mac, self._ip = self.service().getNetInfo(self._vmid)
def __remove(self):
'''
Removes a machine from system
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
if state == on.VmState.UNKNOWN:
raise Exception('Machine not found')
if state != 'down':
self.__pushFrontOp(opStop)
self.__executeQueue()
else:
self.service().removeMachine(self._vmid)
self.service().removeMachine(self._vmid)
def __startMachine(self):
'''
Powers on the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'up': # Already started, return
return
if state != 'down' and state != 'suspended':
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
else:
self.service().startMachine(self._vmid)
def __stopMachine(self):
'''
Powers off the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'down': # Already stoped, return
return
if state != 'up' and state != 'suspended':
self.__pushFrontOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one
else:
self.service().stopMachine(self._vmid)
self.service().startMachine(self._vmid)
def __suspendMachine(self):
'''
Suspends the machine
'''
state = self.service().getMachineState(self._vmid)
if state == 'unknown':
raise Exception('Machine not found')
if state == 'suspended': # Already suspended, return
return
if state != 'up':
self.__pushFrontOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
else:
self.service().suspendMachine(self._vmid)
def __changeMac(self):
'''
Changes the mac of the first nic
'''
self.service().updateMachineMac(self._vmid, self.getUniqueId())
self.service().suspendMachine(self._vmid)
# Check methods
def __checkCreate(self):
'''
Checks the state of a deploy for an user or cache
'''
return self.__checkMachineState('down')
return self.__checkMachineState(on.VmState.ACTIVE)
def __checkStart(self):
'''
Checks if machine has started
'''
return self.__checkMachineState('up')
def __checkStop(self):
'''
Checks if machine has stoped
'''
return self.__checkMachineState('down')
return self.__checkMachineState(on.VmState.ACTIVE)
def __checkSuspend(self):
'''
Check if the machine has suspended
'''
return self.__checkMachineState('suspended')
return self.__checkMachineState(on.VmState.SUSPENDED)
def __checkRemoved(self):
'''
Checks if a machine has been removed
'''
return self.__checkMachineState('unknown')
def __checkMac(self):
'''
Checks if change mac operation has finished.
Changing nic configuration es 1-step operation, so when we check it here, it is already done
'''
return State.FINISHED
return State.FINISHED # No check at all, always true
def checkState(self):
'''
@ -477,10 +409,8 @@ class LiveDeployment(UserDeployment):
opRetry: self.__retry,
opWait: self.__wait,
opStart: self.__checkStart,
opStop: self.__checkStop,
opSuspend: self.__checkSuspend,
opRemove: self.__checkRemoved,
opChangeMac: self.__checkMac
}
try:
@ -570,10 +500,10 @@ class LiveDeployment(UserDeployment):
return self.__error('Machine is already in error state!')
if op == opFinish or op == opWait:
self._queue = [opStop, opRemove, opFinish]
self._queue = [opRemove, opFinish]
return self.__executeQueue()
self._queue = [op, opStop, opRemove, opFinish]
self._queue = [op, opRemove, opFinish]
# Do not execute anything.here, just continue normally
return State.RUNNING
@ -594,14 +524,12 @@ class LiveDeployment(UserDeployment):
return {
opCreate: 'create',
opStart: 'start',
opStop: 'stop',
opSuspend: 'suspend',
opRemove: 'remove',
opWait: 'wait',
opError: 'error',
opFinish: 'finish',
opRetry: 'retry',
opChangeMac: 'changing mac'
}.get(op, '????')
def __debug(self, txt):

View File

@ -35,13 +35,12 @@ from uds.core.transports import protocols
from uds.core.services import Service, types as serviceTypes
from .LivePublication import LivePublication
from .LiveDeployment import LiveDeployment
from . import Helpers
from uds.core.ui import gui
import logging
__updated__ = '2016-02-08'
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
@ -135,7 +134,7 @@ class LiveService(Service):
templates = self.parent().getTemplates()
vals = []
for t in templates:
vals.append(gui.choiceItem(t.id, t.name))
vals.append(gui.choiceItem(t[0], t[1]))
# This is not the same case, values is not the "value" of the field, but
# the list of values shown because this is a "ChoiceField"
@ -144,7 +143,7 @@ class LiveService(Service):
datastores = self.parent().getDatastores()
vals = []
for d in datastores:
vals.append(gui.choiceItem(d.id, d.name))
vals.append(gui.choiceItem(d[0], d[1]))
self.datastore.setValues(vals)
@ -154,20 +153,7 @@ class LiveService(Service):
def makeTemplate(self, templateName):
return self.parent().makeTemplate(self.template.value, templateName, self.datastore.value)
def getTemplateState(self, templateId):
'''
Invokes getTemplateState from parent provider
Args:
templateId: templateId to remove
Returns nothing
Raises an exception if operation fails.
'''
return self.parent().getTemplateState(templateId)
def deployFromTemplate(self, name, comments, templateId):
def deployFromTemplate(self, name, templateId):
'''
Deploys a virtual machine on selected cluster from selected template
@ -183,9 +169,8 @@ class LiveService(Service):
Id of the machine being created form template
'''
logger.debug('Deploying from template {0} machine {1}'.format(templateId, name))
self.datastoreHasSpace()
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value,
self.display.value, int(self.memory.value), int(self.memoryGuaranteed.value))
# self.datastoreHasSpace()
return self.parent().deployFromTemplate(name, templateId)
def removeTemplate(self, templateId):
'''
@ -257,17 +242,11 @@ class LiveService(Service):
'''
return self.parent().removeMachine(machineId)
def updateMachineMac(self, machineId, macAddres):
def getNetInfo(self, machineId, networkId=None):
'''
Changes the mac address of first nic of the machine to the one specified
'''
return self.parent().updateMachineMac(machineId, macAddres)
def getMacRange(self):
'''
Returns de selected mac range
'''
return self.parent().getMacRange()
return self.parent().getNetInfo(machineId, networkId=None)
def getBaseName(self):
'''
@ -286,9 +265,3 @@ class LiveService(Service):
Returns the selected display type (for created machines, for administration
'''
return self.display.value
def getConsoleConnection(self, machineId):
return self.parent().getConsoleConnection(machineId)
def desktopLogin(self, machineId, username, password, domain):
return self.parent().desktopLogin(machineId, username, password, domain)

View File

@ -41,6 +41,7 @@ from uds.core.util import validators
from defusedxml import minidom
from .LiveService import LiveService
from . import on
import logging
@ -49,7 +50,7 @@ import six
# Python bindings for OpenNebula
import oca
__updated__ = '2016-02-08'
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
@ -100,8 +101,6 @@ class Provider(ServiceProvider):
username = gui.TextField(length=32, label=_('Username'), order=4, tooltip=_('User with valid privileges on OpenNebula'), required=True, defvalue='oneadmin')
password = gui.PasswordField(lenth=32, label=_('Password'), order=5, tooltip=_('Password of the user of OpenNebula'), required=True)
timeout = gui.NumericField(length=3, label=_('Timeout'), defvalue='10', order=6, tooltip=_('Timeout in seconds of connection to OpenNebula'), required=True)
macsRange = gui.TextField(length=36, label=_('Macs range'), defvalue='52:54:00:00:00:00-52:54:00:FF:FF:FF', order=7, rdonly=True,
tooltip=_('Range of valid macs for UDS managed machines'), required=True)
# Own variables
_api = None
@ -115,9 +114,8 @@ class Provider(ServiceProvider):
self._api = None
if values is not None:
self.macsRange.value = validators.validateMacRange(self.macsRange.value)
self.timeout.value = validators.validateTimeout(self.timeout.value, returnAsInteger=False)
logger.debug('Endpoint: {}'.format(self.endPoint))
logger.debug('Endpoint: {}'.format(self.endpoint))
@property
def endpoint(self):
@ -135,11 +133,7 @@ class Provider(ServiceProvider):
self._api = None
def sanitizeVmName(self, name):
'''
Ovirt only allows machine names with [a-zA-Z0-9_-]
'''
import re
return re.sub("[^a-zA-Z0-9_-]", "_", name)
return on.sanitizeName(name)
def testConnection(self):
'''
@ -180,128 +174,19 @@ class Provider(ServiceProvider):
return vmpool
def getDatastores(self, datastoreType=0):
'''
0 seems to be images datastore
'''
datastores = oca.DatastorePool(self.api)
datastores.info()
for ds in datastores:
if ds.type == datastoreType:
yield ds
return on.storage.enumerateDatastores(self.api, datastoreType)
def getTemplates(self, force=False):
logger.debug('Api: {}'.format(self.api))
templatesPool = oca.VmTemplatePool(self.api)
templatesPool.info()
for t in templatesPool:
if t.name[:4] != 'UDSP':
yield t
return on.template.getTemplates(self.api, force)
def makeTemplate(self, fromTemplateId, name, toDataStore):
'''
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
the creating machine
Args:
fromTemplateId: id of the base template
name: Name of the machine (care, only ascii characters and no spaces!!!)
Returns
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
Note:
Maybe we need to also clone the hard disk?
'''
try:
# First, we clone the themplate itself
templateId = self.api.call('template.clone', int(fromTemplateId), name)
# Now copy cloned images if possible
try:
imgs = oca.ImagePool(self.api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
info = self.api.call('template.info', templateId)
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
counter = 0
for dsk in template.getElementsByTagName('DISK'):
counter += 1
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
fromId = False
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgName = node.data
# Locate
imgId = imgs[imgName]
else:
fromId = True
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now clone the image
imgName = self.sanitizeVmName(name + ' DSK ' + six.text_type(counter))
newId = self.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
self.api.call('template.update', templateId, template.toxml())
except:
logger.exception('Exception cloning image')
return six.text_type(templateId)
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
raise
return on.template.create(self.api, fromTemplateId, name, toDataStore)
def removeTemplate(self, templateId):
'''
Removes a template from ovirt server
return on.template.remove(self.api, templateId)
Returns nothing, and raises an Exception if it fails
'''
try:
# 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(self.api)
imgs.info()
imgs = dict(((i.name, i.id) for i in imgs))
info = self.api.call('template.info', int(templateId))
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
counter = 0
for dsk in template.getElementsByTagName('DISK'):
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgId = imgs[node.data]
else:
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now delete the image
self.api.call('image.delete', int(imgId))
except:
logger.exception('Exception cloning image')
self.api.call('template.delete', int(templateId))
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
def deployFromTemplate(self, name, templateId):
return on.template.deployFrom(self.api, templateId, name)
def getMachineState(self, machineId):
'''
@ -312,32 +197,15 @@ class Provider(ServiceProvider):
machineId: Id of the machine to get state
Returns:
one of this values:
unassigned, down, up, powering_up, powered_down,
paused, migrating_from, migrating_to, unknown, not_responding,
wait_for_launch, reboot_in_progress, saving_state, restoring_state,
suspended, image_illegal, image_locked or powering_down
Also can return'unknown' if Machine is not known
one of the on.VmState Values
'''
return self.__getApi().getMachineState(machineId)
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))
def deployFromTemplate(self, name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB):
'''
Deploys a virtual machine on selected cluster from selected template
Args:
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
'''
return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId, displayType, memoryMB, guaranteedMB)
def startMachine(self, machineId):
'''
@ -350,7 +218,7 @@ class Provider(ServiceProvider):
Returns:
'''
return self.__getApi().startMachine(machineId)
on.vm.startMachine(self.api, machineId)
def stopMachine(self, machineId):
'''
@ -361,7 +229,7 @@ class Provider(ServiceProvider):
Returns:
'''
return self.__getApi().stopMachine(machineId)
on.vm.stopMachine(self.api, machineId)
def suspendMachine(self, machineId):
'''
@ -372,7 +240,7 @@ class Provider(ServiceProvider):
Returns:
'''
return self.__getApi().suspendMachine(machineId)
on.vm.suspendMachine(self.api, machineId)
def removeMachine(self, machineId):
'''
@ -383,24 +251,13 @@ class Provider(ServiceProvider):
Returns:
'''
return self.__getApi().removeMachine(machineId)
on.vm.removeMachine(self.api, machineId)
def updateMachineMac(self, machineId, macAddres):
def getNetInfo(self, machineId, networkId=None):
'''
Changes the mac address of first nic of the machine to the one specified
'''
return self.__getApi().updateMachineMac(machineId, macAddres)
def getMacRange(self):
return self.macsRange.value
def getConsoleConnection(self, machineId):
return self.__getApi().getConsoleConnection(machineId)
def desktopLogin(self, machineId, username, password, domain):
'''
'''
return self.__getApi().desktopLogin(machineId, username, password, domain)
return on.vm.getNetInfo(self.api, machineId, networkId)
@staticmethod
def test(env, data):

View File

@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
import sys
import imp
import re
import logging
import six
from defusedxml import minidom
# Python bindings for OpenNebula
import oca
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
module = sys.modules[__name__]
VmState = imp.new_module('VmState')
for i in enumerate(['INIT', 'PENDING', 'HOLD', 'ACTIVE', 'STOPPED', 'SUSPENDED', 'DONE', 'FAILED', 'POWEROFF', 'UNDEPLOYED']):
setattr(VmState, i[1], i[0])
# Import submodules
from common import *
import template
import vm
import storage

View File

@ -0,0 +1,55 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
import sys
import imp
import re
import logging
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
module = sys.modules[__name__]
VmState = imp.new_module('VmState')
for i in enumerate(['INIT', 'PENDING', 'HOLD', 'ACTIVE', 'STOPPED', 'SUSPENDED', 'DONE', 'FAILED', 'POWEROFF', 'UNDEPLOYED', 'UNKNOWN']):
setattr(VmState, i[1], i[0])
def sanitizeName(name):
'''
machine names with [a-zA-Z0-9_-]
'''
return re.sub("[^a-zA-Z0-9._-]", "_", name)

View File

@ -0,0 +1,52 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
import logging
import six
import oca
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
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)

View File

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
import logging
import six
import oca
from defusedxml import minidom
# Python bindings for OpenNebula
from .common import sanitizeName
__updated__ = '2016-02-09'
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)
def create(api, fromTemplateId, name, toDataStore):
'''
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
the creating machine
Args:
fromTemplateId: id of the base template
name: Name of the machine (care, only ascii characters and no spaces!!!)
Returns
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
Note:
Maybe we need to also clone the hard disk?
'''
try:
# First, we clone the themplate itself
templateId = api.call('template.clone', int(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))
info = api.call('template.info', templateId)
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
counter = 0
for dsk in template.getElementsByTagName('DISK'):
counter += 1
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
fromId = False
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgName = node.data
# Locate
imgId = imgs[imgName]
else:
fromId = True
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now clone the image
imgName = sanitizeName(name + ' DSK ' + six.text_type(counter))
newId = 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())
except Exception:
logger.exception('Exception cloning image')
return six.text_type(templateId)
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
raise
def remove(api, templateId):
'''
Removes a template from ovirt server
Returns nothing, and raises an Exception if it fails
'''
try:
# 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))
info = api.call('template.info', int(templateId))
template = minidom.parseString(info).getElementsByTagName('TEMPLATE')[0]
logger.debug('XML: {}'.format(template.toxml()))
for dsk in template.getElementsByTagName('DISK'):
imgIds = dsk.getElementsByTagName('IMAGE_ID')
if len(imgIds) == 0:
node = dsk.getElementsByTagName('IMAGE')[0].childNodes[0]
imgId = imgs[node.data]
else:
node = imgIds[0].childNodes[0]
imgId = node.data
logger.debug('Found {} for cloning'.format(imgId))
# Now delete the image
api.call('image.delete', int(imgId))
except:
logger.exception('Exception cloning image')
api.call('template.delete', int(templateId))
except Exception as e:
logger.error('Creating template on OpenNebula: {}'.format(e))
def deployFrom(api, templateId, name):
'''
Deploys a virtual machine on selected cluster from selected template
Args:
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, '')
return six.text_type(vmId)

View File

@ -0,0 +1,181 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012 Virtual Cable S.L.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
# are permitted provided that the following conditions are met:
#
# * Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
'''
.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
'''
import logging
import six
import oca
from defusedxml import minidom
# Python bindings for OpenNebula
from .common import VmState
__updated__ = '2016-02-09'
logger = logging.getLogger(__name__)
def getMachineState(api, machineId):
'''
Returns the state of the machine
This method do not uses cache at all (it always tries to get machine state from OpenNebula server)
Args:
machineId: Id of the machine to get state
Returns:
one of the on.VmState Values
'''
try:
vm = oca.VirtualMachine.new_with_id(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 VmState.UNKNOWN
def startMachine(api, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula.
This start also "resume" suspended/paused machines
Args:
machineId: Id of the machine
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.resume()
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
def stopMachine(api, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.poweroff_hard()
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
def suspendMachine(api, machineId):
'''
Tries to start a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
startMachine(api, machineId)
def removeMachine(api, machineId):
'''
Tries to delete a machine. No check is done, it is simply requested to OpenNebula
Args:
machineId: Id of the machine
Returns:
'''
try:
vm = oca.VirtualMachine.new_with_id(api, int(machineId))
vm.delete()
except Exception as e:
logger.error('Error obtaining machine state for {} on opennebula: {}'.format(machineId, e))
def enumerateMachines(self):
'''
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()
for vm in vmpool:
yield (vm.id, vm.name)
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)))
node = md
for nic in md.getElementsByTagName('NIC'):
netId = nic.getElementsByTagName('NETWORK_ID')[0].childNodes[0].data
if networkId is None or int(netId) == int(networkId):
node = nic
break
# Default, returns first MAC found (or raise an exception if there is no MAC)
return (node.getElementsByTagName('MAC')[0].childNodes[0].data, node.getElementsByTagName('IP')[0].childNodes[0].data)
# Sample NIC Content (there will be as much as nics)
# <NIC>
# <BRIDGE><![CDATA[br0]]></BRIDGE>
# <CLUSTER_ID><![CDATA[100]]></CLUSTER_ID>
# <IP><![CDATA[172.27.0.49]]></IP>
# <IP6_LINK><![CDATA[fe80::400:acff:fe1b:31]]></IP6_LINK>
# <MAC><![CDATA[02:00:ac:1b:00:31]]></MAC>
# <NETWORK><![CDATA[private]]></NETWORK>
# <NETWORK_ID><![CDATA[1]]></NETWORK_ID>
# <NETWORK_UNAME><![CDATA[oneadmin]]></NETWORK_UNAME>
# <NIC_ID><![CDATA[2]]></NIC_ID>
# <VLAN><![CDATA[NO]]></VLAN>
# </NIC>