mirror of
https://github.com/dkmstr/openuds.git
synced 2024-12-22 13:34:04 +03:00
Fixing up fixed_service generalization, and starting a test for this...
This commit is contained in:
parent
97fc8f844e
commit
9defc46083
@ -133,21 +133,21 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
|
|
||||||
def _find_best_server(
|
def _find_best_server(
|
||||||
self,
|
self,
|
||||||
userService: 'models.UserService',
|
userservice: 'models.UserService',
|
||||||
serverGroup: 'models.ServerGroup',
|
server_group: 'models.ServerGroup',
|
||||||
now: datetime.datetime,
|
now: datetime.datetime,
|
||||||
minMemoryMB: int = 0,
|
min_memory_mb: int = 0,
|
||||||
excludeServersUUids: typing.Optional[typing.Set[str]] = None,
|
excluded_servers_uuids: typing.Optional[typing.Set[str]] = None,
|
||||||
) -> tuple['models.Server', 'types.servers.ServerStats']:
|
) -> tuple['models.Server', 'types.servers.ServerStats']:
|
||||||
"""
|
"""
|
||||||
Finds the best server for a service
|
Finds the best server for a service
|
||||||
"""
|
"""
|
||||||
best: typing.Optional[tuple['models.Server', 'types.servers.ServerStats']] = None
|
best: typing.Optional[tuple['models.Server', 'types.servers.ServerStats']] = None
|
||||||
unmanaged_list: list['models.Server'] = []
|
unmanaged_list: list['models.Server'] = []
|
||||||
fltrs = serverGroup.servers.filter(maintenance_mode=False)
|
fltrs = server_group.servers.filter(maintenance_mode=False)
|
||||||
fltrs = fltrs.filter(Q(locked_until=None) | Q(locked_until__lte=now)) # Only unlocked servers
|
fltrs = fltrs.filter(Q(locked_until=None) | Q(locked_until__lte=now)) # Only unlocked servers
|
||||||
if excludeServersUUids:
|
if excluded_servers_uuids:
|
||||||
fltrs = fltrs.exclude(uuid__in=excludeServersUUids)
|
fltrs = fltrs.exclude(uuid__in=excluded_servers_uuids)
|
||||||
|
|
||||||
serversStats = self.get_server_stats(fltrs)
|
serversStats = self.get_server_stats(fltrs)
|
||||||
|
|
||||||
@ -156,7 +156,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
if stats is None:
|
if stats is None:
|
||||||
unmanaged_list.append(server)
|
unmanaged_list.append(server)
|
||||||
continue
|
continue
|
||||||
if minMemoryMB and stats.memused // (1024 * 1024) < minMemoryMB: # Stats has minMemory in bytes
|
if min_memory_mb and stats.memused // (1024 * 1024) < min_memory_mb: # Stats has minMemory in bytes
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if best is None or stats.weight() < best[1].weight():
|
if best is None or stats.weight() < best[1].weight():
|
||||||
@ -184,53 +184,53 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
|
|
||||||
# If best was locked, notify it (will be notified again on assign)
|
# If best was locked, notify it (will be notified again on assign)
|
||||||
if best[0].locked_until is not None:
|
if best[0].locked_until is not None:
|
||||||
requester.ServerApiRequester(best[0]).notify_release(userService)
|
requester.ServerApiRequester(best[0]).notify_release(userservice)
|
||||||
|
|
||||||
return best
|
return best
|
||||||
|
|
||||||
def assign(
|
def assign(
|
||||||
self,
|
self,
|
||||||
userService: 'models.UserService',
|
userservice: 'models.UserService',
|
||||||
serverGroup: 'models.ServerGroup',
|
server_group: 'models.ServerGroup',
|
||||||
serviceType: types.services.ServiceType = types.services.ServiceType.VDI,
|
service_type: types.services.ServiceType = types.services.ServiceType.VDI,
|
||||||
minMemoryMB: int = 0, # Does not apply to unmanged servers
|
min_memory_mb: int = 0, # Does not apply to unmanged servers
|
||||||
lockTime: typing.Optional[datetime.timedelta] = None,
|
lock_interval: typing.Optional[datetime.timedelta] = None,
|
||||||
server: typing.Optional['models.Server'] = None, # If not note
|
server: typing.Optional['models.Server'] = None, # If not note
|
||||||
excludeServersUUids: typing.Optional[typing.Set[str]] = None,
|
excluded_servers_uuids: typing.Optional[typing.Set[str]] = None,
|
||||||
) -> typing.Optional[types.servers.ServerCounter]:
|
) -> typing.Optional[types.servers.ServerCounter]:
|
||||||
"""
|
"""
|
||||||
Select a server for an userservice to be assigned to
|
Select a server for an userservice to be assigned to
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
userService: User service to assign server to (in fact, user of userservice) and to notify
|
userservice: User service to assign server to (in fact, user of userservice) and to notify
|
||||||
serverGroup: Server group to select server from
|
server_group: Server group to select server from
|
||||||
serverType: Type of service to assign
|
service_type: Type of service to assign
|
||||||
minMemoryMB: Minimum memory required for server in MB, does not apply to unmanaged servers
|
min_memory_mb: Minimum memory required for server in MB, does not apply to unmanaged servers
|
||||||
maxLockTime: If not None, lock server for this time
|
lock_interval: If not None, lock server for this time
|
||||||
server: If not None, use this server instead of selecting one from serverGroup. (Used on manual assign)
|
server: If not None, use this server instead of selecting one from serverGroup. (Used on manual assign)
|
||||||
excludeServersUUids: If not None, exclude this servers from selection. Used in case we check the availability of a server
|
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.
|
with some external method and we want to exclude it from selection because it has already failed.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
uuid of server assigned
|
uuid of server assigned
|
||||||
"""
|
"""
|
||||||
if not userService.user:
|
if not userservice.user:
|
||||||
raise exceptions.UDSException(_('No user assigned to service'))
|
raise exceptions.UDSException(_('No user assigned to service'))
|
||||||
|
|
||||||
# Look for existing user asignation through properties
|
# Look for existing user asignation through properties
|
||||||
prop_name = self.property_name(userService.user)
|
prop_name = self.property_name(userservice.user)
|
||||||
now = model_utils.sql_datetime()
|
now = model_utils.sql_datetime()
|
||||||
|
|
||||||
excludeServersUUids = excludeServersUUids or set()
|
excluded_servers_uuids = excluded_servers_uuids or set()
|
||||||
|
|
||||||
with serverGroup.properties as props:
|
with server_group.properties as props:
|
||||||
info: typing.Optional[types.servers.ServerCounter] = types.servers.ServerCounter.from_iterable(
|
info: typing.Optional[types.servers.ServerCounter] = types.servers.ServerCounter.from_iterable(
|
||||||
props.get(prop_name)
|
props.get(prop_name)
|
||||||
)
|
)
|
||||||
# If server is forced, and server is part of the group, use it
|
# If server is forced, and server is part of the group, use it
|
||||||
if server:
|
if server:
|
||||||
if (
|
if (
|
||||||
server.groups.filter(uuid=serverGroup.uuid).exclude(uuid__in=excludeServersUUids).count()
|
server.groups.filter(uuid=server_group.uuid).exclude(uuid__in=excluded_servers_uuids).count()
|
||||||
== 0
|
== 0
|
||||||
):
|
):
|
||||||
raise exceptions.UDSException(_('Server is not part of the group'))
|
raise exceptions.UDSException(_('Server is not part of the group'))
|
||||||
@ -253,7 +253,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
# remove it from saved and use look for another one
|
# remove it from saved and use look for another one
|
||||||
svr = models.Server.objects.filter(uuid=info.server_uuid).first()
|
svr = models.Server.objects.filter(uuid=info.server_uuid).first()
|
||||||
if not svr or (
|
if not svr or (
|
||||||
svr.maintenance_mode or svr.uuid in excludeServersUUids or svr.is_restrained()
|
svr.maintenance_mode or svr.uuid in excluded_servers_uuids or svr.is_restrained()
|
||||||
):
|
):
|
||||||
info = None
|
info = None
|
||||||
del props[prop_name]
|
del props[prop_name]
|
||||||
@ -265,20 +265,20 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
try:
|
try:
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
best = self._find_best_server(
|
best = self._find_best_server(
|
||||||
userService=userService,
|
userservice=userservice,
|
||||||
serverGroup=serverGroup,
|
server_group=server_group,
|
||||||
now=now,
|
now=now,
|
||||||
minMemoryMB=minMemoryMB,
|
min_memory_mb=min_memory_mb,
|
||||||
excludeServersUUids=excludeServersUUids,
|
excluded_servers_uuids=excluded_servers_uuids,
|
||||||
)
|
)
|
||||||
|
|
||||||
info = types.servers.ServerCounter(best[0].uuid, 0)
|
info = types.servers.ServerCounter(best[0].uuid, 0)
|
||||||
best[0].locked_until = now + lockTime if lockTime else None
|
best[0].locked_until = now + lock_interval if lock_interval else None
|
||||||
best[0].save(update_fields=['locked_until'])
|
best[0].save(update_fields=['locked_until'])
|
||||||
except exceptions.UDSException: # No more servers
|
except exceptions.UDSException: # No more servers
|
||||||
return None
|
return None
|
||||||
elif lockTime: # If lockTime is set, update it
|
elif lock_interval: # If lockTime is set, update it
|
||||||
models.Server.objects.filter(uuid=info.server_uuid).update(locked_until=now + lockTime)
|
models.Server.objects.filter(uuid=info.server_uuid).update(locked_until=now + lock_interval)
|
||||||
|
|
||||||
# Notify to server
|
# Notify to server
|
||||||
# Update counter
|
# Update counter
|
||||||
@ -293,7 +293,7 @@ class ServerManager(metaclass=singleton.Singleton):
|
|||||||
|
|
||||||
# Notify assgination in every case, even if reassignation to same server is made
|
# Notify assgination in every case, even if reassignation to same server is made
|
||||||
# This lets the server to keep track, if needed, of multi-assignations
|
# This lets the server to keep track, if needed, of multi-assignations
|
||||||
self.notify_assign(bestServer, userService, serviceType, info.counter)
|
self.notify_assign(bestServer, userservice, service_type, info.counter)
|
||||||
return info
|
return info
|
||||||
|
|
||||||
def release(
|
def release(
|
||||||
|
@ -62,8 +62,8 @@ if typing.TYPE_CHECKING:
|
|||||||
from uds import models
|
from uds import models
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
traceLogger = logging.getLogger('traceLog')
|
trace_logger = logging.getLogger('traceLog')
|
||||||
operationsLogger = logging.getLogger('operationsLog')
|
operations_logger = logging.getLogger('operationsLog')
|
||||||
|
|
||||||
|
|
||||||
class UserServiceManager(metaclass=singleton.Singleton):
|
class UserServiceManager(metaclass=singleton.Singleton):
|
||||||
@ -155,7 +155,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
in_use=False,
|
in_use=False,
|
||||||
)
|
)
|
||||||
|
|
||||||
def _create_assigned_at_db_for_no_publication(self, service_pool: ServicePool, user: User) -> UserService:
|
def _create_assigned_user_service_at_db_from_pool(self, service_pool: ServicePool, user: User) -> UserService:
|
||||||
"""
|
"""
|
||||||
__createCacheAtDb and __createAssignedAtDb uses a publication for create the UserService.
|
__createCacheAtDb and __createAssignedAtDb uses a publication for create the UserService.
|
||||||
There is cases where deployed services do not have publications (do not need them), so we need this method to create
|
There is cases where deployed services do not have publications (do not need them), so we need this method to create
|
||||||
@ -179,7 +179,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
"""
|
"""
|
||||||
Creates a new cache for the deployed service publication at level indicated
|
Creates a new cache for the deployed service publication at level indicated
|
||||||
"""
|
"""
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Creating a new cache element at level %s for publication %s',
|
'Creating a new cache element at level %s for publication %s',
|
||||||
cacheLevel,
|
cacheLevel,
|
||||||
publication,
|
publication,
|
||||||
@ -198,7 +198,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
# First, honor concurrent_creation_limit
|
# First, honor concurrent_creation_limit
|
||||||
if self.can_grow_service_pool(service_pool) is False:
|
if self.can_grow_service_pool(service_pool) is False:
|
||||||
# Cannot create new
|
# Cannot create new
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).'
|
'Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).'
|
||||||
)
|
)
|
||||||
raise ServiceNotReadyError()
|
raise ServiceNotReadyError()
|
||||||
@ -207,7 +207,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
publication = service_pool.active_publication()
|
publication = service_pool.active_publication()
|
||||||
if publication:
|
if publication:
|
||||||
assigned = self._create_assigned_user_service_at_db(publication, user)
|
assigned = self._create_assigned_user_service_at_db(publication, user)
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Creating a new assigned element for user %s for publication %s on pool %s',
|
'Creating a new assigned element for user %s for publication %s on pool %s',
|
||||||
user.pretty_name,
|
user.pretty_name,
|
||||||
publication.revision,
|
publication.revision,
|
||||||
@ -218,12 +218,12 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
f'Invalid publication creating service assignation: {service_pool.name} {user.pretty_name}'
|
f'Invalid publication creating service assignation: {service_pool.name} {user.pretty_name}'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Creating a new assigned element for user %s on pool %s',
|
'Creating a new assigned element for user %s on pool %s',
|
||||||
user.pretty_name,
|
user.pretty_name,
|
||||||
service_pool.name,
|
service_pool.name,
|
||||||
)
|
)
|
||||||
assigned = self._create_assigned_at_db_for_no_publication(service_pool, user)
|
assigned = self._create_assigned_user_service_at_db_from_pool(service_pool, user)
|
||||||
|
|
||||||
assignedInstance = assigned.get_instance()
|
assignedInstance = assigned.get_instance()
|
||||||
state = assignedInstance.deploy_for_user(user)
|
state = assignedInstance.deploy_for_user(user)
|
||||||
@ -232,7 +232,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
|
|
||||||
return assigned
|
return assigned
|
||||||
|
|
||||||
def create_from_assignable(self, service_pool: ServicePool, user: User, assignableId: str) -> UserService:
|
def create_from_assignable(self, service_pool: ServicePool, user: User, assignable_id: str) -> UserService:
|
||||||
"""
|
"""
|
||||||
Creates an assigned service from an "assignable" id
|
Creates an assigned service from an "assignable" id
|
||||||
"""
|
"""
|
||||||
@ -244,9 +244,9 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
publication = service_pool.active_publication()
|
publication = service_pool.active_publication()
|
||||||
if publication:
|
if publication:
|
||||||
assigned = self._create_assigned_user_service_at_db(publication, user)
|
assigned = self._create_assigned_user_service_at_db(publication, user)
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Creating an assigned element from assignable %s for user %s for publication %s on pool %s',
|
'Creating an assigned element from assignable %s for user %s for publication %s on pool %s',
|
||||||
assignableId,
|
assignable_id,
|
||||||
user.pretty_name,
|
user.pretty_name,
|
||||||
publication.revision,
|
publication.revision,
|
||||||
service_pool.name,
|
service_pool.name,
|
||||||
@ -256,20 +256,20 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
f'Invalid publication creating service assignation: {service_pool.name} {user.pretty_name}'
|
f'Invalid publication creating service assignation: {service_pool.name} {user.pretty_name}'
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
operationsLogger.info(
|
operations_logger.info(
|
||||||
'Creating an assigned element from assignable %s for user %s on pool %s',
|
'Creating an assigned element from assignable %s for user %s on pool %s',
|
||||||
assignableId,
|
assignable_id,
|
||||||
user.pretty_name,
|
user.pretty_name,
|
||||||
service_pool.name,
|
service_pool.name,
|
||||||
)
|
)
|
||||||
assigned = self._create_assigned_at_db_for_no_publication(service_pool, user)
|
assigned = self._create_assigned_user_service_at_db_from_pool(service_pool, user)
|
||||||
|
|
||||||
# Now, get from serviceInstance the data
|
# Now, get from serviceInstance the data
|
||||||
assignedInstance = assigned.get_instance()
|
assigned_userservice_instance = assigned.get_instance()
|
||||||
state = serviceInstance.assign_from_assignables(assignableId, user, assignedInstance)
|
state = serviceInstance.assign_from_assignables(assignable_id, user, assigned_userservice_instance)
|
||||||
# assigned.u(assignedInstance)
|
# assigned.u(assignedInstance)
|
||||||
|
|
||||||
UserServiceOpChecker.make_unique(assigned, assignedInstance, state)
|
UserServiceOpChecker.make_unique(assigned, assigned_userservice_instance, state)
|
||||||
|
|
||||||
return assigned
|
return assigned
|
||||||
|
|
||||||
@ -307,7 +307,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
logger.debug('Cancel requested for a non running operation, performing removal instead')
|
logger.debug('Cancel requested for a non running operation, performing removal instead')
|
||||||
return self.remove(user_service)
|
return self.remove(user_service)
|
||||||
|
|
||||||
operationsLogger.info('Canceling userService %s', user_service.name)
|
operations_logger.info('Canceling userService %s', user_service.name)
|
||||||
user_service_instance = user_service.get_instance()
|
user_service_instance = user_service.get_instance()
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -332,7 +332,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
"""
|
"""
|
||||||
with transaction.atomic():
|
with transaction.atomic():
|
||||||
userservice = UserService.objects.select_for_update().get(id=userservice.id)
|
userservice = UserService.objects.select_for_update().get(id=userservice.id)
|
||||||
operationsLogger.info('Removing userService %a', userservice.name)
|
operations_logger.info('Removing userService %a', userservice.name)
|
||||||
if userservice.is_usable() is False and State.from_str(userservice.state).is_removable() is False:
|
if userservice.is_usable() is False and State.from_str(userservice.state).is_removable() is False:
|
||||||
raise OperationException(_('Can\'t remove a non active element'))
|
raise OperationException(_('Can\'t remove a non active element'))
|
||||||
userservice.set_state(State.REMOVING)
|
userservice.set_state(State.REMOVING)
|
||||||
@ -590,7 +590,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
if not user_service.deployed_service.service.get_type().can_reset:
|
if not user_service.deployed_service.service.get_type().can_reset:
|
||||||
return
|
return
|
||||||
|
|
||||||
operationsLogger.info('Reseting %s', user_service)
|
operations_logger.info('Reseting %s', user_service)
|
||||||
|
|
||||||
userServiceInstance = user_service.get_instance()
|
userServiceInstance = user_service.get_instance()
|
||||||
try:
|
try:
|
||||||
@ -821,7 +821,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
user_service,
|
user_service,
|
||||||
transportInstance.get_connection_info(user_service, user, ''),
|
transportInstance.get_connection_info(user_service, user, ''),
|
||||||
)
|
)
|
||||||
traceLogger.info(
|
trace_logger.info(
|
||||||
'READY on service "%s" for user "%s" with transport "%s" (ip:%s)',
|
'READY on service "%s" for user "%s" with transport "%s" (ip:%s)',
|
||||||
user_service.name,
|
user_service.name,
|
||||||
userName,
|
userName,
|
||||||
@ -853,7 +853,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
|||||||
log.LogSource.WEB,
|
log.LogSource.WEB,
|
||||||
)
|
)
|
||||||
|
|
||||||
traceLogger.error(
|
trace_logger.error(
|
||||||
'ERROR %s on service "%s" for user "%s" with transport "%s" (ip:%s)',
|
'ERROR %s on service "%s" for user "%s" with transport "%s" (ip:%s)',
|
||||||
serviceNotReadyCode,
|
serviceNotReadyCode,
|
||||||
user_service.name,
|
user_service.name,
|
||||||
|
@ -339,13 +339,13 @@ class Service(Module):
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
def assign_from_assignables(
|
def assign_from_assignables(
|
||||||
self, assignable_id: str, user: 'models.User', userservice: 'UserService'
|
self, assignable_id: str, user: 'models.User', userservice_instance: 'UserService'
|
||||||
) -> str:
|
) -> str:
|
||||||
"""
|
"""
|
||||||
Assigns from it internal assignable list to an user
|
Assigns from it internal assignable list to an user
|
||||||
|
|
||||||
args:
|
args:
|
||||||
assignableId: Id of the assignable element
|
assignable_id: Id of the assignable element
|
||||||
user: User to assign to
|
user: User to assign to
|
||||||
userDeployment: User deployment to assign
|
userDeployment: User deployment to assign
|
||||||
|
|
||||||
@ -353,7 +353,11 @@ class Service(Module):
|
|||||||
Base implementation does nothing, to be overriden if needed
|
Base implementation does nothing, to be overriden if needed
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
str: The state of the service after the assignation
|
str: The state of the user service after the assignation
|
||||||
|
|
||||||
|
Note:
|
||||||
|
The state is the state of the "user service" after the assignation, not the state of the service itself.
|
||||||
|
This allows to process the assignation as an user service regular task, so it can be processed by the core.
|
||||||
|
|
||||||
"""
|
"""
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
|
@ -95,6 +95,20 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
|
|||||||
tab=_('Machines'),
|
tab=_('Machines'),
|
||||||
old_field_name='useSnapshots',
|
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'),
|
||||||
|
order=22,
|
||||||
|
default='0',
|
||||||
|
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=_('Machines'),
|
||||||
|
choices=[
|
||||||
|
gui.choice_item('no', _('No snapshot')),
|
||||||
|
gui.choice_item('stop', _('Recover snapshot and stop machine')),
|
||||||
|
gui.choice_item('start', _('Recover snapshot and start machine')),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
|
||||||
# Keep name as "machine" so we can use VCHelpers.getMachines
|
# Keep name as "machine" so we can use VCHelpers.getMachines
|
||||||
machines = gui.MultiChoiceField(
|
machines = gui.MultiChoiceField(
|
||||||
@ -132,44 +146,56 @@ class FixedService(services.Service, abc.ABC): # pylint: disable=too-many-publi
|
|||||||
returns:
|
returns:
|
||||||
str: the state to be processes by user service
|
str: the state to be processes by user service
|
||||||
"""
|
"""
|
||||||
return types.states.State.RUNNING
|
return types.states.State.FINISHED
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_machine_name(self, vmid: str) -> str:
|
def get_machine_name(self, vmid: str) -> str:
|
||||||
"""
|
"""
|
||||||
Returns the machine name for the given vmid
|
Returns the machine name for the given vmid
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_and_assign_machine(self) -> str:
|
def get_and_assign_machine(self) -> str:
|
||||||
"""
|
"""
|
||||||
Gets automatically an assigns a machine
|
Gets automatically an assigns a machine
|
||||||
|
Returns the id of the assigned machine
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def remove_and_free_machine(self, vmid: str) -> None:
|
def remove_and_free_machine(self, vmid: str) -> str:
|
||||||
"""
|
"""
|
||||||
Removes and frees a machine
|
Removes and frees a machine
|
||||||
|
Returns an state (State.FINISHED is nothing to do left)
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_first_network_mac(self, vmid: str) -> str:
|
def get_first_network_mac(self, vmid: str) -> str:
|
||||||
"""If no mac, return empty string"""
|
"""If no mac, return empty string
|
||||||
pass
|
Returns the first network mac of the machine
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def get_guest_ip_address(self, vmid: str) -> str:
|
def get_guest_ip_address(self, vmid: str) -> str:
|
||||||
pass
|
"""Returns the guest ip address of the machine
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def enumerate_assignables(self) -> list[tuple[str, str]]:
|
def enumerate_assignables(self) -> list[tuple[str, str]]:
|
||||||
pass
|
"""
|
||||||
|
Returns a list of tuples with the id and the name of the assignables
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def assign_from_assignables(
|
def assign_from_assignables(
|
||||||
self, assignable_id: str, user: 'models.User', user_deployment: 'services.UserService'
|
self, assignable_id: str, user: 'models.User', userservice_instance: 'services.UserService'
|
||||||
) -> str:
|
) -> str:
|
||||||
pass
|
"""
|
||||||
|
Assigns a machine from the assignables
|
||||||
|
"""
|
||||||
|
raise NotImplementedError()
|
||||||
|
@ -36,6 +36,7 @@ import enum
|
|||||||
import logging
|
import logging
|
||||||
import typing
|
import typing
|
||||||
import collections.abc
|
import collections.abc
|
||||||
|
from webbrowser import Opera
|
||||||
|
|
||||||
from uds.core import services, consts
|
from uds.core import services, consts
|
||||||
from uds.core.managers.user_service import UserServiceManager
|
from uds.core.managers.user_service import UserServiceManager
|
||||||
@ -60,6 +61,9 @@ class Operation(enum.IntEnum):
|
|||||||
ERROR = 5
|
ERROR = 5
|
||||||
FINISH = 6
|
FINISH = 6
|
||||||
RETRY = 7
|
RETRY = 7
|
||||||
|
SNAPSHOT_CREATE = 8 # to recall process_snapshot
|
||||||
|
SNAPSHOT_RECOVER = 9 # to recall process_snapshot
|
||||||
|
PROCESS_TOKEN = 10
|
||||||
|
|
||||||
UNKNOWN = 99
|
UNKNOWN = 99
|
||||||
|
|
||||||
@ -84,14 +88,27 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
_vmid = autoserializable.StringField(default='')
|
_vmid = autoserializable.StringField(default='')
|
||||||
_reason = autoserializable.StringField(default='')
|
_reason = autoserializable.StringField(default='')
|
||||||
_task = autoserializable.StringField(default='')
|
_task = autoserializable.StringField(default='')
|
||||||
|
_exec_state = autoserializable.StringField(default=State.RUNNING)
|
||||||
_queue = autoserializable.ListField[Operation]() # Default is empty list
|
_queue = autoserializable.ListField[Operation]() # Default is empty list
|
||||||
|
|
||||||
_create_queue: typing.ClassVar[typing.List[Operation]] = [
|
_create_queue: typing.ClassVar[list[Operation]] = [
|
||||||
Operation.CREATE,
|
Operation.CREATE,
|
||||||
|
Operation.SNAPSHOT_CREATE,
|
||||||
|
Operation.PROCESS_TOKEN,
|
||||||
Operation.START,
|
Operation.START,
|
||||||
Operation.FINISH,
|
Operation.FINISH,
|
||||||
]
|
]
|
||||||
_destrpy_queue: typing.ClassVar[typing.List[Operation]] = [Operation.REMOVE, Operation.FINISH]
|
_destroy_queue: typing.ClassVar[list[Operation]] = [
|
||||||
|
Operation.REMOVE,
|
||||||
|
Operation.SNAPSHOT_RECOVER,
|
||||||
|
Operation.FINISH,
|
||||||
|
]
|
||||||
|
_assign_queue: typing.ClassVar[list[Operation]] = [
|
||||||
|
Operation.CREATE,
|
||||||
|
Operation.SNAPSHOT_CREATE,
|
||||||
|
Operation.PROCESS_TOKEN,
|
||||||
|
Operation.FINISH,
|
||||||
|
]
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def _get_current_op(self) -> Operation:
|
def _get_current_op(self) -> Operation:
|
||||||
@ -175,17 +192,16 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
Deploys an service instance for an user.
|
Deploys an service instance for an user.
|
||||||
"""
|
"""
|
||||||
logger.debug('Deploying for user')
|
logger.debug('Deploying for user')
|
||||||
self._init_queue_for_deploy(False)
|
self._vmid = self.service().get_and_assign_machine()
|
||||||
|
self._queue = FixedUserService._create_queue.copy() # copy is needed to avoid modifying class var
|
||||||
return self._execute_queue()
|
return self._execute_queue()
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def assign(self, vmid: str) -> str:
|
def assign(self, vmid: str) -> str:
|
||||||
logger.debug('Assigning from VM {}'.format(vmid))
|
logger.debug('Assigning from VM {}'.format(vmid))
|
||||||
return self._create(vmid)
|
self._vmid = vmid
|
||||||
|
self._queue = FixedUserService._assign_queue.copy() # copy is needed to avoid modifying class var
|
||||||
@typing.final
|
return self._execute_queue()
|
||||||
def _init_queue_for_deploy(self, for_level2: bool = False) -> None:
|
|
||||||
self._queue = FixedUserService._create_queue.copy() # copy is needed to avoid modifying class var
|
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def _execute_queue(self) -> str:
|
def _execute_queue(self) -> str:
|
||||||
@ -198,17 +214,20 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
if op == Operation.FINISH:
|
if op == Operation.FINISH:
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
|
|
||||||
fncs: collections.abc.Mapping[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
|
fncs: dict[Operation, collections.abc.Callable[[], None]] = {
|
||||||
Operation.CREATE: self._create,
|
Operation.CREATE: self._create,
|
||||||
Operation.RETRY: self._retry,
|
Operation.RETRY: self._retry,
|
||||||
Operation.START: self._start_machine,
|
Operation.START: self._start_machine,
|
||||||
Operation.STOP: self._stop_machine,
|
Operation.STOP: self._stop_machine,
|
||||||
Operation.WAIT: self._wait,
|
Operation.WAIT: self._wait,
|
||||||
Operation.REMOVE: self._remove,
|
Operation.REMOVE: self._remove,
|
||||||
|
Operation.SNAPSHOT_CREATE: self._snapshot_create,
|
||||||
|
Operation.SNAPSHOT_RECOVER: self._snapshot_recover,
|
||||||
|
Operation.PROCESS_TOKEN: self._process_token,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
operation_runner: typing.Optional[collections.abc.Callable[[], str]] = fncs.get(op, None)
|
operation_runner: typing.Optional[collections.abc.Callable[[], None]] = fncs.get(op, None)
|
||||||
|
|
||||||
if not operation_runner:
|
if not operation_runner:
|
||||||
return self._error(f'Unknown operation found at execution queue ({op})')
|
return self._error(f'Unknown operation found at execution queue ({op})')
|
||||||
@ -221,7 +240,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
return self._error(str(e))
|
return self._error(str(e))
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def _retry(self) -> str:
|
def _retry(self) -> None:
|
||||||
"""
|
"""
|
||||||
Used to retry an operation
|
Used to retry an operation
|
||||||
In fact, this will not be never invoked, unless we push it twice, because
|
In fact, this will not be never invoked, unless we push it twice, because
|
||||||
@ -229,62 +248,86 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
|
|
||||||
At executeQueue this return value will be ignored, and it will only be used at check_state
|
At executeQueue this return value will be ignored, and it will only be used at check_state
|
||||||
"""
|
"""
|
||||||
return State.FINISHED
|
pass
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def _wait(self) -> str:
|
def _wait(self) -> None:
|
||||||
"""
|
"""
|
||||||
Executes opWait, it simply waits something "external" to end
|
Executes opWait, it simply waits something "external" to end
|
||||||
"""
|
"""
|
||||||
return State.RUNNING
|
pass
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def _create(self, vmid: str = '') -> str:
|
def _create(self) -> None:
|
||||||
"""
|
"""
|
||||||
Deploys a machine from template for user/cache
|
Deploys a machine from template for user/cache
|
||||||
"""
|
"""
|
||||||
self._vmid = vmid or self.service().get_and_assign_machine()
|
|
||||||
self._mac = self.service().get_first_network_mac(self._vmid) or ''
|
self._mac = self.service().get_first_network_mac(self._vmid) or ''
|
||||||
self._name = self.service().get_machine_name(self._vmid) or f'VM-{self._vmid}'
|
self._name = self.service().get_machine_name(self._vmid) or f'VM-{self._vmid}'
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
def _snapshot_create(self) -> None:
|
||||||
|
"""
|
||||||
|
Creates a snapshot if needed
|
||||||
|
"""
|
||||||
# Try to process snaptshots if needed
|
# Try to process snaptshots if needed
|
||||||
state = self.service().process_snapshot(remove=False, userservice_instace=self)
|
self._exec_state = self.service().process_snapshot(remove=False, userservice_instace=self)
|
||||||
|
|
||||||
if state == State.ERROR:
|
@typing.final
|
||||||
return state
|
def _snapshot_recover(self) -> None:
|
||||||
|
"""
|
||||||
|
Recovers a snapshot if needed
|
||||||
|
"""
|
||||||
|
self._exec_state = self.service().process_snapshot(remove=True, userservice_instace=self)
|
||||||
|
|
||||||
|
@typing.final
|
||||||
|
def _process_token(self) -> None:
|
||||||
# If not to be managed by a token, "autologin" user
|
# If not to be managed by a token, "autologin" user
|
||||||
if not self.service().get_token():
|
if not self.service().get_token():
|
||||||
userService = self.db_obj()
|
userService = self.db_obj()
|
||||||
if userService:
|
if userService:
|
||||||
userService.set_in_use(True)
|
userService.set_in_use(True)
|
||||||
|
|
||||||
return state
|
self._exec_state = State.FINISHED
|
||||||
|
|
||||||
@typing.final
|
def _remove(self) -> None:
|
||||||
def _remove(self) -> str:
|
|
||||||
"""
|
"""
|
||||||
Removes the snapshot if needed and releases the machine again
|
Removes the snapshot if needed and releases the machine again
|
||||||
"""
|
"""
|
||||||
self.service().remove_and_free_machine(self._vmid)
|
self._exec_state = self.service().remove_and_free_machine(self._vmid)
|
||||||
|
|
||||||
state = self.service().process_snapshot(remove=True, userservice_instace=self)
|
|
||||||
|
|
||||||
return state
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _start_machine(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _stop_machine(self) -> str:
|
|
||||||
pass
|
|
||||||
|
|
||||||
# Check methods
|
# Check methods
|
||||||
def _create_checker(self) -> str:
|
def _create_checker(self) -> str:
|
||||||
"""
|
"""
|
||||||
Checks the state of a deploy for an user or cache
|
Checks the state of a deploy for an user or cache
|
||||||
"""
|
"""
|
||||||
|
return self._state_checker()
|
||||||
|
|
||||||
|
def _snapshot_create_checker(self) -> str:
|
||||||
|
"""
|
||||||
|
Checks the state of a snapshot creation
|
||||||
|
"""
|
||||||
|
return self._state_checker()
|
||||||
|
|
||||||
|
def _snapshot_recover_checker(self) -> str:
|
||||||
|
"""
|
||||||
|
Checks the state of a snapshot recovery
|
||||||
|
"""
|
||||||
|
return self._state_checker()
|
||||||
|
|
||||||
|
def _process_token_checker(self) -> str:
|
||||||
|
"""
|
||||||
|
Checks the state of a token processing
|
||||||
|
"""
|
||||||
|
return self._state_checker()
|
||||||
|
|
||||||
|
def _state_checker(self) -> str:
|
||||||
|
return self._exec_state
|
||||||
|
|
||||||
|
def _retry_checker(self) -> str:
|
||||||
|
return State.FINISHED
|
||||||
|
|
||||||
|
def _wait_checker(self) -> str:
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
@ -292,21 +335,28 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
"""
|
"""
|
||||||
Checks if machine has started
|
Checks if machine has started
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _start_machine(self) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def _stop_machine(self) -> None:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
@abc.abstractmethod
|
||||||
def _stop_checker(self) -> str:
|
def _stop_checker(self) -> str:
|
||||||
"""
|
"""
|
||||||
Checks if machine has stoped
|
Checks if machine has stoped
|
||||||
"""
|
"""
|
||||||
pass
|
raise NotImplementedError()
|
||||||
|
|
||||||
@abc.abstractmethod
|
|
||||||
def _removed_checker(self) -> str:
|
def _removed_checker(self) -> str:
|
||||||
"""
|
"""
|
||||||
Checks if a machine has been removed
|
Checks if a machine has been removed
|
||||||
"""
|
"""
|
||||||
pass
|
return self._exec_state
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
def check_state(self) -> str:
|
def check_state(self) -> str:
|
||||||
@ -322,13 +372,16 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
if op == Operation.FINISH:
|
if op == Operation.FINISH:
|
||||||
return State.FINISHED
|
return State.FINISHED
|
||||||
|
|
||||||
fncs: collections.abc.Mapping[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
|
fncs: dict[Operation, collections.abc.Callable[[], str]] = {
|
||||||
Operation.CREATE: self._create_checker,
|
Operation.CREATE: self._create_checker,
|
||||||
Operation.RETRY: self._retry,
|
Operation.RETRY: self._retry_checker,
|
||||||
Operation.WAIT: self._wait,
|
Operation.WAIT: self._wait_checker,
|
||||||
Operation.START: self._start_checker,
|
Operation.START: self._start_checker,
|
||||||
Operation.STOP: self._stop_checker,
|
Operation.STOP: self._stop_checker,
|
||||||
Operation.REMOVE: self._removed_checker,
|
Operation.REMOVE: self._removed_checker,
|
||||||
|
Operation.SNAPSHOT_CREATE: self._snapshot_create_checker,
|
||||||
|
Operation.SNAPSHOT_RECOVER: self._snapshot_recover_checker,
|
||||||
|
Operation.PROCESS_TOKEN: self._process_token_checker,
|
||||||
}
|
}
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@ -371,7 +424,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
"""
|
"""
|
||||||
Invoked for destroying a deployed service
|
Invoked for destroying a deployed service
|
||||||
"""
|
"""
|
||||||
self._queue = FixedUserService._destrpy_queue.copy() # copy is needed to avoid modifying class var
|
self._queue = FixedUserService._destroy_queue.copy() # copy is needed to avoid modifying class var
|
||||||
return self._execute_queue()
|
return self._execute_queue()
|
||||||
|
|
||||||
@typing.final
|
@typing.final
|
||||||
@ -385,7 +438,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
When administrator requests it, the cancel is "delayed" and not
|
When administrator requests it, the cancel is "delayed" and not
|
||||||
invoked directly.
|
invoked directly.
|
||||||
"""
|
"""
|
||||||
logger.debug('Canceling %s with taskId=%s, vmId=%s', self._name, self._task, self._vmid)
|
logger.debug('Canceling %s with taskid=%s, vmid=%s', self._name, self._task, self._vmid)
|
||||||
return self.destroy()
|
return self.destroy()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -399,6 +452,9 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
|
|||||||
Operation.ERROR: 'error',
|
Operation.ERROR: 'error',
|
||||||
Operation.FINISH: 'finish',
|
Operation.FINISH: 'finish',
|
||||||
Operation.RETRY: 'retry',
|
Operation.RETRY: 'retry',
|
||||||
|
Operation.SNAPSHOT_CREATE: 'snapshot_create',
|
||||||
|
Operation.SNAPSHOT_RECOVER: 'snapshot_recover',
|
||||||
|
Operation.PROCESS_TOKEN: 'process_token',
|
||||||
}.get(op, '????')
|
}.get(op, '????')
|
||||||
|
|
||||||
def _debug(self, txt: str) -> None:
|
def _debug(self, txt: str) -> None:
|
||||||
|
@ -65,6 +65,7 @@ class ProxmoxFixedUserService(FixedUserService, autoserializable.AutoSerializabl
|
|||||||
|
|
||||||
# : Recheck every ten seconds by default (for task methods)
|
# : Recheck every ten seconds by default (for task methods)
|
||||||
suggested_delay = 4
|
suggested_delay = 4
|
||||||
|
|
||||||
def _store_task(self, upid: 'client.types.UPID') -> None:
|
def _store_task(self, upid: 'client.types.UPID') -> None:
|
||||||
self._task = '\t'.join([upid.node, upid.upid])
|
self._task = '\t'.join([upid.node, upid.upid])
|
||||||
|
|
||||||
@ -110,20 +111,18 @@ class ProxmoxFixedUserService(FixedUserService, autoserializable.AutoSerializabl
|
|||||||
def error(self, reason: str) -> str:
|
def error(self, reason: str) -> str:
|
||||||
return self._error(reason)
|
return self._error(reason)
|
||||||
|
|
||||||
def _start_machine(self) -> str:
|
def _start_machine(self) -> None:
|
||||||
try:
|
try:
|
||||||
vminfo = self.service().get_machine_info(int(self._vmid))
|
vminfo = self.service().get_machine_info(int(self._vmid))
|
||||||
except client.ProxmoxConnectionError:
|
except client.ProxmoxConnectionError:
|
||||||
return self._retry_later()
|
self._retry_later()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise Exception('Machine not found on start machine') from e
|
raise Exception('Machine not found on start machine') from e
|
||||||
|
|
||||||
if vminfo.status == 'stopped':
|
if vminfo.status == 'stopped':
|
||||||
self._store_task(self.service().start_machine(int(self._vmid)))
|
self._store_task(self.service().start_machine(int(self._vmid)))
|
||||||
|
|
||||||
return State.RUNNING
|
def _stop_machine(self) -> None:
|
||||||
|
|
||||||
def _stop_machine(self) -> str:
|
|
||||||
try:
|
try:
|
||||||
vm_info = self.service().get_machine_info(int(self._vmid))
|
vm_info = self.service().get_machine_info(int(self._vmid))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@ -133,8 +132,6 @@ class ProxmoxFixedUserService(FixedUserService, autoserializable.AutoSerializabl
|
|||||||
logger.debug('Stopping machine %s', vm_info)
|
logger.debug('Stopping machine %s', vm_info)
|
||||||
self._store_task(self.service().stop_machine(int(self._vmid)))
|
self._store_task(self.service().stop_machine(int(self._vmid)))
|
||||||
|
|
||||||
return State.RUNNING
|
|
||||||
|
|
||||||
# Check methods
|
# Check methods
|
||||||
def _check_task_finished(self) -> str:
|
def _check_task_finished(self) -> str:
|
||||||
if self._task == '':
|
if self._task == '':
|
||||||
@ -156,12 +153,6 @@ class ProxmoxFixedUserService(FixedUserService, autoserializable.AutoSerializabl
|
|||||||
return State.RUNNING
|
return State.RUNNING
|
||||||
|
|
||||||
# Check methods
|
# Check methods
|
||||||
def _create_checker(self) -> str:
|
|
||||||
"""
|
|
||||||
Checks the state of a deploy for an user or cache
|
|
||||||
"""
|
|
||||||
return State.FINISHED
|
|
||||||
|
|
||||||
def _start_checker(self) -> str:
|
def _start_checker(self) -> str:
|
||||||
"""
|
"""
|
||||||
Checks if machine has started
|
Checks if machine has started
|
||||||
@ -173,9 +164,3 @@ class ProxmoxFixedUserService(FixedUserService, autoserializable.AutoSerializabl
|
|||||||
Checks if machine has stoped
|
Checks if machine has stoped
|
||||||
"""
|
"""
|
||||||
return self._check_task_finished()
|
return self._check_task_finished()
|
||||||
|
|
||||||
def _removed_checker(self) -> str:
|
|
||||||
"""
|
|
||||||
Checks if a machine has been removed
|
|
||||||
"""
|
|
||||||
return self._check_task_finished()
|
|
||||||
|
@ -201,7 +201,7 @@ class ProxmoxFixedService(FixedService): # pylint: disable=too-many-public-meth
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.do_log(log.LogLevel.WARNING, 'Could not create SNAPSHOT for this VM. ({})'.format(e))
|
self.do_log(log.LogLevel.WARNING, 'Could not create SNAPSHOT for this VM. ({})'.format(e))
|
||||||
|
|
||||||
return types.states.State.RUNNING
|
return types.states.State.FINISHED
|
||||||
|
|
||||||
def get_and_assign_machine(self) -> str:
|
def get_and_assign_machine(self) -> str:
|
||||||
found_vmid: typing.Optional[int] = None
|
found_vmid: typing.Optional[int] = None
|
||||||
@ -245,8 +245,10 @@ class ProxmoxFixedService(FixedService): # pylint: disable=too-many-public-meth
|
|||||||
def get_machine_name(self, vmid: str) -> str:
|
def get_machine_name(self, vmid: str) -> str:
|
||||||
return self.parent().get_machine_info(int(vmid)).name or ''
|
return self.parent().get_machine_info(int(vmid)).name or ''
|
||||||
|
|
||||||
def remove_and_free_machine(self, vmid: str) -> None:
|
def remove_and_free_machine(self, vmid: str) -> str:
|
||||||
try:
|
try:
|
||||||
self._save_assigned_machines(self._get_assigned_machines() - {str(vmid)}) # Remove from assigned
|
self._save_assigned_machines(self._get_assigned_machines() - {str(vmid)}) # Remove from assigned
|
||||||
|
return types.states.State.FINISHED
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warn('Cound not save assigned machines on fixed pool: %s', e)
|
logger.warn('Cound not save assigned machines on fixed pool: %s', e)
|
||||||
|
raise
|
||||||
|
@ -84,9 +84,9 @@ class ServerManagerManagedServersTest(UDSTestCase):
|
|||||||
# commodity call to assign
|
# commodity call to assign
|
||||||
self.assign = functools.partial(
|
self.assign = functools.partial(
|
||||||
self.manager.assign,
|
self.manager.assign,
|
||||||
serverGroup=self.registered_servers_group,
|
server_group=self.registered_servers_group,
|
||||||
serviceType=types.services.ServiceType.VDI,
|
service_type=types.services.ServiceType.VDI,
|
||||||
minMemoryMB=MIN_TEST_MEMORY_MB,
|
min_memory_mb=MIN_TEST_MEMORY_MB,
|
||||||
)
|
)
|
||||||
self.all_uuids: list[str] = list(
|
self.all_uuids: list[str] = list(
|
||||||
self.registered_servers_group.servers.all().values_list('uuid', flat=True)
|
self.registered_servers_group.servers.all().values_list('uuid', flat=True)
|
||||||
@ -217,7 +217,7 @@ class ServerManagerManagedServersTest(UDSTestCase):
|
|||||||
with self.create_mock_api_requester() as mockServerApiRequester:
|
with self.create_mock_api_requester() as mockServerApiRequester:
|
||||||
# Assign all user services with lock
|
# Assign all user services with lock
|
||||||
for userService in self.user_services[:NUM_REGISTEREDSERVERS]:
|
for userService in self.user_services[:NUM_REGISTEREDSERVERS]:
|
||||||
assignation = self.assign(userService, lockTime=datetime.timedelta(seconds=1))
|
assignation = self.assign(userService, lock_interval=datetime.timedelta(seconds=1))
|
||||||
if assignation is None:
|
if assignation is None:
|
||||||
self.fail('Assignation returned None')
|
self.fail('Assignation returned None')
|
||||||
return # For mypy
|
return # For mypy
|
||||||
@ -231,12 +231,12 @@ class ServerManagerManagedServersTest(UDSTestCase):
|
|||||||
|
|
||||||
# Next one should fail returning None
|
# Next one should fail returning None
|
||||||
self.assertIsNone(
|
self.assertIsNone(
|
||||||
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lockTime=datetime.timedelta(seconds=1))
|
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lock_interval=datetime.timedelta(seconds=1))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait a second, and try again, it should work
|
# Wait a second, and try again, it should work
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lockTime=datetime.timedelta(seconds=1))
|
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lock_interval=datetime.timedelta(seconds=1))
|
||||||
|
|
||||||
# notify_release should has been called once
|
# notify_release should has been called once
|
||||||
self.assertEqual(mockServerApiRequester.return_value.notify_release.call_count, 1)
|
self.assertEqual(mockServerApiRequester.return_value.notify_release.call_count, 1)
|
||||||
@ -247,7 +247,7 @@ class ServerManagerManagedServersTest(UDSTestCase):
|
|||||||
for assignations in range(2): # Second pass will get current assignation, not new ones
|
for assignations in range(2): # Second pass will get current assignation, not new ones
|
||||||
for elementNumber, userService in enumerate(self.user_services[:NUM_REGISTEREDSERVERS]):
|
for elementNumber, userService in enumerate(self.user_services[:NUM_REGISTEREDSERVERS]):
|
||||||
# Ensure locking server, so we have to use every server only once
|
# Ensure locking server, so we have to use every server only once
|
||||||
assignation = self.assign(userService, lockTime=datetime.timedelta(seconds=32))
|
assignation = self.assign(userService, lock_interval=datetime.timedelta(seconds=32))
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
serverApiRequester.notify_assign.call_count,
|
serverApiRequester.notify_assign.call_count,
|
||||||
assignations * NUM_REGISTEREDSERVERS + elementNumber + 1,
|
assignations * NUM_REGISTEREDSERVERS + elementNumber + 1,
|
||||||
@ -265,7 +265,7 @@ class ServerManagerManagedServersTest(UDSTestCase):
|
|||||||
|
|
||||||
# Trying to lock a new one, should fail
|
# Trying to lock a new one, should fail
|
||||||
self.assertIsNone(
|
self.assertIsNone(
|
||||||
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lockTime=datetime.timedelta(seconds=32))
|
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lock_interval=datetime.timedelta(seconds=32))
|
||||||
)
|
)
|
||||||
|
|
||||||
# All servers should be locked
|
# All servers should be locked
|
||||||
|
@ -78,8 +78,8 @@ class ServerManagerUnmanagedServersTest(UDSTestCase):
|
|||||||
# commodity call to assign
|
# commodity call to assign
|
||||||
self.assign = functools.partial(
|
self.assign = functools.partial(
|
||||||
self.manager.assign,
|
self.manager.assign,
|
||||||
serverGroup=self.registered_servers_group,
|
server_group=self.registered_servers_group,
|
||||||
serviceType=types.services.ServiceType.VDI,
|
service_type=types.services.ServiceType.VDI,
|
||||||
)
|
)
|
||||||
self.all_uuids: list[str] = list(
|
self.all_uuids: list[str] = list(
|
||||||
self.registered_servers_group.servers.all().values_list('uuid', flat=True)
|
self.registered_servers_group.servers.all().values_list('uuid', flat=True)
|
||||||
@ -176,7 +176,7 @@ class ServerManagerUnmanagedServersTest(UDSTestCase):
|
|||||||
with self.createMockApiRequester() as mockServerApiRequester:
|
with self.createMockApiRequester() as mockServerApiRequester:
|
||||||
# Assign all user services with lock
|
# Assign all user services with lock
|
||||||
for userService in self.user_services[:NUM_REGISTEREDSERVERS]:
|
for userService in self.user_services[:NUM_REGISTEREDSERVERS]:
|
||||||
assignation = self.assign(userService, lockTime=datetime.timedelta(seconds=1.1))
|
assignation = self.assign(userService, lock_interval=datetime.timedelta(seconds=1.1))
|
||||||
if assignation is None:
|
if assignation is None:
|
||||||
self.fail('Assignation returned None')
|
self.fail('Assignation returned None')
|
||||||
return # For mypy
|
return # For mypy
|
||||||
@ -191,12 +191,12 @@ class ServerManagerUnmanagedServersTest(UDSTestCase):
|
|||||||
|
|
||||||
# Next one should fail with aa None return
|
# Next one should fail with aa None return
|
||||||
self.assertIsNone(
|
self.assertIsNone(
|
||||||
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lockTime=datetime.timedelta(seconds=1))
|
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lock_interval=datetime.timedelta(seconds=1))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wait a bit more than a second, and try again, it should work
|
# Wait a bit more than a second, and try again, it should work
|
||||||
time.sleep(1.1)
|
time.sleep(1.1)
|
||||||
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lockTime=datetime.timedelta(seconds=1))
|
self.assign(self.user_services[NUM_REGISTEREDSERVERS], lock_interval=datetime.timedelta(seconds=1))
|
||||||
|
|
||||||
# notify_release should has been called once
|
# notify_release should has been called once
|
||||||
self.assertEqual(mockServerApiRequester.return_value.notify_release.call_count, 1)
|
self.assertEqual(mockServerApiRequester.return_value.notify_release.call_count, 1)
|
||||||
|
30
server/tests/core/services/__init__.py
Normal file
30
server/tests/core/services/__init__.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
#
|
||||||
|
# Copyright (c) 2022 Virtual Cable S.L.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
"""
|
||||||
|
Author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
"""
|
122
server/tests/core/services/specializations/test_fixedservice.py
Normal file
122
server/tests/core/services/specializations/test_fixedservice.py
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright (c) 2022 Virtual Cable S.L.U.
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
|
||||||
|
# may be used to endorse or promote products derived from this software
|
||||||
|
# without specific prior written permission.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||||
|
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||||
|
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||||
|
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||||
|
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
"""
|
||||||
|
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||||
|
"""
|
||||||
|
import typing
|
||||||
|
from uds import models
|
||||||
|
from uds.core import types, services
|
||||||
|
from ....utils.test import UDSTestCase
|
||||||
|
|
||||||
|
from uds.core.services.specializations.fixed_machine import fixed_service, fixed_userservice
|
||||||
|
|
||||||
|
|
||||||
|
class FixedServiceTest(UDSTestCase):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class FixedUserService(fixed_userservice.FixedUserService):
|
||||||
|
started: bool = False
|
||||||
|
counter: int = 0
|
||||||
|
|
||||||
|
def _start_machine(self) -> None:
|
||||||
|
self.started = True
|
||||||
|
self.counter = 2
|
||||||
|
|
||||||
|
def _stop_machine(self) -> None:
|
||||||
|
self.started = False
|
||||||
|
self.counter = 2
|
||||||
|
|
||||||
|
def _start_checker(self) -> str:
|
||||||
|
self.counter = self.counter - 1
|
||||||
|
if self.counter <= 0:
|
||||||
|
return types.states.State.FINISHED
|
||||||
|
return types.states.State.RUNNING
|
||||||
|
|
||||||
|
def _stop_checker(self) -> str:
|
||||||
|
self.counter = self.counter - 1
|
||||||
|
if self.counter <= 0:
|
||||||
|
return types.states.State.FINISHED
|
||||||
|
return types.states.State.RUNNING
|
||||||
|
|
||||||
|
|
||||||
|
class FixedService(fixed_service.FixedService):
|
||||||
|
token = fixed_service.FixedService.token
|
||||||
|
snapshot_type = fixed_service.FixedService.snapshot_type
|
||||||
|
machines = fixed_service.FixedService.machines
|
||||||
|
|
||||||
|
snapshot_proccessed: bool = False
|
||||||
|
is_remove_snapshot: bool = False
|
||||||
|
assigned_machine: str = ''
|
||||||
|
|
||||||
|
user_service_type = FixedUserService
|
||||||
|
|
||||||
|
def process_snapshot(self, remove: bool, userservice_instace: fixed_userservice.FixedUserService) -> str:
|
||||||
|
self.snapshot_proccessed = True
|
||||||
|
self.is_remove_snapshot = remove
|
||||||
|
return super().process_snapshot(remove, userservice_instace)
|
||||||
|
|
||||||
|
def get_machine_name(self, vmid: str) -> str:
|
||||||
|
return f'Machine {vmid}'
|
||||||
|
|
||||||
|
def get_and_assign_machine(self) -> str:
|
||||||
|
self.assigned_machine = 'assigned'
|
||||||
|
return self.assigned_machine
|
||||||
|
|
||||||
|
def remove_and_free_machine(self, vmid: str) -> str:
|
||||||
|
self.assigned_machine = ''
|
||||||
|
return types.states.State.FINISHED
|
||||||
|
|
||||||
|
def get_first_network_mac(self, vmid: str) -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def get_guest_ip_address(self, vmid: str) -> str:
|
||||||
|
raise NotImplementedError()
|
||||||
|
|
||||||
|
def enumerate_assignables(self) -> list[tuple[str, str]]:
|
||||||
|
"""
|
||||||
|
Returns a list of tuples with the id and the name of the assignables
|
||||||
|
"""
|
||||||
|
return [
|
||||||
|
('1', 'Machine 1'),
|
||||||
|
('2', 'Machine 2'),
|
||||||
|
('3', 'Machine 3'),
|
||||||
|
]
|
||||||
|
|
||||||
|
def assign_from_assignables(
|
||||||
|
self, assignable_id: str, user: 'models.User', userservice_instance: 'services.UserService'
|
||||||
|
) -> str:
|
||||||
|
"""
|
||||||
|
Assigns a machine from the assignables
|
||||||
|
"""
|
||||||
|
return types.states.State.FINISHED
|
||||||
|
|
||||||
|
|
||||||
|
class FixedProvider(services.provider.ServiceProvider):
|
||||||
|
offers = [FixedService]
|
Loading…
Reference in New Issue
Block a user