mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-03 01:17:56 +03:00
oVirt provider now publishes and removes poblications. Already unusable until finished oVirtLinkedDeployment at least
This commit is contained in:
parent
7717793604
commit
aebacb166b
@ -129,6 +129,14 @@ class OVirtLinkedService(Service):
|
|||||||
if self.baseName.value.isdigit():
|
if self.baseName.value.isdigit():
|
||||||
raise Service.ValidationException(_('The machine name can\'t be only numbers'))
|
raise Service.ValidationException(_('The machine name can\'t be only numbers'))
|
||||||
|
|
||||||
|
def initGui(self):
|
||||||
|
'''
|
||||||
|
Loads required values inside
|
||||||
|
'''
|
||||||
|
self.ov.value = self.parent().serialize()
|
||||||
|
self.ev.value = self.parent().env().key()
|
||||||
|
|
||||||
|
|
||||||
machines = self.parent().getMachines()
|
machines = self.parent().getMachines()
|
||||||
vals = []
|
vals = []
|
||||||
for m in machines:
|
for m in machines:
|
||||||
@ -140,9 +148,44 @@ class OVirtLinkedService(Service):
|
|||||||
for c in clusters:
|
for c in clusters:
|
||||||
vals.append( gui.choiceItem(c['id'], c['name'] ) )
|
vals.append( gui.choiceItem(c['id'], c['name'] ) )
|
||||||
self.cluster.setValues(vals)
|
self.cluster.setValues(vals)
|
||||||
|
|
||||||
|
def sanitizeVmName(self, name):
|
||||||
|
'''
|
||||||
|
Ovirt only allows machine names with [a-zA-Z0-9_-]
|
||||||
|
'''
|
||||||
|
import re
|
||||||
|
return re.sub("[^a-zA-Z0-9_-]", "_", name)
|
||||||
|
|
||||||
|
def makeTemplate(self, name, comments):
|
||||||
|
'''
|
||||||
|
Invokes makeTemplate from parent provider, completing params
|
||||||
|
|
||||||
|
Args:
|
||||||
|
name: Name to assign to template (must be previously "sanitized"
|
||||||
|
comments: Comments (UTF-8) to add to template
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
template Id of the template created
|
||||||
|
|
||||||
|
Raises an exception if operation fails.
|
||||||
|
'''
|
||||||
|
return self.parent().makeTemplate(name, comments, self.machine.value, self.cluster.value, self.datastore.value)
|
||||||
|
|
||||||
|
def getTemplateState(self, templateId):
|
||||||
|
'''
|
||||||
|
Invokes getTemplateState from parent provider
|
||||||
|
|
||||||
def initGui(self):
|
Args:
|
||||||
self.ov.value = self.parent().serialize()
|
templateId: templateId to remove
|
||||||
self.ev.value = self.parent().env().key()
|
|
||||||
|
Returns nothing
|
||||||
|
|
||||||
|
Raises an exception if operation fails.
|
||||||
|
'''
|
||||||
|
return self.parent().getTemplateState(templateId)
|
||||||
|
|
||||||
|
def removeTemplate(self, templateId):
|
||||||
|
'''
|
||||||
|
invokes removeTemplate from parent provider
|
||||||
|
'''
|
||||||
|
return self.parent().removeTemplate(templateId)
|
||||||
|
@ -232,6 +232,43 @@ class Provider(ServiceProvider):
|
|||||||
'''
|
'''
|
||||||
return self.__getApi().getStorageInfo(storageId, force)
|
return self.__getApi().getStorageInfo(storageId, force)
|
||||||
|
|
||||||
|
def makeTemplate(self, name, comments, vmId, clusterId, storageId):
|
||||||
|
'''
|
||||||
|
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
|
||||||
|
clusterId: id of the cluster that will hold the machine
|
||||||
|
storageId: id of the storage tuat will contain the publication AND linked clones
|
||||||
|
|
||||||
|
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)
|
||||||
|
|
||||||
|
def getTemplateState(self, templateId):
|
||||||
|
'''
|
||||||
|
Returns current template state.
|
||||||
|
|
||||||
|
Returned values could be:
|
||||||
|
ok
|
||||||
|
locked
|
||||||
|
removed
|
||||||
|
|
||||||
|
(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 removeTemplate(self, templateId):
|
||||||
|
'''
|
||||||
|
Removes a template from ovirt server
|
||||||
|
|
||||||
|
Returns nothing, and raises an Exception if it fails
|
||||||
|
'''
|
||||||
|
return self.__getApi().removeTemplate(templateId)
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def test(env, data):
|
def test(env, data):
|
||||||
|
@ -41,43 +41,10 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class OVirtPublication(Publication):
|
class OVirtPublication(Publication):
|
||||||
'''
|
'''
|
||||||
This class shows how a publication is developed.
|
This class provides the publication of a oVirtLinkedService
|
||||||
|
|
||||||
In order to a publication to work correctly, we must provide at least the
|
|
||||||
following methods:
|
|
||||||
* Of course, the __init__
|
|
||||||
* :py:meth:`.publish`
|
|
||||||
* :py:meth:`.checkState`
|
|
||||||
* :py:meth:`.finish`
|
|
||||||
|
|
||||||
Also, of course, methods from :py:class:`uds.core.Serializable.Serializable`
|
|
||||||
|
|
||||||
|
|
||||||
Publication do not have an configuration interface, all data contained
|
|
||||||
inside an instance of a Publication must be serialized if you want them between
|
|
||||||
method calls.
|
|
||||||
|
|
||||||
It's not waranteed that the class will not be serialized/deserialized
|
|
||||||
between methods calls, so, first of all, implement the marshal and umnarshal
|
|
||||||
mehods needed by all serializable classes.
|
|
||||||
|
|
||||||
Also a thing to note is that operations requested to Publications must be
|
|
||||||
*as fast as posible*. The operations executes in a separated thread,
|
|
||||||
and so it cant take a bit more time to execute, but it's recommended that
|
|
||||||
the operations executes as fast as posible, and, if it will take a long time,
|
|
||||||
split operation so we can keep track of state.
|
|
||||||
|
|
||||||
This means that, if we have "slow" operations, we must
|
|
||||||
|
|
||||||
We first of all declares an estimation of how long a publication will take.
|
|
||||||
This value is instance based, so if we override it in our class, the suggested
|
|
||||||
time could change.
|
|
||||||
|
|
||||||
The class attribute that indicates this suggested time is "suggestedTime", and
|
|
||||||
it's expressed in seconds, (i.e. "suggestedTime = 10")
|
|
||||||
'''
|
'''
|
||||||
|
|
||||||
suggestedTime = 5 #: Suggested recheck time if publication is unfinished in seconds
|
suggestedTime = 20 #: Suggested recheck time if publication is unfinished in seconds
|
||||||
|
|
||||||
def initialize(self):
|
def initialize(self):
|
||||||
'''
|
'''
|
||||||
@ -89,15 +56,17 @@ class OVirtPublication(Publication):
|
|||||||
|
|
||||||
# We do not check anything at marshal method, so we ensure that
|
# We do not check anything at marshal method, so we ensure that
|
||||||
# default values are correctly handled by marshal.
|
# default values are correctly handled by marshal.
|
||||||
self._name = 'test'
|
self._name = ''
|
||||||
self._reason = '' # No error, no reason for it
|
self._reason = ''
|
||||||
self._number = 1
|
self._destroyAfter = 'f'
|
||||||
|
self._templateId = ''
|
||||||
|
self._state = 'r'
|
||||||
|
|
||||||
def marshal(self):
|
def marshal(self):
|
||||||
'''
|
'''
|
||||||
returns data from an instance of Sample Publication serialized
|
returns data from an instance of Sample Publication serialized
|
||||||
'''
|
'''
|
||||||
return '\t'.join( [self._name, self._reason, str(self._number)] )
|
return '\t'.join( ['v1', self._name, self._reason, self._destroyAfter, self._templateId, self._state] )
|
||||||
|
|
||||||
def unmarshal(self, data):
|
def unmarshal(self, data):
|
||||||
'''
|
'''
|
||||||
@ -105,115 +74,53 @@ class OVirtPublication(Publication):
|
|||||||
'''
|
'''
|
||||||
logger.debug('Data: {0}'.format(data))
|
logger.debug('Data: {0}'.format(data))
|
||||||
vals = data.split('\t')
|
vals = data.split('\t')
|
||||||
logger.debug('Values: {0}'.format(vals))
|
if vals[0] == 'v1':
|
||||||
self._name = vals[0]
|
self._name, self._reason, self._destroyAfter, self._templateId, self._state = vals[1:]
|
||||||
self._reason = vals[1]
|
|
||||||
self._number = int(vals[2])
|
|
||||||
|
|
||||||
|
|
||||||
def publish(self):
|
def publish(self):
|
||||||
'''
|
'''
|
||||||
This method is invoked whenever the administrator requests a new publication.
|
Realizes the publication of the service
|
||||||
|
|
||||||
The method is not invoked directly (i mean, that the administration request
|
|
||||||
do no makes a call to this method), but a DelayedTask is saved witch will
|
|
||||||
initiate all publication stuff (and, of course, call this method).
|
|
||||||
|
|
||||||
You MUST implement it, so the publication do really something.
|
|
||||||
All publications can be synchronous or asynchronous.
|
|
||||||
|
|
||||||
The main difference between both is that first do whatever needed, (the
|
|
||||||
action must be fast enough to do not block core), returning State.FINISHED.
|
|
||||||
|
|
||||||
The second (asynchronous) are publications that could block the core, so
|
|
||||||
it have to be done in more than one step.
|
|
||||||
|
|
||||||
An example publication could be a copy of a virtual machine, where:
|
|
||||||
* First we invoke the copy operation to virtualization provider
|
|
||||||
* Second, we kept needed values inside instance so we can serialize
|
|
||||||
them whenever requested
|
|
||||||
* Returns an State.RUNNING, indicating the core that the publication
|
|
||||||
has started but has to finish sometime later. (We do no check
|
|
||||||
again the state and keep waiting here, because we will block the
|
|
||||||
core untill this operation is finished).
|
|
||||||
|
|
||||||
In our example wi will simple assign a name, and set number to 5. We
|
|
||||||
will use this number later, to make a "delay" at check if the publication
|
|
||||||
has finished. (see method checkState)
|
|
||||||
|
|
||||||
We also will make this publication an "stepped one", that is, it will not
|
|
||||||
finish at publish call but a later checkState call
|
|
||||||
|
|
||||||
Take care with instantiating threads from here. Whenever a publish returns
|
|
||||||
"State.RUNNING", the core will recheck it later, but not using this instance
|
|
||||||
and maybe that even do not use this server.
|
|
||||||
|
|
||||||
If you want to use threadings or somethin likt it, use DelayedTasks and
|
|
||||||
do not block it. You also musht provide the mechanism to allow those
|
|
||||||
DelayedTask to communicate with the publication.
|
|
||||||
|
|
||||||
One sample could be, for example, to copy a bunch of files, but we know
|
|
||||||
that this copy can take a long time and don't want it to take make it
|
|
||||||
all here, but in a separate task. Now, do you remember that "environment"
|
|
||||||
that is unique for every instance?, well, we can create a delayed task,
|
|
||||||
and pass that environment (owned by this intance) as a mechanism for
|
|
||||||
informing when the task is finished. (We insert at delayed tasks queue
|
|
||||||
an instance, not a class itself, so we can instantiate a class and
|
|
||||||
store it at delayed task queue.
|
|
||||||
|
|
||||||
Also note that, in that case, this class can also acomplish that by simply
|
|
||||||
using the suggestedTime attribute and the checkState method in most cases.
|
|
||||||
'''
|
'''
|
||||||
self._number = 5
|
self._name = self.service().sanitizeVmName('UDS Publication' + ' ' + self.dsName() + "-" + str(self.revision()))
|
||||||
self._reason = ''
|
comments = _('UDS pub for {0} at {1}').format( self.dsName(), str(datetime.now()).split('.')[0] )
|
||||||
|
self._reason = '' # No error, no reason for it
|
||||||
|
self._destroyAfter = 'f'
|
||||||
|
self._state = 'locked'
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._templateId = self.service().makeTemplate(self._name, comments)
|
||||||
|
except Exception as e:
|
||||||
|
self._reason = str(e)
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
def checkState(self):
|
def checkState(self):
|
||||||
'''
|
'''
|
||||||
Our publish method will initiate publication, but will not finish it.
|
Checks state of publication creation
|
||||||
So in our sample, wi will only check if _number reaches 0, and if so
|
|
||||||
return that we have finished, else we will return that we are working
|
|
||||||
on it.
|
|
||||||
|
|
||||||
One publish returns State.RUNNING, this task will get called untill
|
|
||||||
checkState returns State.FINISHED.
|
|
||||||
|
|
||||||
Also, wi will make the publication fail one of every 10 calls to this
|
|
||||||
method.
|
|
||||||
|
|
||||||
Note: Destroying an publication also makes use of this method, so you
|
|
||||||
must keep the info of that you are checking (publishing or destroying...)
|
|
||||||
In our case, destroy is 1-step action so this will no get called while
|
|
||||||
destroying...
|
|
||||||
'''
|
'''
|
||||||
import random
|
if self._state == 'ok':
|
||||||
self._number -= 1
|
|
||||||
# Serialization will take care of storing self._number
|
|
||||||
|
|
||||||
# One of every 10 calls
|
|
||||||
if random.randint(0, 9) == 9:
|
|
||||||
self._reason = _('Random integer was 9!!! :-)')
|
|
||||||
return State.ERROR
|
|
||||||
|
|
||||||
if self._number <= 0:
|
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
else:
|
|
||||||
return State.RUNNING
|
if self._state == 'error':
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
|
self._state = self.service().getTemplateState(self._templateId)
|
||||||
|
|
||||||
|
# If publication os done (template is ready), and cancel was requested, do it just after template becomes ready
|
||||||
|
if self._state == 'ok':
|
||||||
|
if self._destroyAfter == 't':
|
||||||
|
return self.destroy()
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
|
||||||
def finish(self):
|
def finish(self):
|
||||||
'''
|
'''
|
||||||
Invoked when Publication manager noticed that the publication has finished.
|
In our case, finish does nothing
|
||||||
This give us the oportunity of cleaning up things (as stored vars, etc..),
|
|
||||||
or initialize variables that will be needed in a later phase (by deployed
|
|
||||||
services)
|
|
||||||
|
|
||||||
Returned value, if any, is ignored
|
|
||||||
'''
|
'''
|
||||||
import string
|
pass
|
||||||
import random
|
|
||||||
# Make simply a random string
|
|
||||||
self._name = ''.join(random.choice(string.ascii_uppercase + string.digits) for x in range(10))
|
|
||||||
|
|
||||||
def reasonOfError(self):
|
def reasonOfError(self):
|
||||||
'''
|
'''
|
||||||
@ -236,26 +143,22 @@ class OVirtPublication(Publication):
|
|||||||
The retunred value is the same as when publishing, State.RUNNING,
|
The retunred value is the same as when publishing, State.RUNNING,
|
||||||
State.FINISHED or State.ERROR.
|
State.FINISHED or State.ERROR.
|
||||||
'''
|
'''
|
||||||
self._name = ''
|
|
||||||
self._reason = '' # In fact, this is not needed, but cleaning up things... :-)
|
|
||||||
|
|
||||||
# We do not do anything else to destroy this instance of publication
|
# We do not do anything else to destroy this instance of publication
|
||||||
return State.FINISHED
|
if self._state == 'locked':
|
||||||
|
self._destroyAfter = 't'
|
||||||
|
return State.RUNNING
|
||||||
|
|
||||||
|
try:
|
||||||
|
self.service().removeTemplate(self._templateId)
|
||||||
|
except Exception as e:
|
||||||
|
self._reason = str(e)
|
||||||
|
return State.ERROR
|
||||||
|
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
def cancel(self):
|
def cancel(self):
|
||||||
'''
|
'''
|
||||||
Invoked for canceling the current operation.
|
Do same thing as destroy
|
||||||
This can be invoked directly by an administration or by the clean up
|
|
||||||
of the deployed service (indirectly).
|
|
||||||
When administrator requests it, the cancel is "delayed" and not
|
|
||||||
invoked directly.
|
|
||||||
|
|
||||||
Also, take into account that cancel is the initiation of, maybe, a
|
|
||||||
multiple-step action, so it returns, as publish and destroy does.
|
|
||||||
|
|
||||||
In our case, cancel simply invokes "destroy", that cleans up
|
|
||||||
things and returns that the action has finished in 1 step.
|
|
||||||
'''
|
'''
|
||||||
return self.destroy()
|
return self.destroy()
|
||||||
|
|
||||||
|
@ -316,7 +316,7 @@ class Client(object):
|
|||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
def publish(self, name, vmId, clusterId, storageId):
|
def makeTemplate(self, name, comments, vmId, clusterId, storageId):
|
||||||
'''
|
'''
|
||||||
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
Publish the machine (makes a template from it so we can create COWs) and returns the template id of
|
||||||
the creating machine
|
the creating machine
|
||||||
@ -330,6 +330,7 @@ class Client(object):
|
|||||||
Returns
|
Returns
|
||||||
Raises an exception if operation could not be acomplished, or returns the id of the template being created.
|
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, vmId, clusterId, storageId)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
lock.acquire(True)
|
lock.acquire(True)
|
||||||
@ -337,25 +338,67 @@ class Client(object):
|
|||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
storage = api.storagedomains.get(id=storageId)
|
storage = api.storagedomains.get(id=storageId)
|
||||||
|
storage_domain = params.StorageDomain(storage)
|
||||||
|
|
||||||
cluster = api.clusters.get(id=clusterId)
|
cluster = api.clusters.get(id=clusterId)
|
||||||
vm = api.vms.get(id=vmId)
|
vm = api.vms.get(id=vmId)
|
||||||
|
|
||||||
if vm.get_status().get_state() != 'down':
|
if vm.get_status().get_state() != 'down':
|
||||||
raise Exception('Machine must be in down state to publish it')
|
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)
|
||||||
|
|
||||||
|
api.templates.add(template)
|
||||||
|
|
||||||
api.templates.add(params.Template(storage_domain=storage, origin = 'UDS', name=name, vm=vm, cluster=cluster))
|
|
||||||
return api.templates.get(name=name).get_id()
|
return api.templates.get(name=name).get_id()
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
|
|
||||||
def getPublishState(self, templateId):
|
def getTemplateState(self, templateId):
|
||||||
|
'''
|
||||||
|
Returns current template state.
|
||||||
|
|
||||||
|
Returned values could be:
|
||||||
|
ok
|
||||||
|
locked
|
||||||
|
removed
|
||||||
|
|
||||||
|
(don't know if ovirt returns something more right now, will test what happens when template can't be published)
|
||||||
|
'''
|
||||||
try:
|
try:
|
||||||
lock.acquire(True)
|
lock.acquire(True)
|
||||||
|
|
||||||
api = self.__getApi()
|
api = self.__getApi()
|
||||||
|
|
||||||
return api.templates.get(id=templateId).get_status().get_state()
|
template = api.templates.get(id=templateId)
|
||||||
|
|
||||||
|
if template is None:
|
||||||
|
return 'removed'
|
||||||
|
|
||||||
|
return template.get_status().get_state()
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
lock.release()
|
lock.release()
|
||||||
|
|
||||||
|
def removeTemplate(self, templateId):
|
||||||
|
'''
|
||||||
|
Removes a template from ovirt server
|
||||||
|
|
||||||
|
Returns nothing, and raises an Exception if it fails
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
lock.acquire(True)
|
||||||
|
|
||||||
|
api = self.__getApi()
|
||||||
|
|
||||||
|
template = api.templates.get(id=templateId)
|
||||||
|
if template is None:
|
||||||
|
raise Exception('Template does not exists')
|
||||||
|
|
||||||
|
template.delete()
|
||||||
|
# This returns nothing, if it fails it raises an exception
|
||||||
|
finally:
|
||||||
|
lock.release()
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue
Block a user