forked from shaba/openuds
273 lines
11 KiB
Python
273 lines
11 KiB
Python
# -*- 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
|
|
'''
|
|
|
|
from django.utils.translation import ugettext as _
|
|
from uds.core.services import Publication
|
|
from uds.core.util.State import State
|
|
from datetime import datetime
|
|
import logging
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
class SamplePublication(Publication):
|
|
'''
|
|
This class shows how a publication is developed.
|
|
|
|
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
|
|
|
|
def initialize(self):
|
|
'''
|
|
This method will be invoked by default __init__ of base class, so it gives
|
|
us the oportunity to initialize whataver we need here.
|
|
|
|
In our case, we setup a few attributes..
|
|
'''
|
|
|
|
# We do not check anything at marshal method, so we ensure that
|
|
# default values are correctly handled by marshal.
|
|
self._name = 'test'
|
|
self._reason = '' # No error, no reason for it
|
|
self._number = 1
|
|
|
|
def marshal(self):
|
|
'''
|
|
returns data from an instance of Sample Publication serialized
|
|
'''
|
|
return '\t'.join( [self._name, self._reason, str(self._number)] )
|
|
|
|
def unmarshal(self, data):
|
|
'''
|
|
deserializes the data and loads it inside instance.
|
|
'''
|
|
logger.debug('Data: {0}'.format(data))
|
|
vals = data.split('\t')
|
|
logger.debug('Values: {0}'.format(vals))
|
|
self._name = vals[0]
|
|
self._reason = vals[1]
|
|
self._number = int(vals[2])
|
|
|
|
|
|
def publish(self):
|
|
'''
|
|
This method is invoked whenever the administrator requests a new publication.
|
|
|
|
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._reason = ''
|
|
return State.RUNNING
|
|
|
|
def checkState(self):
|
|
'''
|
|
Our publish method will initiate publication, but will not finish it.
|
|
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
|
|
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
|
|
else:
|
|
return State.RUNNING
|
|
|
|
|
|
def finish(self):
|
|
'''
|
|
Invoked when Publication manager noticed that the publication has finished.
|
|
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
|
|
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):
|
|
'''
|
|
If a publication produces an error, here we must notify the reason why
|
|
it happened. This will be called just after publish or checkState
|
|
if they return State.ERROR
|
|
|
|
Returns an string, in our case, set at checkState
|
|
'''
|
|
return self._reason
|
|
|
|
def destroy(self):
|
|
'''
|
|
This is called once a publication is no more needed.
|
|
|
|
This method do whatever needed to clean up things, such as
|
|
removing created "external" data (environment gets cleaned by core),
|
|
etc..
|
|
|
|
The retunred value is the same as when publishing, State.RUNNING,
|
|
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
|
|
return State.FINISHED
|
|
|
|
|
|
def cancel(self):
|
|
'''
|
|
Invoked for canceling the current operation.
|
|
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()
|
|
|
|
# Here ends the publication needed methods.
|
|
# Methods provided below are specific for this publication
|
|
# and will be used by user deployments that uses this kind of publication
|
|
|
|
def getBaseName(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
|
|
'''
|
|
return self._name
|