1
0
mirror of https://github.com/dkmstr/openuds.git synced 2025-01-03 01:17:56 +03:00

Fixed config

Done migration tests for PhysicalMachines
This commit is contained in:
Adolfo Gómez García 2024-04-26 00:58:48 +02:00
parent c39c8c9583
commit 30bbf1ef0f
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
3 changed files with 108 additions and 48 deletions

View File

@ -45,13 +45,6 @@ from uds.models.config import Config as DBConfig
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
_for_saving_later: list[tuple['Config.Value', typing.Any]] = []
_for_recovering_later: list['Config.Value'] = []
_is_migrating: bool = False
# For custom params (for choices mainly)
_config_params: dict[str, typing.Any] = {}
# Pair of section/value removed from current UDS version # Pair of section/value removed from current UDS version
# Note: As of version 4.0, all previous REMOVED values has been moved to migration script 0043 # Note: As of version 4.0, all previous REMOVED values has been moved to migration script 0043
REMOVED_CONFIG_ELEMENTS = { REMOVED_CONFIG_ELEMENTS = {
@ -72,7 +65,22 @@ REMOVED_CONFIG_ELEMENTS = {
), ),
} }
class Config: class Config:
# Global configuration values
_for_saving_later: typing.ClassVar[list[tuple['Config.Value', typing.Any]]] = []
_for_recovering_later: typing.ClassVar[list['Config.Value']] = []
# For custom params (for choices mainly)
_config_params: typing.ClassVar[dict[str, typing.Any]] = {}
# If we are migrating, we do not want to access database
_is_migrating: typing.ClassVar[bool] = False
# If initialization has been done
_initialization_finished: typing.ClassVar[bool] = False
# Fields types, so inputs get more "beautiful" # Fields types, so inputs get more "beautiful"
class FieldType(enum.IntEnum): class FieldType(enum.IntEnum):
UNKNOWN = -1 UNKNOWN = -1
@ -158,12 +166,12 @@ class Config:
logger.debug(self) logger.debug(self)
def get(self, force: bool = False) -> str: def get(self, force: bool = False) -> str:
if apps.ready and _is_migrating is False: if apps.ready and Config._is_migrating is False:
if not GlobalConfig.isInitialized(): if not GlobalConfig.isInitialized():
logger.debug('Initializing configuration & updating db values') logger.debug('Initializing configuration & updating db values')
GlobalConfig.initialize() GlobalConfig.initialize()
else: else:
_for_recovering_later.append(self) Config._for_recovering_later.append(self)
return self._default return self._default
try: try:
@ -194,7 +202,7 @@ class Config:
return self._data return self._data
def set_params(self, params: typing.Any) -> None: def set_params(self, params: typing.Any) -> None:
_config_params[self._section.name() + self._key] = params Config._config_params[self._section.name() + self._key] = params
def as_int(self, force: bool = False) -> int: def as_int(self, force: bool = False) -> int:
try: try:
@ -233,14 +241,14 @@ class Config:
return self._type return self._type
def get_params(self) -> typing.Any: def get_params(self) -> typing.Any:
return _config_params.get(self._section.name() + self._key, None) return Config._config_params.get(self._section.name() + self._key, None)
def get_help(self) -> str: def get_help(self) -> str:
return gettext(self._help) return gettext(self._help)
def set(self, value: typing.Union[str, bool, int]) -> None: def set(self, value: typing.Union[str, bool, int]) -> None:
if GlobalConfig.isInitialized() is False or _is_migrating is True: if GlobalConfig.isInitialized() is False or Config._is_migrating is True:
_for_saving_later.append((self, value)) Config._for_saving_later.append((self, value))
return return
if isinstance(value, bool): if isinstance(value, bool):
@ -311,9 +319,7 @@ class Config:
def update(section: 'Config.SectionType', key: str, value: str, check_type: bool = False) -> bool: def update(section: 'Config.SectionType', key: str, value: str, check_type: bool = False) -> bool:
# If cfg value does not exists, simply ignore request # If cfg value does not exists, simply ignore request
try: try:
cfg: DBConfig = DBConfig.objects.filter(section=section, key=key)[ cfg: DBConfig = DBConfig.objects.filter(section=section, key=key)[0]
0
]
if check_type and cfg.field_type in ( if check_type and cfg.field_type in (
Config.FieldType.READ, Config.FieldType.READ,
Config.FieldType.HIDDEN, Config.FieldType.HIDDEN,
@ -772,16 +778,14 @@ class GlobalConfig:
help=_('Enable VNC menu for user services'), help=_('Enable VNC menu for user services'),
) )
_initDone = False
@staticmethod @staticmethod
def isInitialized() -> bool: def isInitialized() -> bool:
return GlobalConfig._initDone return Config._initialization_finished
@staticmethod @staticmethod
def initialize() -> None: def initialize() -> None:
if GlobalConfig._initDone is False: if Config._initialization_finished is False:
GlobalConfig._initDone = True Config._initialization_finished = True
try: try:
# Tries to initialize database data for global config so it is stored asap and get cached for use # Tries to initialize database data for global config so it is stored asap and get cached for use
for v in GlobalConfig.__dict__.values(): for v in GlobalConfig.__dict__.values():
@ -789,16 +793,16 @@ class GlobalConfig:
v.get() v.get()
logger.debug('Initialized global config value %s=%s', v.key(), v.get()) logger.debug('Initialized global config value %s=%s', v.key(), v.get())
for c in _for_recovering_later: for c in Config._for_recovering_later:
logger.debug('Get later: %s', c) logger.debug('Get later: %s', c)
c.get() c.get()
_for_recovering_later[:] = [] # pyright: ignore[reportUnknownArgumentType] Config._for_recovering_later[:] = [] # pyright: ignore[reportUnknownArgumentType]
for c, v in _for_saving_later: for c, v in Config._for_saving_later:
logger.debug('Saving delayed value: %s', c) logger.debug('Saving delayed value: %s', c)
c.set(v) c.set(v)
_for_saving_later[:] = [] # pyright: ignore[reportUnknownArgumentType] Config._for_saving_later[:] = [] # pyright: ignore[reportUnknownArgumentType]
# Process some global config parameters # Process some global config parameters
# GlobalConfig.UDS_THEME.setParams(['html5', 'semantic']) # GlobalConfig.UDS_THEME.setParams(['html5', 'semantic'])
@ -810,14 +814,12 @@ class GlobalConfig:
# Signals for avoid saving config values on migrations # Signals for avoid saving config values on migrations
def _pre_migrate(sender: typing.Any, **kwargs: typing.Any) -> None: def _pre_migrate(sender: typing.Any, **kwargs: typing.Any) -> None:
# logger.info('Migrating database, AVOID saving config values') # logger.info('Migrating database, AVOID saving config values')
global _is_migrating Config._is_migrating = True
_is_migrating = True
def _post_migrate(sender: typing.Any, **kwargs: typing.Any) -> None: def _post_migrate(sender: typing.Any, **kwargs: typing.Any) -> None:
# logger.info('Migration DONE, ALLOWING saving config values') # logger.info('Migration DONE, ALLOWING saving config values')
global _is_migrating Config._is_migrating = False
_is_migrating = False
signals.pre_migrate.connect(_pre_migrate) signals.pre_migrate.connect(_pre_migrate)

