Skip to content

Commit

Permalink
Adding support for putting back userservices into cache instead of re…
Browse files Browse the repository at this point in the history
…moving them (if possible)
  • Loading branch information
dkmstr committed Sep 2, 2024
1 parent 7c2a816 commit 207a784
Show file tree
Hide file tree
Showing 17 changed files with 105 additions and 54 deletions.
2 changes: 1 addition & 1 deletion server/src/uds/REST/methods/actor_v3.py
Original file line number Diff line number Diff line change
Expand Up @@ -515,7 +515,7 @@ def action(self) -> dict[str, typing.Any]:

if osmanager:
osmanager.to_ready(userService)
UserServiceManager().notify_ready_from_os_manager(userService, '') # Currently, no data is received for os manager
UserServiceManager.manager().notify_ready_from_os_manager(userService, '') # Currently, no data is received for os manager

# Generates a certificate and send it to client.
privateKey, cert, password = security.create_self_signed_cert(self._params['ip'])
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/REST/methods/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ def process(self, ticket: str, scrambler: str) -> dict[str, typing.Any]:
userServiceInstance,
transport,
transportInstance,
) = UserServiceManager().get_user_service_info(
) = UserServiceManager.manager().get_user_service_info(
self._request.user,
self._request.os,
self._request.ip,
Expand Down
4 changes: 2 additions & 2 deletions server/src/uds/REST/methods/connection.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,7 @@ def connection(self, idService: str, idTransport: str, skip: str = '') -> dict[s
_, # iads,
_, # trans,
itrans,
) = UserServiceManager().get_user_service_info( # pylint: disable=unused-variable
) = UserServiceManager.manager().get_user_service_info( # pylint: disable=unused-variable
self._user,
self._request.os,
self._request.ip,
Expand Down Expand Up @@ -134,7 +134,7 @@ def connection(self, idService: str, idTransport: str, skip: str = '') -> dict[s

def script(self, idService: str, idTransport: str, scrambler: str, hostname: str) -> dict[str, typing.Any]:
try:
res = UserServiceManager().get_user_service_info(
res = UserServiceManager.manager().get_user_service_info(
self._user, self._request.os, self._request.ip, idService, idTransport
)
userService: 'models.UserService'
Expand Down
4 changes: 2 additions & 2 deletions server/src/uds/REST/methods/services_pools.py
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ def item_as_dict(self, item: 'Model') -> dict[str, typing.Any]:
if item.is_in_maintenance():
state = State.MAINTENANCE
# This needs a lot of queries, and really does not apport anything important to the report
# elif UserServiceManager().canInitiateServiceFromDeployedService(item) is False:
# elif UserServiceManager.manager().canInitiateServiceFromDeployedService(item) is False:
# state = State.SLOWED_DOWN
val: dict[str, typing.Any] = {
'id': item.uuid,
Expand Down Expand Up @@ -679,7 +679,7 @@ def create_from_assignable(self, item: 'Model') -> typing.Any:
return self.invalid_request_response('Invalid parameters')

logger.debug('Creating from assignable: %s', self._params)
UserServiceManager().create_from_assignable(
UserServiceManager.manager().create_from_assignable(
item,
User.objects.get(uuid__iexact=process_uuid(self._params['user_id'])),
self._params['assignable_id'],
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/REST/methods/user_services.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,7 +257,7 @@ def save_item(self, parent: 'Model', item: typing.Optional[str]) -> None:

def reset(self, parent: 'models.ServicePool', item: str) -> typing.Any:
userService = parent.userServices.get(uuid=process_uuid(item))
UserServiceManager().reset(userService)
UserServiceManager.manager().reset(userService)


class CachedService(AssignedService):
Expand Down
23 changes: 17 additions & 6 deletions server/src/uds/core/managers/userservice.py
Original file line number Diff line number Diff line change
Expand Up @@ -318,7 +318,7 @@ def forced_move_assigned_to_cache_l1(self, user_service: UserService) -> None:

# Save the new element, for reference
user_service_copy.save()

# Now, move the original to cache, but do it "hard" way, so we do not need to check for state
user_service.state = State.USABLE
user_service.os_state = State.USABLE
Expand Down Expand Up @@ -381,21 +381,21 @@ def get_cache_servicepool_stats(self, servicepool: ServicePool) -> 'types.servic
# to create new items over the limit stablisshed, so we will not remove them anymore
l1_cache_count: int = (
servicepool.cached_users_services()
.filter(UserServiceManager().get_cache_state_filter(servicepool, types.services.CacheLevel.L1))
.filter(UserServiceManager.manager().get_cache_state_filter(servicepool, types.services.CacheLevel.L1))
.count()
)
l2_cache_count: int = (
(
servicepool.cached_users_services()
.filter(UserServiceManager().get_cache_state_filter(servicepool, types.services.CacheLevel.L2))
.filter(UserServiceManager.manager().get_cache_state_filter(servicepool, types.services.CacheLevel.L2))
.count()
)
if service_instance.uses_cache_l2
else 0
)
assigned_count: int = (
servicepool.assigned_user_services()
.filter(UserServiceManager().get_state_filter(servicepool.service))
.filter(UserServiceManager.manager().get_state_filter(servicepool.service))
.count()
)
pool_stat = types.services.ServicePoolStats(servicepool, l1_cache_count, l2_cache_count, assigned_count)
Expand Down Expand Up @@ -497,10 +497,21 @@ def release_on_logout(self, userservice: UserService) -> None:
This method will take care of removing the service if no cache is desired of cache already full (on servicepool)
"""
if userservice.deployed_service.service.get_instance().allows_put_back_to_cache() is False:
if userservice.allow_putting_back_to_cache() is False:
userservice.release() # Normal release
return

# Some sanity checks, should never happen
if userservice.cache_level != types.services.CacheLevel.NONE:
logger.error('Cache level is not NONE for userservice %s on release_on_logout', userservice)
userservice.release()
return


if userservice.is_usable() is False:
logger.error('State is not USABLE for userservice %s on release_on_logout', userservice)
userservice.release()
return

stats = self.get_cache_servicepool_stats(userservice.deployed_service)
# Note that only moves to cache L1
# Also, we can get values for L2 cache, thats why we check L1 for overflow and needed
Expand Down
71 changes: 54 additions & 17 deletions server/src/uds/core/services/generics/dynamic/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub
uses_cache_l2 = False # L2 Cache are running machines in suspended state
needs_osmanager = False # If the service needs a s.o. manager (managers are related to agents provided by services, i.e. virtual machines with agent)

must_stop_before_deletion = True # If the service must be stopped before deletion
must_stop_before_deletion = True # If the service must be stopped before deletion

# Gui remplates, to be "incorporated" by inherited classes if needed
base_machine = gui.ChoiceField(
Expand All @@ -85,6 +85,16 @@ class DynamicService(services.Service, abc.ABC): # pylint: disable=too-many-pub
order=104,
tab=types.ui.Tab.ADVANCED,
)
put_back_to_cache = gui.ChoiceField(
order=105,
label=_('Put back to cache'),
tooltip=_('On machine releasy by logout, put it back to cache instead of deleting if possible.'),
choices=[
{'id': 'no', 'text': _('No. Never put it back to cache')},
{'id': 'yes', 'text': _('Yes, try to put it back to cache')},
],
tab=types.ui.Tab.ADVANCED,
)

def initialize(self, values: 'types.core.ValuesType') -> None:
"""
Expand All @@ -96,6 +106,13 @@ def initialize(self, values: 'types.core.ValuesType') -> None:
if values:
validators.validate_basename(self.basename.value, self.lenname.value)

def allow_putting_back_to_cache(self) -> bool:
if self.has_field('put_back_to_cache') and isinstance(
getattr(self, 'put_back_to_cache', None), gui.ChoiceField
):
return self.put_back_to_cache.value == 'yes'
return False

def get_basename(self) -> str:
return self.basename.value

Expand Down Expand Up @@ -127,7 +144,9 @@ def find_duplicates(self, name: str, mac: str) -> collections.abc.Iterable[str]:
This method must be be provided if the field remove_duplicates is used
If not, will raise a NotImplementedError
"""
raise NotImplementedError(f'{self.__class__}: find_duplicates must be implemented if remove_duplicates is used!')
raise NotImplementedError(
f'{self.__class__}: find_duplicates must be implemented if remove_duplicates is used!'
)

@typing.final
def perform_find_duplicates(self, name: str, mac: str) -> collections.abc.Iterable[str]:
Expand All @@ -152,15 +171,19 @@ def perform_find_duplicates(self, name: str, mac: str) -> collections.abc.Iterab
return []

@abc.abstractmethod
def get_ip(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> str:
def get_ip(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> str:
"""
Returns the ip of the machine
If cannot be obtained, MUST raise an exception
"""
...

@abc.abstractmethod
def get_mac(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> str:
def get_mac(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> str:
"""
Returns the mac of the machine
If cannot be obtained, MUST raise an exception
Expand All @@ -171,51 +194,63 @@ def get_mac(self, caller_instance: typing.Optional['DynamicUserService | Dynamic
...

@abc.abstractmethod
def is_running(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> bool:
def is_running(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> bool:
"""
Returns if the machine is ready and running
"""
...

@abc.abstractmethod
def start(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None:
def start(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> None:
"""
Starts the machine
Returns None. If a task is needed for anything, use the caller_instance to notify
"""
...

@abc.abstractmethod
def stop(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None:
def stop(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> None:
"""
Stops the machine
Returns None. If a task is needed for anything, use the caller_instance to notify
"""
...

def shutdown(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None:
def shutdown(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> None:
"""
Shutdowns the machine. Defaults to stop
Returns None. If a task is needed for anything, use the caller_instance to notify
"""
return self.stop(caller_instance, vmid)

def reset(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None:
def reset(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> None:
"""
Resets the machine
Returns None. If a task is needed for anything, use the caller_instance to notify
"""
# Default is to stop "hard"
return self.stop(caller_instance, vmid)

def delete(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> None:

def delete(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> None:
"""
Removes the machine, or queues it for removal, or whatever :)
Use the caller_instance to notify anything if needed, or to identify caller
"""
# Store the deletion has started for future reference
self.set_deleting_state(vmid)

DeferredDeletionWorker.add(self, vmid)

# Method for deferred deletion
Expand All @@ -229,31 +264,33 @@ def execute_delete(self, vmid: str) -> None:
you provided a custom delete method, but better to have it implemented
"""
...

def is_deleted(self, vmid: str) -> bool:
"""
Checks if the deferred deletion of a machine is done
Default implementation is return True always
"""
return True

def notify_deleted(self, vmid: str) -> None:
"""
Called when the deferred deletion has been done
"""
# Remove the deletion started flag
with self.storage.as_dict() as storage:
del storage[f'deleting_{vmid}']

def set_deleting_state(self, vmid: str) -> None:
"""
Marks a machine as deleting
"""
with self.storage.as_dict() as storage:
# Store deleting vmid
storage[f'deleting_{vmid}'] = True

def is_deletion_in_progress(self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str) -> bool:

def is_deletion_in_progress(
self, caller_instance: typing.Optional['DynamicUserService | DynamicPublication'], vmid: str
) -> bool:
"""
Checks if the deferred deletion of a machine is running
Default implementation is return False always
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/core/services/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,7 @@ def allows_errored_userservice_cleanup(self) -> bool:
"""
return True

def allows_put_back_to_cache(self) -> bool:
def allow_putting_back_to_cache(self) -> bool:
"""
Returns if this service can be put back to cache. This is used to check if a service can be put back to cache
when the user logouts instead of being removed. By default, this method returns False.
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/models/calendar_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,7 @@ def _del_all_groups() -> None:
def _clear_cache() -> None:
# 4.- Remove all cache_l1_srvs
for i in self.service_pool.cached_users_services().filter(
UserServiceManager().get_cache_state_filter(
UserServiceManager.manager().get_cache_state_filter(
self.service_pool,
(
types.services.CacheLevel.L1
Expand Down
7 changes: 5 additions & 2 deletions server/src/uds/models/user_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ def needs_osmanager(self) -> bool:
Returns True if this User Service needs an os manager (i.e. parent services pools is marked to use an os manager)
"""
return bool(self.get_osmanager())

def allow_putting_back_to_cache(self) -> bool:
return self.deployed_service.service.get_instance().allow_putting_back_to_cache()

def transforms_user_or_password_for_service(self) -> bool:
"""
Expand Down Expand Up @@ -466,7 +469,7 @@ def set_in_use(self, inUse: bool) -> None:

if not inUse: # Service released, check y we should mark it for removal
# If our publication is not current, mark this for removal
UserServiceManager().check_for_removal(self)
UserServiceManager.manager().check_for_removal(self)

def start_accounting(self) -> None:
# 1.- If do not have any account associated, do nothing
Expand Down Expand Up @@ -577,7 +580,7 @@ def move_to_level(self, cacheLevel: types.services.CacheLevel) -> None:
# pylint: disable=import-outside-toplevel
from uds.core.managers.userservice import UserServiceManager

UserServiceManager().move_to_level(self, cacheLevel)
UserServiceManager.manager().move_to_level(self, cacheLevel)

def set_comms_endpoint(self, commsUrl: typing.Optional[str] = None) -> None:
self.properties['comms_url'] = commsUrl
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/services/OVirt/deployment_linked.py
Original file line number Diff line number Diff line change
Expand Up @@ -282,7 +282,7 @@ def desktop_login(
# operations.writeToPipe("\\\\.\\pipe\\VDSMDPipe", packet, True)
dbUserService = self.db_obj()
if dbUserService:
UserServiceManager().send_script(dbUserService, script)
UserServiceManager.manager().send_script(dbUserService, script)

def process_ready_from_os_manager(self, data: typing.Any) -> types.states.TaskState:
# Here we will check for suspending the VM (when full ready)
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/transports/X2GO/x2go_base.py
Original file line number Diff line number Diff line change
Expand Up @@ -296,5 +296,5 @@ def getAuthorizeScript(self, user: str, pubKey: str) -> str:
def getAndPushKey(self, userName: str, userService: 'models.UserService') -> tuple[str, str]:
priv, pub = self.genKeyPairForSsh()
authScript = self.getAuthorizeScript(userName, pub)
UserServiceManager().send_script(userService, authScript)
UserServiceManager.manager().send_script(userService, authScript)
return priv, pub
2 changes: 1 addition & 1 deletion server/src/uds/web/util/services.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,7 +428,7 @@ def enable_service(
# If meta service, process and rebuild idService & idTransport

try:
res = UserServiceManager().get_user_service_info(
res = UserServiceManager.manager().get_user_service_info(
request.user, request.os, request.ip, idService, idTransport, validate_with_test=False
)
scrambler = CryptoManager().random_string(32)
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/web/views/auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,7 @@ def ticket_auth(
# Check if servicePool is part of the ticket
if poolUuid:
# Request service, with transport = None so it is automatic
res = UserServiceManager().get_user_service_info(
res = UserServiceManager.manager().get_user_service_info(
request.user, request.os, request.ip, poolUuid, None, False
)
_, userservice, _, transport, _ = res
Expand Down
2 changes: 1 addition & 1 deletion server/src/uds/web/views/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,7 @@ def _is_ticket_valid(data: collections.abc.Mapping[str, typing.Any]) -> bool:
userService = models.UserService.objects.get(
uuid=data['ticket-info'].get('userService', None)
)
UserServiceManager().notify_preconnect(
UserServiceManager.manager().notify_preconnect(
userService,
types.connections.ConnectionData(
username=username,
Expand Down
Loading

0 comments on commit 207a784

Please sign in to comment.