mirror of
https://github.com/dkmstr/openuds.git
synced 2025-03-20 06:50:23 +03:00
old_field_name and servers improvement.
Improved old_field_name to allow more than 1 "old field name", just in case the future. Also, improved servers manager to allow a "threshold" value, so we can get intead of the less loaded server, one with some load, but not too loaded...
This commit is contained in:
parent
d60f47aa7a
commit
43389248c8
@ -319,3 +319,13 @@ class TestingUserInterfaceFieldName(UserInterface):
|
||||
default='', # Will be loaded from orig
|
||||
old_field_name='strField',
|
||||
)
|
||||
|
||||
class TestingUserInterfaceFieldNameSeveral(UserInterface):
|
||||
str2_field = gui.TextField(
|
||||
label='Text Field',
|
||||
order=0,
|
||||
tooltip='This is a text field',
|
||||
required=True,
|
||||
default='', # Will be loaded from orig
|
||||
old_field_name=['str_field', 'strField'],
|
||||
)
|
||||
|
@ -46,6 +46,7 @@ from .fixtures import (
|
||||
TestingOldUserInterface,
|
||||
DEFAULTS,
|
||||
TestingUserInterfaceFieldName,
|
||||
TestingUserInterfaceFieldNameSeveral,
|
||||
TestingUserInterfaceFieldNameOrig,
|
||||
)
|
||||
|
||||
@ -156,7 +157,9 @@ class UserinterfaceTest(UDSTestCase):
|
||||
ui2.randomize_values() # Ensure data is different
|
||||
ui2.deserialize_from_old_format(data)
|
||||
|
||||
self.assertEqual(ui2, ui) # Important, TestingUserInterface has __eq__ method, but TestingOldUserInterface has not
|
||||
self.assertEqual(
|
||||
ui2, ui
|
||||
) # Important, TestingUserInterface has __eq__ method, but TestingOldUserInterface has not
|
||||
self.ensure_values_fine(ui2)
|
||||
|
||||
# Now deserialize old data with new method, (will internally call oldUnserializeForm)
|
||||
@ -183,27 +186,36 @@ class UserinterfaceTest(UDSTestCase):
|
||||
# This test is to ensure that new serialized data can be loaded
|
||||
# mock logging warning
|
||||
ui = TestingUserInterfaceFieldNameOrig()
|
||||
data = ui.serialize_fields()
|
||||
data_ui = ui.serialize_fields()
|
||||
ui2 = TestingUserInterfaceFieldName()
|
||||
self.assertFalse(ui2.deserialize_fields(data)) # Should not need upgrade
|
||||
ui3 = TestingUserInterfaceFieldNameSeveral()
|
||||
|
||||
self.assertFalse(ui2.deserialize_fields(data_ui)) # Should not need upgrade
|
||||
self.assertEqual(ui.strField.value, ui2.str_field.value)
|
||||
|
||||
# Now, we will try to load data from ui to ui3, that has 2 "old_field_names" on the field
|
||||
self.assertFalse(ui3.deserialize_fields(data_ui)) # Should not need upgrade
|
||||
self.assertEqual(ui.strField.value, ui3.str2_field.value)
|
||||
|
||||
data_ui2 = ui2.serialize_fields()
|
||||
self.assertFalse(ui3.deserialize_fields(data_ui2)) # Should not need upgrade
|
||||
self.assertEqual(ui2.str_field.value, ui3.str2_field.value)
|
||||
|
||||
# On current version, we do noallow backwards compatibility, so a warning is issued
|
||||
# and the field is not loaded
|
||||
with mock.patch('logging.Logger.warning') as mock_warning:
|
||||
ui2.str_field.value = 'new value'
|
||||
data = ui2.serialize_fields() # Should store str_field as strField
|
||||
data_ui = ui2.serialize_fields() # Should store str_field as strField
|
||||
|
||||
self.assertFalse(ui.deserialize_fields(data)) # Should need upgrade, current format serialized
|
||||
self.assertFalse(ui.deserialize_fields(data_ui)) # Should need upgrade, current format serialized
|
||||
|
||||
# Logger.warning should has been called (because there is an unknown field)
|
||||
mock_warning.assert_called()
|
||||
|
||||
|
||||
# And field is not loaded
|
||||
self.assertNotEqual(ui.strField.value, ui2.str_field.value)
|
||||
|
||||
# Previously:
|
||||
|
||||
# Previously:
|
||||
# mock_warning.assert_not_called()
|
||||
|
||||
# And strField should be loaded from str_field
|
||||
|
@ -148,12 +148,14 @@ class ServerManager(metaclass=singleton.Singleton):
|
||||
|
||||
stats_and_servers = self.get_server_stats(fltrs)
|
||||
|
||||
def _weight_threshold(stats: 'types.servers.ServerStats') -> float:
|
||||
def _real_weight(stats: 'types.servers.ServerStats') -> float:
|
||||
if weight_threshold == 0:
|
||||
return stats.weight()
|
||||
# Values under threshold are better, weight is in between 0 and 1, lower is better
|
||||
# To values over threshold, we will add 1, so they are always worse than any value under threshold
|
||||
return stats.weight() if stats.weight() < weight_threshold else 1 + stats.weight()
|
||||
# No matter if over threshold is overcalculed, it will be always worse than any value under threshold
|
||||
# and all values over threshold will be affected in the same way
|
||||
return weight_threshold - stats.weight() if stats.weight() < weight_threshold else 1 + stats.weight()
|
||||
|
||||
# Now, cachedStats has a list of tuples (stats, server), use it to find the best server
|
||||
for stats, server in stats_and_servers:
|
||||
@ -166,7 +168,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
||||
if best is None:
|
||||
best = (server, stats)
|
||||
|
||||
if _weight_threshold(stats) < _weight_threshold(best[1]):
|
||||
if _real_weight(stats) < _real_weight(best[1]):
|
||||
best = (server, stats)
|
||||
|
||||
# stats.weight() < best[1].weight()
|
||||
@ -206,6 +208,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
||||
lock_interval: typing.Optional[datetime.timedelta] = None,
|
||||
server: typing.Optional['models.Server'] = None, # If not note
|
||||
excluded_servers_uuids: typing.Optional[typing.Set[str]] = None,
|
||||
weight_threshold: int = 0,
|
||||
) -> typing.Optional[types.servers.ServerCounter]:
|
||||
"""
|
||||
Select a server for an userservice to be assigned to
|
||||
@ -219,6 +222,20 @@ class ServerManager(metaclass=singleton.Singleton):
|
||||
server: If not None, use this server instead of selecting one from server_group. (Used on manual assign)
|
||||
excluded_servers_uuids: If not None, exclude this servers from selection. Used in case we check the availability of a server
|
||||
with some external method and we want to exclude it from selection because it has already failed.
|
||||
weight_threshold: If not 0, basically will prefer values below an near this value
|
||||
|
||||
Note:
|
||||
weight_threshold is used to select a server with a weight as near as possible, without going over, to this value.
|
||||
If none is found, the server with the lowest weight will be selected.
|
||||
If 0, no weight threshold is applied.
|
||||
The calculation is done as follows (with weight_threshold > 0 ofc):
|
||||
* if weight is below threshold, (threshold - weight) is returned (so nearer to threshold, better)
|
||||
* if weight is over threshold, 1 + weight is returned (so, all values over threshold are worse than any value under threshold)
|
||||
that is:
|
||||
real_weight = weight_threshold - weight if weight < weight_threshold else 1 + weight
|
||||
|
||||
The idea behind this is to be able to select a server not fully empty, but also not fully loaded, so it can be used
|
||||
to leave servers empty as soon as possible, but also to not overload servers that are near to be full.
|
||||
|
||||
Returns:
|
||||
uuid of server assigned
|
||||
@ -281,6 +298,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
||||
now=now,
|
||||
min_memory_mb=min_memory_mb,
|
||||
excluded_servers_uuids=excluded_servers_uuids,
|
||||
weight_threshold=weight_threshold,
|
||||
)
|
||||
|
||||
info = types.servers.ServerCounter(best[0].uuid, 0)
|
||||
|
@ -60,7 +60,7 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
|
||||
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)
|
||||
# can_reset = True
|
||||
|
||||
# If machine has an alternate field with it, it will be used instead of "machines" field
|
||||
# If machines has an alternate field with it, it will be used instead of "machines" field
|
||||
alternate_machines_field: typing.Optional[str] = None
|
||||
|
||||
# : Types of publications (preparated data for deploys)
|
||||
@ -213,6 +213,13 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
|
||||
Defaults to True
|
||||
"""
|
||||
return True
|
||||
|
||||
def is_running(self, vmid: str) -> bool:
|
||||
"""
|
||||
Returns if the machine is running
|
||||
Defaults to self.is_ready()
|
||||
"""
|
||||
return self.is_ready(vmid)
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_mac(self, vmid: str) -> str:
|
||||
|
@ -432,8 +432,11 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
||||
def op_start_checker(self) -> types.states.TaskState:
|
||||
"""
|
||||
Checks if machine has started
|
||||
Defaults to is_ready method from service
|
||||
"""
|
||||
return types.states.TaskState.FINISHED
|
||||
if self.service().is_running(self._vmid):
|
||||
return types.states.TaskState.FINISHED
|
||||
return types.states.TaskState.RUNNING
|
||||
|
||||
def op_stop(self) -> None:
|
||||
"""
|
||||
@ -444,8 +447,11 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
||||
def op_stop_checker(self) -> types.states.TaskState:
|
||||
"""
|
||||
Checks if machine has stoped
|
||||
Default to is_ready method from service
|
||||
"""
|
||||
return types.states.TaskState.FINISHED
|
||||
if not self.service().is_running(self._vmid):
|
||||
return types.states.TaskState.FINISHED
|
||||
return types.states.TaskState.RUNNING
|
||||
|
||||
# Not abstract methods, defaults to stop machine
|
||||
def op_shutdown(self) -> None:
|
||||
|
@ -36,6 +36,8 @@ import collections.abc
|
||||
|
||||
from django.utils.translation import gettext_noop
|
||||
|
||||
# Old Field name type
|
||||
OldFieldNameType = typing.Union[str,list[str],None]
|
||||
|
||||
class Tab(enum.StrEnum):
|
||||
ADVANCED = gettext_noop('Advanced')
|
||||
@ -144,7 +146,7 @@ class FieldInfo:
|
||||
tooltip: str
|
||||
order: int
|
||||
type: FieldType
|
||||
old_field_name: typing.Optional[str] = None
|
||||
old_field_name: OldFieldNameType = None
|
||||
readonly: typing.Optional[bool] = None
|
||||
value: typing.Union[collections.abc.Callable[[], typing.Any], typing.Any] = None
|
||||
default: typing.Optional[typing.Union[collections.abc.Callable[[], str], str]] = None
|
||||
|
@ -292,7 +292,7 @@ class gui:
|
||||
self,
|
||||
label: str,
|
||||
type: types.ui.FieldType,
|
||||
old_field_name: typing.Optional[str],
|
||||
old_field_name: types.ui.OldFieldNameType,
|
||||
order: int = 0,
|
||||
tooltip: str = '',
|
||||
length: typing.Optional[int] = None,
|
||||
@ -342,11 +342,13 @@ class gui:
|
||||
def is_serializable(self) -> bool:
|
||||
return True
|
||||
|
||||
def old_field_name(self) -> typing.Optional[str]:
|
||||
def old_field_name(self) -> list[str]:
|
||||
"""
|
||||
Returns the name of the field
|
||||
"""
|
||||
return self._fields_info.old_field_name
|
||||
if isinstance(self._fields_info.old_field_name, list):
|
||||
return self._fields_info.old_field_name
|
||||
return [self._fields_info.old_field_name] if self._fields_info.old_field_name else []
|
||||
|
||||
@property
|
||||
def value(self) -> typing.Any:
|
||||
@ -505,7 +507,7 @@ class gui:
|
||||
value: typing.Optional[str] = None,
|
||||
pattern: typing.Union[str, types.ui.FieldPatternType] = types.ui.FieldPatternType.NONE,
|
||||
lines: int = 0,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -617,7 +619,7 @@ class gui:
|
||||
dict[str, str],
|
||||
None,
|
||||
] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
label=label,
|
||||
@ -676,7 +678,7 @@ class gui:
|
||||
value: typing.Optional[int] = None,
|
||||
min_value: typing.Optional[int] = None,
|
||||
max_value: typing.Optional[int] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -728,7 +730,7 @@ class gui:
|
||||
typing.Union[collections.abc.Callable[[], datetime.date], datetime.date]
|
||||
] = None,
|
||||
value: typing.Optional[typing.Union[str, datetime.date]] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -825,7 +827,7 @@ class gui:
|
||||
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
|
||||
default: typing.Union[collections.abc.Callable[[], str], str] = '',
|
||||
value: typing.Optional[str] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
):
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -906,7 +908,7 @@ class gui:
|
||||
default: typing.Any = None, # May be also callable
|
||||
value: typing.Any = None,
|
||||
serializable: bool = False,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -960,7 +962,7 @@ class gui:
|
||||
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
|
||||
default: typing.Union[collections.abc.Callable[[], bool], bool] = False,
|
||||
value: typing.Optional[bool] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
):
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -1101,7 +1103,7 @@ class gui:
|
||||
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
|
||||
default: typing.Union[collections.abc.Callable[[], str], str, None] = None,
|
||||
value: typing.Optional[str] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -1168,7 +1170,7 @@ class gui:
|
||||
tab: typing.Optional[typing.Union[str, types.ui.Tab]] = None,
|
||||
default: typing.Union[collections.abc.Callable[[], str], str, None] = None,
|
||||
value: typing.Optional[str] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
):
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -1261,7 +1263,7 @@ class gui:
|
||||
collections.abc.Callable[[], str], collections.abc.Callable[[], list[str]], list[str], str, None
|
||||
] = None,
|
||||
value: typing.Optional[collections.abc.Iterable[str]] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
):
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -1355,7 +1357,7 @@ class gui:
|
||||
collections.abc.Callable[[], str], collections.abc.Callable[[], list[str]], list[str], str, None
|
||||
] = None,
|
||||
value: typing.Optional[collections.abc.Iterable[str]] = None,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
old_field_name=old_field_name,
|
||||
@ -1413,7 +1415,7 @@ class gui:
|
||||
label: str,
|
||||
title: str,
|
||||
help: str,
|
||||
old_field_name: typing.Optional[str] = None,
|
||||
old_field_name: types.ui.OldFieldNameType = None,
|
||||
) -> None:
|
||||
super().__init__(
|
||||
label=label, default=[title, help], type=types.ui.FieldType.INFO, old_field_name=old_field_name
|
||||
@ -1574,8 +1576,8 @@ class UserInterface(metaclass=UserInterfaceType):
|
||||
|
||||
# Any unexpected type will raise an exception
|
||||
# Note that we always store CURRENT field name, so once migrated forward
|
||||
# we cannot reverse it to original...
|
||||
# if required, we can use field.old_field_name(), but better not
|
||||
# we cannot reverse it to original... (unless we reverse old_field_name to current, and then current to old)
|
||||
# but this is not recommended :)
|
||||
fields = [
|
||||
(field_name, field.field_type.name, FIELDS_ENCODERS[field.field_type](field))
|
||||
for field_name, field in self._all_serializable_fields()
|
||||
@ -1645,17 +1647,16 @@ class UserInterface(metaclass=UserInterfaceType):
|
||||
field.value = field.default
|
||||
|
||||
for field_name, field_type, field_value in fields:
|
||||
if field_name in field_names_translations:
|
||||
field_name = field_names_translations[field_name] # Convert old field name to new one if needed
|
||||
field_name = field_names_translations.get(field_name, field_name)
|
||||
if field_name not in self._gui:
|
||||
logger.warning('Field %s not found in form', field_name)
|
||||
continue
|
||||
internal_field_type = self._gui[field_name].field_type
|
||||
if internal_field_type not in FIELD_DECODERS:
|
||||
logger.warning('Field %s has no converter', field_name)
|
||||
logger.warning('Field %s has no decoder', field_name)
|
||||
continue
|
||||
if field_type != internal_field_type.name:
|
||||
logger.warning('Field %s has different type than expected', field_name)
|
||||
logger.warning('Field %s has different type than expected: %s != %s', field_name, field_type, internal_field_type.name)
|
||||
continue
|
||||
self._gui[field_name].value = FIELD_DECODERS[internal_field_type](field_value)
|
||||
|
||||
@ -1764,11 +1765,13 @@ class UserInterface(metaclass=UserInterfaceType):
|
||||
|
||||
def _get_fieldname_translations(self) -> dict[str, str]:
|
||||
# Dict of translations from old_field_name to field_name
|
||||
# Note that if an old_field_name is repeated on different fields, only the FIRST will be used
|
||||
# Also, order of fields is not guaranteed, so we we cannot assure that the first one will be chosen
|
||||
field_names_translations: dict[str, str] = {}
|
||||
for fld_name, fld in self._all_serializable_fields():
|
||||
fld_old_field_name = fld.old_field_name()
|
||||
if fld_old_field_name and fld_old_field_name != fld_name:
|
||||
field_names_translations[fld_old_field_name] = fld_name
|
||||
for fld_old_field_name in fld.old_field_name():
|
||||
if fld_old_field_name != fld_name:
|
||||
field_names_translations[fld_old_field_name] = fld_name
|
||||
|
||||
return field_names_translations
|
||||
|
||||
|
@ -189,17 +189,17 @@ def user_service_status(
|
||||
@web_login_required(admin=False)
|
||||
@never_cache
|
||||
def action(request: 'ExtendedHttpRequestWithUser', service_id: str, action_string: str) -> HttpResponse:
|
||||
userService = UserServiceManager().locate_meta_service(request.user, service_id)
|
||||
userService = UserServiceManager.manager().locate_meta_service(request.user, service_id)
|
||||
if not userService:
|
||||
userService = UserServiceManager().locate_user_service(request.user, service_id, create=False)
|
||||
|
||||
response: typing.Any = None
|
||||
rebuild: bool = False
|
||||
if userService:
|
||||
if action_string == 'release' and userService.deployed_service.allow_users_remove:
|
||||
if action_string == 'release' and userService.service_pool.allow_users_remove:
|
||||
rebuild = True
|
||||
log.log(
|
||||
userService.deployed_service,
|
||||
userService.service_pool,
|
||||
types.log.LogLevel.INFO,
|
||||
"Removing User Service {} as requested by {} from {}".format(
|
||||
userService.friendly_name, request.user.pretty_name, request.ip
|
||||
@ -210,12 +210,12 @@ def action(request: 'ExtendedHttpRequestWithUser', service_id: str, action_strin
|
||||
userService.release()
|
||||
elif (
|
||||
action_string == 'reset'
|
||||
and userService.deployed_service.allow_users_reset
|
||||
and userService.deployed_service.service.get_type().can_reset
|
||||
and userService.service_pool.allow_users_reset
|
||||
and userService.service_pool.service.get_type().can_reset
|
||||
):
|
||||
rebuild = True
|
||||
log.log(
|
||||
userService.deployed_service,
|
||||
userService.service_pool,
|
||||
types.log.LogLevel.INFO,
|
||||
"Reseting User Service {} as requested by {} from {}".format(
|
||||
userService.friendly_name, request.user.pretty_name, request.ip
|
||||
|
Loading…
x
Reference in New Issue
Block a user