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:
parent
19754f7ed7
commit
9cb9fc6de1
@ -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'))
|
||||
|
@ -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'))
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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')
|
||||
|
@ -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
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user