mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-31 17:17:53 +03:00
Advancing a lot with ovirt. Detected something on machine creations that maybe an vdsm bug (ovirt). Going to make a bug report to see if it really is an error or is something i'm missing (seems like a bug anyway... :-) )
This commit is contained in:
parent
aebacb166b
commit
400ac892f0
@ -37,6 +37,8 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
opCreate, opStart, opStop, opSuspend, opWait, opError, opFinish, opRetry = range(8)
|
||||
|
||||
class OVirtLinkedDeployment(UserDeployment):
|
||||
'''
|
||||
This class generates the user consumable elements of the service tree.
|
||||
@ -52,6 +54,14 @@ class OVirtLinkedDeployment(UserDeployment):
|
||||
#: Recheck every five seconds by default (for task methods)
|
||||
suggestedTime = 5
|
||||
|
||||
def initialize(self):
|
||||
self._name = ''
|
||||
self._ip = ''
|
||||
self._vmid = ''
|
||||
self._reason = ''
|
||||
self._queue = []
|
||||
self._destroyAfter = 'f'
|
||||
|
||||
# Serializable needed methods
|
||||
def marshal(self):
|
||||
'''
|
||||
@ -87,14 +97,10 @@ class OVirtLinkedDeployment(UserDeployment):
|
||||
a new unique name, so we keep the first generated name cached and don't
|
||||
generate more names. (Generator are simple utility classes)
|
||||
'''
|
||||
name = self.storage().readData('name')
|
||||
if name is None:
|
||||
name = self.nameGenerator().get( self.service().getBaseName()
|
||||
+ '-' + self.service().getColour(), 3 )
|
||||
# Store value for persistence
|
||||
self.storage().saveData('name', name)
|
||||
|
||||
return name
|
||||
if self._name == '':
|
||||
self._name = self.nameGenerator().get( self.service().getBaseName(), self.service().getLenName() )
|
||||
return self._name
|
||||
|
||||
|
||||
def setIp(self, ip):
|
||||
'''
|
||||
@ -108,7 +114,8 @@ class OVirtLinkedDeployment(UserDeployment):
|
||||
:note: This IP is the IP of the "consumed service", so the transport can
|
||||
access it.
|
||||
'''
|
||||
self.storage().saveData('ip', str(ip))
|
||||
logger.debug('Setting IP to %s' % ip)
|
||||
self._ip = ip
|
||||
|
||||
def getUniqueId(self):
|
||||
'''
|
||||
@ -120,11 +127,9 @@ class OVirtLinkedDeployment(UserDeployment):
|
||||
The get method of a mac generator takes one param, that is the mac range
|
||||
to use to get an unused mac.
|
||||
'''
|
||||
mac = self.storage().readData('mac')
|
||||
if mac is None:
|
||||
mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' )
|
||||
self.storage().saveData('mac', mac)
|
||||
return mac
|
||||
if self._mac == '':
|
||||
self._mac = self.macGenerator().get( self.service().getMacRange() )
|
||||
return self._mac
|
||||
|
||||
def getIp(self):
|
||||
'''
|
||||
@ -144,120 +149,189 @@ class OVirtLinkedDeployment(UserDeployment):
|
||||
show the IP to the administrator, this method will get called
|
||||
|
||||
'''
|
||||
ip = self.storage().readData('ip')
|
||||
if ip is None:
|
||||
ip = '192.168.0.34' # Sample IP for testing purposses only
|
||||
return ip
|
||||
return self._ip
|
||||
|
||||
def setReady(self):
|
||||
'''
|
||||
This is a task method. As that, the expected return values are
|
||||
State values RUNNING, FINISHED or ERROR.
|
||||
|
||||
The method is invoked whenever a machine is provided to an user, right
|
||||
before presenting it (via transport rendering) to the user.
|
||||
|
||||
This method exist for this kind of situations (i will explain it with a
|
||||
sample)
|
||||
|
||||
Imagine a Service tree (Provider, Service, ...) for virtual machines.
|
||||
This machines will get created by the UserDeployment implementation, but,
|
||||
at some time, the machine can be put at in an state (suspend, shut down)
|
||||
that will make the transport impossible to connect with it.
|
||||
|
||||
This method, in this case, will check the state of the machine, and if
|
||||
it is "ready", that is, powered on and accesible, it will return
|
||||
"State.FINISHED". If the machine is not accesible (has ben erased, for
|
||||
example), it will return "State.ERROR" and store a reason of error so UDS
|
||||
can ask for it and present this information to the Administrator.
|
||||
|
||||
If the machine powered off, or suspended, or any other state that is not
|
||||
directly usable but can be put in an usable state, it will return
|
||||
"State.RUNNING", and core will use checkState to see when the operation
|
||||
has finished.
|
||||
|
||||
I hope this sample is enough to explain the use of this method..
|
||||
'''
|
||||
if self.cache().get('ready') == '1':
|
||||
return State.FINISHED
|
||||
|
||||
# In our case, the service is always ready
|
||||
state = self.service().getMachineState(self._vmid)
|
||||
|
||||
if state == 'unknown':
|
||||
return self.__error('Machine is not available anymore')
|
||||
|
||||
if state not in ('up', 'powering_up', 'restoring_state'):
|
||||
return self.__powerOn()
|
||||
|
||||
self.cache().put('ready', '1')
|
||||
return State.FINISHED
|
||||
|
||||
def notifyReadyFromOsManager(self, data):
|
||||
# Here we will check for suspending the VM (when full ready)
|
||||
logger.debug('Checking if cache 2 for {0}'.format(self._name))
|
||||
if self.__getCurrentOp() == opWait:
|
||||
logger.debug('Machine is ready. Moving to level 2')
|
||||
self.__popCurrentOp() # Remove current state
|
||||
return self.__executeQueue()
|
||||
|
||||
#if self._squeue.getCurrent() == stWaitReady:
|
||||
# logger.debug('Move to level 2, suspending machine')
|
||||
# return self.moveToCache(self.L2_CACHE)
|
||||
return State.FINISHED
|
||||
|
||||
def __executeQueue(self):
|
||||
op = self.__getCurrentOp()
|
||||
|
||||
if op == opError:
|
||||
return State.ERROR
|
||||
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
if op == opCreate:
|
||||
return self.__create()
|
||||
|
||||
def __initQueueForDeploy(self, forLevel2 = False):
|
||||
|
||||
if forLevel2 is False:
|
||||
self._queue = [opCreate, opStart, opFinish]
|
||||
else:
|
||||
self._queue = [opCreate, opStart, opWait, opSuspend, opFinish]
|
||||
|
||||
def __getCurrentOp(self):
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
return self._queue[0]
|
||||
|
||||
def __popCurrentOp(self):
|
||||
if len(self._queue) == 0:
|
||||
return opFinish
|
||||
|
||||
res = self._queue.pop(0)
|
||||
return res
|
||||
|
||||
def __pushFrontOp(self, op):
|
||||
self._queue.insert(0, op)
|
||||
|
||||
def __pushBackOp(self, op):
|
||||
self._queue.append(op)
|
||||
|
||||
def __error(self, reason):
|
||||
'''
|
||||
Internal method to set object as error state
|
||||
|
||||
Returns:
|
||||
State.ERROR, so we can do "return self.__error(reason)"
|
||||
'''
|
||||
self._queue = [opError]
|
||||
self._reason = str(reason)
|
||||
return State.ERROR
|
||||
|
||||
# Queue execution methods
|
||||
def __retry(self):
|
||||
'''
|
||||
Used to retry an operation
|
||||
In fact, this will not be never invoked, unless we push it twice, because
|
||||
checkState method will "pop" first item when a check operation returns State.FINISHED
|
||||
'''
|
||||
return State.FINISHED
|
||||
|
||||
def __create(self):
|
||||
'''
|
||||
Deploys a machine from template for user/cache
|
||||
'''
|
||||
templateId = self.publication().getTemplateId()
|
||||
name = self.service().sanitizeVmName('UDS service ' + self.getName())
|
||||
comments = 'UDS Linked clone for'
|
||||
|
||||
try:
|
||||
self._vmid = self.service().deployFromTemplate(name, comments, templateId)
|
||||
if self._vmid is None:
|
||||
raise Exception('Can\'t create machine')
|
||||
except Exception as e:
|
||||
return self.__error(e)
|
||||
|
||||
return State.RUNNING
|
||||
|
||||
def __powerOn(self):
|
||||
'''
|
||||
Powers on the machine
|
||||
'''
|
||||
state = self.service.getMachineState(self._vmid)
|
||||
if state == 'down':
|
||||
pass
|
||||
|
||||
def deployForUser(self, user):
|
||||
'''
|
||||
Deploys an service instance for an user.
|
||||
|
||||
This is a task method. As that, the excepted return values are
|
||||
State values RUNNING, FINISHED or ERROR.
|
||||
|
||||
The user parameter is not realy neded, but provided. It indicates the
|
||||
Database User Object (see py:mod:`uds.modules`) to which this deployed
|
||||
user service will be assigned to.
|
||||
|
||||
This method will get called whenever a new deployed service for an user
|
||||
is needed. This will give this class the oportunity to create
|
||||
a service that is assigned to an user.
|
||||
|
||||
The way of using this method is as follows:
|
||||
|
||||
If the service gets created in "one step", that is, before the return
|
||||
of this method, the consumable service for the user gets created, it
|
||||
will return "State.FINISH".
|
||||
If the service needs more steps (as in this case), we will return
|
||||
"State.RUNNING", and if it has an error, it wil return "State.ERROR" and
|
||||
store an error string so administration interface can show it.
|
||||
|
||||
We do not use user for anything, as in most cases will be.
|
||||
'''
|
||||
import random
|
||||
|
||||
self.storage().saveData('count', '0')
|
||||
|
||||
# random fail
|
||||
if random.randint(0, 9) == 9:
|
||||
self.storage().saveData('error', 'Random error at deployForUser :-)')
|
||||
return State.ERROR
|
||||
|
||||
return State.RUNNING
|
||||
self.__initQueueForDeploy(False)
|
||||
return self.__executeQueue()
|
||||
|
||||
def deployForCache(self, cacheLevel):
|
||||
'''
|
||||
Deploys an service instance for cache
|
||||
'''
|
||||
forLevel2 = cacheLevel == self.L2_CACHE
|
||||
self.__initQueueForDeploy(forLevel2)
|
||||
return self.__executeQueue()
|
||||
|
||||
def __checkDeploy(self):
|
||||
'''
|
||||
Checks the state of a deploy for an user or cache
|
||||
'''
|
||||
try:
|
||||
state = self.service().getMachineState(self._vmid)
|
||||
if state != 'down':
|
||||
return State.RUNNING
|
||||
except Exception as e:
|
||||
return self.__error(e)
|
||||
|
||||
return State.FINISHED
|
||||
|
||||
|
||||
def checkState(self):
|
||||
'''
|
||||
Our deployForUser method will initiate the consumable service deployment,
|
||||
but will not finish it.
|
||||
|
||||
So in our sample, we will only check if a number reaches 5, and if so
|
||||
return that we have finished, else we will return that we are working
|
||||
on it.
|
||||
|
||||
One deployForUser returns State.RUNNING, this task will get called until
|
||||
checkState returns State.FINISHED.
|
||||
|
||||
Also, we will make the publication fail one of every 10 calls to this
|
||||
method.
|
||||
|
||||
Note: Destroying, canceling and deploying for cache also makes use of
|
||||
this method, so you must keep the info of that you are checking if you
|
||||
need it.
|
||||
In our case, destroy is 1-step action so this will no get called while
|
||||
destroying, and cancel will simply invoke destroy
|
||||
Check what operation is going on, and acts acordly to it
|
||||
'''
|
||||
import random
|
||||
op = self.__getCurrentOp()
|
||||
|
||||
count = int(self.storage().readData('count')) + 1
|
||||
# Count is always a valid value, because this method will never get
|
||||
# called before deployForUser, deployForCache, destroy or cancel.
|
||||
# In our sample, we only use checkState in case of deployForUser,
|
||||
# so at first call count will be 0.
|
||||
if count >= 5:
|
||||
return State.FINISHED
|
||||
|
||||
# random fail
|
||||
if random.randint(0, 9) == 9:
|
||||
self.storage().saveData('error', 'Random error at checkState :-)')
|
||||
if op == opError:
|
||||
return State.ERROR
|
||||
|
||||
self.storage().saveData('count', str(count))
|
||||
return State.RUNNING
|
||||
if op == opFinish:
|
||||
return State.FINISHED
|
||||
|
||||
res = None
|
||||
|
||||
if op == opCreate:
|
||||
res = self.__checkDeploy()
|
||||
|
||||
if op == opStart:
|
||||
res = self.__checkPowerOn()
|
||||
|
||||
if op == opStop:
|
||||
res = self.__checkPowerOff()
|
||||
|
||||
if op == opWait:
|
||||
res = State.RUNNING
|
||||
|
||||
if op == opSuspend:
|
||||
res = self.__checkSuspend()
|
||||
|
||||
if res is None:
|
||||
return self.__error('Unexpected operation found')
|
||||
|
||||
if res == State.FINISHED:
|
||||
self.__popCurrentOp()
|
||||
return State.RUNNING
|
||||
return res
|
||||
|
||||
|
||||
def finish(self):
|
||||
'''
|
||||
|
@ -177,15 +177,53 @@ class OVirtLinkedService(Service):
|
||||
|
||||
Args:
|
||||
templateId: templateId to remove
|
||||
|
||||
|
||||
Returns nothing
|
||||
|
||||
Raises an exception if operation fails.
|
||||
'''
|
||||
return self.parent().getTemplateState(templateId)
|
||||
|
||||
def deployFromTemplate(self, name, comments, templateId):
|
||||
'''
|
||||
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
|
||||
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
'''
|
||||
return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value)
|
||||
|
||||
def getMachineState(self, machineId):
|
||||
'''
|
||||
Invokes getMachineState from parent provider
|
||||
(returns if machine is "active" or "inactive"
|
||||
|
||||
Args:
|
||||
machineId: If of the machine to get state
|
||||
|
||||
Returns:
|
||||
'down': Machine is not running
|
||||
'unknown': Machine is not known
|
||||
'powering_up': Machine is powering up
|
||||
'up': Machine is up and running
|
||||
'saving_state': Machine is "suspending"
|
||||
'suspended': Machine is suspended
|
||||
'restoring_state': Machine is restoring state (unsuspending)
|
||||
'powering_down': Machine is powering down
|
||||
'image_locked': Machine is creating/cloning and is not usable
|
||||
'''
|
||||
return self.parent().getMachineState(machineId)
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
'''
|
||||
invokes removeTemplate from parent provider
|
||||
'''
|
||||
return self.parent().removeTemplate(templateId)
|
||||
|
||||
def getMacRange(self):
|
||||
return self.parent().getMacRange()
|
||||
|
@ -34,6 +34,7 @@ Created on Jun 22, 2012
|
||||
'''
|
||||
|
||||
from django.utils.translation import ugettext_noop as translatable, ugettext as _
|
||||
from uds.core.util.State import State
|
||||
from uds.core.services import ServiceProvider
|
||||
from OVirtLinkedService import OVirtLinkedService
|
||||
from uds.core.ui import gui
|
||||
@ -260,6 +261,29 @@ class Provider(ServiceProvider):
|
||||
(don't know if ovirt returns something more right now, will test what happens when template can't be published)
|
||||
'''
|
||||
return self.__getApi().getTemplateState(templateId)
|
||||
|
||||
def getMachineState(self, machineId):
|
||||
'''
|
||||
Returns the state of the machine
|
||||
This method do not uses cache at all (it always tries to get machine state from oVirt server)
|
||||
|
||||
Args:
|
||||
machineId: Id of the machine to get state
|
||||
|
||||
Returns:
|
||||
'down': Machine is not running
|
||||
'unknown': Machine is not known
|
||||
'powering_up': Machine is powering up
|
||||
'up': Machine is up and running
|
||||
'saving_state': Machine is "suspending"
|
||||
'suspended': Machine is suspended
|
||||
'restoring_state': Machine is restoring state (unsuspending)
|
||||
'powering_down': Machine is powering down
|
||||
|
||||
'''
|
||||
return self.__getApi().getMachineState(machineId)
|
||||
|
||||
return State.INACTIVE
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
'''
|
||||
@ -269,6 +293,23 @@ class Provider(ServiceProvider):
|
||||
'''
|
||||
return self.__getApi().removeTemplate(templateId)
|
||||
|
||||
def deployFromTemplate(self, name, comments, templateId, clusterId):
|
||||
'''
|
||||
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
|
||||
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
'''
|
||||
return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId)
|
||||
|
||||
def getMacRange(self):
|
||||
return self.macsRange.value
|
||||
|
||||
@staticmethod
|
||||
def test(env, data):
|
||||
|
@ -166,10 +166,8 @@ class OVirtPublication(Publication):
|
||||
# Methods provided below are specific for this publication
|
||||
# and will be used by user deployments that uses this kind of publication
|
||||
|
||||
def getBaseName(self):
|
||||
def getTemplateId(self):
|
||||
'''
|
||||
This sample method (just for this sample publication), provides
|
||||
the name generater for this publication. This is just a sample, and
|
||||
this will do the work
|
||||
Returns the template id associated with the publication
|
||||
'''
|
||||
return self._name
|
||||
return self._templateId
|
||||
|
@ -337,8 +337,8 @@ class Client(object):
|
||||
|
||||
api = self.__getApi()
|
||||
|
||||
storage = api.storagedomains.get(id=storageId)
|
||||
storage_domain = params.StorageDomain(storage)
|
||||
#storage = api.storagedomains.get(id=storageId)
|
||||
storage_domain = params.StorageDomain(id=storageId)
|
||||
|
||||
cluster = api.clusters.get(id=clusterId)
|
||||
vm = api.vms.get(id=vmId)
|
||||
@ -348,9 +348,9 @@ class Client(object):
|
||||
|
||||
template = params.Template(name=name,storage_domain=storage_domain, vm=vm, cluster=cluster, description=comments)
|
||||
|
||||
api.templates.add(template)
|
||||
return api.templates.add(template).get_id()
|
||||
|
||||
return api.templates.get(name=name).get_id()
|
||||
#return api.templates.get(name=name).get_id()
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
@ -358,6 +358,7 @@ class Client(object):
|
||||
def getTemplateState(self, templateId):
|
||||
'''
|
||||
Returns current template state.
|
||||
This method do not uses cache at all (it always tries to get template state from oVirt server)
|
||||
|
||||
Returned values could be:
|
||||
ok
|
||||
@ -381,6 +382,42 @@ class Client(object):
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def deployFromTemplate(self, name, comments, templateId, clusterId):
|
||||
'''
|
||||
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
|
||||
|
||||
Returns:
|
||||
Id of the machine being created form template
|
||||
'''
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
|
||||
cluster = api.clusters.get(id=clusterId)
|
||||
template = api.templates.get(id=templateId)
|
||||
|
||||
if cluster is None:
|
||||
raise Exception('Cluster not found')
|
||||
|
||||
if template is None:
|
||||
raise Exception('Template not found')
|
||||
|
||||
par = params.VM(name=name, cluster=cluster, template=template, description=comments)
|
||||
|
||||
params.Display()
|
||||
|
||||
return api.vms.add(par).get_id()
|
||||
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
def removeTemplate(self, templateId):
|
||||
'''
|
||||
Removes a template from ovirt server
|
||||
@ -400,5 +437,51 @@ class Client(object):
|
||||
# This returns nothing, if it fails it raises an exception
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
|
||||
def getMachineState(self, machineId):
|
||||
'''
|
||||
Returns current state of a machine (running, suspended, ...).
|
||||
This method do not uses cache at all (it always tries to get machine state from oVirt server)
|
||||
|
||||
Args:
|
||||
machineId: Id of the machine to get status
|
||||
|
||||
Returns:
|
||||
'down': Machine is not running
|
||||
'unknown': Machine is not known
|
||||
'powering_up': Machine is powering up
|
||||
'up': Machine is up and running
|
||||
'saving_state': Machine is "suspending"
|
||||
'suspended': Machine is suspended
|
||||
'restoring_state': Machine is restoring state (unsuspending)
|
||||
'powering_down': Machine is powering down
|
||||
|
||||
'''
|
||||
try:
|
||||
lock.acquire(True)
|
||||
|
||||
api = self.__getApi()
|
||||
|
||||
vm = api.vms.get(id=machineId)
|
||||
|
||||
if vm is None or vm.get_status() is None:
|
||||
return 'unknown'
|
||||
|
||||
return vm.get_status().get_state()
|
||||
|
||||
finally:
|
||||
lock.release()
|
||||
|
||||
|
||||
def powerOnMachine(self, machineId):
|
||||
'''
|
||||
Tries to power on a machine. No check is done, it is simply requested to oVirt
|
||||
|
||||
Args:
|
||||
machineId: Id of the machine
|
||||
|
||||
Returns:
|
||||
|
||||
'''
|
||||
|
Loading…
Reference in New Issue
Block a user