1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-03-11 00:58:39 +03:00

Refactor ChoiceItem id field to accept only string values and fixed

This commit is contained in:
Adolfo Gómez García 2024-10-02 17:30:00 +02:00
parent 1c80fb6ac9
commit 752ae78ce7
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
8 changed files with 153 additions and 145 deletions

View File

@ -91,14 +91,14 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
:param item: Service item (db)
:param full: If full is requested, add "extra" fields to complete information
"""
itemType = item.get_type()
item_type = item.get_type()
ret_value: dict[str, typing.Any] = {
'id': item.uuid,
'name': item.name,
'tags': [tag.tag for tag in item.tags.all()],
'comments': item.comments,
'type': item.data_type,
'type_name': _(itemType.mod_name()),
'type_name': _(item_type.mod_name()),
'deployed_services_count': item.deployedServices.count(),
'user_services_count': models.UserService.objects.filter(deployed_service__service=item)
.exclude(state__in=State.INFO_STATES)
@ -168,14 +168,13 @@ class Services(DetailHandler): # pylint: disable=too-many-public-methods
service.tags.set([models.Tag.objects.get_or_create(tag=val)[0] for val in tags])
serviceInstance = service.get_instance(self._params)
service_instance = service.get_instance(self._params)
# Store token if this service provides one
service.token = serviceInstance.get_token() or None # If '', use "None" to
service.token = service_instance.get_token() or None # If '', use "None" to
service.data = (
serviceInstance.serialize()
) # This may launch an validation exception (the get_instance(...) part)
# This may launch an validation exception (the get_instance(...) part)
service.data = service_instance.serialize()
service.save()
except models.Service.DoesNotExist:

View File

@ -543,60 +543,39 @@ class UserServiceManager(metaclass=singleton.Singleton):
return None
def get_assignation_for_user(
self, service_pool: ServicePool, user: User
self, servicepool: ServicePool, user: User
) -> typing.Optional[UserService]: # pylint: disable=too-many-branches
if service_pool.service.get_instance().spawns_new is False:
assignedUserService = self.get_existing_assignation_for_user(service_pool, user)
if servicepool.service.get_instance().spawns_new is False: # Locate first if we have an assigned one
assigned_userservice = self.get_existing_assignation_for_user(servicepool, user)
else:
assignedUserService = None
assigned_userservice = None
# If has an assigned user service, returns this without any more work
if assignedUserService:
return assignedUserService
if assigned_userservice:
return assigned_userservice
if service_pool.is_restrained():
if servicepool.is_restrained():
raise InvalidServiceException(_('The requested service is restrained'))
cache: typing.Optional[UserService] = None
# Now try to locate 1 from cache already "ready" (must be usable and at level 1)
with transaction.atomic():
caches = typing.cast(
list[UserService],
service_pool.cached_users_services()
.select_for_update()
.filter(
cache_level=types.services.CacheLevel.L1,
state=State.USABLE,
os_state=State.USABLE,
)[:1],
)
if caches:
cache = caches[0]
# Ensure element is reserved correctly on DB
if (
service_pool.cached_users_services()
.select_for_update()
.filter(user=None, uuid=cache.uuid)
.update(user=user, cache_level=0)
!= 1
):
cache = None
else:
cache = None
# Out of previous atomic
if not cache:
if servicepool.uses_cache:
cache: typing.Optional[UserService] = None
# Now try to locate 1 from cache already "ready" (must be usable and at level 1)
with transaction.atomic():
caches = typing.cast(
list[UserService],
service_pool.cached_users_services()
servicepool.cached_users_services()
.select_for_update()
.filter(cache_level=types.services.CacheLevel.L1, state=State.USABLE)[:1],
.filter(
cache_level=types.services.CacheLevel.L1,
state=State.USABLE,
os_state=State.USABLE,
)[:1],
)
if caches: # If there is a cache, we will use it
if caches:
cache = caches[0]
# Ensure element is reserved correctly on DB
if (
service_pool.cached_users_services()
servicepool.cached_users_services()
.select_for_update()
.filter(user=None, uuid=cache.uuid)
.update(user=user, cache_level=0)
@ -606,89 +585,111 @@ class UserServiceManager(metaclass=singleton.Singleton):
else:
cache = None
# Out of atomic transaction
if cache:
# Early assign
cache.assign_to(user)
# Out of previous atomic
if not cache:
with transaction.atomic():
caches = typing.cast(
list[UserService],
servicepool.cached_users_services()
.select_for_update()
.filter(cache_level=types.services.CacheLevel.L1, state=State.USABLE)[:1],
)
if caches: # If there is a cache, we will use it
cache = caches[0]
if (
servicepool.cached_users_services()
.select_for_update()
.filter(user=None, uuid=cache.uuid)
.update(user=user, cache_level=0)
!= 1
):
cache = None
else:
cache = None
logger.debug(
'Found a cached-ready service from %s for user %s, item %s',
service_pool,
user,
cache,
)
events.add_event(
service_pool,
types.stats.EventType.CACHE_HIT,
fld1=service_pool.cached_users_services()
.filter(cache_level=types.services.CacheLevel.L1, state=State.USABLE)
.count(),
)
return cache
# Out of atomic transaction
if cache:
# Early assign
cache.assign_to(user)
# Cache missed
# Now find if there is a preparing one
with transaction.atomic():
caches = list(
service_pool.cached_users_services()
.select_for_update()
.filter(cache_level=types.services.CacheLevel.L1, state=State.PREPARING)[:1]
)
if caches: # If there is a cache, we will use it
cache = caches[0]
if (
service_pool.cached_users_services()
.select_for_update()
.filter(user=None, uuid=cache.uuid)
.update(user=user, cache_level=0)
!= 1
):
cache = None
else:
cache = None
# Out of atomic transaction
if cache:
cache.assign_to(user)
logger.debug(
'Found a cached-preparing service from %s for user %s, item %s',
service_pool,
user,
cache,
)
events.add_event(
service_pool,
events.types.stats.EventType.CACHE_MISS,
fld1=service_pool.cached_users_services()
.filter(cache_level=types.services.CacheLevel.L1, state=State.PREPARING)
.count(),
)
return cache
# Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
serviceType = service_pool.service.get_type()
if serviceType.uses_cache:
inAssigned = (
service_pool.assigned_user_services()
.filter(self.get_state_filter(service_pool.service))
.count()
)
if (
inAssigned >= service_pool.max_srvs
): # cacheUpdater will drop unnecesary L1 machines, so it's not neccesary to check against inCacheL1
log.log(
service_pool,
types.log.LogLevel.WARNING,
f'Max number of services reached: {service_pool.max_srvs}',
types.log.LogSource.INTERNAL,
logger.debug(
'Found a cached-ready service from %s for user %s, item %s',
servicepool,
user,
cache,
)
raise MaxServicesReachedError()
events.add_event(
servicepool,
types.stats.EventType.CACHE_HIT,
fld1=servicepool.cached_users_services()
.filter(cache_level=types.services.CacheLevel.L1, state=State.USABLE)
.count(),
)
return cache
# Can create new service, create it
events.add_event(service_pool, events.types.stats.EventType.CACHE_MISS, fld1=0)
return self.create_assigned_for(service_pool, user)
# Cache missed
# Now find if there is a preparing one
with transaction.atomic():
caches = list(
servicepool.cached_users_services()
.select_for_update()
.filter(cache_level=types.services.CacheLevel.L1, state=State.PREPARING)[:1]
)
if caches: # If there is a cache, we will use it
cache = caches[0]
if (
servicepool.cached_users_services()
.select_for_update()
.filter(user=None, uuid=cache.uuid)
.update(user=user, cache_level=0)
!= 1
):
cache = None
else:
cache = None
# Out of atomic transaction
if cache:
cache.assign_to(user)
logger.debug(
'Found a cached-preparing service from %s for user %s, item %s',
servicepool,
user,
cache,
)
events.add_event(
servicepool,
events.types.stats.EventType.CACHE_MISS,
fld1=servicepool.cached_users_services()
.filter(cache_level=types.services.CacheLevel.L1, state=State.PREPARING)
.count(),
)
return cache
# Can't assign directly from L2 cache... so we check if we can create e new service in the limits requested
service_type = servicepool.service.get_type()
if service_type.uses_cache:
in_assigned = (
servicepool.assigned_user_services()
.filter(self.get_state_filter(servicepool.service))
.count()
)
if (
in_assigned >= servicepool.max_srvs
): # cacheUpdater will drop unnecesary L1 machines, so it's not neccesary to check against inCacheL1
log.log(
servicepool,
types.log.LogLevel.WARNING,
f'Max number of services reached: {servicepool.max_srvs}',
types.log.LogSource.INTERNAL,
)
raise MaxServicesReachedError()
# Can create new service, create it
events.add_event(servicepool, events.types.stats.EventType.CACHE_MISS, fld1=0)
return self.create_assigned_for(servicepool, user)
def count_userservices_in_states_for_provider(self, provider: 'models.Provider', states: list[str]) -> int:
"""

