1
0
mirror of https://github.com/dkmstr/openuds.git synced 2024-12-24 21:34:41 +03:00

* Explained why stop shuld be ignored if no user service is found

* Small type checking addons
* Added method to allow "check" the availability of a service, currently used before removal
* Fixed removal so concurrent removal refers to real removals, not to marked for removal
This commit is contained in:
Adolfo Gómez García 2022-01-03 14:02:41 +01:00
parent 6d873ceccd
commit 12e74c16b5
16 changed files with 51 additions and 28 deletions

View File

@ -80,7 +80,10 @@ class TunnelTicket(Handler):
token = self._args[2][:48]
if not models.TunnelToken.validateToken(token):
if self._args[1][:4] == 'stop':
# "Eat" invalid stop requests, because Applications does not like them
# "Discard" invalid stop requests, because Applications does not like them.
# RDS connections keep alive for a while after the application is finished,
# Also, same tunnel can be used for multiple applications, so we need to
# discard invalid stop requests. (because the data provided is also for "several" applications)")
return {}
logger.error('Invalid token %s from %s', token, self._request.ip)
raise AccessDenied()

View File

@ -562,6 +562,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
)
serviceInstance = servicePool.service.getInstance()
if (
serviceInstance.isAvailable() and
removing >= serviceInstance.parent().getMaxRemovingServices()
and serviceInstance.parent().getIgnoreLimits() is False
):

View File

