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:
parent
1c80fb6ac9
commit
752ae78ce7
@ -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:
|
||||
|
@ -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:
|
||||
"""
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
|
||||
|
@ -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}')
|
||||
|
@ -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:
|
||||
|
@ -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],
|
||||
}
|
||||
]
|
||||
|
@ -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',
|
||||
)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user