View File

@ -96,17 +96,6 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
rows=10,
)
use_snapshots = gui.CheckBoxField(
label=_('Use snapshots'),
default=False,
order=33,
tooltip=_(
'If active, UDS will try to create an snapshot (if one already does not exists) before accessing a machine, and restore it after usage.'
),
tab=types.ui.Tab.MACHINE,
old_field_name='useSnapshots',
)
# This one replaces use_snapshots, and is used to select the snapshot type (No snapshot, recover snapshot and stop machine, recover snapshot and start machine)
snapshot_type = gui.ChoiceField(
label=_('Snapshot type'),
@ -135,6 +124,18 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
order=103,
tab=types.ui.Tab.ADVANCED,
)
use_snapshots = gui.CheckBoxField(
label=_('Use snapshots'),
default=False,
order=104,
tooltip=_(
'If active, UDS will try to create an snapshot (if one already does not exists) before accessing a machine, and restore it after usage.'
),
tab=types.ui.Tab.ADVANCED,
old_field_name='useSnapshots',
)
def initialize(self, values: 'types.core.ValuesType') -> None:
"""
@ -151,8 +152,6 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
with self._assigned_access() as assigned_vms:
assigned_vms &= set(self.machines.as_list())
self.token.value = self.token.value.strip()
# Recover userservice
self.userservices_limit = len(self.machines.as_list())
@contextlib.contextmanager
def _assigned_access(self) -> typing.Iterator[set[str]]:
@ -175,6 +174,11 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
Removes the snapshot for the machine
"""
return
def unmarshal(self, data: bytes) -> None:
super().unmarshal(data)
# Recover userservice
self.userservices_limit = len(self.machines.as_list())
@abc.abstractmethod
def get_name(self, vmid: str) -> str:

