1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-29 21:47:17 +03:00

Added "delayed cancel" for some kind of services that are specially difficult to "cancel" while running...

This commit is contained in:
Adolfo Gómez García 2019-11-04 12:40:42 +01:00
parent 19754f7ed7
commit 9cb9fc6de1
7 changed files with 70 additions and 34 deletions

View File

@ -193,18 +193,18 @@ class MetaAssignedService(DetailHandler):
raise self.invalidItemException()
def deleteItem(self, parent: MetaPool, item: str) -> None:
service = self._getAssignedService(parent, item)
userService = self._getAssignedService(parent, item)
if service.user:
logStr = 'Deleted assigned service {} to user {} by {}'.format(service.friendly_name, service.user.pretty_name, self._user.pretty_name)
if userService.user:
logStr = 'Deleted assigned service {} to user {} by {}'.format(userService.friendly_name, userService.user.pretty_name, self._user.pretty_name)
else:
logStr = 'Deleted cached service {} by {}'.format(service.friendly_name, self._user.pretty_name)
logStr = 'Deleted cached service {} by {}'.format(userService.friendly_name, self._user.pretty_name)
if service.state in (State.USABLE, State.REMOVING):
service.remove()
elif service.state == State.PREPARING:
service.cancel()
elif service.state == State.REMOVABLE:
if userService.state in (State.USABLE, State.REMOVING):
userService.remove()
elif userService.state == State.PREPARING:
userService.cancel()
elif userService.state == State.REMOVABLE:
raise self.invalidItemException(_('Item already being removed'))
else:
raise self.invalidItemException(_('Item is not removable'))

View File

@ -66,7 +66,7 @@ class AssignedService(DetailHandler):
'id_deployed_service': item.deployed_service.uuid,
'unique_id': item.unique_id,
'friendly_name': item.friendly_name,
'state': item.state,
'state': item.state if not props.get('destroy_after') else State.CANCELING,
'os_state': item.os_state,
'state_date': item.state_date,
'creation_date': item.creation_date,
@ -145,21 +145,21 @@ class AssignedService(DetailHandler):
# This is also used by CachedService, so we use "userServices" directly and is valid for both
def deleteItem(self, parent: models.ServicePool, item: str) -> None:
try:
service: models.UserService = parent.userServices.get(uuid=processUuid(item))
userService: models.UserService = parent.userServices.get(uuid=processUuid(item))
except Exception:
logger.exception('deleteItem')
raise self.invalidItemException()
if service.user:
logStr = 'Deleted assigned service {} to user {} by {}'.format(service.friendly_name, service.user.pretty_name, self._user.pretty_name)
if userService.user:
logStr = 'Deleted assigned service {} to user {} by {}'.format(userService.friendly_name, userService.user.pretty_name, self._user.pretty_name)
else:
logStr = 'Deleted cached service {} by {}'.format(service.friendly_name, self._user.pretty_name)
logStr = 'Deleted cached service {} by {}'.format(userService.friendly_name, self._user.pretty_name)
if service.state in (State.USABLE, State.REMOVING):
service.remove()
elif service.state == State.PREPARING:
service.cancel()
elif service.state == State.REMOVABLE:
if userService.state in (State.USABLE, State.REMOVING):
userService.remove()
elif userService.state == State.PREPARING:
userService.cancel()
elif userService.state == State.REMOVABLE:
raise self.invalidItemException(_('Item already being removed'))
else:
raise self.invalidItemException(_('Item is not removable'))

View File