View File

@ -50,16 +50,6 @@ logger = logging.getLogger(__name__)
class IPSingleMachineService(services.Service): class IPSingleMachineService(services.Service):
# Gui
host = gui.TextField(
length=64,
label=_('Machine IP (and possibly MAC)'),
order=1,
tooltip=_('Machine IP'),
required=True,
old_field_name='ip',
)
# Description of service # Description of service
type_name = _('Static Single IP') type_name = _('Static Single IP')
type_type = 'IPSingleMachineService' type_type = 'IPSingleMachineService'
@ -75,6 +65,17 @@ class IPSingleMachineService(services.Service):
services_type_provided = types.services.ServiceType.VDI services_type_provided = types.services.ServiceType.VDI
# Gui
host = gui.TextField(
length=64,
label=_('Host IP/FQDN'),
order=1,
tooltip=_('IP or FQDN of the server to connect to. Can include MAC address separated by ";" after the IP/Hostname'),
required=True,
old_field_name='ip',
)
def get_host_mac(self) -> typing.Tuple[str, str]: def get_host_mac(self) -> typing.Tuple[str, str]:
if ';' in self.host.as_str(): if ';' in self.host.as_str():
return typing.cast(tuple[str, str], tuple(self.host.as_str().split(';', 2)[:2])) return typing.cast(tuple[str, str], tuple(self.host.as_str().split(';', 2)[:2]))

