From 830e1b2e6f6ee1d187001ba4e2902b68c6c90661 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez?= Date: Fri, 23 Nov 2012 11:47:58 +0000 Subject: [PATCH] oVirt connector SHOULD work fine right now (but needs more testing... :P). Have the "volPath" problem (inside vsdm/clientIF.py) at this time, solved by patching my installation, but i have to find a definitive solution. Will do also tests with "real" vms (not just small 1Gb with no os to test platform), but this should work fine. (we will see..) --- .../services/OVirt/OVirtLinkedDeployment.py | 121 +++++++++++------- .../uds/services/OVirt/OVirtLinkedService.py | 23 +++- .../src/uds/services/OVirt/OVirtProvider.py | 17 ++- .../uds/services/OVirt/client/oVirtClient.py | 72 ++++++++--- 4 files changed, 163 insertions(+), 70 deletions(-) diff --git a/server/src/uds/services/OVirt/OVirtLinkedDeployment.py b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py index b80404194..e571e757a 100644 --- a/server/src/uds/services/OVirt/OVirtLinkedDeployment.py +++ b/server/src/uds/services/OVirt/OVirtLinkedDeployment.py @@ -38,7 +38,7 @@ import logging logger = logging.getLogger(__name__) -opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry = range(9) +opCreate, opStart, opStop, opSuspend, opRemove, opWait, opError, opFinish, opRetry, opChangeMac = range(10) class OVirtLinkedDeployment(UserDeployment): ''' @@ -203,14 +203,18 @@ class OVirtLinkedDeployment(UserDeployment): def __initQueueForDeploy(self, forLevel2 = False): if forLevel2 is False: - self._queue = [opCreate, opStart, opFinish] + self._queue = [opCreate, opChangeMac, opStart, opFinish] else: - self._queue = [opCreate, opStart, opWait, opSuspend, opFinish] + self._queue = [opCreate, opChangeMac, opStart, opWait, opSuspend, opFinish] def __checkMachineState(self, chkState): logger.debug('Checking that state of machine {0} is {1}'.format(self._vmid, 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': + return self.__error('Machine not found') + if state != chkState: return State.RUNNING @@ -249,7 +253,7 @@ class OVirtLinkedDeployment(UserDeployment): return State.ERROR def __executeQueue(self): - self.__debugQueue('executeQueue') + self.__debug('executeQueue') op = self.__getCurrentOp() if op == opError: @@ -259,12 +263,13 @@ class OVirtLinkedDeployment(UserDeployment): return State.FINISHED fncs = { opCreate: self.__create, - opRetry: self.__retry, + opRetry: self.__retry, opStart: self.__startMachine, opStop: self.__stopMachine, opSuspend: self.__suspendMachine, opWait: self.__wait, - opRemove: self.__remove + opRemove: self.__remove, + opChangeMac: self.__changeMac } try: @@ -273,9 +278,9 @@ class OVirtLinkedDeployment(UserDeployment): if execFnc is None: return self.__error('Unknown operation found at execution queue ({0})'.format(op)) - state = execFnc() + execFnc() - return state + return State.RUNNING except Exception as e: return self.__error(e) @@ -285,6 +290,8 @@ class OVirtLinkedDeployment(UserDeployment): 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 + + At executeQueue this return value will be ignored, and it will only be used at checkState ''' return State.FINISHED @@ -306,65 +313,76 @@ class OVirtLinkedDeployment(UserDeployment): if self._vmid is None: raise Exception('Can\'t create machine') - return State.RUNNING - def __remove(self): ''' Removes a machine from system ''' state = self.service().getMachineState(self._vmid) + if state == 'unknown': + raise Exception('Machine not found') + if state != 'down' and state != 'suspended': self.__pushFrontOp(opStop) - return State.RUNNING - - self.service().removeMachine(self._vmid) - return State.RUNNING + else: + self.service().removeMachine(self._vmid) def __startMachine(self): ''' Powers on the machine ''' state = self.service().getMachineState(self._vmid) - if state == 'up': - return State.FINISHED + + if state == 'unknown': + raise Exception('Machine not found') + + if state == 'up': # Already started, return + return if state != 'down': - self.__pushFrontOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return - return State.FINISHED - - self.service().startMachine(self._vmid) - return State.RUNNING + 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 == 'down': - return State.FINISHED + + if state == 'unknown': + raise Exception('Machine not found') + + if state == 'down': # Already stoped, return + return if state != 'up': - self.__pushBackOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return - return State.FINISHED - - self.service().stopMachine(self._vmid) - return State.RUNNING + self.__pushBackOp(opRetry) # Will call "check Retry", that will finish inmediatly and again call this one + else: + self.service().stopMachine(self._vmid) def __suspendMachine(self): ''' Suspends the machine ''' state = self.service().getMachineState(self._vmid) - if state == 'suspended': - return State.FINISHED + + if state == 'unknown': + raise Exception('Machine not found') + + if state == 'suspended': # Already suspended, return + return if state != 'up': self.__pushBackOp(opRetry) # Remember here, the return State.FINISH will make this retry be "poped" right ar return - return State.FINISHED + else: + self.service().suspendMachine(self._vmid) - self.service().suspendMachine(self._vmid) - return State.RUNNING + def __changeMac(self): + ''' + Changes the mac of the first nic + ''' + self.service().updateMachineMac(self._vmid, self.getUniqueId()) # Check methods def __checkCreate(self): @@ -397,11 +415,19 @@ class OVirtLinkedDeployment(UserDeployment): ''' 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 + def checkState(self): ''' Check what operation is going on, and acts acordly to it ''' - self.__debugQueue('checkState') + self.__debug('checkState') op = self.__getCurrentOp() if op == opError: @@ -416,7 +442,8 @@ class OVirtLinkedDeployment(UserDeployment): opStart: self.__checkStart, opStop: self.__checkStop, opSuspend: self.__checkSuspend, - opRemove: self.__checkRemoved + opRemove: self.__checkRemoved, + opChangeMac: self.__checkMac } try: @@ -438,8 +465,8 @@ class OVirtLinkedDeployment(UserDeployment): ''' Invoked when the core notices that the deployment of a service has finished. (No matter wether it is for cache or for an user) - ''' + self.__debug('finish') pass def assignToUser(self, user): @@ -483,16 +510,19 @@ class OVirtLinkedDeployment(UserDeployment): ''' Invoked for destroying a deployed service ''' - + self.__debug('destroy') # If executing something, wait until finished to remove it # We simply replace the execution queue op = self.__getCurrentOp() - if op == opFinish or op == opWait: - self._queue = [opStop, opRemove] - return self.__executeQueue() + if op == opError: + return self.__error('Machine is already in error state!') - self._queue = [op, opStop, opRemove] + if op == opFinish or op == opWait: + self._queue = [opStop, opRemove, opFinish] + return self.__executeQueue() + + self._queue = [op, opStop, opRemove, opFinish] # Do not execute anything.here, just continue normally return State.RUNNING @@ -519,9 +549,14 @@ class OVirtLinkedDeployment(UserDeployment): opWait: 'wait', opError: 'error', opFinish: 'finish', - opRetry: 'retry' + opRetry: 'retry', + opChangeMac: 'changing mac' }.get(op, '????') - def __debugQueue(self, txt): + def __debug(self, txt): + logger.debug('_name {0}: {1}'.format(txt, self._name)) + logger.debug('_ip {0}: {1}'.format(txt, self._ip)) + logger.debug('_mac {0}: {1}'.format(txt, self._mac)) + logger.debug('_vmid {0}: {1}'.format(txt, self._vmid)) logger.debug('Queue at {0}: {1}'.format(txt,[OVirtLinkedDeployment.__op2str(op) for op in self._queue ])) \ No newline at end of file diff --git a/server/src/uds/services/OVirt/OVirtLinkedService.py b/server/src/uds/services/OVirt/OVirtLinkedService.py index 47843bf83..2ccb0e52a 100644 --- a/server/src/uds/services/OVirt/OVirtLinkedService.py +++ b/server/src/uds/services/OVirt/OVirtLinkedService.py @@ -112,6 +112,14 @@ class OVirtLinkedService(Service): lenName = gui.NumericField(length = 1, label = translatable('Name Length'), defvalue = 5, order = 5, tooltip = translatable('Length of numeric part for the names of this machines (betwen 3 and 6'), required = True) + display = gui.ChoiceField(label = translatable('Display'), rdonly = False, order = 6, + tooltip = translatable('Display type (only for administration pourposses)'), + values = [ gui.choiceItem('spice', 'Spice'), + gui.choiceItem('vnc', 'Vnc') + ], + defvalue = '1' # Default value is the ID of the choicefield + ) + ov = gui.HiddenField() ev = gui.HiddenField() # We need to keep the env so we can instantiate the Provider @@ -169,7 +177,7 @@ class OVirtLinkedService(Service): Raises an exception if operation fails. ''' - return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value) + return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value, self.display.value) def getTemplateState(self, templateId): ''' @@ -196,7 +204,7 @@ class OVirtLinkedService(Service): Returns: Id of the machine being created form template ''' - return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value) + return self.parent().deployFromTemplate(name, comments, templateId, self.cluster.value, self.display.value) def removeTemplate(self, templateId): ''' @@ -267,6 +275,12 @@ class OVirtLinkedService(Service): Returns: ''' return self.parent().removeMachine(machineId) + + def updateMachineMac(self, machineId, macAddres): + ''' + Changes the mac address of first nic of the machine to the one specified + ''' + return self.parent().updateMachineMac(machineId, macAddres) def getMacRange(self): ''' @@ -286,4 +300,9 @@ class OVirtLinkedService(Service): ''' return int(self.lenName.value) + def getDisplay(self): + ''' + Returns the selected display type (for created machines, for administration + ''' + return self.display.value diff --git a/server/src/uds/services/OVirt/OVirtProvider.py b/server/src/uds/services/OVirt/OVirtProvider.py index dd6d56f23..fa2291a99 100644 --- a/server/src/uds/services/OVirt/OVirtProvider.py +++ b/server/src/uds/services/OVirt/OVirtProvider.py @@ -233,21 +233,22 @@ class Provider(ServiceProvider): ''' return self.__getApi().getStorageInfo(storageId, force) - def makeTemplate(self, name, comments, vmId, clusterId, storageId): + def makeTemplate(self, name, comments, machineId, clusterId, storageId, displayType): ''' Publish the machine (makes a template from it so we can create COWs) and returns the template id of the creating machine Args: name: Name of the machine (care, only ascii characters and no spaces!!!) - vmId: id of the machine to be published + machineId: id of the machine to be published clusterId: id of the cluster that will hold the machine storageId: id of the storage tuat will contain the publication AND linked clones + displayType: type of display (for oVirt admin interface only) Returns Raises an exception if operation could not be acomplished, or returns the id of the template being created. ''' - return self.__getApi().makeTemplate(name, comments, vmId, clusterId, storageId) + return self.__getApi().makeTemplate(name, comments, machineId, clusterId, storageId, displayType) def getTemplateState(self, templateId): ''' @@ -290,7 +291,7 @@ class Provider(ServiceProvider): ''' return self.__getApi().removeTemplate(templateId) - def deployFromTemplate(self, name, comments, templateId, clusterId): + def deployFromTemplate(self, name, comments, templateId, clusterId, displayType): ''' Deploys a virtual machine on selected cluster from selected template @@ -303,7 +304,7 @@ class Provider(ServiceProvider): Returns: Id of the machine being created form template ''' - return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId) + return self.__getApi().deployFromTemplate(name, comments, templateId, clusterId, displayType) def startMachine(self, machineId): ''' @@ -351,6 +352,12 @@ class Provider(ServiceProvider): ''' return self.__getApi().removeMachine(machineId) + def updateMachineMac(self, machineId, macAddres): + ''' + 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 diff --git a/server/src/uds/services/OVirt/client/oVirtClient.py b/server/src/uds/services/OVirt/client/oVirtClient.py index 181b6b5b0..c8e6b499a 100644 --- a/server/src/uds/services/OVirt/client/oVirtClient.py +++ b/server/src/uds/services/OVirt/client/oVirtClient.py @@ -10,6 +10,7 @@ from ovirtsdk.api import API import threading import logging import ovirtsdk +from roman import OutOfRangeError logger = logging.getLogger(__name__) @@ -63,7 +64,7 @@ class Client(object): pass cached_api_key = aKey - cached_api = API(url='https://'+self._host, username=self._username, password=self._password, timeout=self._timeout, insecure=True, debug=False) + cached_api = API(url='https://'+self._host, username=self._username, password=self._password, timeout=self._timeout, insecure=True, debug=True) return cached_api def __init__(self, host, username, password, timeout, cache): @@ -317,7 +318,7 @@ class Client(object): lock.release() - def makeTemplate(self, name, comments, machineId, clusterId, storageId): + def makeTemplate(self, name, comments, machineId, clusterId, storageId, displayType): ''' Publish the machine (makes a template from it so we can create COWs) and returns the template id of the creating machine @@ -327,19 +328,18 @@ class Client(object): machineId: id of the machine to be published clusterId: id of the cluster that will hold the machine storageId: id of the storage tuat will contain the publication AND linked clones + displayType: type of display (for oVirt admin interface only) Returns Raises an exception if operation could not be acomplished, or returns the id of the template being created. ''' - print "n: {0}, c: {1}, vm: {2}, cl: {3}, st: {3}".format(name, comments, machineId, clusterId, storageId) + logger.debug("n: {0}, c: {1}, vm: {2}, cl: {3}, st: {3}, dt: {4}".format(name, comments, machineId, clusterId, storageId, displayType)) try: lock.acquire(True) api = self.__getApi() - #storage = api.storagedomains.get(id=storageId) - storage_domain = params.StorageDomain(id=storageId) cluster = api.clusters.get(id=clusterId) vm = api.vms.get(id=machineId) @@ -350,14 +350,27 @@ class Client(object): if cluster is None: raise Exception('Cluster not found') - if vm.get_status().get_state() != 'down': raise Exception('Machine must be in down state to publish it') - template = params.Template(name=name,storage_domain=storage_domain, vm=vm, cluster=cluster, description=comments) + # Create disks description to be created in specified storage domain, one for each disk + sd = params.StorageDomains(storage_domain=[params.StorageDomain(id=storageId)]) + + dsks = [] + for dsk in vm.disks.list(): + dsks.append(params.Disk(id=dsk.get_id(), storage_domains=sd)) + + disks = params.Disks(disk=dsks) + + # Create display description + display = params.Display(type_=displayType) + + template = params.Template(name=name, vm=params.VM(id=vm.get_id(), disks=disks), + cluster=params.Cluster(id=cluster.get_id()), description=comments, + display=display) return api.templates.add(template).get_id() - + #return api.templates.get(name=name).get_id() finally: lock.release() @@ -390,7 +403,7 @@ class Client(object): finally: lock.release() - def deployFromTemplate(self, name, comments, templateId, clusterId): + def deployFromTemplate(self, name, comments, templateId, clusterId, displayType): ''' Deploys a virtual machine on selected cluster from selected template @@ -410,16 +423,9 @@ class Client(object): cluster = params.Cluster(id=clusterId) template = params.Template(id=templateId) + display = params.Display(type_=displayType) - 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() + par = params.VM(name=name, cluster=cluster, template=template, description=comments, display=display) return api.vms.add(par).get_id() @@ -550,7 +556,7 @@ class Client(object): finally: lock.release() - + def removeMachine(self, machineId): ''' Tries to delete a machine. No check is done, it is simply requested to oVirt @@ -574,4 +580,30 @@ class Client(object): finally: lock.release() - \ No newline at end of file + + def updateMachineMac(self, machineId, macAddres): + ''' + Changes the mac address of first nic of the machine to the one specified + ''' + try: + lock.acquire(True) + + api = self.__getApi() + + vm = api.vms.get(id=machineId) + + if vm is None: + raise Exception('Machine not found') + + nic = vm.nics.list()[0] # If has no nic, will raise an exception (IndexError) + + nic.get_mac().set_address(macAddres) + + nic.update() # Updates the nic + + except IndexError: + raise Exception('Machine do not have network interfaces!!') + + finally: + lock.release() + \ No newline at end of file