@ -215,18 +215,28 @@ class UserServiceManager:
Cancels an user service creation
@return: the Uservice canceling
"""
userService = UserService.objects.get(pk=userService.id)
userService.refresh_from_db()
logger.debug('Canceling userService %s creation', userService)
if userService.isPreparing() is False:
logger.info('Cancel requested for a non running operation, performing removal instead')
return self.remove(userService)
userServiceInstance = userService.getInstance()
# We simply notify service that it should cancel operation
state = userServiceInstance.cancel()
userService.setState(State.CANCELING)
# Data will be serialized on makeUnique process
UserServiceOpChecker.makeUnique(userService, userServiceInstance, state)
if not userServiceInstance.supportsCancel(): # Does not supports cancel, but destroy, so mark it for "later" destroy
# State is kept, just mark it for destroy after finished preparing
userService.setProperty('destroy_after', 'y')
else:
userService.setState(State.CANCELING)
# We simply notify service that it should cancel operation
state = userServiceInstance.cancel()
# Data will be serialized on makeUnique process
# If cancel is not supported, base cancel always returns "FINISHED", and
# opchecker will set state to "removable"
UserServiceOpChecker.makeUnique(userService, userServiceInstance, state)
return userService
def remove(self, userService: UserService) -> UserService:

View File

@ -48,6 +48,8 @@ USERSERVICE_TAG = 'cm-'
# State updaters
# This will be executed on current service state for checking transitions to new state, task states, etc..
class StateUpdater:
userService: UserService
userServiceInstalce: UserDeployment
def __init__(self, userService: UserService, userServiceInstance: typing.Optional[UserDeployment] = None):
self.userService = userService
@ -81,13 +83,15 @@ class StateUpdater:
State.FINISHED: self.finish
}.get(state, self.error)
logger.debug('Running updater with state %s and executor %s', State.toString(state), executor)
logger.debug('Running Executor for %s with state %s and executor %s', self.userService.friendly_name, State.toString(state), executor)
try:
executor()
except Exception as e:
self.setError('Exception: {}'.format(e))
logger.debug('Executor for %s done', self.userService.friendly_name)
def finish(self):
raise NotImplementedError('finish method must be overriden')
@ -101,7 +105,7 @@ class StateUpdater:
class UpdateFromPreparing(StateUpdater):
def checkOsManagerRelated(self):
def checkOsManagerRelated(self) -> str:
osManager = self.userServiceInstance.osmanager()
state = State.USABLE
@ -133,6 +137,11 @@ class UpdateFromPreparing(StateUpdater):
return state
def finish(self):
if self.userService.getProperty('destroy_after'): # Marked for destroyal
self.userService.setProperty('destroy_after', '') # Cleanup..
self.save(State.REMOVABLE) # And start removing it
return
state = State.REMOVABLE # By default, if not valid publication, service will be marked for removal on preparation finished
if self.userService.isValidPublication():
logger.debug('Publication is valid for %s', self.userService.friendly_name)
@ -203,7 +212,7 @@ class UserServiceOpChecker(DelayedTask):
State.CANCELING: UpdateFromCanceling
}.get(userService.state, UpdateFromOther)
logger.debug('Updating from %s with updater %s and state %s', State.toString(userService.state), updater, state)
logger.debug('Updating %s from %s with updater %s and state %s', userService.friendly_name, State.toString(userService.state), updater, state)
updater(userService, userServiceInstance).run(state)

View File

@ -567,7 +567,14 @@ class UserDeployment(Environmentable, Serializable): # pylint: disable=too-many
to the core. Take that into account and handle exceptions inside
this method.
"""
raise NotImplementedError('cancel method for class {0} not provided!'.format(self.__class__.__name__))
return State.RUNNING
@classmethod
def supportsCancel(cls) -> bool:
"""
Helper to query if a class is custom (implements getJavascript method)
"""
return cls.cancel != UserDeployment.cancel
def reset(self) -> None:
"""

View File

@ -98,7 +98,11 @@ class ServiceCacheUpdater(Job):
continue
# Get data related to actual state of cache
inCacheL1: int = servicePool.cachedUserServices().filter(userServiceManager().getCacheStateFilter(services.UserDeployment.L1_CACHE)).count()
inCacheL1: int = servicePool.cachedUserServices().filter(
userServiceManager().getCacheStateFilter(services.UserDeployment.L1_CACHE)
).exclude(
Q(properties__name='destroy_after') & Q(properties__value='y')
).count()
inCacheL2: int = servicePool.cachedUserServices().filter(userServiceManager().getCacheStateFilter(services.UserDeployment.L2_CACHE)).count()
inAssigned: int = servicePool.assignedUserServices().filter(userServiceManager().getStateFilter()).count()
# if we bypasses max cache, we will reduce it in first place. This is so because this will free resources on service provider
@ -204,9 +208,15 @@ class ServiceCacheUpdater(Job):
def reduceL1Cache(self, servicePool: ServicePool, cacheL1: int, cacheL2: int, assigned: int):
logger.debug("Reducing L1 cache erasing a service in cache for %s", servicePool)
# We will try to destroy the newest cacheL1 element that is USABLE if the deployer can't cancel a new service creation
cacheItems: typing.List[UserService] = list(servicePool.cachedUserServices().filter(
userServiceManager().getCacheStateFilter(services.UserDeployment.L1_CACHE)
).order_by('-creation_date').iterator())
cacheItems: typing.List[UserService] = list(
servicePool.cachedUserServices().filter(
userServiceManager().getCacheStateFilter(services.UserDeployment.L1_CACHE)
).exclude(
Q(properties__name='destroy_after') & Q(properties__value='y')
).order_by(
'-creation_date'
).iterator()
)
if not cacheItems:
logger.debug('There is more services than max configured, but could not reduce cache L1 cause its already empty')

View File

@ -30,7 +30,7 @@
"""
from django.http import HttpResponse
from django.views.decorators.cache import cache_page
# from django.views.decorators.cache import cache_page
from uds.core.util.config import Config