View File

@ -34,11 +34,21 @@ import typing
import datetime import datetime
from unittest import mock from unittest import mock
from uds import models
from uds.core import consts, types
from uds.core.util import fields
from ...utils.test import UDSTransactionTestCase from ...utils.test import UDSTransactionTestCase
from uds import models
from uds.migrations.fixers.providers_v4 import physical_machine_multiple from uds.migrations.fixers.providers_v4 import physical_machine_multiple
from uds.services.PhysicalMachines import (
service_single,
service_multi,
deployment,
deployment_multi,
)
# Data from 3.6 version # Data from 3.6 version
PROVIDER_DATA: typing.Final[dict[str, typing.Any]] = { PROVIDER_DATA: typing.Final[dict[str, typing.Any]] = {
@ -74,6 +84,9 @@ SERVICES_DATA: typing.Final[list[dict[str, typing.Any]]] = [
}, },
] ]
SINGLE_IP_SERVICE_IDX: typing.Final[int] = 1
MULTIPLE_IP_SERVICE_IDX: typing.Final[int] = 0
SERVICEPOOLS_DATA: typing.Final[list[dict[str, typing.Any]]] = [ SERVICEPOOLS_DATA: typing.Final[list[dict[str, typing.Any]]] = [
{ {
'id': 100, 'id': 100,
@ -129,6 +142,8 @@ SERVICEPOOLS_DATA: typing.Final[list[dict[str, typing.Any]]] = [
}, },
] ]
SINGLE_IP_SERVICEPOOL_IDX: typing.Final[int] = 1
MULTIPLE_IP_SERVICEPOOL_IDX: typing.Final[int] = 0
USERSERVICES_DATA: typing.Final[list[dict[str, typing.Any]]] = [ USERSERVICES_DATA: typing.Final[list[dict[str, typing.Any]]] = [
{ {
@ -136,8 +151,8 @@ USERSERVICES_DATA: typing.Final[list[dict[str, typing.Any]]] = [
'uuid': 'f1ac7d5-58c8-55c3-8bab-24ea1ed40be5', 'uuid': 'f1ac7d5-58c8-55c3-8bab-24ea1ed40be5',
'deployed_service_id': 100, 'deployed_service_id': 100,
'publication_id': None, 'publication_id': None,
'unique_id': 'dc.dkmon.local', 'unique_id': 'localhost',
'friendly_name': 'dc.dkmon.local', 'friendly_name': 'localhost',
'state': 'U', 'state': 'U',
'os_state': 'U', 'os_state': 'U',
'state_date': datetime.datetime(2024, 4, 25, 2, 51, 13), 'state_date': datetime.datetime(2024, 4, 25, 2, 51, 13),
@ -200,7 +215,7 @@ STORAGE_DATA: typing.Final[list[dict[str, typing.Any]]] = [
{ {
'owner': 't-service-144', 'owner': 't-service-144',
'key': '848d16fb421048c690c9761c11dc1699', 'key': '848d16fb421048c690c9761c11dc1699',
'data': 'gASVQwAAAAAAAABdlCiMDTE3Mi4yNy4xLjI1fjCUjA0xNzIuMjcuMS4yNn4xlIwNMTcyLjI3LjEu\nMjd+MpSMC2xvY2FsaG9zdH4zlGUu\n', 'data': 'gASVVQAAAAAAAABdlCiMDTE3Mi4yNy4xLjI1fjCUjA0xNzIuMjcuMS4yNn4xlIwNMTcyLjI3LjEuMjd+MpSMHWxvY2FsaG9zdDswMToyMzo0NTo2Nzo4OTpBQn4zlGUu\n',
'attr1': '', 'attr1': '',
}, },
{'owner': 't-service-142', 'key': 'b6ac33477ae0a82fa2681c4d398d88d7', 'data': 'gARLAS4=\n', 'attr1': ''}, {'owner': 't-service-142', 'key': 'b6ac33477ae0a82fa2681c4d398d88d7', 'data': 'gARLAS4=\n', 'attr1': ''},
@ -251,7 +266,7 @@ class TestPhysicalMigration(UDSTransactionTestCase):
""" """
# We have 2 services: # We have 2 services:
# - Single IP # - Single IP
# - IP is localhost, should be migrated to (localhost, 127.0.0.1) # - IP is localhost, should be migrated to localhost
# - One service pool # - One service pool
# - One user service # - One user service
# - Multiple IP # - Multiple IP
@ -267,13 +282,55 @@ class TestPhysicalMigration(UDSTransactionTestCase):
# - 172.27.1.25 # - 172.27.1.25
# - 172.27.1.26 # - 172.27.1.26
# - 172.27.1.27 # - 172.27.1.27
# - localhost # - localhost;01:23:45:67:89:AB
# - One service pool # - One service pool
# - Two user services # - Two user services
# * First one, is from an already unexisting machine "172.27.1.15" (removed from the list BEFORE migration) # * First one, is localhost
# * Second one is 172.27.1.26 # * Second one is 172.27.1.26
# First, proceed to migration of data # First, proceed to migration of data
physical_machine_multiple.migrate(self.apps_mock(), None) physical_machine_multiple.migrate(self.apps_mock(), None)
# Now check that data has been migrated correctly # Now check that data has been migrated correctly
# Single ip
single_ip = typing.cast('service_single.IPSingleMachineService', models.Service.objects.get(uuid=SERVICES_DATA[SINGLE_IP_SERVICE_IDX]['uuid']).get_instance())
self.assertEqual(single_ip.host.value, 'localhost')
# Multiple ip
multi_ip = typing.cast('service_multi.IPMachinesService', models.Service.objects.get(uuid=SERVICES_DATA[MULTIPLE_IP_SERVICE_IDX]['uuid']).get_instance())
server_group = fields.get_server_group_from_field(multi_ip.server_group)
self.assertEqual(server_group.name, 'Physical Machines Server Group for Multiple IPS')
ips_to_check = {'172.27.1.25', '172.27.1.26', '172.27.1.27', '127.0.0.1'}
for server in server_group.servers.all():
self.assertEqual(server.server_type, types.servers.ServerType.UNMANAGED, f'Invalid server type for {server.ip}')
self.assertIn(server.ip, ips_to_check, f'Invalid server ip {server.ip}: {ips_to_check}')
ips_to_check.remove(server.ip)
# Ensure has a hostname, and MAC is empty
self.assertNotEqual(server.hostname, '')
# Localhost has a MAC, rest of servers have MAC_UNKNOWN (empty equivalent)
# Also, should have 127.0.0.1 as ip if localhost
if server.hostname == 'localhost':
self.assertEqual(server.ip, '127.0.0.1')
self.assertEqual(server.mac, '01:23:45:67:89:AB')
else:
self.assertEqual(server.mac, consts.MAC_UNKNOWN)
# If is 172.27.1.26 ensure is locked
if server.ip == '172.27.1.26' or server.hostname == 'localhost':
self.assertTrue(server.locked_until is not None and server.locked_until > datetime.datetime.now(), f'Server {server.ip} is not locked')
else:
self.assertIsNone(server.locked_until, f'Server {server.ip} is locked')
# Ensure all ips have been checked
self.assertEqual(len(ips_to_check), 0)
# Now, check UserServices
for userservice_data in USERSERVICES_DATA:
# Get the user service
if userservice_data['deployed_service_id'] == SERVICEPOOLS_DATA[SINGLE_IP_SERVICEPOOL_IDX]['id']:
userservice = typing.cast('deployment.IPMachineUserService', models.UserService.objects.get(uuid=userservice_data['uuid']).get_instance())
self.assertEqual(userservice._ip, 'dc.dkmon.local~1') # Same as original data
else:
userservice = typing.cast('deployment_multi.IPMachinesUserService', models.UserService.objects.get(uuid=userservice_data['uuid']).get_instance())
self.assertEqual(userservice._ip, userservice_data['unique_id'], f'Invalid IP for {userservice_data["unique_id"]}: {userservice._ip}')