@ -243,6 +243,14 @@ class Service(Module):
"""
return self._provider
def isAvailable(self) -> bool:
"""
Returns if this service is reachable (that is, we can operate with it). This is used, for example, to check
if a service is "operable" before removing an user service (that is, pass from "waiting for remove" to "removing")
By default, this method returns True.
"""
return True
def requestServicesForAssignation(
self, **kwargs
) -> typing.Iterable[UserDeployment]:

View File

@ -93,4 +93,4 @@ class AssignedAndUnused(Job):
logger.debug(
'Found unused assigned service with no OS Manager %s', us
)
us.remove()
us.release()

View File

@ -47,7 +47,7 @@ class HangedCleaner(Job):
frecuency_cfg = GlobalConfig.MAX_INITIALIZING_TIME
friendly_name = 'Hanged services checker'
def run(self):
def run(self) -> None:
now = getSqlDatetime()
since_state = now - timedelta(
seconds=GlobalConfig.MAX_INITIALIZING_TIME.getInt()
@ -111,7 +111,7 @@ class HangedCleaner(Job):
us.friendly_name
),
)
us.remove() # Mark it again as removable, and let's see
us.release() # Mark it again as removable, and let's see
else:
log.doLog(
us,
@ -126,4 +126,4 @@ class HangedCleaner(Job):
us.friendly_name
),
)
us.removeOrCancel()
us.releaseOrCancel()

View File

@ -50,7 +50,7 @@ class PublicationInfoItemsCleaner(Job):
) # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload
friendly_name = 'Publications Info Cleaner'
def run(self):
def run(self) -> None:
removeFrom = getSqlDatetime() - timedelta(
seconds=GlobalConfig.KEEP_INFO_TIME.getInt(True)
)
@ -66,7 +66,7 @@ class PublicationCleaner(Job):
) # Request run publication "removal" every configued seconds. If config value is changed, it will be used at next reload
friendly_name = 'Publication Cleaner'
def run(self):
def run(self) -> None:
removables: typing.Iterable[
ServicePoolPublication
] = ServicePoolPublication.objects.filter(

View File

@ -43,7 +43,7 @@ class ScheduledAction(Job):
frecuency = 29 # Frecuncy for this job
friendly_name = 'Scheduled action runner'
def run(self):
def run(self) -> None:
configuredAction: CalendarAction
for configuredAction in CalendarAction.objects.filter(
service_pool__service__provider__maintenance_mode=False, # Avoid maintenance

View File

@ -52,7 +52,7 @@ class SchedulerHousekeeping(Job):
frecuency = 301 # Frecuncy for this job
friendly_name = 'Scheduler house keeping'
def run(self):
def run(self) -> None:
"""
Look for "hanged" scheduler tasks and reschedule them
"""

View File

@ -49,7 +49,7 @@ class DeployedServiceInfoItemsCleaner(Job):
) # Request run cache "info" cleaner every configured seconds. If config value is changed, it will be used at next reload
friendly_name = 'Deployed Service Info Cleaner'
def run(self):
def run(self) -> None:
removeFrom = getSqlDatetime() - timedelta(
seconds=GlobalConfig.KEEP_INFO_TIME.getInt()
)
@ -65,10 +65,10 @@ class DeployedServiceRemover(Job):
) # Request run publication "removal" every configued seconds. If config value is changed, it will be used at next reload
friendly_name = 'Deployed Service Cleaner'
def startRemovalOf(self, servicePool: ServicePool):
def startRemovalOf(self, servicePool: ServicePool) -> None:
if (
servicePool.service is None
): # Maybe an inconsistent value? (must not, but if no ref integrity in db, maybe someone "touched.. ;)")
): # Maybe an inconsistent value? (must not, but if no ref integrity in db, maybe someone hand-changed something.. ;)")
logger.error('Found service pool %s without service', servicePool.name)
servicePool.delete() # Just remove it "a las bravas", the best we can do
return
@ -91,8 +91,11 @@ class DeployedServiceRemover(Job):
servicePool.name += ' (removed)'
servicePool.save()
def continueRemovalOf(self, servicePool: ServicePool):
# Recheck that there is no publication created in "bad moment"
def continueRemovalOf(self, servicePool: ServicePool) -> None:
# get current time
now = getSqlDatetime()
# Recheck that there is no publication created just after "startRemovalOf"
try:
for pub in servicePool.publications.filter(state=State.PREPARING):
pub.cancel()
@ -116,9 +119,7 @@ class DeployedServiceRemover(Job):
state__in=State.INFO_STATES
).delete()
# Mark usable user services as removable
now = getSqlDatetime()
# Mark usable user services as removable, as batch
with transaction.atomic():
servicePool.userServices.select_for_update().filter(
state=State.USABLE
@ -143,7 +144,7 @@ class DeployedServiceRemover(Job):
except Exception:
logger.exception('Cought unexpected exception at continueRemovalOf: ')
def run(self):
def run(self) -> None:
# First check if there is someone in "removable" estate
removableServicePools: typing.Iterable[
ServicePool

View File

@ -360,7 +360,7 @@ class ServiceCacheUpdater(Job):
cache: UserService = cacheItems[0] # type: ignore # Slicing is not supported by pylance right now
cache.removeOrCancel()
def run(self):
def run(self) -> None:
logger.debug('Starting cache checking')
# We need to get
servicesThatNeedsUpdate = self.servicesPoolsNeedingCacheUpdate()

View File

@ -50,7 +50,7 @@ class DeployedServiceStatsCollector(Job):
frecuency = 599 # Once every ten minutes, 601 is prime, 599 also is prime, i like primes... :)
friendly_name = 'Deployed Service Stats'
def run(self):
def run(self) -> None:
logger.debug('Starting Deployed service stats collector')
servicePoolsToCheck: typing.Iterable[ServicePool] = ServicePool.objects.filter(

View File

@ -56,7 +56,7 @@ class StuckCleaner(Job):
frecuency = 3601 * 8 # Executes Once a day
friendly_name = 'Stuck States cleaner'
def run(self):
def run(self) -> None:
since_state: datetime = getSqlDatetime() - timedelta(seconds=MAX_STUCK_TIME)
# Filter for locating machine stuck on removing, cancelling, etc..
# Locate service pools with pending assigned service in use

View File

@ -47,7 +47,7 @@ class CacheCleaner(Job):
frecuency = 3600 * 24 # Once a day
friendly_name = 'Utility Cache Cleaner'
def run(self):
def run(self) -> None:
logger.debug('Starting cache cleanup')
Cache.cleanUp()
logger.debug('Done cache cleanup')
@ -58,7 +58,7 @@ class TicketStoreCleaner(Job):
frecuency = 60 # every minute (60 seconds)
friendly_name = 'Ticket Storage Cleaner'
def run(self):
def run(self) -> None:
logger.debug('Starting ticket storage cleanup')
TicketStore.cleanup()
logger.debug('Done ticket storage cleanup')
@ -69,7 +69,7 @@ class SessionsCleaner(Job):
frecuency = 3600 * 24 * 7 # Once a week will be enough
friendly_name = 'User Sessions cleaner'
def run(self):
def run(self) -> None:
logger.debug('Starting session cleanup')
try:
engine: typing.Any = import_module(settings.SESSION_ENGINE)

View File

@ -43,7 +43,7 @@ class UsageAccounting(Job):
frecuency = 60
friendly_name = 'Usage Accounting update'
def run(self):
def run(self) -> None:
with transaction.atomic():
AccountUsage.objects.select_for_update().filter(
user_service__in_use=True

View File

@ -88,12 +88,16 @@ class UserServiceRemover(Job):
state=State.REMOVABLE,
state_date__lt=removeFrom,
deployed_service__service__provider__maintenance_mode=False,
)[
0:removeAtOnce # type: ignore # Slicing is not supported by pylance right now
].iterator()
).iterator(chunk_size=removeAtOnce)
manager = managers.userServiceManager()
for removableUserService in removableUserServices:
# if removal limit is reached, we stop
if removeAtOnce <= 0:
break
# decrease how many we can remove
removeAtOnce -= 1
logger.debug('Checking removal of %s', removableUserService.name)
try:
if manager.canRemoveServiceFromDeployedService(

View File

@ -506,6 +506,12 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
else:
self.cancel()
def releaseOrCancel(self) -> None:
"""
A much more convenient method name that "removeOrCancel" (i think :) )
"""
self.removeOrCancel()
def moveToLevel(self, cacheLevel: int) -> None:
"""
Moves cache items betwen levels, managed directly