1
0
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:
Adolfo Gómez 2012-11-21 13:04:58 +00:00
parent aebacb166b
commit 400ac892f0
5 changed files with 351 additions and 117 deletions

View File

@ -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):
'''

View File

@ -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()

View File

@ -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):

View File

@ -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

View File

@ -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:
'''