openuds/server/documentation/_downloads/samples/services/SamplePublication.py
2012-07-19 23:47:54 +00:00

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