View File

@ -130,7 +130,7 @@ class Filler(typing.TypedDict):
class ChoiceItem(typing.TypedDict):
id: 'str|int'
id: 'str'
text: str
img: typing.NotRequired[str] # Only for IMAGECHOICE

View File

@ -126,14 +126,14 @@ class gui:
] = {}
@staticmethod
def choice_item(id_: typing.Union[str, int], text: 'str|Promise|typing.Any') -> 'types.ui.ChoiceItem':
def choice_item(id_: 'str|int', text: 'str|Promise|typing.Any') -> 'types.ui.ChoiceItem':
"""
Helper method to create a single choice item.
"""
if not isinstance(text, (str, Promise)):
text = str(text)
return {
'id': id_,
'id': str(id_),
'text': typing.cast(str, text),
} # Cast to avoid mypy error, Promise is at all effects a str
@ -161,7 +161,7 @@ class gui:
return vals
# Helper to convert an item to a dict
def _choice_from_value(val: typing.Union[str, int, types.ui.ChoiceItem]) -> 'types.ui.ChoiceItem':
def _choice_from_value(val: typing.Union[str, types.ui.ChoiceItem]) -> 'types.ui.ChoiceItem':
if isinstance(val, dict):
if 'id' not in val or 'text' not in val:
raise ValueError(f'Invalid choice dict: {val}')

View File

@ -245,6 +245,10 @@ class ServicePool(UUIDModel, TaggingMixin):
@property
def owned_by_meta(self) -> bool:
return self.memberOfMeta.count() > 0
@property
def uses_cache(self) -> bool:
return self.cache_l1_srvs > 0 or self.cache_l2_srvs > 0
@property
def visual_name(self) -> str:

View File

@ -89,6 +89,6 @@ def get_machines(parameters: typing.Any) -> types.ui.CallbackResultType:
return [
{
'name': 'machines',
'choices': [gui.choice_item(member.vmid, member.vmname) for member in pool_info.members],
'choices': [gui.choice_item(str(member.vmid), member.vmname) for member in pool_info.members],
}
]

View File

@ -87,7 +87,7 @@ class ProxmoxServiceFixed(FixedService): # pylint: disable=too-many-public-meth
},
tooltip=_('Resource Pool containing base machines'),
required=True,
tab=_('Machines'),
tab=types.ui.Tab.MACHINE,
old_field_name='resourcePool',
)