mirror of
https://github.com/dkmstr/openuds.git
synced 2025-01-10 01:17:59 +03:00
* Fixed "double cancel" of a publication (cancelling over an already
cancelling publication will "force" the cancellation, meaning that no check is done, it simply stops publication and mark it as "cancelled" (leaving a log on service pool, ofc)
This commit is contained in:
parent
7d44bd7b65
commit
e1f26d2157
@ -30,6 +30,8 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib.sessions.backends.db import SessionStore
|
||||
@ -134,18 +136,22 @@ class Handler(object):
|
||||
def header(self, headerName):
|
||||
'''
|
||||
Get's an specific header name from REST request
|
||||
:param headerName: name of header to get
|
||||
'''
|
||||
return self._headers.get(headerName)
|
||||
|
||||
def addHeader(self, header, value):
|
||||
'''
|
||||
Inserts a new header inside the headers list
|
||||
:param header: name of header to insert
|
||||
:param value: value of header
|
||||
'''
|
||||
self._headers[header] = value
|
||||
|
||||
def removeHeader(self, header):
|
||||
'''
|
||||
Removes an specific header from the headers list
|
||||
:param header: Name of header to remove
|
||||
'''
|
||||
try:
|
||||
del self._headers[header]
|
||||
@ -163,6 +169,12 @@ class Handler(object):
|
||||
def storeSessionAuthdata(session, id_auth, username, locale, is_admin, staff_member):
|
||||
'''
|
||||
Stores the authentication data inside current session
|
||||
:param session: session handler (Djano user session object)
|
||||
:param id_auth: Authenticator id (DB object id)
|
||||
:param username: Name of user (login name)
|
||||
:param locale: Assigned locale
|
||||
:param is_admin: If user is considered admin or not
|
||||
:param staff_member: If is considered as staff member
|
||||
'''
|
||||
if is_admin:
|
||||
staff_member = True # Make admins also staff members :-)
|
||||
@ -179,6 +191,11 @@ class Handler(object):
|
||||
'''
|
||||
Generates the authentication token from a session, that is basically
|
||||
the session key itself
|
||||
:param id_auth: Authenticator id (DB object id)
|
||||
:param username: Name of user (login name)
|
||||
:param locale: Assigned locale
|
||||
:param is_admin: If user is considered admin or not
|
||||
:param staf_member: If user is considered staff member or not
|
||||
'''
|
||||
session = SessionStore()
|
||||
session.set_expiry(GlobalConfig.ADMIN_IDLE_TIME.getInt())
|
||||
|
@ -30,6 +30,9 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.utils.translation import ugettext as _
|
||||
@ -40,7 +43,6 @@ from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
from uds.REST.model import DetailHandler
|
||||
from uds.REST import ResponseError
|
||||
from uds.core.util.State import State
|
||||
|
||||
import logging
|
||||
|
||||
@ -155,7 +157,7 @@ class CachedService(AssignedService):
|
||||
else:
|
||||
k = parent.cachedUserServices().get(uuid=item)
|
||||
return AssignedService.itemToDict(k, True)
|
||||
except:
|
||||
except Exception:
|
||||
logger.exception('getItems')
|
||||
self.invalidItemException()
|
||||
|
||||
@ -179,11 +181,14 @@ class CachedService(AssignedService):
|
||||
item = parent.cachedUserServices().get(uuid=item)
|
||||
logger.debug('Getting logs for {0}'.format(item))
|
||||
return log.getLogs(item)
|
||||
except:
|
||||
except Exception:
|
||||
self.invalidItemException()
|
||||
|
||||
|
||||
class Groups(DetailHandler):
|
||||
'''
|
||||
Processes the groups detail requests of a Service Pool
|
||||
'''
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'id': i.uuid,
|
||||
@ -214,6 +219,9 @@ class Groups(DetailHandler):
|
||||
|
||||
|
||||
class Transports(DetailHandler):
|
||||
'''
|
||||
Processes the transports detail requests of a Service Pool
|
||||
'''
|
||||
def getItems(self, parent, item):
|
||||
return [{
|
||||
'id': i.uuid,
|
||||
@ -243,14 +251,27 @@ class Transports(DetailHandler):
|
||||
|
||||
|
||||
class Publications(DetailHandler):
|
||||
custom_methods = ['publish', 'cancel']
|
||||
'''
|
||||
Processes the publications detail requests of a Service Pool
|
||||
'''
|
||||
custom_methods = ['publish', 'cancel'] # We provided these custom methods
|
||||
|
||||
def publish(self, parent):
|
||||
'''
|
||||
Custom method "publish", provided to initiate a publication of a deployed service
|
||||
:param parent: Parent service pool
|
||||
'''
|
||||
logger.debug('Custom "publish" invoked')
|
||||
parent.publish()
|
||||
return self.success()
|
||||
|
||||
def cancel(self, parent, uuid):
|
||||
'''
|
||||
Invoked to cancel a running publication
|
||||
Double invocation (this means, invoking cancel twice) will mean that is a "forced cancelation"
|
||||
:param parent: Parent service pool
|
||||
:param uuid: uuid of the publication
|
||||
'''
|
||||
try:
|
||||
ds = DeployedServicePublication.objects.get(uuid=uuid)
|
||||
ds.cancel()
|
||||
|
@ -29,6 +29,8 @@
|
||||
'''
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
'''
|
||||
# pylint: disable=too-many-public-methods
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from uds.REST.handlers import NotFound, RequestError, ResponseError
|
||||
@ -47,7 +49,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
__updated__ = '2015-01-31'
|
||||
__updated__ = '2015-02-03'
|
||||
|
||||
|
||||
# a few constants
|
||||
@ -73,9 +75,13 @@ class BaseModelHandler(Handler):
|
||||
Base Handler for Master & Detail Handlers
|
||||
'''
|
||||
|
||||
def addField(self, gui, field):
|
||||
def addField(self, gui, field): # pylint: disable=no-self-use
|
||||
'''
|
||||
Add a field to a "gui" description
|
||||
Add a field to a "gui" description.
|
||||
This method checks that every required field element is in there.
|
||||
If not, defaults are assigned
|
||||
:param gui: List of "gui" items where the field will be added
|
||||
:param field: Field to be added (dictionary)
|
||||
'''
|
||||
gui.append({
|
||||
'name': field.get('name', ''),
|
||||
@ -100,6 +106,8 @@ class BaseModelHandler(Handler):
|
||||
def addDefaultFields(self, gui, flds):
|
||||
'''
|
||||
Adds default fields (based in a list) to a "gui" description
|
||||
:param gui: Gui list where the "default" fielsds will be added
|
||||
:param flds: List of fields names requested to be added. Valid values are 'name', 'comments', 'priority' and 'small_name'
|
||||
'''
|
||||
if 'name' in flds:
|
||||
self.addField(gui, {
|
||||
@ -141,7 +149,7 @@ class BaseModelHandler(Handler):
|
||||
|
||||
return gui
|
||||
|
||||
def typeInfo(self, type_):
|
||||
def typeInfo(self, type_): # pylint: disable=no-self-use
|
||||
'''
|
||||
Returns info about the type
|
||||
In fact, right now, it returns an empty dict, that will be extended by typeAsDict
|
||||
@ -161,7 +169,7 @@ class BaseModelHandler(Handler):
|
||||
})
|
||||
return res
|
||||
|
||||
def processTableFields(self, title, fields, row_style):
|
||||
def processTableFields(self, title, fields, row_style): # pylint: disable=no-self-use
|
||||
'''
|
||||
Returns a dict containing the table fields description
|
||||
'''
|
||||
@ -171,7 +179,12 @@ class BaseModelHandler(Handler):
|
||||
'row-style': row_style
|
||||
}
|
||||
|
||||
def readFieldsFromParams(self, fldList):
|
||||
def readFieldsFromParams(self, fldList): # pylint: disable=no-self-use
|
||||
'''
|
||||
Reads the indicated fields from the parameters received, and if
|
||||
:param fldList: List of required fields
|
||||
:return: A dictionary containing all required fields
|
||||
'''
|
||||
args = {}
|
||||
try:
|
||||
for key in fldList:
|
||||
@ -182,7 +195,13 @@ class BaseModelHandler(Handler):
|
||||
|
||||
return args
|
||||
|
||||
def fillIntanceFields(self, item, res):
|
||||
def fillIntanceFields(self, item, res): # pylint: disable=no-self-use
|
||||
'''
|
||||
For Managed Objects (db element that contains a serialized object), fills a dictionary with the "field" parameters values.
|
||||
For non managed objects, it does nothing
|
||||
:param item: Item to extract fields
|
||||
:param res: Dictionary to "extend" with instance key-values pairs
|
||||
'''
|
||||
if hasattr(item, 'getInstance'):
|
||||
for key, value in item.getInstance().valuesDict().iteritems():
|
||||
if type(value) in (unicode, str):
|
||||
@ -195,6 +214,7 @@ class BaseModelHandler(Handler):
|
||||
def invalidRequestException(self, message=None):
|
||||
'''
|
||||
Raises an invalid request error with a default translated string
|
||||
:param message: Custom message to add to exception. If it is None, "Invalid Request" is used
|
||||
'''
|
||||
message = _('Invalid Request') if message is None else message
|
||||
raise RequestError('{} {}: {}'.format(message, self.__class__, self._args))
|
||||
@ -230,7 +250,7 @@ class BaseModelHandler(Handler):
|
||||
|
||||
# Details do not have types at all
|
||||
# so, right now, we only process details petitions for Handling & tables info
|
||||
class DetailHandler(BaseModelHandler):
|
||||
class DetailHandler(BaseModelHandler): # pylint: disable=abstract-class-not-used
|
||||
'''
|
||||
Detail handler (for relations such as provider-->services, authenticators-->users,groups, deployed services-->cache,assigned, groups, transports
|
||||
Urls recognized for GET are:
|
||||
@ -253,8 +273,11 @@ class DetailHandler(BaseModelHandler):
|
||||
'''
|
||||
custom_methods = []
|
||||
|
||||
def __init__(self, parentHandler, path, params, *args, **kwargs):
|
||||
# pylint: disable=super-init-not-called
|
||||
def __init__(self, parentHandler, path, params, *args, **kwargs): # pylint: disable=super-init-not-called
|
||||
'''
|
||||
Detail Handlers in fact "disabled" handler most initialization, that is no needed because
|
||||
parent modelhandler has already done it (so we must access through parent handler)
|
||||
'''
|
||||
self._parent = parentHandler
|
||||
self._path = path
|
||||
self._params = params
|
||||
@ -272,16 +295,20 @@ class DetailHandler(BaseModelHandler):
|
||||
if check in self.custom_methods:
|
||||
try:
|
||||
operation = getattr(self, check)
|
||||
|
||||
if arg is None:
|
||||
return operation(parent)
|
||||
else:
|
||||
return operation(parent, arg)
|
||||
except Exception:
|
||||
self.invalidMethodException()
|
||||
|
||||
if arg is None:
|
||||
return operation(parent)
|
||||
else:
|
||||
return operation(parent, arg)
|
||||
return None
|
||||
|
||||
def get(self):
|
||||
def get(self): # pylint: disable=too-many-branches,too-many-return-statements
|
||||
'''
|
||||
Processes GET method for a detail Handler
|
||||
'''
|
||||
# Process args
|
||||
logger.debug("Detail args for GET: {0}".format(self._args))
|
||||
nArgs = len(self._args)
|
||||
@ -326,7 +353,9 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
def put(self):
|
||||
'''
|
||||
Put is delegated to specific implementation
|
||||
Process the "PUT" operation, making the correspondent checks.
|
||||
Evaluates if it is a new element or a "modify" operation (based on if it has parameter),
|
||||
and invokes "saveItem" with parent & item (that can be None for a new Item)
|
||||
'''
|
||||
# logger.debug("Detail args for PUT: {0}, {1}".format(self._args, self._params))
|
||||
|
||||
@ -345,13 +374,16 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
def post(self):
|
||||
'''
|
||||
Post will be used for, for example, testing
|
||||
Process the "POST" operation
|
||||
Post can be used for, for example, testing.
|
||||
Right now is an invalid method for Detail elements
|
||||
'''
|
||||
self.invalidRequestException('This method does not accepts POST')
|
||||
|
||||
def delete(self):
|
||||
'''
|
||||
Put is delegated to specific implementation
|
||||
Process the "DELETE" operation, making the correspondent checks.
|
||||
Extracts the item id and invokes deleteItem with parent item and item id (uuid)
|
||||
'''
|
||||
logger.debug("Detail args for DELETE: {0}".format(self._args))
|
||||
|
||||
@ -362,9 +394,10 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
return self.deleteItem(parent, self._args[0])
|
||||
|
||||
# Invoked if default get can't process request
|
||||
def fallbackGet(self):
|
||||
'''
|
||||
Invoked if default get can't process request.
|
||||
Here derived classes can process "non default" (and so, not understood) GET constructions
|
||||
'''
|
||||
raise self.invalidRequestException('Fallback invoked')
|
||||
|
||||
@ -372,7 +405,7 @@ class DetailHandler(BaseModelHandler):
|
||||
# Default (as sample) getItems
|
||||
def getItems(self, parent, item):
|
||||
'''
|
||||
This must be overriden by desdendants
|
||||
This MUST be overridden by derived classes
|
||||
Excepts to return a list of dictionaries or a single dictionary, depending on "item" param
|
||||
If "item" param is None, ALL items are expected to be returned as a list of dictionaries
|
||||
If "Item" param has an id (normally an uuid), one item is expected to be returned as dictionary
|
||||
@ -384,30 +417,83 @@ class DetailHandler(BaseModelHandler):
|
||||
|
||||
# Default save
|
||||
def saveItem(self, parent, item):
|
||||
'''
|
||||
Invoked for a valid "put" operation
|
||||
If this method is not overridden, the detail class will not have "Save/modify" operations.
|
||||
Parameters (probably object fields) must be retrieved from "_params" member variable
|
||||
:param parent: Parent of this detail (parent DB Object)
|
||||
:param item: Item id (uuid)
|
||||
:return: Normally "success" is expected, but can throw any "exception"
|
||||
'''
|
||||
logger.debug('Default saveItem handler caller for {0}'.format(self._path))
|
||||
self.invalidRequestException()
|
||||
|
||||
# Default delete
|
||||
def deleteItem(self, parent, item):
|
||||
'''
|
||||
Invoked for a valid "delete" operation.
|
||||
If this method is not overriden, the detail class will not have "delete" operation.
|
||||
:param parent: Parent of this detail (parent DB Object)
|
||||
:param item: Item id (uuid)
|
||||
:return: Normally "success" is expected, but can throw any "exception"
|
||||
'''
|
||||
self.invalidRequestException()
|
||||
|
||||
# A detail handler must also return title & fields for tables
|
||||
def getTitle(self, parent):
|
||||
def getTitle(self, parent): # pylint: disable=no-self-use
|
||||
'''
|
||||
A "generic" title for a view based on this detail.
|
||||
If not overridden, defaults to ''
|
||||
:param parent: Parent object
|
||||
:return: Expected to return an string that is the "title".
|
||||
'''
|
||||
return ''
|
||||
|
||||
def getFields(self, parent):
|
||||
def getFields(self, parent): # pylint: disable=no-self-use
|
||||
'''
|
||||
A "generic" list of fields for a view based on this detail.
|
||||
If not overridden, defaults to emty list
|
||||
:param parent: Parent object
|
||||
:return: Expected to return a list of fields
|
||||
'''
|
||||
return []
|
||||
|
||||
def getRowStyle(self, parent):
|
||||
def getRowStyle(self, parent): # pylint: disable=no-self-use
|
||||
'''
|
||||
A "generic" row style based on row field content.
|
||||
If not overridden, defaults to {}
|
||||
:param parent: Parent object
|
||||
:return: Expected to return a dictionary that contains 'field' & 'prefix' fields
|
||||
'''
|
||||
return {}
|
||||
|
||||
def getGui(self, parent, forType):
|
||||
def getGui(self, parent, forType): # pylint: disable=no-self-use
|
||||
'''
|
||||
Gets the gui that is needed in order to "edit/add" new items on this detail
|
||||
If not overriden, means that the detail has no edit/new Gui
|
||||
:param parent: Parent object
|
||||
:param forType: Type of object needing gui
|
||||
:return: a "gui" (list of gui fields)
|
||||
'''
|
||||
raise RequestError('Gui not provided for this type of object')
|
||||
|
||||
def getTypes(self, parent, forType):
|
||||
def getTypes(self, parent, forType): # pylint: disable=no-self-use
|
||||
'''
|
||||
The default is that detail element will not have any types (they are "homogeneous")
|
||||
but we provided this method, that can be overridden, in case one detail needs it
|
||||
:param parent: Parent object
|
||||
:param forType: Request argument in fact
|
||||
:return: list of strings that repressents the detail types
|
||||
'''
|
||||
return [] # Default is that details do not have types
|
||||
|
||||
def getLogs(self, parent, item):
|
||||
'''
|
||||
If the detail has any log associated with it items, provide it overriding this method
|
||||
:param parent:
|
||||
:param item:
|
||||
:return: a list of log elements (normally got using "uds.core.util.log.getLogs" method)
|
||||
'''
|
||||
self.invalidMethodException()
|
||||
|
||||
|
||||
|
@ -38,7 +38,8 @@ from uds.core.jobs.DelayedTaskRunner import DelayedTaskRunner
|
||||
from uds.core.util.Config import GlobalConfig
|
||||
from uds.core.services.Exceptions import PublishException
|
||||
from uds.models import DeployedServicePublication, getSqlDatetime
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util.State import State
|
||||
from uds.core.util import log
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -47,27 +48,32 @@ PUBTAG = 'pm-'
|
||||
|
||||
|
||||
class PublicationOldMachinesCleaner(DelayedTask):
|
||||
'''
|
||||
This delayed task is for removing a pending "removable" publication
|
||||
'''
|
||||
def __init__(self, publicationId):
|
||||
super(PublicationOldMachinesCleaner, self).__init__()
|
||||
self._id = publicationId
|
||||
|
||||
def run(self):
|
||||
try:
|
||||
dsp = DeployedServicePublication.objects.get(pk=self._id)
|
||||
if (dsp.state != State.REMOVABLE):
|
||||
servicePoolPub = DeployedServicePublication.objects.get(pk=self._id)
|
||||
if servicePoolPub.state != State.REMOVABLE:
|
||||
logger.info('Already removed')
|
||||
|
||||
now = getSqlDatetime()
|
||||
activePub = dsp.deployed_service.activePublication()
|
||||
dsp.deployed_service.userServices.filter(in_use=True).update(in_use=False, state_date=now)
|
||||
dsp.deployed_service.markOldUserServicesAsRemovables(activePub)
|
||||
except:
|
||||
activePub = servicePoolPub.deployed_service.activePublication()
|
||||
servicePoolPub.deployed_service.userServices.filter(in_use=True).update(in_use=False, state_date=now)
|
||||
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(activePub)
|
||||
except Exception:
|
||||
logger.info("Machine removal for {0} not executed because publication is already removed")
|
||||
# Removed provider, no problem at all, no update is done
|
||||
pass
|
||||
|
||||
|
||||
class PublicationLauncher(DelayedTask):
|
||||
'''
|
||||
This delayed task if for launching a new publication
|
||||
'''
|
||||
def __init__(self, publish):
|
||||
super(PublicationLauncher, self).__init__()
|
||||
self._publishId = publish.id
|
||||
@ -76,97 +82,103 @@ class PublicationLauncher(DelayedTask):
|
||||
logger.debug('Publishing')
|
||||
try:
|
||||
with transaction.atomic():
|
||||
dsp = DeployedServicePublication.objects.select_for_update().get(pk=self._publishId)
|
||||
if dsp.state != State.LAUNCHING: # If not preparing (may has been canceled by user) just return
|
||||
servicePoolPub = DeployedServicePublication.objects.select_for_update().get(pk=self._publishId)
|
||||
if servicePoolPub.state != State.LAUNCHING: # If not preparing (may has been canceled by user) just return
|
||||
return
|
||||
dsp.state = State.PREPARING
|
||||
dsp.save()
|
||||
pi = dsp.getInstance()
|
||||
servicePoolPub.state = State.PREPARING
|
||||
servicePoolPub.save()
|
||||
pi = servicePoolPub.getInstance()
|
||||
state = pi.publish()
|
||||
deployedService = dsp.deployed_service
|
||||
deployedService = servicePoolPub.deployed_service
|
||||
deployedService.current_pub_revision += 1
|
||||
deployedService.save()
|
||||
PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
|
||||
except Exception:
|
||||
logger.exception("Exception launching publication")
|
||||
dsp.state = State.ERROR
|
||||
dsp.save()
|
||||
servicePoolPub.state = State.ERROR
|
||||
servicePoolPub.save()
|
||||
|
||||
|
||||
# Delayed Task that checks if a publication is done
|
||||
class PublicationFinishChecker(DelayedTask):
|
||||
def __init__(self, publish):
|
||||
'''
|
||||
This delayed task is responsible of checking if a publication is finished
|
||||
'''
|
||||
def __init__(self, servicePoolPub):
|
||||
super(PublicationFinishChecker, self).__init__()
|
||||
self._publishId = publish.id
|
||||
self._state = publish.state
|
||||
self._publishId = servicePoolPub.id
|
||||
self._state = servicePoolPub.state
|
||||
|
||||
@staticmethod
|
||||
def checkAndUpdateState(dsp, pi, state):
|
||||
def checkAndUpdateState(servicePoolPub, pi, state):
|
||||
'''
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the dsp database object
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
|
||||
Return True if it has to continue checking, False if finished
|
||||
'''
|
||||
try:
|
||||
prevState = dsp.state
|
||||
prevState = servicePoolPub.state
|
||||
checkLater = False
|
||||
if State.isFinished(state):
|
||||
# Now we mark, if it exists, the previous usable publication as "Removable"
|
||||
if State.isPreparing(prevState):
|
||||
for old in dsp.deployed_service.publications.filter(state=State.USABLE):
|
||||
for old in servicePoolPub.deployed_service.publications.filter(state=State.USABLE):
|
||||
old.state = State.REMOVABLE
|
||||
old.save()
|
||||
pc = PublicationOldMachinesCleaner(old.id)
|
||||
pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
|
||||
|
||||
dsp.setState(State.USABLE)
|
||||
dsp.deployed_service.markOldUserServicesAsRemovables(dsp)
|
||||
servicePoolPub.setState(State.USABLE)
|
||||
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(servicePoolPub)
|
||||
elif State.isRemoving(prevState):
|
||||
dsp.setState(State.REMOVED)
|
||||
servicePoolPub.setState(State.REMOVED)
|
||||
else: # State is canceling
|
||||
dsp.setState(State.CANCELED)
|
||||
servicePoolPub.setState(State.CANCELED)
|
||||
# Mark all previous publications deployed services as removables
|
||||
# and make this usable
|
||||
pi.finish()
|
||||
dsp.updateData(pi)
|
||||
servicePoolPub.updateData(pi)
|
||||
elif State.isErrored(state):
|
||||
dsp.updateData(pi)
|
||||
dsp.state = State.ERROR
|
||||
servicePoolPub.updateData(pi)
|
||||
servicePoolPub.state = State.ERROR
|
||||
else:
|
||||
checkLater = True # The task is running
|
||||
dsp.updateData(pi)
|
||||
servicePoolPub.updateData(pi)
|
||||
|
||||
dsp.save()
|
||||
servicePoolPub.save()
|
||||
if checkLater:
|
||||
PublicationFinishChecker.checkLater(dsp, pi)
|
||||
except:
|
||||
PublicationFinishChecker.checkLater(servicePoolPub, pi)
|
||||
except Exception:
|
||||
logger.exception('At checkAndUpdate for publication')
|
||||
PublicationFinishChecker.checkLater(dsp, pi)
|
||||
PublicationFinishChecker.checkLater(servicePoolPub, pi)
|
||||
|
||||
@staticmethod
|
||||
def checkLater(dsp, pi):
|
||||
def checkLater(servicePoolPub, pi):
|
||||
'''
|
||||
Inserts a task in the delayedTaskRunner so we can check the state of this publication
|
||||
@param dps: Database object for DeployedServicePublication
|
||||
@param pi: Instance of Publication manager for the object
|
||||
'''
|
||||
DelayedTaskRunner.runner().insert(PublicationFinishChecker(dsp), pi.suggestedTime, PUBTAG + str(dsp.id))
|
||||
DelayedTaskRunner.runner().insert(PublicationFinishChecker(servicePoolPub), pi.suggestedTime, PUBTAG + str(servicePoolPub.id))
|
||||
|
||||
def run(self):
|
||||
logger.debug('Checking publication finished {0}'.format(self._publishId))
|
||||
try:
|
||||
dsp = DeployedServicePublication.objects.get(pk=self._publishId)
|
||||
if dsp.state != self._state:
|
||||
servicePoolPub = DeployedServicePublication.objects.get(pk=self._publishId)
|
||||
if servicePoolPub.state != self._state:
|
||||
logger.debug('Task overrided by another task (state of item changed)')
|
||||
else:
|
||||
pi = dsp.getInstance()
|
||||
pi = servicePoolPub.getInstance()
|
||||
logger.debug("publication instance class: {0}".format(pi.__class__))
|
||||
state = pi.checkState()
|
||||
PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pi, state)
|
||||
except Exception, e:
|
||||
logger.debug('Deployed service not found (erased from database) {0} : {1}'.format(e.__class__, e))
|
||||
|
||||
|
||||
class PublicationManager(object):
|
||||
'''
|
||||
Manager responsible of controlling publications
|
||||
'''
|
||||
_manager = None
|
||||
|
||||
def __init__(self):
|
||||
@ -174,54 +186,83 @@ class PublicationManager(object):
|
||||
|
||||
@staticmethod
|
||||
def manager():
|
||||
'''
|
||||
Returns the singleton to this manager
|
||||
'''
|
||||
if PublicationManager._manager is None:
|
||||
PublicationManager._manager = PublicationManager()
|
||||
return PublicationManager._manager
|
||||
|
||||
def publish(self, deployedService):
|
||||
if deployedService.publications.filter(state__in=State.PUBLISH_STATES).count() > 0:
|
||||
def publish(self, servicePool): # pylint: disable=no-self-use
|
||||
'''
|
||||
Initiates the publication of a service pool, or raises an exception if this cannot be done
|
||||
:param servicePool: Service pool object (db object)
|
||||
'''
|
||||
if servicePool.publications.filter(state__in=State.PUBLISH_STATES).count() > 0:
|
||||
raise PublishException(_('Already publishing. Wait for previous publication to finish and try again'))
|
||||
|
||||
if deployedService.isInMaintenance():
|
||||
if servicePool.isInMaintenance():
|
||||
raise PublishException(_('Service is in maintenance mode and new publications are not allowed'))
|
||||
|
||||
try:
|
||||
now = getSqlDatetime()
|
||||
dsp = deployedService.publications.create(state=State.LAUNCHING, state_date=now, publish_date=now, revision=deployedService.current_pub_revision)
|
||||
dsp = None
|
||||
dsp = servicePool.publications.create(state=State.LAUNCHING, state_date=now, publish_date=now, revision=servicePool.current_pub_revision)
|
||||
DelayedTaskRunner.runner().insert(PublicationLauncher(dsp), 4, PUBTAG + str(dsp.id))
|
||||
except Exception as e:
|
||||
logger.debug('Caught exception at publish: {0}'.format(e))
|
||||
if dsp is not None:
|
||||
try:
|
||||
dsp.delete()
|
||||
except Exception:
|
||||
logger.info('Could not delete {}'.format(dsp))
|
||||
raise PublishException(str(e))
|
||||
|
||||
def cancel(self, dsp):
|
||||
dsp = DeployedServicePublication.objects.get(pk=dsp.id)
|
||||
if dsp.state not in State.PUBLISH_STATES:
|
||||
raise PublishException(_('Can\'t cancel non running publication'))
|
||||
def cancel(self, servicePoolPub): # pylint: disable=no-self-use
|
||||
'''
|
||||
Invoked to cancel a publication.
|
||||
Double invokation (i.e. invokation over a "cancelling" item) will lead to a "forced" cancellation (unclean)
|
||||
:param servicePoolPub: Service pool publication (db object for a publication)
|
||||
'''
|
||||
servicePoolPub = DeployedServicePublication.objects.get(pk=servicePoolPub.id)
|
||||
if servicePoolPub.state not in State.PUBLISH_STATES:
|
||||
if servicePoolPub.state == State.CANCELING: # Double cancel
|
||||
logger.info('Double cancel invoked for a publication')
|
||||
log.doLog(servicePoolPub.deployed_service, log.WARN, 'Forced cancel on publication, you must check uncleaned resources manually', log.ADMIN)
|
||||
servicePoolPub.setState(State.CANCELED)
|
||||
servicePoolPub.save()
|
||||
return
|
||||
else:
|
||||
raise PublishException(_('Can\'t cancel non running publication'))
|
||||
|
||||
if dsp.state == State.LAUNCHING:
|
||||
dsp.state = State.CANCELED
|
||||
dsp.save()
|
||||
return dsp
|
||||
if servicePoolPub.state == State.LAUNCHING:
|
||||
servicePoolPub.state = State.CANCELED
|
||||
servicePoolPub.save()
|
||||
return servicePoolPub
|
||||
|
||||
try:
|
||||
pi = dsp.getInstance()
|
||||
state = pi.cancel()
|
||||
dsp.setState(State.CANCELING)
|
||||
PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
|
||||
return dsp
|
||||
pubInstance = servicePoolPub.getInstance()
|
||||
state = pubInstance.cancel()
|
||||
servicePoolPub.setState(State.CANCELING)
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pubInstance, state)
|
||||
return servicePoolPub
|
||||
except Exception, e:
|
||||
raise PublishException(str(e))
|
||||
|
||||
def unpublish(self, dsp):
|
||||
if State.isUsable(dsp.state) == False and State.isRemovable(dsp.state) == False:
|
||||
raise PublishException(_('Can\'t unpublish non usable publication'))
|
||||
# TODO: Call assignation manager to remove removable items
|
||||
if dsp.userServices.exclude(state__in=State.INFO_STATES).count() > 0:
|
||||
def unpublish(self, servicePoolPub): # pylint: disable=no-self-use
|
||||
'''
|
||||
Unpublishes an active (usable) or removable publication
|
||||
:param servicePoolPub: Publication to unpublish
|
||||
'''
|
||||
if State.isUsable(servicePoolPub.state) is False and State.isRemovable(servicePoolPub.state) is False:
|
||||
raise PublishException(_('Can\'t unpublish non usable publication')
|
||||
)
|
||||
if servicePoolPub.userServices.exclude(state__in=State.INFO_STATES).count() > 0:
|
||||
raise PublishException(_('Can\'t unpublish publications with services in process'))
|
||||
try:
|
||||
pi = dsp.getInstance()
|
||||
state = pi.destroy()
|
||||
dsp.setState(State.REMOVING)
|
||||
PublicationFinishChecker.checkAndUpdateState(dsp, pi, state)
|
||||
pubInstance = servicePoolPub.getInstance()
|
||||
state = pubInstance.destroy()
|
||||
servicePoolPub.setState(State.REMOVING)
|
||||
PublicationFinishChecker.checkAndUpdateState(servicePoolPub, pubInstance, state)
|
||||
except Exception, e:
|
||||
raise PublishException(str(e))
|
||||
|
@ -71,7 +71,7 @@ class UserServiceOpChecker(DelayedTask):
|
||||
@staticmethod
|
||||
def checkAndUpdateState(userService, userServiceInstance, state):
|
||||
'''
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the dsp database object
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
|
||||
Return True if it has to continue checking, False if finished
|
||||
'''
|
||||
try:
|
||||
|
@ -113,7 +113,7 @@ class ClusterMigrationTask(DelayedTask):
|
||||
@staticmethod
|
||||
def checkAndUpdateState(userService, userServiceInstance, state):
|
||||
'''
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the dsp database object
|
||||
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
|
||||
Return True if it has to continue checking, False if finished
|
||||
'''
|
||||
try:
|
||||
|
Loading…
Reference in New Issue
Block a user