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

Added type checking to all deployment tasks

This commit is contained in:
Adolfo Gómez García 2024-02-22 21:50:20 +01:00
parent 7e33f381d4
commit 73042dd76f
No known key found for this signature in database
GPG Key ID: DD1ABF20724CDA23
51 changed files with 868 additions and 897 deletions

View File

@ -32,11 +32,10 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import datetime
import logging
import re
import typing
import collections.abc
import xml.sax # nosec: used to parse trusted xml provided only by administrators
from urllib import parse
from urllib.parse import urlparse
import requests
from django.utils.translation import gettext
@ -55,6 +54,7 @@ from uds.core.util.model import sql_datetime
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from django.http import HttpRequest
from urllib.parse import ParseResult
from uds.core.types.requests import ExtendedHttpRequestWithUser
@ -454,9 +454,7 @@ class SAMLAuthenticator(auths.Authenticator):
xml.sax.parseString(idp_metadata, xml.sax.ContentHandler()) # type: ignore # nosec: url provided by admin
except Exception as e:
msg = (gettext(' (obtained from URL)') if from_url else '') + str(e)
raise exceptions.ui.ValidationError(
gettext('XML does not seem valid for IDP Metadata ') + msg
)
raise exceptions.ui.ValidationError(gettext('XML does not seem valid for IDP Metadata ') + msg)
# Now validate regular expressions, if they exists
auth_utils.validate_regex_field(self.attrs_username)
@ -468,7 +466,7 @@ class SAMLAuthenticator(auths.Authenticator):
request: 'ExtendedHttpRequest',
params: typing.Optional['types.auth.AuthCallbackParams'] = None,
) -> dict[str, typing.Any]:
manage_url_obj: 'parse.ParseResult' = parse.urlparse(self.manage_url.value)
manage_url_obj = typing.cast('ParseResult', urlparse(self.manage_url.value))
script_path: str = manage_url_obj.path
host: str = manage_url_obj.netloc
if ':' in host:
@ -524,7 +522,7 @@ class SAMLAuthenticator(auths.Authenticator):
else:
val = self.idp_metadata.value
return OneLogin_Saml2_IdPMetadataParser.parse(val)
return OneLogin_Saml2_IdPMetadataParser.parse(val) # pyright: ignore reportUnknownVariableType
def build_onelogin_settings(self) -> dict[str, typing.Any]:
return {
@ -547,14 +545,17 @@ class SAMLAuthenticator(auths.Authenticator):
'idp': self.get_idp_metadata_dict()['idp'],
'security': {
# in days, converted to seconds, this is a duration
'metadataCacheDuration': self.metadata_cache_duration.as_int() * 86400
if self.metadata_cache_duration.value > 0
else 86400 * 365 * 10,
'metadataCacheDuration': (
self.metadata_cache_duration.as_int() * 86400
if self.metadata_cache_duration.value > 0
else 86400 * 365 * 10
),
# This is a date of end of validity
'metadataValidUntil': sql_datetime()
+ datetime.timedelta(days=self.metadata_validity_duration.as_int())
if self.metadata_cache_duration.value > 0
else sql_datetime() + datetime.timedelta(days=365 * 10),
'metadataValidUntil': (
sql_datetime() + datetime.timedelta(days=self.metadata_validity_duration.as_int())
if self.metadata_cache_duration.value > 0
else sql_datetime() + datetime.timedelta(days=365 * 10)
),
'nameIdEncrypted': self.use_name_id_encrypted.as_bool(),
'authnRequestsSigned': self.use_authn_requests_signed.as_bool(),
'logoutRequestSigned': self.logout_request_signed.as_bool(),
@ -585,8 +586,10 @@ class SAMLAuthenticator(auths.Authenticator):
)
def get_sp_metadata(self) -> str:
saml_settings = OneLogin_Saml2_Settings(settings=self.build_onelogin_settings())
metadata = saml_settings.get_sp_metadata()
errors = saml_settings.validate_metadata(metadata)
metadata: typing.Any = saml_settings.get_sp_metadata()
errors: list[typing.Any] = saml_settings.validate_metadata( # pyright: ignore reportUnknownVariableType
metadata
)
if len(errors) > 0:
raise exceptions.auth.AuthenticatorException(
gettext('Error validating SP metadata: ') + str(errors)
@ -640,12 +643,14 @@ class SAMLAuthenticator(auths.Authenticator):
settings = OneLogin_Saml2_Settings(settings=self.build_onelogin_settings())
auth = OneLogin_Saml2_Auth(req, settings)
url = auth.process_slo(request_id=logout_req_id)
url: str = auth.process_slo(request_id=logout_req_id) # pyright: ignore reportUnknownVariableType
errors = auth.get_errors()
errors: list[str] = auth.get_errors()
if errors:
logger.debug('Error on SLO: %s', auth.get_last_response_xml())
logger.debug(
'Error on SLO: %s', auth.get_last_response_xml() # pyright: ignore reportUnknownVariableType
)
logger.debug('post_data: %s', req['post_data'])
logger.info('Errors processing logout request: %s', errors)
raise exceptions.auth.AuthenticatorException(gettext('Error processing SLO: ') + str(errors))
@ -664,20 +669,20 @@ class SAMLAuthenticator(auths.Authenticator):
self,
parameters: 'types.auth.AuthCallbackParams',
gm: 'auths.GroupsManager',
request: 'ExtendedHttpRequestWithUser',
request: 'ExtendedHttpRequest',
) -> types.auth.AuthenticationResult:
req = self.build_req_from_request(request, params=parameters)
if 'logout' in parameters.get_params:
return self.logout_callback(req, request)
return self.logout_callback(req, typing.cast('ExtendedHttpRequestWithUser', request))
try:
settings = OneLogin_Saml2_Settings(settings=self.build_onelogin_settings())
auth = OneLogin_Saml2_Auth(req, settings)
auth.process_response()
auth.process_response() # pyright: ignore reportUnknownVariableType
except Exception as e:
raise exceptions.auth.AuthenticatorException(gettext('Error processing SAML response: ') + str(e))
errors = auth.get_errors()
errors: list[str] = auth.get_errors()
if errors:
raise exceptions.auth.AuthenticatorException('SAML response error: ' + str(errors))
@ -705,24 +710,34 @@ class SAMLAuthenticator(auths.Authenticator):
# url=auth.redirect_to(req['post_data']['RelayState'])
# )
attributes = auth.get_attributes().copy()
attributes: dict[str, typing.Any] = ( # pyright: ignore reportUnknownVariableType
auth.get_attributes().copy() # pyright: ignore reportUnknownVariableType
)
# Append attributes by its friendly name
attributes.update(auth.get_friendlyname_attributes())
attributes.update(auth.get_friendlyname_attributes()) # pyright: ignore reportUnknownVariableType
if not attributes:
raise exceptions.auth.AuthenticatorException(gettext('No attributes returned from IdP'))
logger.debug("Attributes: %s", attributes)
logger.debug("Attributes: %s", attributes) # pyright: ignore reportUnknownVariableType
# Now that we have attributes, we can extract values from this, map groups, etc...
username = ''.join(
auth_utils.process_regex_field(self.attrs_username.value, attributes)
auth_utils.process_regex_field(
self.attrs_username.value, attributes # pyright: ignore reportUnknownVariableType
)
) # in case of multiple values is returned, join them
logger.debug('Username: %s', username)
groups = auth_utils.process_regex_field(self.attrs_groupname.value, attributes)
groups = auth_utils.process_regex_field(
self.attrs_groupname.value, attributes # pyright: ignore reportUnknownVariableType
)
logger.debug('Groups: %s', groups)
realName = ' '.join(auth_utils.process_regex_field(self.attrs_realname.value, attributes))
realName = ' '.join(
auth_utils.process_regex_field(
self.attrs_realname.value, attributes # pyright: ignore reportUnknownVariableType
)
)
logger.debug('Real name: %s', realName)
# store groups for this username at storage, so we can check it at a later stage
@ -732,7 +747,11 @@ class SAMLAuthenticator(auths.Authenticator):
if self.mfa_attr.value.strip():
self.storage.put_pickle(
self.mfa_storage_key(username),
''.join(auth_utils.process_regex_field(self.mfa_attr.value, attributes)),
''.join(
auth_utils.process_regex_field(
self.mfa_attr.value, attributes # pyright: ignore reportUnknownVariableType
)
),
) # in case multipel values is returned, join them
else:
self.storage.remove(self.mfa_storage_key(username))
@ -768,7 +787,7 @@ class SAMLAuthenticator(auths.Authenticator):
return types.auth.AuthenticationResult(
success=types.auth.AuthenticationState.REDIRECT,
url=auth.logout(
url=auth.logout( # pyright: ignore reportUnknownVariableType
name_id=saml.get('nameid'),
session_index=saml.get('session_index'),
nq=saml.get('nameid_namequalifier'),
@ -796,11 +815,11 @@ class SAMLAuthenticator(auths.Authenticator):
req = self.build_req_from_request(request)
auth = OneLogin_Saml2_Auth(req, self.build_onelogin_settings())
return f'window.location="{auth.login()}";'
return f'window.location="{auth.login()}";' # pyright: ignore reportUnknownVariableType
def remove_user(self, username):
def remove_user(self, username: str) -> None:
"""
Clean ups storage data
"""
self.storage.remove(username)
self.storage.remove('lasso-' + username)
self.mfa_clean(username)

View File

@ -48,6 +48,7 @@ if typing.TYPE_CHECKING:
HttpResponse,
)
from uds import models
from uds.core.types.requests import ExtendedHttpRequest
from uds.core.environment import Environment
from uds.core import types
from .groups_manager import GroupsManager
@ -516,7 +517,7 @@ class Authenticator(Module):
"""
raise NotImplementedError
def get_javascript(self, request: 'HttpRequest') -> typing.Optional[str]:
def get_javascript(self, request: 'ExtendedHttpRequest') -> typing.Optional[str]:
"""
If you override this method, and returns something different of None,
UDS will consider your authenticator as "Owner draw", that is, that it

View File

@ -72,7 +72,7 @@ BROWSERS_RE: dict[types.os.KnownBrowser, tuple[typing.Pattern[str], ...]] = {
types.os.KnownBrowser.EDGE: (re.compile(r'Edg/([0-9.]+)'),),
}
BROWSER_RULES: dict[types.os.KnownBrowser, tuple] = {
BROWSER_RULES: dict[types.os.KnownBrowser, tuple[types.os.KnownBrowser, tuple[types.os.KnownBrowser, ...]]] = {
types.os.KnownBrowser.EDGE: (types.os.KnownBrowser.EDGE, ()),
types.os.KnownBrowser.CHROME: (
types.os.KnownBrowser.CHROME,

View File

@ -86,7 +86,7 @@ class DelayedTaskRunner(metaclass=singleton.Singleton):
_hostname: typing.ClassVar[str] # "Our" hostname
_keep_running: typing.ClassVar[bool] # If we should keep it running
def __init__(self):
def __init__(self) -> None:
DelayedTaskRunner._hostname = gethostname()
DelayedTaskRunner._keep_running = True
logger.debug("Initialized delayed task runner for host %s", DelayedTaskRunner._hostname)
@ -117,7 +117,7 @@ class DelayedTaskRunner(metaclass=singleton.Singleton):
task: DBDelayedTask = (
DBDelayedTask.objects.select_for_update()
.filter(filt)
.order_by('execution_time')[0] # type: ignore # Slicing is not supported by pylance right now
.order_by('execution_time')[0]
) # @UndefinedVariable
if task.insert_date > now + timedelta(seconds=30):
logger.warning('Executed %s due to insert_date being in the future!', task.type)

View File

@ -161,7 +161,7 @@ class Scheduler:
job: DBScheduler = (
DBScheduler.objects.select_for_update()
.filter(fltr)
.order_by('next_execution')[0]# type: ignore # Slicing is not supported by pylance right now
.order_by('next_execution')[0]
)
if job.last_execution > now:
logger.warning(

View File

@ -270,7 +270,7 @@ class PublicationManager(metaclass=singleton.Singleton):
Manager responsible of controlling publications
"""
def __init__(self):
def __init__(self) -> None:
pass
@staticmethod
@ -284,7 +284,7 @@ class PublicationManager(metaclass=singleton.Singleton):
def publish(
self, servicePool: ServicePool, changeLog: typing.Optional[str] = None
): # pylint: disable=no-self-use
) -> None:
"""
Initiates the publication of a service pool, or raises an exception if this cannot be done
:param servicePool: Service pool object (db object)
@ -330,7 +330,7 @@ class PublicationManager(metaclass=singleton.Singleton):
def cancel(
self, publication: ServicePoolPublication
): # pylint: disable=no-self-use
) -> ServicePoolPublication:
"""
Invoked to cancel a publication.
Double invokation (i.e. invokation over a "cancelling" item) will lead to a "forced" cancellation (unclean)
@ -372,7 +372,7 @@ class PublicationManager(metaclass=singleton.Singleton):
def unpublish(
self, servicepool_publication: ServicePoolPublication
): # pylint: disable=no-self-use
) -> None:
"""
Unpublishes an active (usable) or removable publication
:param servicePoolPub: Publication to unpublish

View File

@ -30,7 +30,6 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import datetime
import logging
import datetime
import typing
import collections.abc
from concurrent.futures import ThreadPoolExecutor
@ -60,10 +59,8 @@ class ServerManager(metaclass=singleton.Singleton):
MAX_COUNTERS_AGE: typing.Final[datetime.timedelta] = datetime.timedelta(days=3)
BASE_PROPERTY_NAME: typing.Final[str] = 'sm_usr_'
last_counters_clean: datetime.datetime
def __init__(self):
self.last_counters_clean = datetime.datetime.now()
# Singleton, can initialize here
last_counters_clean: datetime.datetime = datetime.datetime.now()
@staticmethod
def manager() -> 'ServerManager':
@ -230,7 +227,7 @@ class ServerManager(metaclass=singleton.Singleton):
# If server is forced, and server is part of the group, use it
if server:
if (
server.groups.filter(uuid=server_group.uuid).exclude(uuid__in=excluded_servers_uuids).count()
server.groups.filter(uuid=server_group.uuid).exclude(uuid__in=excluded_servers_uuids).count()
== 0
):
raise exceptions.UDSException(_('Server is not part of the group'))
@ -481,7 +478,7 @@ class ServerManager(metaclass=singleton.Singleton):
Args:
serverGroup: Server group to realize maintenance on
"""
for k, v in serverGroup.properties.items():
for k, _ in serverGroup.properties.items():
if k.startswith(self.BASE_PROPERTY_NAME):
uuid = k[len(self.BASE_PROPERTY_NAME) :]
try:

View File

@ -28,6 +28,7 @@
"""
@author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import abc
import threading
import time
import signal
@ -45,24 +46,26 @@ from uds.core.util import singleton
logger = logging.getLogger(__name__)
class BaseThread(threading.Thread):
def request_stop(self):
class BaseThread(threading.Thread, abc.ABC):
@abc.abstractmethod
def request_stop(self) -> None:
raise NotImplementedError
class SchedulerThread(BaseThread):
def run(self):
def run(self) -> None:
Scheduler.scheduler().run()
def request_stop(self):
def request_stop(self) -> None:
Scheduler.scheduler().notify_termination()
class DelayedTaskThread(BaseThread):
def run(self):
def run(self) -> None:
DelayedTaskRunner.runner().run()
def request_stop(self):
def request_stop(self) -> None:
DelayedTaskRunner.runner().request_stop()
@ -73,7 +76,7 @@ class TaskManager(metaclass=singleton.Singleton):
keep_running: bool
threads: list[BaseThread]
def __init__(self):
def __init__(self) -> None:
self.keep_running = True
self.threads = []

View File

@ -231,7 +231,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
assigned = self._create_assigned_user_service_at_db_from_pool(service_pool, user)
assignedInstance = assigned.get_instance()
state = assignedInstance.deploy_for_user(user)
state = types.states.State.from_str(assignedInstance.deploy_for_user(user))
UserServiceOpChecker.make_unique(assigned, assignedInstance, state)
@ -271,7 +271,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
# Now, get from serviceInstance the data
assigned_userservice_instance = assigned.get_instance()
state = serviceInstance.assign_from_assignables(assignable_id, user, assigned_userservice_instance)
state = types.states.State.from_str(serviceInstance.assign_from_assignables(assignable_id, user, assigned_userservice_instance))
# assigned.u(assignedInstance)
UserServiceOpChecker.make_unique(assigned, assigned_userservice_instance, state)
@ -323,7 +323,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
else:
user_service.set_state(State.CANCELING)
# We simply notify service that it should cancel operation
state = user_service_instance.cancel()
state = types.states.State.from_str(user_service_instance.cancel())
# Data will be serialized on makeUnique process
# If cancel is not supported, base cancel always returns "FINISHED", and
@ -690,7 +690,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
if kind in 'A': # This is an assigned service
logger.debug('Getting A service %s', uuid_user_service)
user_service = UserService.objects.get(uuid=uuid_user_service, user=user)
typing.cast(UserService, user_service).deployed_service.validate_user(user)
user_service.deployed_service.validate_user(user)
else:
try:
service_pool: ServicePool = ServicePool.objects.get(uuid=uuid_user_service)

View File

@ -77,13 +77,13 @@ class StateUpdater:
def log_ip(self):
ip = self.user_service_instance.get_ip()
if ip is not None and ip != '':
if ip:
self.user_service.log_ip(ip)
def check_later(self):
UserServiceOpChecker.check_later(self.user_service, self.user_service_instance)
def run(self, state):
def run(self, state: 'State'):
executor = {
State.RUNNING: self.running,
State.ERROR: self.error,
@ -104,7 +104,7 @@ class StateUpdater:
logger.debug('Executor for %s done', self.user_service.friendly_name)
def finish(self):
def finish(self) -> None:
raise NotImplementedError()
def running(self):
@ -212,7 +212,7 @@ class UserServiceOpChecker(DelayedTask):
self._state = service.state
@staticmethod
def make_unique(userservice: UserService, userservice_instance: services.UserService, state: str):
def make_unique(userservice: UserService, userservice_instance: services.UserService, state: State):
"""
This method ensures that there will be only one delayedtask related to the userService indicated
"""
@ -220,7 +220,7 @@ class UserServiceOpChecker(DelayedTask):
UserServiceOpChecker.state_updater(userservice, userservice_instance, state)
@staticmethod
def state_updater(userservice: UserService, userservice_instance: services.UserService, state: str):
def state_updater(userservice: UserService, userservice_instance: services.UserService, state: State):
"""
Checks the value returned from invocation to publish or checkPublishingState, updating the servicePoolPub database object
Return True if it has to continue checking, False if finished
@ -278,7 +278,7 @@ class UserServiceOpChecker(DelayedTask):
logger.debug('Checking user service finished %s', self._svrId)
user_service: 'UserService|None' = None
try:
user_service = typing.cast(UserService, UserService.objects.get(pk=self._svrId))
user_service = UserService.objects.get(pk=self._svrId)
if user_service.state != self._state:
logger.debug('Task overrided by another task (state of item changed)')
# This item is no longer valid, returning will not check it again (no checkLater called)

View File

@ -53,7 +53,7 @@ class MessageProcessorThread(BaseThread):
]
_cached_stamp: float
def __init__(self):
def __init__(self) -> None:
super().__init__()
self.name = 'MessageProcessorThread'
self._cached_providers = None
@ -73,7 +73,7 @@ class MessageProcessorThread(BaseThread):
self._cached_stamp = time.time()
return self._cached_providers
def run(self):
def run(self) -> None:
while self._keep_running:
# Locate all notifications from "persistent" and try to process them
# If no notification can be fully resolved, it will be kept in the database
@ -149,5 +149,5 @@ class MessageProcessorThread(BaseThread):
break
time.sleep(1)
def request_stop(self):
def request_stop(self) -> None:
self._keep_running = False

View File

@ -33,6 +33,7 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
import abc
import typing
import collections.abc
from uds.core import types
from uds.core.environment import Environmentable
from uds.core.serializable import Serializable
@ -178,7 +179,7 @@ class Publication(Environmentable, Serializable):
return self._uuid
@abc.abstractmethod
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
This method is invoked whenever the administrator requests a new publication.
@ -214,7 +215,7 @@ class Publication(Environmentable, Serializable):
raise NotImplementedError(f'publish method for class {self.__class__.__name__} not provided! ')
@abc.abstractmethod
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
This is a task method. As that, the expected return values are
State values RUNNING, FINISHED or ERROR.
@ -267,7 +268,7 @@ class Publication(Environmentable, Serializable):
return 'unknown'
@abc.abstractmethod
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is a task method. As that, the expected return values are
State values RUNNING, FINISHED or ERROR.
@ -288,7 +289,7 @@ class Publication(Environmentable, Serializable):
raise NotImplementedError(f'destroy method for class {self.__class__.__name__} not provided!')
@abc.abstractmethod
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the expected return values are
State values RUNNING, FINISHED or ERROR.

View File

@ -382,7 +382,7 @@ class Service(Module):
"""
return None
def get_valid_id(self, idsList: collections.abc.Iterable[str]) -> typing.Optional[str]:
def get_valid_id(self, ids: collections.abc.Iterable[str]) -> typing.Optional[str]:
"""
Looks for an "owned" id in the provided list. If found, returns it, else return None

View File

@ -36,11 +36,9 @@ import enum
import logging
import typing
import collections.abc
from webbrowser import Opera
from uds.core import services, consts
from uds.core import services, consts, types
from uds.core.managers.userservice import UserServiceManager
from uds.core.types.states import State
from uds.core.util import log, autoserializable
from uds.core.util.model import sql_stamp_seconds
@ -136,9 +134,9 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
@typing.final
def _retry_later(self) -> str:
self._push_front_op(Operation.RETRY)
return State.RUNNING
return types.states.State.RUNNING
def _error(self, reason: typing.Union[str, Exception]) -> str:
def _error(self, reason: typing.Union[str, Exception]) -> types.states.State:
"""
Internal method to set object as error state
@ -159,7 +157,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
self._queue = [Operation.ERROR]
self._reason = reason
return State.ERROR
return types.states.State.ERROR
# Utility overrides for type checking...
# Probably, overriden again on child classes
@ -188,7 +186,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
return ''
@typing.final
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -205,15 +203,15 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
return self._execute_queue()
@typing.final
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self._debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, collections.abc.Callable[[], None]] = {
Operation.CREATE: self._create,
@ -237,7 +235,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
operation_runner()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
logger.exception('Unexpected FixedUserService exception: %s', e)
return self._error(str(e))
@ -305,38 +303,38 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
self.service().remove_and_free_machine(self._vmid)
# Check methods
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
return State.FINISHED
return types.states.State.FINISHED
def _snapshot_create_checker(self) -> str:
def _snapshot_create_checker(self) -> types.states.State:
"""
Checks the state of a snapshot creation
"""
return State.FINISHED
return types.states.State.FINISHED
def _snapshot_recover_checker(self) -> str:
def _snapshot_recover_checker(self) -> types.states.State:
"""
Checks the state of a snapshot recovery
"""
return State.FINISHED
return types.states.State.FINISHED
def _process_token_checker(self) -> str:
def _process_token_checker(self) -> types.states.State:
"""
Checks the state of a token processing
"""
return State.FINISHED
return types.states.State.FINISHED
def _retry_checker(self) -> str:
return State.FINISHED
def _retry_checker(self) -> types.states.State:
return types.states.State.FINISHED
def _wait_checker(self) -> str:
return State.FINISHED
def _wait_checker(self) -> types.states.State:
return types.states.State.FINISHED
def _nop_checker(self) -> str:
return State.FINISHED
def _nop_checker(self) -> types.states.State:
return types.states.State.FINISHED
def _start_machine(self) -> None:
"""
@ -344,11 +342,11 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
"""
pass
def _start_checker(self) -> str:
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
return State.FINISHED
return types.states.State.FINISHED
def _stop_machine(self) -> None:
"""
@ -356,11 +354,11 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
"""
pass
def _stop_checker(self) -> str:
def _stop_checker(self) -> types.states.State:
"""
Checks if machine has stoped
"""
return State.FINISHED
return types.states.State.FINISHED
# Not abstract methods, defaults to stop machine
def _soft_shutdown_machine(self) -> None:
@ -368,17 +366,17 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
"""
return self._stop_machine() # Default is to stop the machine
def _soft_shutdown_checker(self) -> str:
def _soft_shutdown_checker(self) -> types.states.State:
return self._stop_checker() # Default is to check if machine has stopped
def _removed_checker(self) -> str:
def _removed_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return State.FINISHED
return types.states.State.FINISHED
@typing.final
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
@ -386,12 +384,12 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, collections.abc.Callable[[], str]] = {
fncs: dict[Operation, collections.abc.Callable[[], types.states.State]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry_checker,
Operation.WAIT: self._wait_checker,
@ -406,13 +404,13 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
}
try:
check_function: typing.Optional[collections.abc.Callable[[], str]] = fncs.get(op, None)
check_function: typing.Optional[collections.abc.Callable[[], types.states.State]] = fncs.get(op, None)
if check_function is None:
return self._error('Unknown operation found at check queue ({0})'.format(op))
state = check_function()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op, till now only was "peek"
return self._execute_queue()
@ -441,7 +439,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
return self._reason
@typing.final
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
@ -449,7 +447,7 @@ class FixedUserService(services.UserService, autoserializable.AutoSerializable,
return self._execute_queue()
@typing.final
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.

View File

@ -30,23 +30,21 @@
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import typing
import collections.abc
from uds.core import types
import typing
from uds.core import types
from uds.core.environment import Environmentable
from uds.core.serializable import Serializable
from uds.core.types.states import State
from uds.core.util import log
if typing.TYPE_CHECKING:
from uds import models
from uds.core import services
from uds.core import osmanagers
from uds.core import osmanagers, services
from uds.core.environment import Environment
from uds.core.util.unique_name_generator import UniqueNameGenerator
from uds.core.util.unique_mac_generator import UniqueMacGenerator
from uds.core.util.unique_gid_generator import UniqueGIDGenerator
from uds.core.util.unique_mac_generator import UniqueMacGenerator
from uds.core.util.unique_name_generator import UniqueNameGenerator
class UserService(Environmentable, Serializable):
@ -275,7 +273,7 @@ class UserService(Environmentable, Serializable):
"""
raise NotImplementedError('Base getUniqueId for User Deployment called!!!')
def process_ready_from_os_manager(self, data: typing.Any) -> str: # pylint: disable=unused-argument
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -301,7 +299,7 @@ class UserService(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
return State.FINISHED
return types.states.State.FINISHED
def get_ip(self) -> str:
"""
@ -320,7 +318,7 @@ class UserService(Environmentable, Serializable):
If you assign the service IP by your own methods, do not override this
"""
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -352,9 +350,9 @@ class UserService(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys a user deployment as cache.
@ -393,7 +391,7 @@ class UserService(Environmentable, Serializable):
"""
raise Exception(f'Base deploy for cache invoked! for class {self.__class__.__name__}')
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
@ -428,7 +426,7 @@ class UserService(Environmentable, Serializable):
"""
raise NotImplementedError(f'Base deploy for user invoked! for class {self.__class__.__name__}')
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
This is a task method. As that, the expected return values are
State values RUNNING, FINISHED or ERROR.
@ -466,8 +464,9 @@ class UserService(Environmentable, Serializable):
not needed, but can be provided (default implementation of base class does
nothing)
"""
pass
def move_to_cache(self, newLevel: int) -> str: # pylint: disable=unused-argument
def move_to_cache(self, level: int) -> types.states.State: # pylint: disable=unused-argument
"""
This method is invoked whenever the core needs to move from the current
cache level to a new cache level an user deployment.
@ -494,7 +493,7 @@ class UserService(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
return State.FINISHED
return types.states.State.FINISHED
def user_logged_in(self, username: str) -> None:
"""
@ -510,6 +509,7 @@ class UserService(Environmentable, Serializable):
The user provided is just an string, that is provided by actors.
"""
pass
def user_logged_out(self, username: str) -> None:
"""
@ -525,6 +525,7 @@ class UserService(Environmentable, Serializable):
The user provided is just an string, that is provided by actor.
"""
pass
def error_reason(self) -> str:
"""
@ -541,7 +542,7 @@ class UserService(Environmentable, Serializable):
"""
return 'unknown'
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -559,7 +560,7 @@ class UserService(Environmentable, Serializable):
"""
raise NotImplementedError(f'destroy method for class {self.__class__.__name__} not provided!')
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -576,7 +577,7 @@ class UserService(Environmentable, Serializable):
to the core. Take that into account and handle exceptions inside
this method.
"""
return State.RUNNING
return types.states.State.RUNNING
@classmethod
def supports_cancel(cls: type['UserService']) -> bool:

View File

@ -44,9 +44,9 @@ class ExtendedHttpRequest(HttpRequest):
ip_version: int
ip_proxy: str
os: 'types.os.DetectedOsInfo'
user: typing.Optional['User']
user: typing.Optional['User'] # type: ignore # Override base user to be optional
authorized: bool
class ExtendedHttpRequestWithUser(ExtendedHttpRequest):
user: 'User'
user: 'User' # type: ignore # Has the user always

View File

@ -40,7 +40,7 @@ from uds.core.util import ensure
logger = logging.getLogger(__name__)
def validate_regex_field(field: ui.gui.TextField, field_value: typing.Optional[str] = None):
def validate_regex_field(field: ui.gui.TextField, field_value: typing.Optional[str] = None) -> None:
"""
Validates the multi line fields refering to attributes
"""
@ -70,7 +70,7 @@ def get_attributes_regex_field(field: 'ui.gui.TextField|str') -> set[str]:
res: set[str] = set()
for line in content.splitlines():
attr, pattern = (line.split('=')[0:2] + [''])[0:2]
attr, _pattern = (line.split('=')[0:2] + [''])[0:2]
# If attributes concateated with +, add all
if '+' in attr:

View File

@ -30,7 +30,6 @@ Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import dataclasses
import functools
import hashlib
import inspect
import logging
import threading

View File

@ -58,16 +58,16 @@ def uds_link(request: 'HttpRequest', ticket: str, scrambler: str) -> str:
def uds_access_link(
request: 'HttpRequest', # pylint: disable=unused-argument
serviceId: str,
transportId: typing.Optional[str],
service_id: str,
transport_id: typing.Optional[str],
) -> str:
'''
If transportId (uuid) is None, this will be a metaLink
'''
return f'{consts.system.UDS_ACTION_SCHEME}{serviceId}/{transportId or "meta"}'
return f'{consts.system.UDS_ACTION_SCHEME}{service_id}/{transport_id or "meta"}'
def parse_date(dateToParse) -> datetime.date:
def parse_date(string_date: str) -> datetime.date:
if get_language() == 'fr':
date_format = '%d/%m/%Y'
else:
@ -75,28 +75,15 @@ def parse_date(dateToParse) -> datetime.date:
formats.get_format('SHORT_DATE_FORMAT').replace('Y', '%Y').replace('m', '%m').replace('d', '%d')
) # pylint: disable=maybe-no-member
return datetime.datetime.strptime(dateToParse, date_format).date()
return datetime.datetime.strptime(string_date, date_format).date()
def date_to_literal(date) -> str:
def date_to_literal(date: datetime.datetime) -> str:
# Fix for FR lang for datepicker
if get_language() == 'fr':
date = date.strftime('%d/%m/%Y')
d = date.strftime('%d/%m/%Y')
else:
date = formats.date_format(date, 'SHORT_DATE_FORMAT')
d = formats.date_format(date, 'SHORT_DATE_FORMAT')
return date
return d
def extract_key(
dictionary: dict, key: typing.Any, fmt: typing.Optional[str] = None, default: typing.Any = None
):
fmt = fmt or '{0}'
default = default or ''
if key in dictionary:
value = fmt.format(dictionary[key])
del dictionary[key]
else:
value = default
return value

View File

@ -36,6 +36,8 @@ import logging
import typing
import collections.abc
from numpy import mat
from uds.core import types, consts
logger = logging.getLogger(__name__)
@ -47,7 +49,7 @@ def detect_os(
"""
Basic OS Client detector (very basic indeed :-))
"""
ua = (headers.get('User-Agent') or types.os.KnownOS.UNKNOWN.value[0])
ua = headers.get('User-Agent') or types.os.KnownOS.UNKNOWN.value[0]
res = types.os.DetectedOsInfo(
os=types.os.KnownOS.UNKNOWN, browser=types.os.KnownBrowser.OTHER, version='0.0'
@ -87,16 +89,16 @@ def detect_os(
# Try to detect browser from User-Agent
match = None
ruleKey, ruleValue = None, None
for ruleKey, ruleValue in consts.os.BROWSER_RULES.items():
must, mustNot = ruleValue
browser_type = None
for browser_type, rules in consts.os.BROWSER_RULES.items():
must, must_not = rules
for mustRe in consts.os.BROWSERS_RE[must]:
match = mustRe.search(ua)
for must_re in consts.os.BROWSERS_RE[must]:
match = must_re.search(ua)
if match is None:
continue
# Check against no maching rules
for mustNotREs in mustNot:
for mustNotREs in must_not:
for cre in consts.os.BROWSERS_RE[mustNotREs]:
if cre.search(ua) is not None:
match = None
@ -109,8 +111,8 @@ def detect_os(
break
if match is not None:
res.browser = ruleKey or types.os.KnownBrowser.OTHER
res.version = match.groups(1)[0]
res.browser = browser_type or types.os.KnownBrowser.OTHER
res.version = '0.0'
logger.debug('Detected: %s %s', res.os, res.browser)

View File

@ -91,7 +91,7 @@ def add_user_permission(
user: 'models.User',
obj: 'Model',
permission: PermissionType = PermissionType.READ,
):
) -> None:
# Some permissions added to some object types needs at least READ_PERMISSION on parent
models.Permissions.add_permission(
user=user,
@ -105,7 +105,7 @@ def add_group_permission(
group: 'models.Group',
obj: 'Model',
permission: PermissionType = PermissionType.READ,
):
) -> None:
models.Permissions.add_permission(
group=group,
object_type=objtype.ObjectType.from_model(obj),
@ -119,7 +119,7 @@ def has_access(
obj: 'Model',
permission: PermissionType = PermissionType.ALL,
for_type: bool = False,
):
) -> bool:
return effective_permissions(user, obj, for_type).contains(permission)

View File

@ -54,27 +54,27 @@ CounterClass = typing.TypeVar('CounterClass', Provider, Service, ServicePool, Au
# Helpers
def _get_id(obj):
def _get_id(obj: 'CounterClass') -> typing.Optional[int]:
return obj.id if obj.id != -1 else None
def _get_prov_serv_ids(provider) -> tuple:
def _get_prov_serv_ids(provider: 'Provider') -> tuple[int, ...]:
return tuple(i.id for i in provider.services.all())
def _get_serv_pool_ids(service) -> tuple:
def _get_serv_pool_ids(service: 'Service') -> tuple[int, ...]:
return tuple(i.id for i in service.deployedServices.all())
def _get_prov_serv_pool_ids(provider) -> tuple:
res: tuple = ()
def _get_prov_serv_pool_ids(provider: 'Provider') -> tuple[int, ...]:
res: tuple[int, ...] = ()
for i in provider.services.all():
res += _get_serv_pool_ids(i)
return res
_id_retriever: typing.Final[
collections.abc.Mapping[type[Model], collections.abc.Mapping[int, collections.abc.Callable]]
collections.abc.Mapping[type[Model], collections.abc.Mapping[int, collections.abc.Callable[[typing.Any], typing.Any]]]
] = {
Provider: {
types.stats.CounterType.LOAD: _get_id,
@ -149,7 +149,7 @@ def add_counter(
def enumerate_counters(
obj: CounterClass, counterType: types.stats.CounterType, **kwargs
obj: CounterClass, counterType: types.stats.CounterType, **kwargs: typing.Any
) -> typing.Generator[tuple[datetime.datetime, int], None, None]:
"""
Get counters
@ -184,7 +184,7 @@ def enumerate_counters(
return
if not kwargs.get('all', False):
owner_ids = fnc(obj)
owner_ids = fnc(obj) # pyright: ignore
else:
owner_ids = None

View File

@ -126,6 +126,7 @@ def get_owner(
return OSManager.objects.get(pk=ownerId)
return None
@dataclasses.dataclass
class EventTupleType:
stamp: datetime.datetime
@ -136,7 +137,7 @@ class EventTupleType:
event_type: types.stats.EventType
# aliases for fields
def __getitem__(self, item) -> typing.Any:
def __getitem__(self, item: str) -> typing.Any:
if item in _REVERSE_FLDS_EQUIV:
item = _REVERSE_FLDS_EQUIV[item]
return self.__getattribute__(item)
@ -150,7 +151,7 @@ class EventTupleType:
EventClass = typing.Union[Provider, Service, ServicePool, Authenticator]
def add_event(obj: EventClass, eventType: types.stats.EventType, **kwargs) -> bool:
def add_event(obj: EventClass, eventType: types.stats.EventType, **kwargs: typing.Any) -> bool:
"""
Adds a event stat to specified object
@ -164,7 +165,9 @@ def add_event(obj: EventClass, eventType: types.stats.EventType, **kwargs) -> bo
return StatsManager.manager().add_event(_OWNER_FROM_MODEL[type(obj)], obj.id, eventType, **kwargs)
def get_events(obj: EventClass, eventType: types.stats.EventType, **kwargs) -> typing.Generator[EventTupleType, None, None]:
def get_events(
obj: EventClass, eventType: types.stats.EventType, **kwargs: typing.Any
) -> typing.Generator[EventTupleType, None, None]:
"""
Get events

View File

@ -72,7 +72,7 @@ def _decode_value(dbk: str, value: typing.Optional[str]) -> tuple[str, typing.An
if isinstance(v, tuple) and v[0] == MARK:
return typing.cast(tuple[str, typing.Any], v[1:])
# Fix value so it contains also the "key" (in this case, the original key is lost, we have only the hash value...)
return ('#' + dbk, v)
return ('#' + dbk, typing.cast(typing.Any, v))
except Exception:
try:
return ('#' + dbk, base64.b64decode(value.encode()).decode())
@ -110,7 +110,7 @@ class StorageAsDict(MutableMapping[str, typing.Any]):
self._atomic = atomic # Not used right now, maybe removed
@property
def _db(self) -> typing.Union[models.QuerySet, models.Manager]:
def _db(self) -> typing.Union[models.QuerySet[DBStorage], models.Manager[DBStorage]]:
if self._atomic:
return DBStorage.objects.select_for_update()
return DBStorage.objects
@ -131,7 +131,7 @@ class StorageAsDict(MutableMapping[str, typing.Any]):
return _old_calculate_key(self._owner.encode(), key.encode())
def __getitem__(self, key: str) -> typing.Any:
if not isinstance(key, str):
if not isinstance(key, str): # pyright: ignore reportUnnecessaryIsInstance
raise TypeError(f'Key must be str, {type(key)} found')
# First, try new key, and, if needed, old key
@ -139,11 +139,11 @@ class StorageAsDict(MutableMapping[str, typing.Any]):
for use_old_method in (False, True):
db_key = self._key(key, old_method=use_old_method)
try:
c: DBStorage = typing.cast(DBStorage, self._db.get(pk=db_key))
c: DBStorage = self._db.get(pk=db_key)
if c.owner != self._owner: # Maybe a key collision,
logger.error('Key collision detected for key %s', key)
return None
okey, value = _decode_value(db_key, c.data)
_, value = _decode_value(db_key, c.data)
if use_old_method:
# Update key on db
c.delete()
@ -154,7 +154,7 @@ class StorageAsDict(MutableMapping[str, typing.Any]):
return None
def __setitem__(self, key: str, value: typing.Any) -> None:
if not isinstance(key, str):
if not isinstance(key, str): # pyright: ignore reportUnnecessaryIsInstance
raise TypeError(f'Key must be str type, {type(key)} found')
dbk = self._key(key)
@ -246,7 +246,7 @@ class Storage:
if isinstance(owner, bytes):
self._owner = owner.decode('utf-8')
self._bowner = owner
elif isinstance(owner, str):
elif isinstance(owner, str): # pyright: ignore reportUnnecessaryIsInstance Wants to ensure that it is a string no runtime error
self._owner = owner
self._bowner = owner.encode('utf8')
else:
@ -356,8 +356,8 @@ class Storage:
keys: 'collections.abc.Iterable[str|bytes]'
if isinstance(skey, (str, bytes)):
keys = [skey]
elif isinstance(skey, collections.abc.Iterable):
keys = typing.cast('collections.abc.Iterable[str|bytes]', skey)
else:
keys = skey # typing.cast('collections.abc.Iterable[str|bytes]', skey)
try:
# Process several keys at once

View File

@ -118,14 +118,14 @@ class ServiceCacheUpdater(Job):
friendly_name = 'Service Cache Updater'
@staticmethod
def _notify_restrain(servicePool) -> None:
def _notify_restrain(servicepool: 'ServicePool') -> None:
log.log(
servicePool,
servicepool,
log.LogLevel.WARNING,
'Service Pool is restrained due to excesive errors',
log.LogSource.INTERNAL,
)
logger.info('%s is restrained, will check this later', servicePool.name)
logger.info('%s is restrained, will check this later', servicepool.name)
def service_pools_needing_cache_update(
self,
@ -148,7 +148,7 @@ class ServiceCacheUpdater(Job):
for servicepool in candidate_servicepools:
servicepool.user_services.update() # Cleans cached queries
# If this deployedService don't have a publication active and needs it, ignore it
service_instance = servicepool.service.get_instance() # type: ignore
service_instance = servicepool.service.get_instance()
if service_instance.uses_cache is False:
logger.debug(
@ -200,7 +200,7 @@ class ServiceCacheUpdater(Job):
)
assigned_count: int = (
servicepool.assigned_user_services()
.filter(UserServiceManager().get_state_filter(servicepool.service)) # type: ignore
.filter(UserServiceManager().get_state_filter(servicepool.service))
.count()
)
pool_stat = ServicePoolStats(servicepool, l1_cache_count, l2_cache_count, assigned_count)
@ -212,7 +212,6 @@ class ServiceCacheUpdater(Job):
l2_cache_count,
assigned_count,
)
l1_assigned_count = l1_cache_count + assigned_count
# We have more than we want
if pool_stat.l1_cache_overflow():
@ -390,7 +389,7 @@ class ServiceCacheUpdater(Job):
def reduce_l2_cache(
self,
servicepool_stats: ServicePoolStats,
):
) -> None:
logger.debug("Reducing L2 cache erasing a service in cache for %s", servicepool_stats.servicepool.name)
if servicepool_stats.l2_cache_count > 0:
cacheItems = (

View File

@ -142,7 +142,7 @@ class StatsCleaner(Job):
frecuency = 3600 * 24 * 15 # Ejecuted just once every 15 days
friendly_name = 'Statistic housekeeping'
def run(self):
def run(self) -> None:
logger.debug('Starting statistics cleanup')
try:
StatsManager.manager().perform_counters_maintenance()
@ -169,7 +169,7 @@ class StatsAccumulator(Job):
)
friendly_name = 'Statistics acummulator'
def run(self):
def run(self) -> None:
try:
StatsManager.manager().acummulate(config.GlobalConfig.STATS_ACCUM_MAX_CHUNK_TIME.as_int())
except Exception:

View File

@ -86,7 +86,7 @@ class ServerGroup(UUIDModel, TaggingMixin, properties.PropertiesMixin):
def get_owner_id_and_type(self) -> tuple[str, str]:
return self.uuid, 'servergroup'
class Meta:
class Meta: # pyright: ignore
# Unique for host and port, so we can have only one group for each host:port
app_label = 'uds'
@ -194,12 +194,12 @@ class Server(UUIDModel, TaggingMixin, properties.PropertiesMixin):
# Group (of registered servers) this server belongs to
# Note that only Tunnel servers can belong to more than one servergroup
groups = models.ManyToManyField(
groups: 'models.ManyToManyField[ServerGroup, Server]' = models.ManyToManyField(
ServerGroup,
related_name='servers',
)
class Meta: # pylint: disable=too-few-public-methods
class Meta: # pyright: ignore
app_label = 'uds'
# For properties

View File

@ -56,9 +56,16 @@ from .uuid_model import UUIDModel
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
from uds.models import (CalendarAccess, CalendarAction, Group,
MetaPoolMember, ServicePoolPublication,
ServicePoolPublicationChangelog, User, UserService)
from uds.models import (
CalendarAccess,
CalendarAction,
Group,
MetaPoolMember,
ServicePoolPublication,
ServicePoolPublicationChangelog,
User,
UserService,
)
logger = logging.getLogger(__name__)
@ -84,8 +91,12 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
related_name='deployedServices',
on_delete=models.CASCADE,
)
transports = models.ManyToManyField(Transport, related_name='deployedServices', db_table='uds__ds_trans')
assignedGroups = models.ManyToManyField(Group, related_name='deployedServices', db_table='uds__ds_grps')
transports: 'models.ManyToManyField[Transport, ServicePool]' = models.ManyToManyField(
Transport, related_name='deployedServices', db_table='uds__ds_trans'
)
assignedGroups: 'models.ManyToManyField[Group, ServicePool]' = models.ManyToManyField(
Group, related_name='deployedServices', db_table='uds__ds_grps'
)
state = models.CharField(max_length=1, default=types.states.State.ACTIVE, db_index=True)
state_date = models.DateTimeField(default=consts.NEVER)
show_transports = models.BooleanField(default=True)
@ -132,7 +143,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
current_pub_revision = models.PositiveIntegerField(default=1)
# "fake" declarations for type checking
objects: 'models.manager.Manager["ServicePool"]'
# objects: 'models.manager.Manager["ServicePool"]'
publications: 'models.manager.RelatedManager[ServicePoolPublication]'
memberOfMeta: 'models.manager.RelatedManager[MetaPoolMember]'
userServices: 'models.manager.RelatedManager[UserService]'
@ -153,7 +164,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
def calendar_access(self) -> 'models.manager.RelatedManager[CalendarAccess]':
return self.calendarAccess
class Meta(UUIDModel.Meta): # pylint: disable=too-few-public-methods
class Meta(UUIDModel.Meta): # pyright: ignore
"""
Meta class to declare the name of the table at database
"""
@ -199,13 +210,10 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
@staticmethod
def restraineds_queryset() -> 'models.QuerySet[ServicePool]':
from django.db.models import \
Count # pylint: disable=import-outside-toplevel
from django.db.models import Count # pylint: disable=import-outside-toplevel
from uds.core.util.config import \
GlobalConfig # pylint: disable=import-outside-toplevel
from uds.models.user_service import \
UserService # pylint: disable=import-outside-toplevel
from uds.core.util.config import GlobalConfig # pylint: disable=import-outside-toplevel
from uds.models.user_service import UserService # pylint: disable=import-outside-toplevel
if GlobalConfig.RESTRAINT_TIME.as_int() <= 0:
return (
@ -215,12 +223,13 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
date = sql_datetime() - timedelta(seconds=GlobalConfig.RESTRAINT_TIME.as_int())
min_ = GlobalConfig.RESTRAINT_COUNT.as_int()
res = []
for v in (
res: list[dict[str, typing.Any]] = []
for v in typing.cast(
list[dict[str, typing.Any]],
UserService.objects.filter(state=types.states.State.ERROR, state_date__gt=date)
.values('deployed_service')
.annotate(how_many=Count('deployed_service'))
.order_by('deployed_service')
.order_by('deployed_service'),
):
if v['how_many'] >= min_:
res.append(v['deployed_service'])
@ -236,7 +245,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
@property
def visual_name(self) -> str:
logger.debug("SHORT: %s %s %s", self.short_name, self.short_name is not None, self.name)
logger.debug("SHORT: %s %s %s", self.short_name, self.short_name != '', self.name)
if self.short_name and str(self.short_name).strip():
return str(self.short_name.strip())
return str(self.name)
@ -255,13 +264,12 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
The time that a service is in restrain mode is 20 minutes by default (1200 secs), but it can be modified
at globalconfig variables
"""
from uds.core.util.config import \
GlobalConfig # pylint: disable=import-outside-toplevel
from uds.core.util.config import GlobalConfig # pylint: disable=import-outside-toplevel
if GlobalConfig.RESTRAINT_TIME.as_int() <= 0:
return False # Do not perform any restraint check if we set the globalconfig to 0 (or less)
date = typing.cast(datetime, sql_datetime()) - timedelta(seconds=GlobalConfig.RESTRAINT_TIME.as_int())
date = sql_datetime() - timedelta(seconds=GlobalConfig.RESTRAINT_TIME.as_int())
if (
self.userServices.filter(state=types.states.State.ERROR, state_date__gt=date).count()
>= GlobalConfig.RESTRAINT_COUNT.as_int()
@ -312,23 +320,23 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return None
def is_access_allowed(self, chkDateTime=None) -> bool:
def is_access_allowed(self, check_datetime: typing.Optional[datetime] = None) -> bool:
"""
Checks if the access for a service pool is allowed or not (based esclusively on associated calendars)
"""
if chkDateTime is None:
chkDateTime = sql_datetime()
if check_datetime is None:
check_datetime = sql_datetime()
access = self.fallbackAccess
# Let's see if we can access by current datetime
for ac in sorted(self.calendarAccess.all(), key=operator.attrgetter('priority')):
if calendar.CalendarChecker(ac.calendar).check(chkDateTime):
if calendar.CalendarChecker(ac.calendar).check(check_datetime):
access = ac.access
break # Stops on first rule match found
return access == types.states.State.ALLOW
def get_deadline(self, chkDateTime: typing.Optional[datetime] = None) -> typing.Optional[int]:
def get_deadline(self, check_datetime: typing.Optional[datetime] = None) -> typing.Optional[int]:
"""Gets the deadline for an access on chkDateTime in seconds
Keyword Arguments:
@ -337,30 +345,30 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
Returns:
typing.Optional[int] -- [Returns deadline in secods. If no deadline (forever), will return None]
"""
if chkDateTime is None:
chkDateTime = typing.cast(datetime, sql_datetime())
if check_datetime is None:
check_datetime = sql_datetime()
if self.is_access_allowed(chkDateTime) is False:
if self.is_access_allowed(check_datetime) is False:
return -1
deadLine = None
deadline = None
for ac in self.calendarAccess.all():
if ac.access == types.states.State.ALLOW and self.fallbackAccess == types.states.State.DENY:
nextE = calendar.CalendarChecker(ac.calendar).next_event(chkDateTime, False)
if not deadLine or (nextE and deadLine > nextE):
deadLine = nextE
nextE = calendar.CalendarChecker(ac.calendar).next_event(check_datetime, False)
if not deadline or (nextE and deadline > nextE):
deadline = nextE
elif ac.access == types.states.State.DENY: # DENY
nextE = calendar.CalendarChecker(ac.calendar).next_event(chkDateTime, True)
if not deadLine or (nextE and deadLine > nextE):
deadLine = nextE
nextE = calendar.CalendarChecker(ac.calendar).next_event(check_datetime, True)
if not deadline or (nextE and deadline > nextE):
deadline = nextE
if deadLine is None:
if deadline is None:
if self.fallbackAccess == types.states.State.ALLOW:
return None
return -1
return int((deadLine - chkDateTime).total_seconds())
return int((deadline - check_datetime).total_seconds())
def set_value(self, name: str, value: typing.Any):
"""
@ -480,7 +488,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
):
raise InvalidServiceException()
def validate_transport(self, transport) -> None:
def validate_transport(self, transport: 'Transport') -> None:
if (
self.transports.filter(id=transport.id).count() # pylint: disable=no-member
== 0 # pylint: disable=no-member
@ -520,8 +528,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
Returns:
List of accesible deployed services
"""
from uds.core import \
services # pylint: disable=import-outside-toplevel
from uds.core import services # pylint: disable=import-outside-toplevel
servicesNotNeedingPub = [t.get_type() for t in services.factory().services_not_needing_publication()]
# Get services that HAS publications
@ -590,8 +597,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
No check is done, it simply redirects the request to PublicationManager, where checks are done.
"""
from uds.core.managers import \
publication_manager # pylint: disable=import-outside-toplevel
from uds.core.managers import publication_manager # pylint: disable=import-outside-toplevel
publication_manager().publish(self, changeLog)
@ -634,25 +640,25 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
"""
return self.userServices.filter(cache_level=0, user=None)
def usage(self, cachedValue=-1) -> types.pools.UsageInfo:
def usage(self, cached_value: int = -1) -> types.pools.UsageInfo:
"""
Returns the % used services, then count and the max related to "maximum" user services
If no "maximum" number of services, will return 0% ofc
cachedValue is used to optimize (if known the number of assigned services, we can avoid to query the db)
"""
maxs = self.max_srvs
if maxs == 0 and self.service:
if maxs == 0:
maxs = self.service.get_instance().userservices_limit
if cachedValue == -1:
cachedValue = (
if cached_value == -1:
cached_value = (
self.assigned_user_services().filter(state__in=types.states.State.VALID_STATES).count()
)
if maxs == 0 or max == consts.UNLIMITED:
return types.pools.UsageInfo(cachedValue, consts.UNLIMITED)
if maxs == 0 or maxs == consts.UNLIMITED:
return types.pools.UsageInfo(cached_value, consts.UNLIMITED)
return types.pools.UsageInfo(cachedValue, maxs)
return types.pools.UsageInfo(cached_value, maxs)
def test_connectivity(self, host: str, port: typing.Union[str, int], timeout: float = 4) -> bool:
return bool(self.service) and self.service.test_connectivity(host, port, timeout)
@ -662,7 +668,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
log.log(self, level, message, log.LogSource.INTERNAL)
@staticmethod
def pre_delete(sender, **kwargs) -> None: # pylint: disable=unused-argument
def pre_delete(sender: typing.Any, **kwargs: typing.Any) -> None: # pylint: disable=unused-argument
"""
Used to invoke the Service class "Destroy" before deleting it from database.
@ -671,19 +677,18 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
:note: If destroy raises an exception, the deletion is not taken.
"""
from uds.core.util.permissions import \
clean # pylint: disable=import-outside-toplevel
from uds.core.util.permissions import clean # pylint: disable=import-outside-toplevel
toDelete: 'ServicePool' = kwargs['instance']
to_delete: 'ServicePool' = kwargs['instance']
logger.debug('Deleting Service Pool %s', toDelete)
toDelete.get_environment().clean_related_data()
logger.debug('Deleting Service Pool %s', to_delete)
to_delete.get_environment().clean_related_data()
# Clears related logs
log.clear_logs(toDelete)
log.clear_logs(to_delete)
# Clears related permissions
clean(toDelete)
clean(to_delete)
# returns CSV header
@staticmethod

View File

@ -38,7 +38,6 @@ import typing
from uds.core import consts, services, types
from uds.core.managers.userservice import UserServiceManager
from uds.core.types.states import State
from uds.core.util import autoserializable, log
from .jobs import OVirtDeferredRemoval
@ -193,13 +192,13 @@ class OVirtLinkedDeployment(services.UserService, autoserializable.AutoSerializa
"""
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
The method is invoked whenever a machine is provided to an user, right
before presenting it (via transport rendering) to the user.
"""
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
state = self.service().get_machine_state(self._vmid)
@ -216,7 +215,7 @@ class OVirtLinkedDeployment(services.UserService, autoserializable.AutoSerializa
self.do_log(log.LogLevel.ERROR, f'Error on setReady: {e}')
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
"""
@ -247,7 +246,7 @@ if sys.platform == 'win32':
if dbUserService:
UserServiceManager().send_script(dbUserService, script)
def process_ready_from_os_manager(self, data: typing.Any) -> str:
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for %s', self._name)
if self._get_current_op() == Operation.WAIT:
@ -255,9 +254,9 @@ if sys.platform == 'win32':
self._pop_current_op() # Remove current state
return self._execute_queue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -265,15 +264,15 @@ if sys.platform == 'win32':
self._init_queue_for_deploy(False)
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
self._init_queue_for_deploy(cacheLevel == self.L2_CACHE)
self._init_queue_for_deploy(level == self.L2_CACHE)
return self._execute_queue()
def _init_queue_for_deploy(self, forLevel2: bool = False) -> None:
if forLevel2 is False:
def _init_queue_for_deploy(self, for_level_2: bool = False) -> None:
if for_level_2 is False:
self._queue = [Operation.CREATE, Operation.CHANGEMAC, Operation.START, Operation.FINISH]
else:
self._queue = [
@ -285,7 +284,7 @@ if sys.platform == 'win32':
Operation.FINISH,
]
def _check_machine_state(self, check_state: collections.abc.Iterable[str]) -> str:
def _check_machine_state(self, check_state: collections.abc.Iterable[str]) -> types.states.State:
logger.debug(
'Checking that state of machine %s (%s) is %s',
self._vmid,
@ -298,15 +297,15 @@ if sys.platform == 'win32':
if state == 'unknown' and check_state != 'unknown':
return self._error('Machine not found')
ret = State.RUNNING
ret = types.states.State.RUNNING
if isinstance(check_state, (list, tuple)):
for cks in check_state:
if state == cks:
ret = State.FINISHED
ret = types.states.State.FINISHED
break
else:
if state == check_state:
ret = State.FINISHED
ret = types.states.State.FINISHED
return ret
@ -325,12 +324,12 @@ if sys.platform == 'win32':
def _push_front_op(self, op: Operation) -> None:
self._queue.insert(0, op)
def _error(self, reason: typing.Union[str, Exception]) -> str:
def _error(self, reason: typing.Union[str, Exception]) -> types.states.State:
"""
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
types.states.State.ERROR, so we can do "return self.__error(reason)"
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
@ -341,17 +340,17 @@ if sys.platform == 'win32':
self._queue = [Operation.ERROR]
self._reason = reason
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self._debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
Operation.CREATE: self._create,
@ -372,26 +371,26 @@ if sys.platform == 'win32':
operation_runner()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
return self._error(e)
# Queue execution methods
def _retry(self) -> str:
def _retry(self) -> types.states.State:
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
return State.FINISHED
return types.states.State.FINISHED
def _wait(self) -> str:
def _wait(self) -> types.states.State:
"""
Executes opWait, it simply waits something "external" to end
"""
return State.RUNNING
return types.states.State.RUNNING
def _create(self) -> str:
"""
@ -410,10 +409,10 @@ if sys.platform == 'win32':
comments = 'UDS Linked clone'
self._vmid = self.service().deploy_from_template(name, comments, template_id)
if self._vmid is None:
if not self._vmid:
raise Exception('Can\'t create machine')
return State.RUNNING
return types.states.State.RUNNING
def _remove(self) -> str:
"""
@ -430,7 +429,7 @@ if sys.platform == 'win32':
else:
self.service().remove_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _start_machine(self) -> str:
"""
@ -442,7 +441,7 @@ if sys.platform == 'win32':
raise Exception('Machine not found')
if state in UP_STATES: # Already started, return
return State.RUNNING
return types.states.State.RUNNING
if state not in ('down', 'suspended'):
self._push_front_op(
@ -450,7 +449,7 @@ if sys.platform == 'win32':
) # Will call "check Retry", that will finish inmediatly and again call this one
self.service().start_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _stop_machine(self) -> str:
"""
@ -462,7 +461,7 @@ if sys.platform == 'win32':
raise Exception('Machine not found')
if state == 'down': # Already stoped, return
return State.RUNNING
return types.states.State.RUNNING
if state not in ('up', 'suspended'):
self._push_front_op(
@ -471,7 +470,7 @@ if sys.platform == 'win32':
else:
self.service().stop_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _suspend_machine(self) -> str:
"""
@ -483,16 +482,16 @@ if sys.platform == 'win32':
raise Exception('Machine not found')
if state == 'suspended': # Already suspended, return
return State.RUNNING
return types.states.State.RUNNING
if state != 'up':
self._push_front_op(
Operation.RETRY
) # Remember here, the return State.FINISH will make this retry be "poped" right ar return
) # Remember here, the return types.states.State.FINISH will make this retry be "poped" right ar return
else:
self.service().suspend_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _change_mac(self) -> str:
"""
@ -502,48 +501,48 @@ if sys.platform == 'win32':
# Fix usb if needed
self.service().fix_usb(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
return self._check_machine_state('down')
def _start_checker(self) -> str:
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
return self._check_machine_state(UP_STATES)
def _stop_checker(self) -> str:
def _stop_checker(self) -> types.states.State:
"""
Checks if machine has stoped
"""
return self._check_machine_state('down')
def _suspend_checker(self) -> str:
def _suspend_checker(self) -> types.states.State:
"""
Check if the machine has suspended
"""
return self._check_machine_state('suspended')
def _remove_checker(self) -> str:
def _remove_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return self._check_machine_state('unknown')
def _mac_checker(self) -> str:
def _mac_checker(self) -> types.states.State:
"""
Checks if change mac operation has finished.
Changing nic configuration es 1-step operation, so when we check it here, it is already done
"""
return State.FINISHED
return types.states.State.FINISHED
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
@ -551,12 +550,12 @@ if sys.platform == 'win32':
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], types.states.State]]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry,
Operation.WAIT: self._wait,
@ -568,13 +567,13 @@ if sys.platform == 'win32':
}
try:
operation_checker: typing.Optional[typing.Optional[collections.abc.Callable[[], str]]] = fncs.get(op, None)
operation_checker: typing.Optional[typing.Optional[collections.abc.Callable[[], types.states.State]]] = fncs.get(op, None)
if operation_checker is None:
return self._error(f'Unknown operation found at check queue ({op})')
state = operation_checker()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op
return self._execute_queue()
@ -582,14 +581,14 @@ if sys.platform == 'win32':
except Exception as e:
return self._error(e)
def move_to_cache(self, newLevel: int) -> str:
def move_to_cache(self, level: int) -> types.states.State:
"""
Moves machines between cache levels
"""
if Operation.REMOVE in self._queue:
return State.RUNNING
return types.states.State.RUNNING
if newLevel == self.L1_CACHE:
if level == self.L1_CACHE:
self._queue = [Operation.START, Operation.FINISH]
else:
self._queue = [Operation.START, Operation.SUSPEND, Operation.FINISH]
@ -606,7 +605,7 @@ if sys.platform == 'win32':
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
@ -614,7 +613,7 @@ if sys.platform == 'win32':
if self._vmid == '':
self._queue = []
self._reason = "canceled"
return State.FINISHED
return types.states.State.FINISHED
# If executing something, wait until finished to remove it
# We simply replace the execution queue
@ -629,12 +628,12 @@ if sys.platform == 'win32':
self._queue = [op, Operation.STOP, Operation.REMOVE, Operation.FINISH]
# Do not execute anything.here, just continue normally
return State.RUNNING
return types.states.State.RUNNING
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
types.states.State.values RUNNING, FINISHED or ERROR.
This can be invoked directly by an administration or by the clean up
of the deployed service (indirectly).
@ -660,7 +659,7 @@ if sys.platform == 'win32':
def _debug(self, txt: str) -> None:
logger.debug(
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
'types.states.State.at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
txt,
self._name,
self._ip,

View File

@ -38,7 +38,7 @@ from datetime import datetime
from django.utils.translation import gettext as _
from uds.core.services import Publication
from uds.core.types.states import State
from uds.core import types
from uds.core.util import autoserializable
# Not imported at runtime, just for type checking
@ -87,7 +87,7 @@ class OVirtPublication(Publication, autoserializable.AutoSerializable):
self._destroy_after = destroy_after == 't'
self.mark_for_upgrade() # Mark so manager knows it has to be saved again
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
Realizes the publication of the service
"""
@ -106,19 +106,19 @@ class OVirtPublication(Publication, autoserializable.AutoSerializable):
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Checks state of publication creation
"""
if self._state == 'ok':
return State.FINISHED
return types.states.State.FINISHED
if self._state == 'error':
return State.ERROR
return types.states.State.ERROR
try:
self._state = self.service().get_template_state(self._template_id)
@ -127,28 +127,28 @@ class OVirtPublication(Publication, autoserializable.AutoSerializable):
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
# If publication os done (template is ready), and cancel was requested, do it just after template becomes ready
if self._state == 'ok':
if self._destroy_after:
self._destroy_after = False
return self.destroy()
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
def error_reason(self) -> str:
"""
If a publication produces an error, here we must notify the reason why
it happened. This will be called just after publish or check_state
if they return State.ERROR
if they return types.states.State.ERROR
Returns an string, in our case, set at check_state
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is called once a publication is no more needed.
@ -156,24 +156,24 @@ class OVirtPublication(Publication, autoserializable.AutoSerializable):
removing created "external" data (environment gets cleaned by core),
etc..
The retunred value is the same as when publishing, State.RUNNING,
State.FINISHED or State.ERROR.
The retunred value is the same as when publishing, types.states.State.RUNNING,
types.states.State.FINISHED or types.states.State.ERROR.
"""
# We do not do anything else to destroy this instance of publication
if self._state == 'locked':
self._destroy_after = True
return State.RUNNING
return types.states.State.RUNNING
try:
self.service().remove_template(self._template_id)
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
Do same thing as destroy
"""

View File

@ -36,9 +36,8 @@ import logging
import typing
import collections.abc
from uds.core import services
from uds.core import services, types
from uds.core.managers.crypto import CryptoManager
from uds.core.types.states import State
from uds.core.util import log, autoserializable
from uds.core.util.model import sql_stamp_seconds
@ -145,7 +144,7 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
def get_ip(self) -> str:
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
Notifies the current "deadline" to the user, before accessing by UDS
The machine has been already been started.
@ -154,17 +153,17 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
"""
dbs = self.db_obj()
if not dbs:
return State.FINISHED
return types.states.State.FINISHED
try:
# First, check Machine is alive..
status = self._check_machine_is_ready()
if status == State.FINISHED:
if status == types.states.State.FINISHED:
self.service().notify_deadline(self._machine_id, dbs.deployed_service.get_deadline())
return State.FINISHED
return types.states.State.FINISHED
if status == State.ERROR:
return State.ERROR
if status == types.states.State.ERROR:
return types.states.State.ERROR
# Machine powered off, check what to do...
if not self.service().try_start_if_unavailable():
@ -179,7 +178,7 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
except Exception as e:
return self._error(f'Error setting ready state: {e}')
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -187,7 +186,7 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
self._init_queue_for_deploy()
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
@ -197,7 +196,7 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
def _init_queue_for_deploy(self) -> None:
self._queue = [Operation.CREATE, Operation.FINISH]
def _check_machine_is_ready(self) -> str:
def _check_machine_is_ready(self) -> types.states.State:
logger.debug(
'Checking that state of machine %s (%s) is ready',
self._machine_id,
@ -212,9 +211,9 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
# possible status are ("off", "oglive", "busy", "linux", "windows", "macos" o "unknown").
if status['status'] in ("linux", "windows", "macos"):
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
def _get_current_op(self) -> Operation:
if len(self._queue) == 0:
@ -229,12 +228,12 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
res = self._queue.pop(0)
return res
def _error(self, reason: typing.Any) -> str:
def _error(self, reason: typing.Any) -> types.states.State:
"""
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
types.states.State.ERROR, so we can do "return self.__error(reason)"
"""
logger.debug('Setting error state, reason: %s', reason)
self.do_log(log.LogLevel.ERROR, reason)
@ -247,17 +246,17 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
self._queue = [Operation.ERROR]
self._reason = str(reason)
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self._debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[int, typing.Optional[collections.abc.Callable[[], str]]] = {
Operation.CREATE: self._create,
@ -274,21 +273,21 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
execFnc()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
# logger.exception('Got Exception')
return self._error(e)
# Queue execution methods
def _retry(self) -> str:
def _retry(self) -> types.states.State:
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
return State.FINISHED
return types.states.State.FINISHED
def _create(self) -> str:
"""
@ -329,12 +328,12 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
dbs.properties['token'] = token
dbs.log_ip(self._ip)
return State.RUNNING
return types.states.State.RUNNING
def _start(self) -> str:
if self._machine_id:
self.service().power_on(self._machine_id)
return State.RUNNING
return types.states.State.RUNNING
def _remove(self) -> str:
"""
@ -347,10 +346,10 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
# so we can avoid double unreserve
if dbs.properties.get('from_release') is None:
self.service().unreserve(self._machine_id)
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
@ -359,13 +358,13 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
# Alias for poweron check
_checkStart = _create_checker
def _removed_checker(self) -> str:
def _removed_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return State.FINISHED # No check at all, always true
return types.states.State.FINISHED # No check at all, always true
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
@ -373,12 +372,12 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], types.states.State]]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry,
Operation.REMOVE: self._removed_checker,
@ -386,13 +385,13 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
}
try:
chkFnc: typing.Optional[typing.Optional[collections.abc.Callable[[], str]]] = fncs.get(op)
chkFnc: typing.Optional[typing.Optional[collections.abc.Callable[[], types.states.State]]] = fncs.get(op)
if chkFnc is None:
return self._error(f'Unknown operation found at check queue ({op})')
state = chkFnc()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op
return self._execute_queue()
@ -410,7 +409,7 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
@ -420,10 +419,10 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
self._queue = [Operation.REMOVE, Operation.FINISH]
return self._execute_queue()
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
types.states.State.values RUNNING, FINISHED or ERROR.
This can be invoked directly by an administration or by the clean up
of the deployed service (indirectly).
@ -442,9 +441,9 @@ class OpenGnsysUserService(services.UserService, autoserializable.AutoSerializab
Operation.RETRY: 'retry',
}.get(op, '????')
def _debug(self, txt) -> None:
def _debug(self, txt: str) -> None:
logger.debug(
'State at %s: name: %s, ip: %s, mac: %s, machine:%s, queue: %s',
'types.states.State.at %s: name: %s, ip: %s, mac: %s, machine:%s, queue: %s',
txt,
self._name,
self._ip,

View File

@ -34,7 +34,7 @@ import typing
import collections.abc
from uds.core.services import Publication
from uds.core.types.states import State
from uds.core import types
from uds.core.util import autoserializable
from uds.core.util.model import sql_datetime
@ -57,23 +57,23 @@ class OpenGnsysPublication(Publication, autoserializable.AutoSerializable):
def service(self) -> 'OGService':
return typing.cast('OGService', super().service())
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
Realizes the publication of the service, on OpenGnsys, does nothing
"""
return State.FINISHED
return types.states.State.FINISHED
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Checks state of publication creation
"""
return State.FINISHED
return types.states.State.FINISHED
def error_reason(self) -> str:
return 'No error possible :)'
def destroy(self) -> str:
return State.FINISHED
def destroy(self) -> types.states.State:
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()

View File

@ -38,7 +38,6 @@ import typing
import collections.abc
from uds.core import services, consts, types
from uds.core.types.states import State
from uds.core.util import log, autoserializable
from . import on
@ -136,9 +135,9 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
def get_ip(self) -> str:
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
state = self.service().getMachineState(self._vmid)
@ -153,7 +152,7 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
self.do_log(log.LogLevel.ERROR, 'Error on setReady: {}'.format(e))
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
if self._vmid != '':
@ -165,7 +164,7 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
def desktop_login(self, username: str, password: str, domain: str = ''):
return self.service().desktop_login(self._vmid, username, password, domain)
def process_ready_from_os_manager(self, data: typing.Any) -> str:
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for %s', self._name)
if self._get_current_op() == Operation.WAIT:
@ -173,9 +172,9 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
self._get_and_pop_current_op() # Remove current state
return self._execute_queue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -183,15 +182,15 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
self._init_queue_for_deploy(False)
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
self._init_queue_for_deploy(cacheLevel == self.L2_CACHE)
self._init_queue_for_deploy(level == self.L2_CACHE)
return self._execute_queue()
def _init_queue_for_deploy(self, forLevel2: bool = False) -> None:
if forLevel2 is False:
def _init_queue_for_deploy(self, for_level_2: bool = False) -> None:
if for_level_2 is False:
self._queue = [Operation.CREATE, Operation.START, Operation.FINISH]
else:
self._queue = [
@ -202,7 +201,7 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
Operation.FINISH,
]
def _check_machine_state(self, state: on.types.VmState) -> str:
def _check_machine_state(self, state: on.types.VmState) -> types.states.State:
logger.debug(
'Checking that state of machine %s (%s) is %s',
self._vmid,
@ -218,14 +217,14 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
]: # @UndefinedVariable
return self._error('Machine not found')
ret = State.RUNNING
ret = types.states.State.RUNNING
if isinstance(state, (list, tuple)):
if state in state:
ret = State.FINISHED
ret = types.states.State.FINISHED
else:
if state == state:
ret = State.FINISHED
ret = types.states.State.FINISHED
return ret
@ -244,12 +243,12 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
def _push_front_op(self, op: Operation) -> None:
self._queue.insert(0, op)
def _error(self, reason: typing.Any) -> str:
def _error(self, reason: typing.Any) -> types.states.State:
"""
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
types.states.State.ERROR, so we can do "return self.__error(reason)"
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
@ -263,17 +262,17 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
self._queue = [Operation.ERROR]
self._reason = str(reason)
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self.__debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
Operation.CREATE: self._create,
@ -292,27 +291,27 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
operation_executor()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
logger.exception('Got Exception')
return self._error(e)
# Queue execution methods
def _retry(self) -> str:
def _retry(self) -> types.states.State:
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
return State.FINISHED
return types.states.State.FINISHED
def _wait(self) -> str:
def _wait(self) -> types.states.State:
"""
Executes opWait, it simply waits something "external" to end
"""
return State.RUNNING
return types.states.State.RUNNING
def _create(self) -> str:
"""
@ -330,13 +329,13 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
) # OpenNebula don't let us to create machines with more than 15 chars!!!
self._vmid = self.service().deploy_from_template(name, templateId)
if self._vmid is None:
if not self._vmid:
raise Exception('Can\'t create machine')
# Get IP & MAC (early stage)
# self._mac, self._ip = self.service().getNetInfo(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _remove(self) -> str:
"""
@ -352,11 +351,11 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
if subState < 3: # Less than running
logger.info('Must wait before remove: %s', subState)
self._push_front_op(Operation.RETRY)
return State.RUNNING
return types.states.State.RUNNING
self.service().removeMachine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _start_machine(self) -> str:
"""
@ -367,41 +366,41 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
# Get IP & MAC (later stage, after "powering on")
self._mac, self._ip = self.service().getNetInfo(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _shutdown_machine(self) -> str:
"""
Suspends the machine
"""
self.service().shutdownMachine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
return self._check_machine_state(on.types.VmState.ACTIVE) # @UndefinedVariable
def _start_checker(self) -> str:
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
return self._check_machine_state(on.types.VmState.ACTIVE) # @UndefinedVariable
def _shutdown_checker(self) -> str:
def _shutdown_checker(self) -> types.states.State:
"""
Check if the machine has suspended
"""
return self._check_machine_state(on.types.VmState.POWEROFF) # @UndefinedVariable
def _remove_checker(self) -> str:
def _remove_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return State.FINISHED # No check at all, always true
return types.states.State.FINISHED # No check at all, always true
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts based on it
"""
@ -409,12 +408,12 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], types.states.State]]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry,
Operation.WAIT: self._wait,
@ -424,13 +423,13 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
}
try:
chkFnc: typing.Optional[collections.abc.Callable[[], str]] = fncs.get(op, None)
chkFnc: typing.Optional[collections.abc.Callable[[], types.states.State]] = fncs.get(op, None)
if chkFnc is None:
return self._error('Unknown operation found at check queue ({0})'.format(op))
state = chkFnc()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._get_and_pop_current_op() # Remove runing op
return self._execute_queue()
@ -438,14 +437,14 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
except Exception as e:
return self._error(e)
def move_to_cache(self, newLevel: int) -> str:
def move_to_cache(self, level: int) -> types.states.State:
"""
Moves machines between cache levels
"""
if Operation.REMOVE in self._queue:
return State.RUNNING
return types.states.State.RUNNING
if newLevel == self.L1_CACHE:
if level == self.L1_CACHE:
self._queue = [Operation.START, Operation.FINISH]
else:
self._queue = [Operation.START, Operation.SHUTDOWN, Operation.FINISH]
@ -462,7 +461,7 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
@ -480,12 +479,12 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
self._queue = [op, Operation.REMOVE, Operation.FINISH]
# Do not execute anything.here, just continue normally
return State.RUNNING
return types.states.State.RUNNING
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
types.states.State.values RUNNING, FINISHED or ERROR.
This can be invoked directly by an administration or by the clean up
of the deployed service (indirectly).
@ -509,7 +508,7 @@ class OpenNebulaLiveDeployment(services.UserService, autoserializable.AutoSerial
def __debug(self, txt: str) -> None:
logger.debug(
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
'types.states.State.at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
txt,
self._name,
self._ip,

View File

@ -35,7 +35,7 @@ import typing
import collections.abc
from uds.core.services import Publication
from uds.core.types.states import State
from uds.core import types
from uds.core.util import autoserializable
# Not imported at runtime, just for type checking
@ -81,7 +81,7 @@ class OpenNebulaLivePublication(Publication, autoserializable.AutoSerializable):
self.mark_for_upgrade() # Flag so manager can save it again with new format
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
Realizes the publication of the service
"""
@ -96,47 +96,47 @@ class OpenNebulaLivePublication(Publication, autoserializable.AutoSerializable):
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Checks state of publication creation
"""
if self._state == 'running':
try:
if self.service().check_template_published(self._template_id) is False:
return State.RUNNING
return types.states.State.RUNNING
self._state = 'ok'
except Exception as e:
self._state = 'error'
self._reason = str(e)
if self._state == 'error':
return State.ERROR
return types.states.State.ERROR
if self._state == 'ok':
if self._destroy_after: # If we must destroy after publication, do it now
self._destroy_after = False
return self.destroy()
return State.FINISHED
return types.states.State.FINISHED
self._state = 'ok'
return State.FINISHED
return types.states.State.FINISHED
def error_reason(self) -> str:
"""
If a publication produces an error, here we must notify the reason why
it happened. This will be called just after publish or check_state
if they return State.ERROR
if they return types.states.State.ERROR
Returns an string, in our case, set at check_state
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is called once a publication is no more needed.
@ -144,15 +144,15 @@ class OpenNebulaLivePublication(Publication, autoserializable.AutoSerializable):
removing created "external" data (environment gets cleaned by core),
etc..
The retunred value is the same as when publishing, State.RUNNING,
State.FINISHED or State.ERROR.
The retunred value is the same as when publishing, types.states.State.RUNNING,
types.states.State.FINISHED or types.states.State.ERROR.
"""
if self._state == 'error':
return State.ERROR
return types.states.State.ERROR
if self._state == 'running':
self._destroy_after = True
return State.RUNNING
return types.states.State.RUNNING
# We do not do anything else to destroy this instance of publication
try:
@ -160,11 +160,11 @@ class OpenNebulaLivePublication(Publication, autoserializable.AutoSerializable):
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
Do same thing as destroy
"""

View File

@ -161,12 +161,10 @@ class OpenNebulaLiveService(services.Service):
Loads required values inside
"""
t: 'on.types.TemplateType'
self.template.set_choices(
[gui.choice_item(t.id, t.name) for t in self.provider().getTemplates()]
)
d: 'on.types.StorageType'
self.datastore.set_choices(
[gui.choice_item(d.id, d.name) for d in self.provider().getDatastores()]
)

View File

@ -36,8 +36,7 @@ import logging
import pickle # nosec: not insecure, we are loading our own data
import typing
from uds.core import consts, services
from uds.core.types.states import State
from uds.core import consts, services, types
from uds.core.util import autoserializable, log
from . import openstack
@ -145,13 +144,13 @@ class OpenStackLiveDeployment(
def get_ip(self) -> str:
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
The method is invoked whenever a machine is provided to an user, right
before presenting it (via transport rendering) to the user.
"""
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
status = self.service().get_machine_state(self._vmid)
@ -171,13 +170,13 @@ class OpenStackLiveDeployment(
self.do_log(log.LogLevel.ERROR, 'Error on setReady: {}'.format(e))
# Treat as operation done, maybe the machine is ready and we can continue
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
if self._vmid != '':
self.service().reset_machine(self._vmid)
def process_ready_from_os_manager(self, data: typing.Any) -> str:
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for %s', self._name)
if self._get_current_op() == Operation.WAIT:
@ -185,9 +184,9 @@ class OpenStackLiveDeployment(
self._pop_current_op() # Remove current state
return self._execute_queue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -195,11 +194,11 @@ class OpenStackLiveDeployment(
self._init_queue_for_deploy(False)
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
self._init_queue_for_deploy(cacheLevel == self.L2_CACHE)
self._init_queue_for_deploy(level == self.L2_CACHE)
return self._execute_queue()
def _init_queue_for_deploy(self, forLevel2: bool = False) -> None:
@ -208,7 +207,7 @@ class OpenStackLiveDeployment(
else:
self._queue = [Operation.CREATE, Operation.WAIT, Operation.SUSPEND, Operation.FINISH]
def _check_machine_state(self, chkState: str) -> str:
def _check_machine_state(self, chkState: str) -> types.states.State:
logger.debug(
'Checking that state of machine %s (%s) is %s',
self._vmid,
@ -221,10 +220,10 @@ class OpenStackLiveDeployment(
if openstack.status_is_lost(status):
return self._error('Machine not available. ({})'.format(status))
ret = State.RUNNING
ret = types.states.State.RUNNING
chkStates = [chkState] if not isinstance(chkState, (list, tuple)) else chkState
if status in chkStates:
ret = State.FINISHED
ret = types.states.State.FINISHED
return ret
@ -240,12 +239,12 @@ class OpenStackLiveDeployment(
return self._queue.pop(0)
def _error(self, reason: typing.Any) -> str:
def _error(self, reason: typing.Any) -> types.states.State:
"""
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
types.states.State.ERROR, so we can do "return self.__error(reason)"
"""
logger.debug('Setting error state, reason: %s', reason)
self._queue = [Operation.ERROR]
@ -259,25 +258,25 @@ class OpenStackLiveDeployment(
except Exception:
logger.warning('Can\t set machine %s state to stopped', self._vmid)
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
self.__debug('executeQueue')
def _execute_queue(self) -> types.states.State:
self._debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[int, collections.abc.Callable[[], str]] = {
Operation.CREATE: self.__create,
Operation.RETRY: self.__retry,
Operation.START: self.__startMachine,
Operation.SUSPEND: self.__suspendMachine,
Operation.WAIT: self.__wait,
Operation.REMOVE: self.__remove,
Operation.CREATE: self._create,
Operation.RETRY: self._retry,
Operation.START: self._start_machine,
Operation.SUSPEND: self._suspend_machine,
Operation.WAIT: self._wait,
Operation.REMOVE: self._remove,
}
try:
@ -286,32 +285,32 @@ class OpenStackLiveDeployment(
fncs[op]()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
return self._error(e)
# Queue execution methods
def __retry(self) -> str:
def _retry(self) -> types.states.State:
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
return State.FINISHED
return types.states.State.FINISHED
def __wait(self) -> str:
def _wait(self) -> types.states.State:
"""
Executes opWait, it simply waits something "external" to end
"""
return State.RUNNING
return types.states.State.RUNNING
def __create(self) -> str:
def _create(self) -> str:
"""
Deploys a machine from template for user/cache
"""
templateId = self.publication().getTemplateId()
templateId = self.publication().get_template_id()
name = self.get_name()
if name == consts.NO_MORE_NAMES:
raise Exception(
@ -326,9 +325,9 @@ class OpenStackLiveDeployment(
if not self._vmid:
raise Exception('Can\'t create machine')
return State.RUNNING
return types.states.State.RUNNING
def __remove(self) -> str:
def _remove(self) -> str:
"""
Removes a machine from system
"""
@ -339,74 +338,74 @@ class OpenStackLiveDeployment(
self.service().remove_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def __startMachine(self) -> str:
def _start_machine(self) -> str:
"""
Powers on the machine
"""
self.service().start_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def __suspendMachine(self) -> str:
def _suspend_machine(self) -> str:
"""
Suspends the machine
"""
self.service().suspend_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def __checkCreate(self) -> str:
def _check_create(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
ret = self._check_machine_state(openstack.ACTIVE)
if ret == State.FINISHED:
if ret == types.states.State.FINISHED:
# Get IP & MAC (early stage)
self._mac, self._ip = self.service().get_network_info(self._vmid)
return ret
def __checkStart(self) -> str:
def _check_start(self) -> types.states.State:
"""
Checks if machine has started
"""
return self._check_machine_state(openstack.ACTIVE)
def __checkSuspend(self) -> str:
def _check_suspend(self) -> types.states.State:
"""
Check if the machine has suspended
"""
return self._check_machine_state(openstack.SUSPENDED)
def __checkRemoved(self) -> str:
def _check_removed(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return State.FINISHED # No check at all, always true
return types.states.State.FINISHED # No check at all, always true
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
self.__debug('check_state')
self._debug('check_state')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[int, collections.abc.Callable[[], str]] = {
Operation.CREATE: self.__checkCreate,
Operation.RETRY: self.__retry,
Operation.WAIT: self.__wait,
Operation.START: self.__checkStart,
Operation.SUSPEND: self.__checkSuspend,
Operation.REMOVE: self.__checkRemoved,
fncs: dict[int, collections.abc.Callable[[], types.states.State]] = {
Operation.CREATE: self._check_create,
Operation.RETRY: self._retry,
Operation.WAIT: self._wait,
Operation.START: self._check_start,
Operation.SUSPEND: self._check_suspend,
Operation.REMOVE: self._check_removed,
}
try:
@ -414,7 +413,7 @@ class OpenStackLiveDeployment(
return self._error('Unknown operation found at execution queue ({0})'.format(op))
state = fncs[op]()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op
return self._execute_queue()
@ -422,14 +421,14 @@ class OpenStackLiveDeployment(
except Exception as e:
return self._error(e)
def move_to_cache(self, newLevel: int) -> str:
def move_to_cache(self, level: int) -> types.states.State:
"""
Moves machines between cache levels
"""
if Operation.REMOVE in self._queue:
return State.RUNNING
return types.states.State.RUNNING
if newLevel == self.L1_CACHE:
if level == self.L1_CACHE:
self._queue = [Operation.START, Operation.FINISH]
else:
self._queue = [Operation.START, Operation.SUSPEND, Operation.FINISH]
@ -439,11 +438,11 @@ class OpenStackLiveDeployment(
def error_reason(self) -> str:
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
self.__debug('destroy')
self._debug('destroy')
# If executing something, wait until finished to remove it
# We simply replace the execution queue
op = self._get_current_op()
@ -457,12 +456,12 @@ class OpenStackLiveDeployment(
self._queue = [op, Operation.REMOVE, Operation.FINISH]
# Do not execute anything.here, just continue normally
return State.RUNNING
return types.states.State.RUNNING
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
types.states.State.values RUNNING, FINISHED or ERROR.
This can be invoked directly by an administration or by the clean up
of the deployed service (indirectly).
@ -472,7 +471,7 @@ class OpenStackLiveDeployment(
return self.destroy()
@staticmethod
def __op2str(op: Operation) -> str:
def _op2str(op: Operation) -> str:
return {
Operation.CREATE: 'create',
Operation.START: 'start',
@ -484,13 +483,13 @@ class OpenStackLiveDeployment(
Operation.RETRY: 'retry',
}.get(op, '????')
def __debug(self, txt: str) -> None:
def _debug(self, txt: str) -> None:
logger.debug(
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
'types.states.State.at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
txt,
self._name,
self._ip,
self._mac,
self._vmid,
[OpenStackLiveDeployment.__op2str(op) for op in self._queue],
[OpenStackLiveDeployment._op2str(op) for op in self._queue],
)

View File

@ -35,7 +35,7 @@ import typing
import collections.abc
from uds.core.services import Publication
from uds.core.types.states import State
from uds.core import types
from uds.core.util import autoserializable
# Not imported at runtime, just for type checking
@ -84,7 +84,7 @@ class OpenStackLivePublication(Publication, autoserializable.AutoSerializable):
self.mark_for_upgrade() # This will force remarshalling
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
Realizes the publication of the service
"""
@ -102,19 +102,19 @@ class OpenStackLivePublication(Publication, autoserializable.AutoSerializable):
except Exception as e:
self._state = 'error'
self._reason = 'Got error {}'.format(e)
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Checks state of publication creation
"""
if self._state == 'error':
return State.ERROR
return types.states.State.ERROR
if self._state == 'available':
return State.FINISHED
return types.states.State.FINISHED
self._state = self.service().get_template(self._template_id)['status'] # For next check
@ -122,37 +122,37 @@ class OpenStackLivePublication(Publication, autoserializable.AutoSerializable):
self._destroy_after = False
return self.destroy()
return State.RUNNING
return types.states.State.RUNNING
def error_reason(self) -> str:
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
# We do not do anything else to destroy this instance of publication
if self._state == 'error':
return State.ERROR # Nothing to cancel
return types.states.State.ERROR # Nothing to cancel
if self._state == 'creating':
self._destroy_after = True
return State.RUNNING
return types.states.State.RUNNING
try:
self.service().remove_template(self._template_id)
except Exception as e:
self._state = 'error'
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()
# Here ends the publication needed methods.
# Methods provided below are specific for this publication
# and will be used by user deployments that uses this kind of publication
def getTemplateId(self) -> str:
def get_template_id(self) -> str:
"""
Returns the template id associated with the publication
"""

View File

@ -36,8 +36,7 @@ import collections.abc
import dns.resolver
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
from uds.core.util import net
from uds.core.util import log, autoserializable, auto_attributes
@ -60,14 +59,14 @@ class OldIPSerialData(auto_attributes.AutoAttributes):
auto_attributes.AutoAttributes.__init__(self, ip=str, reason=str, state=str)
self._ip = ''
self._reason = ''
self._state = State.FINISHED
self._state = types.states.State.FINISHED
class IPMachineUserService(services.UserService, autoserializable.AutoSerializable):
suggested_delay = 10
_ip = autoserializable.StringField(default='')
_reason = autoserializable.StringField(default='')
_state = autoserializable.StringField(default=State.FINISHED)
_state = autoserializable.StringField(default=types.states.State.FINISHED)
# Utility overrides for type checking...
def service(self) -> 'IPServiceBase':
@ -109,23 +108,23 @@ class IPMachineUserService(services.UserService, autoserializable.AutoSerializab
# If multiple and has a ';' on IP, the values is IP;MAC
return self._ip.replace('~', ':').split(';')[0]
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
# If single machine, ip is IP~counter,
# If multiple and has a ';' on IP, the values is IP;MAC
host = HostInfo.from_str(self._ip)
if host.mac:
self.service().wakeup(host)
self._state = State.FINISHED
self._state = types.states.State.FINISHED
return self._state
def _deploy(self) -> str:
def _deploy(self) -> types.states.State:
ip = self.service().get_unassigned_host()
if ip is None:
self._reason = 'No machines left'
self._state = State.ERROR
self._state = types.states.State.ERROR
else:
self._ip = ip.as_identifier()
self._state = State.FINISHED
self._state = types.states.State.FINISHED
# If not to be managed by a token, autologin user
if not self.service().get_token():
@ -135,14 +134,14 @@ class IPMachineUserService(services.UserService, autoserializable.AutoSerializab
return self._state
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
logger.debug("Starting deploy of %s for user %s", self._ip, user)
return self._deploy()
def assign(self, ip: str) -> str:
logger.debug('Assigning from assignable with ip %s', ip)
self._ip = ip
self._state = State.FINISHED
self._state = types.states.State.FINISHED
if not self.service().get_token():
dbService = self.db_obj()
if dbService:
@ -151,29 +150,29 @@ class IPMachineUserService(services.UserService, autoserializable.AutoSerializab
return self._state
def error(self, reason: str) -> str:
self._state = State.ERROR
self._state = types.states.State.ERROR
self._ip = ''
self._reason = reason
return self._state
def check_state(self) -> str:
return self._state
def check_state(self) -> types.states.State:
return types.states.State.from_str(self._state)
def error_reason(self) -> str:
"""
If a publication produces an error, here we must notify the reason why it happened. This will be called just after
publish or checkPublishingState if they return State.ERROR
publish or checkPublishingState if they return types.states.State.ERROR
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
host = HostInfo.from_str(self._ip)
if host.host:
self.service().unassign_host(host)
self._state = State.FINISHED
self._state = types.states.State.FINISHED
return self._state
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()
def unmarshal(self, data: bytes) -> None:

View File

@ -60,13 +60,10 @@ class IPMachinesService(IPServiceBase):
# Gui
token = gui.TextField(
order=1,
label=typing.cast(str, _('Service Token')),
label=_('Service Token'),
length=64,
tooltip=typing.cast(
str,
_(
'Service token that will be used by actors to communicate with service. Leave empty for persistent assignation.'
),
tooltip=_(
'Service token that will be used by actors to communicate with service. Leave empty for persistent assignation.'
),
default='',
required=False,
@ -74,28 +71,26 @@ class IPMachinesService(IPServiceBase):
)
list_of_hosts = gui.EditableListField(
label=typing.cast(str, _('List of hosts')),
tooltip=typing.cast(str, _('List of hosts available for this service')),
label=_('List of hosts'),
tooltip=_('List of hosts available for this service'),
old_field_name='ipList',
)
port = gui.NumericField(
length=5,
label=typing.cast(str, _('Check Port')),
label=_('Check Port'),
default=0,
order=2,
tooltip=typing.cast(
str, _('If non zero, only hosts responding to connection on that port will be served.')
),
tooltip=_('If non zero, only hosts responding to connection on that port will be served.'),
required=True,
tab=types.ui.Tab.ADVANCED,
)
ignore_minutes_on_failure = gui.NumericField(
length=6,
label=typing.cast(str, _('Ignore minutes on failure')),
label=_('Ignore minutes on failure'),
default=0,
order=2,
tooltip=typing.cast(str, _('If a host fails to check, skip it for this time (in minutes).')),
tooltip=_('If a host fails to check, skip it for this time (in minutes).'),
min_value=0,
required=True,
tab=types.ui.Tab.ADVANCED,
@ -104,33 +99,26 @@ class IPMachinesService(IPServiceBase):
max_session_hours = gui.NumericField(
length=3,
label=typing.cast(str, _('Max session duration')),
label=_('Max session duration'),
default=0,
order=3,
tooltip=typing.cast(
str,
_(
'Max session duration before UDS releases a presumed locked machine (hours). 0 signifies "never".'
),
),
tooltip=_('Max session duration before UDS releases a presumed locked machine (hours). 0 signifies "never".'),
min_value=0,
required=True,
tab=types.ui.Tab.ADVANCED,
old_field_name='maxSessionForMachine',
)
lock_on_external_access = gui.CheckBoxField(
label=typing.cast(str, _('Lock machine by external access')),
tooltip=typing.cast(str, _('If checked, UDS will lock the machine if it is accesed from outside UDS.')),
label=_('Lock machine by external access'),
tooltip=_('If checked, UDS will lock the machine if it is accessed from outside UDS.'),
default=False,
order=4,
tab=types.ui.Tab.ADVANCED,
old_field_name='lockByExternalAccess',
)
randomize_host = gui.CheckBoxField(
label=typing.cast(str, _('Use random host')),
tooltip=typing.cast(
str, _('When enabled, UDS selects a random, rather than sequential, host from the list.')
),
label=_('Use random host'),
tooltip=_('When enabled, UDS selects a random, rather than sequential, host from the list.'),
default=False,
order=5,
tab=types.ui.Tab.ADVANCED,
@ -138,9 +126,9 @@ class IPMachinesService(IPServiceBase):
)
# Description of service
type_name = typing.cast(str, _('Static Multiple IP'))
type_name = _('Static Multiple IP')
type_type = 'IPMachinesService'
type_description = typing.cast(str, _('This service provides access to POWERED-ON Machines by IP'))
type_description = _('This service provides access to POWERED-ON Machines by IP')
icon_file = 'machines.png'
uses_cache = False # Cache are running machine awaiting to be assigned
@ -187,7 +175,7 @@ class IPMachinesService(IPServiceBase):
def hosts(self) -> typing.List['HostInfo']:
if self._cached_hosts is None:
d = self.storage.read_from_db('ips')
hosts_list = pickle.loads(d) if d and isinstance(d, bytes) else [] # nosec: pickle is safe here
hosts_list: list[str] = pickle.loads(d) if d and isinstance(d, bytes) else [] # nosec: pickle is safe here
self._cached_hosts = IPMachinesService.compose_hosts_info(hosts_list)
return self._cached_hosts
@ -329,18 +317,18 @@ class IPMachinesService(IPServiceBase):
self,
assignable_id: str,
user: 'models.User',
userDeployment: 'services.UserService',
userservice_instance: 'services.UserService',
) -> str:
userservice_instance: IPMachineUserService = typing.cast(IPMachineUserService, userDeployment)
ipmachine_instance: IPMachineUserService = typing.cast(IPMachineUserService, userservice_instance)
host = HostInfo.from_str(assignable_id)
now = sql_stamp_seconds()
locked = self.storage.get_unpickle(host.host)
if self.is_usable(locked, now):
self.storage.put_pickle(host.host, now)
return userservice_instance.assign(host.as_identifier())
return ipmachine_instance.assign(host.as_identifier())
return userservice_instance.error('IP already assigned')
return ipmachine_instance.error('IP already assigned')
def process_login(self, id: str, remote_login: bool) -> None:
'''

View File

@ -83,9 +83,12 @@ class IPSingleMachineService(IPServiceBase):
return
if ';' in self.host.as_str():
ip, mac = self.host.as_str().split(';')
host, _mac = self.host.as_str().split(';')
else:
host = self.host.as_str()
_mac = ''
if not net.is_valid_host(self.host.value):
if not net.is_valid_host(host):
raise exceptions.ui.ValidationError(
gettext('Invalid server used: "{}"'.format(self.host.value))
)

View File

@ -4,21 +4,21 @@ import re
import typing
import collections.abc
NETWORK_RE: typing.Final[typing.Pattern] = re.compile(r'([a-zA-Z0-9]+)=([^,]+)') # May have vla id at end
NETWORK_RE: typing.Final[typing.Pattern[str]] = re.compile(r'([a-zA-Z0-9]+)=([^,]+)') # May have vla id at end
# Conversor from dictionary to NamedTuple
CONVERSORS: typing.Final[collections.abc.MutableMapping[typing.Any, collections.abc.Callable]] = {
CONVERSORS: typing.Final[dict[typing.Any, collections.abc.Callable[[typing.Type[typing.Any]], typing.Any]]] = {
str: lambda x: str(x or ''),
typing.Optional[str]: lambda x: str(x) if x is not None else None,
typing.Optional[str]: lambda x: str(x) if x is not None else None, # pyright: ignore
bool: lambda x: bool(x),
typing.Optional[bool]: lambda x: bool(x) if x is not None else None,
int: lambda x: int(x or '0'),
typing.Optional[int]: lambda x: int(x or '0') if x is not None else None,
float: lambda x: float(x or '0'),
typing.Optional[float]: lambda x: float(x or '0') if x is not None else None,
datetime.datetime: lambda x: datetime.datetime.fromtimestamp(int(x)),
typing.Optional[bool]: lambda x: bool(x) if x is not None else None, # pyright: ignore
int: lambda x: int(x or '0'), # pyright: ignore
typing.Optional[int]: lambda x: int(x or '0') if x is not None else None, # pyright: ignore
float: lambda x: float(x or '0'), # pyright: ignore
typing.Optional[float]: lambda x: float(x or '0') if x is not None else None, # pyright: ignore
datetime.datetime: lambda x: datetime.datetime.fromtimestamp(int(x)), # pyright: ignore
typing.Optional[datetime.datetime]: lambda x: (
datetime.datetime.fromtimestamp(int(x)) if x is not None else None
datetime.datetime.fromtimestamp(int(x)) if x is not None else None # pyright: ignore
),
}

View File

@ -38,7 +38,6 @@ import collections.abc
from uds.core import services, consts, types
from uds.core.managers.userservice import UserServiceManager
from uds.core.types.states import State
from uds.core.util import log, autoserializable
from uds.core.util.model import sql_stamp_seconds
@ -183,9 +182,9 @@ class ProxmoxUserserviceLinked(services.UserService, autoserializable.AutoSerial
def get_ip(self) -> str:
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
vmInfo = self.service().get_machine_info(int(self._vmid))
@ -199,7 +198,7 @@ class ProxmoxUserserviceLinked(services.UserService, autoserializable.AutoSerial
return self._execute_queue()
self.cache.put('ready', '1')
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
"""
@ -236,7 +235,7 @@ if sys.platform == 'win32':
except Exception as e:
logger.info('Exception sending loggin to %s: %s', dbService, e)
def process_ready_from_os_manager(self, data: typing.Any) -> str:
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for %s', self._name)
if self._get_current_op() == Operation.WAIT:
@ -244,9 +243,9 @@ if sys.platform == 'win32':
self._pop_current_op() # Remove current state
return self._execute_queue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -254,11 +253,11 @@ if sys.platform == 'win32':
self._init_queue_for_deploy(False)
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
self._init_queue_for_deploy(cacheLevel == self.L2_CACHE)
self._init_queue_for_deploy(level == self.L2_CACHE)
return self._execute_queue()
def _init_queue_for_deploy(self, forLevel2: bool = False) -> None:
@ -299,14 +298,14 @@ if sys.platform == 'win32':
def _retry_later(self) -> str:
self._push_front_op(Operation.RETRY)
return State.RUNNING
return types.states.State.RUNNING
def _error(self, reason: typing.Union[str, Exception]) -> str:
def _error(self, reason: typing.Union[str, Exception]) -> types.states.State:
"""
Internal method to set object as error state
Returns:
State.ERROR, so we can do "return self.__error(reason)"
types.states.State.ERROR, so we can do "return self.__error(reason)"
"""
reason = str(reason)
logger.debug('Setting error state, reason: %s', reason)
@ -317,17 +316,17 @@ if sys.platform == 'win32':
self._queue = [Operation.ERROR]
self._reason = reason
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self._debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: collections.abc.Mapping[Operation, typing.Optional[collections.abc.Callable[[], None]]] = {
Operation.CREATE: self._create,
@ -349,7 +348,7 @@ if sys.platform == 'win32':
operation_executor()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
return self._error(e)
@ -358,17 +357,17 @@ if sys.platform == 'win32':
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
pass
def _retry_checker(self) -> str:
def _retry_checker(self) -> types.states.State:
"""
This method is not used, because retry operation is never used
"""
return State.FINISHED
return types.states.State.FINISHED
def _wait(self) -> None:
"""
@ -376,11 +375,11 @@ if sys.platform == 'win32':
"""
pass
def _wait_checker(self) -> str:
def _wait_checker(self) -> types.states.State:
"""
This method is not used, because wait operation is never used
"""
return State.FINISHED
return types.states.State.FINISHED
def _create(self) -> None:
"""
@ -489,50 +488,50 @@ if sys.platform == 'win32':
raise Exception(f'Error setting MAC and HA on proxmox: {e}') from e
# Check methods
def _check_task_finished(self) -> str:
def _check_task_finished(self) -> types.states.State:
if self._task == '':
return State.FINISHED
return types.states.State.FINISHED
node, upid = self._retrieve_task()
try:
task = self.service().provider().get_task_info(node, upid)
except client.ProxmoxConnectionError:
return State.RUNNING # Try again later
return types.states.State.RUNNING # Try again later
if task.is_errored():
return self._error(task.exitstatus)
if task.is_completed():
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
return self._check_task_finished()
def _start_checker(self) -> str:
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
return self._check_task_finished()
def _stop_checker(self) -> str:
def _stop_checker(self) -> types.states.State:
"""
Checks if machine has stoped
"""
return self._check_task_finished()
def _shutdown_checker(self) -> str:
def _shutdown_checker(self) -> types.states.State:
"""
Check if the machine has suspended
"""
return self._check_task_finished()
def _graceful_stop_checker(self) -> str:
def _graceful_stop_checker(self) -> types.states.State:
"""
Check if the machine has gracely stopped (timed shutdown)
"""
@ -541,7 +540,7 @@ if sys.platform == 'win32':
if shutdown_start < 0: # Was already stopped
# Machine is already stop
logger.debug('Machine WAS stopped')
return State.FINISHED
return types.states.State.FINISHED
if shutdown_start == 0: # Was shut down a las bravas
logger.debug('Macine DO NOT HAVE guest tools')
@ -550,7 +549,7 @@ if sys.platform == 'win32':
logger.debug('Checking State')
# Check if machine is already stopped
if self.service().get_machine_info(int(self._vmid)).status == 'stopped':
return State.FINISHED # It's stopped
return types.states.State.FINISHED # It's stopped
logger.debug('State is running')
if sql_stamp_seconds() - shutdown_start > consts.os.MAX_GUEST_SHUTDOWN_WAIT:
@ -563,23 +562,23 @@ if sys.platform == 'win32':
self.storage.put_pickle('shutdown', 0)
self._stop_machine() # Launch "hard" stop
return State.RUNNING
return types.states.State.RUNNING
def _remove_checker(self) -> str:
def _remove_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return self._check_task_finished()
def _mac_checker(self) -> str:
def _mac_checker(self) -> types.states.State:
"""
Checks if change mac operation has finished.
Changing nic configuration is 1-step operation, so when we check it here, it is already done
"""
return State.FINISHED
return types.states.State.FINISHED
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
@ -587,12 +586,12 @@ if sys.platform == 'win32':
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], types.states.State]]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry_checker,
Operation.WAIT: self._wait_checker,
@ -605,7 +604,7 @@ if sys.platform == 'win32':
}
try:
operation_checker: typing.Optional[typing.Optional[collections.abc.Callable[[], str]]] = fncs.get(
operation_checker: typing.Optional[typing.Optional[collections.abc.Callable[[], types.states.State]]] = fncs.get(
op, None
)
@ -613,7 +612,7 @@ if sys.platform == 'win32':
return self._error(f'Unknown operation found at check queue ({op})')
state = operation_checker()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op
return self._execute_queue()
@ -621,14 +620,14 @@ if sys.platform == 'win32':
except Exception as e:
return self._error(e)
def move_to_cache(self, newLevel: int) -> str:
def move_to_cache(self, level: int) -> types.states.State:
"""
Moves machines between cache levels
"""
if Operation.REMOVE in self._queue:
return State.RUNNING
return types.states.State.RUNNING
if newLevel == self.L1_CACHE:
if level == self.L1_CACHE:
self._queue = [Operation.START, Operation.FINISH]
else:
self._queue = [Operation.START, Operation.SHUTDOWN, Operation.FINISH]
@ -645,7 +644,7 @@ if sys.platform == 'win32':
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
Invoked for destroying a deployed service
"""
@ -653,7 +652,7 @@ if sys.platform == 'win32':
if self._vmid == '':
self._queue = []
self._reason = "canceled"
return State.FINISHED
return types.states.State.FINISHED
# If executing something, wait until finished to remove it
# We simply replace the execution queue
@ -671,9 +670,9 @@ if sys.platform == 'win32':
self._queue = [op] + queue
# Do not execute anything.here, just continue normally
return State.RUNNING
return types.states.State.RUNNING
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -701,7 +700,7 @@ if sys.platform == 'win32':
Operation.GET_MAC: 'getting mac',
}.get(op, '????')
def _debug(self, txt):
def _debug(self, txt: str) -> None:
logger.debug(
'State at %s: name: %s, ip: %s, mac: %s, vmid:%s, queue: %s',
txt,

View File

@ -35,9 +35,8 @@ import typing
import collections.abc
from django.utils.translation import gettext as _
from uds.core import services
from uds.core import services, types
from uds.core.util import autoserializable
from uds.core.types.states import State
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
@ -48,9 +47,9 @@ logger = logging.getLogger(__name__)
class ProxmoxPublication(services.Publication, autoserializable.AutoSerializable):
suggested_delay = 20
_name = autoserializable.StringField(default='')
_vmid = autoserializable.StringField(default='')
_task = autoserializable.StringField(default='')
@ -69,7 +68,7 @@ class ProxmoxPublication(services.Publication, autoserializable.AutoSerializable
"""
if not data.startswith(b'v'):
return super().unmarshal(data)
logger.debug('Data: %s', data)
vals = data.decode('utf8').split('\t')
if vals[0] == 'v1':
@ -84,69 +83,58 @@ class ProxmoxPublication(services.Publication, autoserializable.AutoSerializable
) = vals[1:]
else:
raise ValueError('Invalid data format')
self._destroy_after = destroy_after != ''
self.mark_for_upgrade() # Flag so manager can save it again with new format
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
If no space is available, publication will fail with an error
"""
try:
# First we should create a full clone, so base machine do not get fullfilled with "garbage" delta disks...
self._name = (
'UDS '
+ _('Publication')
+ ' '
+ self.servicepool_name()
+ "-"
+ str(self.revision())
)
self._name = 'UDS ' + _('Publication') + ' ' + self.servicepool_name() + "-" + str(self.revision())
comments = _('UDS Publication for {0} created at {1}').format(
self.servicepool_name(), str(datetime.now()).split('.')[0]
)
task = self.service().clone_machine(self._name, comments)
self._vmid = str(task.vmid)
self._task = ','.join((task.upid.node, task.upid.upid))
self._state = State.RUNNING
self._state = types.states.State.RUNNING
self._operation = 'p' # Publishing
self._destroy_after = False
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
logger.exception('Caught exception %s', e)
self._reason = str(e)
return State.ERROR
return types.states.State.ERROR
def check_state(
self,
) -> str: # pylint: disable = too-many-branches,too-many-return-statements
if self._state != State.RUNNING:
return self._state
def check_state(self) -> types.states.State:
if self._state != types.states.State.RUNNING:
return types.states.State.from_str(self._state)
node, upid = self._task.split(',')
try:
task = self.service().provider().get_task_info(node, upid)
if task.is_running():
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
logger.exception('Proxmox publication')
self._state = State.ERROR
self._state = types.states.State.ERROR
self._reason = str(e)
return self._state
if task.is_errored():
self._reason = task.exitstatus
self._state = State.ERROR
self._state = types.states.State.ERROR
else: # Finished
if self._destroy_after:
return self.destroy()
self._state = State.FINISHED
self._state = types.states.State.FINISHED
if self._operation == 'p': # not Destroying
# Disable Protection (removal)
self.service().provider().set_protection(int(self._vmid), protection=False)
time.sleep(
0.5
) # Give some tome to proxmox. We have observed some concurrency issues
time.sleep(0.5) # Give some tome to proxmox. We have observed some concurrency issues
# And add it to HA if
self.service().enable_machine_ha(int(self._vmid))
time.sleep(0.5)
@ -163,26 +151,30 @@ class ProxmoxPublication(services.Publication, autoserializable.AutoSerializable
self._task = ''
self._destroy_after = False
def destroy(self) -> str:
def destroy(self) -> types.states.State:
if (
self._state == State.RUNNING and self._destroy_after is False
self._state == types.states.State.RUNNING and self._destroy_after is False
): # If called destroy twice, will BREAK STOP publication
self._destroy_after = True
return State.RUNNING
return types.states.State.RUNNING
self._state = State.RUNNING
self._state = types.states.State.RUNNING
self._operation = 'd'
self._destroy_after = False
try:
task = self.service().remove_machine(self.machine())
self._task = ','.join((task.node, task.upid))
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
self._reason = str(e) # Store reason of error
logger.warning('Problem destroying publication %s: %s. Please, check machine state On Proxmox', self.machine(), e)
return State.ERROR
logger.warning(
'Problem destroying publication %s: %s. Please, check machine state On Proxmox',
self.machine(),
e,
)
return types.states.State.ERROR
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()
def error_reason(self) -> str:

View File

@ -34,8 +34,7 @@ import logging
import typing
import collections.abc
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
from . import service as sample_service
@ -118,7 +117,7 @@ class SampleUserServiceOne(services.UserService):
generate more names. (Generator are simple utility classes)
"""
name: str = typing.cast(str, self.storage.read_from_db('name'))
if name is None:
if not name:
name = self.name_generator().get(
self.service().get_basename() + '-' + self.service().getColour(), 3
)
@ -152,7 +151,7 @@ class SampleUserServiceOne(services.UserService):
to use to get an unused mac.
"""
mac = typing.cast(str, self.storage.read_from_db('mac'))
if mac is None:
if not mac:
mac = self.mac_generator().get('00:00:00:00:00:00-00:FF:FF:FF:FF:FF')
self.storage.save_to_db('mac', mac)
return mac
@ -176,11 +175,11 @@ class SampleUserServiceOne(services.UserService):
"""
ip = typing.cast(str, self.storage.read_from_db('ip'))
if ip is None:
if not ip:
ip = '192.168.0.34' # Sample IP for testing purposses only
return ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
This is a task method. As that, the expected return values are
State values RUNNING, FINISHED or ERROR.
@ -198,22 +197,22 @@ class SampleUserServiceOne(services.UserService):
This method, in this case, will check the state of the machine, and if
it is "ready", that is, powered on and accesible, it will return
"State.FINISHED". If the machine is not accesible (has ben erased, for
example), it will return "State.ERROR" and store a reason of error so UDS
"types.states.State.FINISHED". If the machine is not accesible (has ben erased, for
example), it will return "types.states.State.ERROR" and store a reason of error so UDS
can ask for it and present this information to the Administrator.
If the machine powered off, or suspended, or any other state that is not
directly usable but can be put in an usable state, it will return
"State.RUNNING", and core will use check_state to see when the operation
"types.states.State.RUNNING", and core will use check_state to see when the operation
has finished.
I hope this sample is enough to explain the use of this method..
"""
# In our case, the service is always ready
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
@ -232,9 +231,9 @@ class SampleUserServiceOne(services.UserService):
If the service gets created in "one step", that is, before the return
of this method, the consumable service for the user gets created, it
will return "State.FINISH".
will return "types.states.State.FINISH".
If the service needs more steps (as in this case), we will return
"State.RUNNING", and if it has an error, it wil return "State.ERROR" and
"types.states.State.RUNNING", and if it has an error, it wil return "types.states.State.ERROR" and
store an error string so administration interface can show it.
We do not use user for anything, as in most cases will be.
@ -246,11 +245,11 @@ class SampleUserServiceOne(services.UserService):
# random fail
if random.randint(0, 9) == 9: # nosec: just testing values
self.storage.save_to_db('error', 'Random error at deployForUser :-)')
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Our deployForUser method will initiate the consumable service deployment,
but will not finish it.
@ -259,8 +258,8 @@ class SampleUserServiceOne(services.UserService):
return that we have finished, else we will return that we are working
on it.
One deployForUser returns State.RUNNING, this task will get called until
check_state returns State.FINISHED.
One deployForUser returns types.states.State.RUNNING, this task will get called until
check_state returns types.states.State.FINISHED.
Also, we will make the publication fail one of every 10 calls to this
method.
@ -284,15 +283,15 @@ class SampleUserServiceOne(services.UserService):
# In our sample, we only use check_state in case of deployForUser,
# so at first call count will be 0.
if count >= 5:
return State.FINISHED
return types.states.State.FINISHED
# random fail
if random.randint(0, 9) == 9: # nosec: just testing values
self.storage.save_to_db('error', 'Random error at check_state :-)')
return State.ERROR
return types.states.State.ERROR
self.storage.save_to_db('count', str(count))
return State.RUNNING
return types.states.State.RUNNING
def finish(self) -> None:
"""
@ -324,7 +323,7 @@ class SampleUserServiceOne(services.UserService):
# We store the value at storage, but never get used, just an example
self.storage.save_to_db('user', username)
def user_logged_out(self, username) -> None:
def user_logged_out(self, username: str) -> None:
"""
This method must be available so os managers can invoke it whenever
an user get logged out if a service.
@ -351,18 +350,18 @@ class SampleUserServiceOne(services.UserService):
"""
return typing.cast(str, self.storage.read_from_db('error')) or 'No error'
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
Invoked for destroying a deployed service
Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
@return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using check_state)
@return: types.states.State.FINISHED if no more checks/steps for deployment are needed, types.states.State.RUNNING if more steps are needed (steps checked using check_state)
"""
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -372,4 +371,4 @@ class SampleUserServiceOne(services.UserService):
When administrator requests it, the cancel is "delayed" and not
invoked directly.
"""
return State.FINISHED
return types.states.State.FINISHED

View File

@ -35,8 +35,7 @@ import logging
import typing
import collections.abc
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
# Not imported at runtime, just for type checking
if typing.TYPE_CHECKING:
@ -227,7 +226,7 @@ class SampleUserServiceTwo(services.UserService):
return '192.168.0.34' # Sample IP for testing purposes only
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -245,22 +244,22 @@ class SampleUserServiceTwo(services.UserService):
This method, in this case, will check the state of the machine, and if
it is "ready", that is, powered on and accessible, it will return
"State.FINISHED". If the machine is not accessible (has been erased, for
example), it will return "State.ERROR" and store a reason of error so UDS
"types.states.State.FINISHED". If the machine is not accessible (has been erased, for
example), it will return "types.states.State.ERROR" and store a reason of error so UDS
can ask for it and present this information to the Administrator.
If the machine powered off, or suspended, or any other state that is not
directly usable but can be put in an usable state, it will return
"State.RUNNING", and core will use check_state to see when the operation
"types.states.State.RUNNING", and core will use check_state to see when the operation
has finished.
I hope this sample is enough to explain the use of this method..
"""
# In our case, the service is always ready
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
@ -279,9 +278,9 @@ class SampleUserServiceTwo(services.UserService):
If the service gets created in "one step", that is, before the return
of this method, the consumable service for the user gets created, it
will return "State.FINISH".
will return "types.states.State.FINISH".
If the service needs more steps (as in this case), we will return
"State.RUNNING", and if it has an error, it wil return "State.ERROR" and
"types.states.State.RUNNING", and if it has an error, it wil return "types.states.State.ERROR" and
store an error string so administration interface can show it.
We do not use user for anything, as in most cases will be.
@ -295,11 +294,11 @@ class SampleUserServiceTwo(services.UserService):
# Note that we can mark this string as translatable, and return
# it translated at error_reason method
self._error = 'Random error at deployForUser :-)'
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys a user deployment as cache.
@ -316,9 +315,9 @@ class SampleUserServiceTwo(services.UserService):
cache level (L1, L2) is the deployment
"""
self._count = 0
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Our deployForUser method will initiate the consumable service deployment,
but will not finish it.
@ -327,8 +326,8 @@ class SampleUserServiceTwo(services.UserService):
return that we have finished, else we will return that we are working
on it.
One deployForUser returns State.RUNNING, this task will get called until
check_state returns State.FINISHED.
One deployForUser returns types.states.State.RUNNING, this task will get called until
check_state returns types.states.State.FINISHED.
Also, we will make the user deployment fail one of every 10 calls to this
method.
@ -350,14 +349,14 @@ class SampleUserServiceTwo(services.UserService):
# In our sample, we only use check_state in case of deployForUser,
# so at first call count will be 0.
if self._count >= 5:
return State.FINISHED
return types.states.State.FINISHED
# random fail
if random.randint(0, 9) == 9: # nosec: just testing values
self._error = 'Random error at check_state :-)'
return State.ERROR
return types.states.State.ERROR
return State.RUNNING
return types.states.State.RUNNING
def finish(self):
"""
@ -390,7 +389,7 @@ class SampleUserServiceTwo(services.UserService):
# We store the value at storage, but never get used, just an example
self.storage.save_to_db('user', username)
def user_logged_out(self, username) -> None:
def user_logged_out(self, username: str) -> None:
"""
This method must be available so os managers can invoke it whenever
an user get logged out if a service.
@ -422,18 +421,18 @@ class SampleUserServiceTwo(services.UserService):
"""
return self._error
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
Invoked for destroying a deployed service
Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...)
@return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using check_state)
@return: types.states.State.FINISHED if no more checks/steps for deployment are needed, types.states.State.RUNNING if more steps are needed (steps checked using check_state)
"""
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
This is a task method. As that, the excepted return values are
State values RUNNING, FINISHED or ERROR.
@ -443,4 +442,4 @@ class SampleUserServiceTwo(services.UserService):
When administrator requests it, the cancel is "delayed" and not
invoked directly.
"""
return State.FINISHED
return types.states.State.FINISHED

View File

@ -37,8 +37,7 @@ import typing
import collections.abc
from django.utils.translation import gettext as _
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
logger = logging.getLogger(__name__)
@ -123,7 +122,7 @@ class SamplePublication(services.Publication):
self._reason = vals[1]
self._number = int(vals[2])
def publish(self) -> str:
def publish(self) -> types.states.State:
"""
This method is invoked whenever the administrator requests a new publication.
@ -135,7 +134,7 @@ class SamplePublication(services.Publication):
All publications can be synchronous or asynchronous.
The main difference between both is that first do whatever needed, (the
action must be fast enough to do not block core), returning State.FINISHED.
action must be fast enough to do not block core), returning types.states.State.FINISHED.
The second (asynchronous) are publications that could block the core, so
it have to be done in more than one step.
@ -144,7 +143,7 @@ class SamplePublication(services.Publication):
* First we invoke the copy operation to virtualization provider
* Second, we kept needed values inside instance so we can serialize
them whenever requested
* Returns an State.RUNNING, indicating the core that the publication
* Returns an types.states.State.RUNNING, indicating the core that the publication
has started but has to finish sometime later. (We do no check
again the state and keep waiting here, because we will block the
core untill this operation is finished).
@ -157,7 +156,7 @@ class SamplePublication(services.Publication):
finish at publish call but a later check_state call
Take care with instantiating threads from here. Whenever a publish returns
"State.RUNNING", the core will recheck it later, but not using this instance
"types.states.State.RUNNING", the core will recheck it later, but not using this instance
and maybe that even do not use this server.
If you want to use threadings or somethin likt it, use DelayedTasks and
@ -178,17 +177,17 @@ class SamplePublication(services.Publication):
"""
self._number = 5
self._reason = ''
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Our publish method will initiate publication, but will not finish it.
So in our sample, wi will only check if _number reaches 0, and if so
return that we have finished, else we will return that we are working
on it.
One publish returns State.RUNNING, this task will get called untill
check_state returns State.FINISHED.
One publish returns types.states.State.RUNNING, this task will get called untill
check_state returns types.states.State.FINISHED.
Also, wi will make the publication fail one of every 10 calls to this
method.
@ -204,11 +203,11 @@ class SamplePublication(services.Publication):
# One of every 10 calls
if random.randint(0, 9) == 9:
self._reason = _('Random integer was 9!!! :-)')
return State.ERROR
return types.states.State.ERROR
if self._number <= 0:
return State.FINISHED
return State.RUNNING
return types.states.State.FINISHED
return types.states.State.RUNNING
def finish(self) -> None:
"""
@ -228,13 +227,13 @@ class SamplePublication(services.Publication):
"""
If a publication produces an error, here we must notify the reason why
it happened. This will be called just after publish or check_state
if they return State.ERROR
if they return types.states.State.ERROR
Returns an string, in our case, set at check_state
"""
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
"""
This is called once a publication is no more needed.
@ -242,16 +241,16 @@ class SamplePublication(services.Publication):
removing created "external" data (environment gets cleaned by core),
etc..
The retunred value is the same as when publishing, State.RUNNING,
State.FINISHED or State.ERROR.
The retunred value is the same as when publishing, types.states.State.RUNNING,
types.states.State.FINISHED or types.states.State.ERROR.
"""
self._name = ''
self._reason = '' # In fact, this is not needed, but cleaning up things... :-)
# We do not do anything else to destroy this instance of publication
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
"""
Invoked for canceling the current operation.
This can be invoked directly by an administration or by the clean up

View File

@ -35,7 +35,7 @@ import typing
import collections.abc
from django.utils.translation import gettext_noop as _
from uds.core import services, exceptions
from uds.core import services, exceptions, types
from uds.core.ui import gui
from .publication import SamplePublication
@ -226,16 +226,6 @@ class ServiceTwo(services.Service):
# Gui, we will use here the EditableList field
names = gui.EditableListField(label=_('List of names'))
def __init__(self, environment, parent, values=None):
"""
We here can get a HUGE list from client.
Right now, this is treated same as other fields, in a near
future we will se how to handle this better
"""
super(ServiceTwo, self).__init__(environment, parent, values)
# No checks here
def get_names(self) -> list[str]:
"""
For using at deployed services, really nothing

View File

@ -35,8 +35,7 @@ import dataclasses
import typing
import collections.abc
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
from . import service
@ -59,6 +58,7 @@ class TestUserService(services.UserService):
"""
This is the data we will store in the storage
"""
count: int = -1
ready: bool = False
name: str = ''
@ -92,35 +92,33 @@ class TestUserService(services.UserService):
def get_unique_id(self) -> str:
logger.info('Getting unique id of deployment %s', self.data)
if not self.data.mac:
self.data.mac = self.mac_generator().get(
'00:00:00:00:00:00-00:FF:FF:FF:FF:FF'
)
self.data.mac = self.mac_generator().get('00:00:00:00:00:00-00:FF:FF:FF:FF:FF')
return self.data.mac
def get_ip(self) -> str:
logger.info('Getting ip of deployment %s', self.data)
ip = typing.cast(str, self.storage.read_from_db('ip'))
if ip is None:
if not ip:
ip = '8.6.4.2' # Sample IP for testing purposses only
return ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
logger.info('Setting ready %s', self.data)
self.data.ready = True
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
logger.info('Deploying for user %s %s', user, self.data)
self.data.count = 3
return State.RUNNING
return types.states.State.RUNNING
def check_state(self) -> str:
def check_state(self) -> types.states.State:
logger.info('Checking state of deployment %s', self.data)
if self.data.count <= 0:
return State.FINISHED
return types.states.State.FINISHED
self.data.count -= 1
return State.RUNNING
return types.states.State.RUNNING
def finish(self) -> None:
logger.info('Finishing deployment %s', self.data)
@ -129,17 +127,16 @@ class TestUserService(services.UserService):
def user_logged_in(self, username: str) -> None:
logger.info('User %s has logged in', username)
def user_logged_out(self, username) -> None:
def user_logged_out(self, username: str) -> None:
logger.info('User %s has logged out', username)
def error_reason(self) -> str:
return 'No error'
def destroy(self) -> str:
def destroy(self) -> types.states.State:
logger.info('Destroying deployment %s', self.data)
self.data.count = -1
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()

View File

@ -38,8 +38,7 @@ import typing
import collections.abc
from django.utils.translation import gettext as _
from uds.core import services
from uds.core.types.states import State
from uds.core import services, types
logger = logging.getLogger(__name__)
@ -80,32 +79,32 @@ class TestPublication(services.Publication):
# We do not check anything at marshal method, so we ensure that
# default values are correctly handled by marshal.
self.data.name = ''.join(random.choices(string.ascii_letters, k=8))
self.data.state = State.RUNNING
self.data.state = types.states.State.RUNNING
self.data.reason = 'none'
self.data.number = 10
def publish(self) -> str:
def publish(self) -> types.states.State:
logger.info('Publishing publication %s: %s remaining',self.data.name, self.data.number)
self.data.number -= 1
if self.data.number <= 0:
self.data.state = State.FINISHED
return self.data.state
self.data.state = types.states.State.FINISHED
return types.states.State.from_str(self.data.state)
def finish(self) -> None:
# Make simply a random string
logger.info('Finishing publication %s', self.data.name)
self.data.number = 0
self.data.state = State.FINISHED
self.data.state = types.states.State.FINISHED
def error_reason(self) -> str:
return self.data.reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
logger.info('Destroying publication %s', self.data.name)
return State.FINISHED
return types.states.State.FINISHED
def cancel(self) -> str:
def cancel(self) -> types.states.State:
logger.info('Canceling publication %s', self.data.name)
return self.destroy()

View File

@ -35,7 +35,7 @@ import logging
import typing
import collections.abc
from uds.core import services, consts
from uds.core import services, consts, types
from uds.core.types.states import State
from uds.core.util import autoserializable, log
@ -142,9 +142,9 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
def get_ip(self) -> str:
return self._ip
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
state = self.service().get_machine_power_state(self._vmid)
@ -159,13 +159,13 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
self.do_log(log.LogLevel.ERROR, 'Error setting machine state: {}'.format(e))
# return self.__error('Machine is not available anymore')
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
if self._vmid:
self.service().reset_machine(self._vmid) # Reset in sync
def process_ready_from_os_manager(self, data: typing.Any) -> str:
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
# Here we will check for suspending the VM (when full ready)
logger.debug('Checking if cache 2 for %s', self._name)
if self._get_current_op() == Operation.WAIT:
@ -173,9 +173,9 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
self._pop_current_op() # Remove current state
return self._execute_queue()
# Do not need to go to level 2 (opWait is in fact "waiting for moving machine to cache level 2)
return State.FINISHED
return types.states.State.FINISHED
def deploy_for_user(self, user: 'models.User') -> str:
def deploy_for_user(self, user: 'models.User') -> types.states.State:
"""
Deploys an service instance for an user.
"""
@ -183,11 +183,11 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
self._init_queue_for_deployment(False)
return self._execute_queue()
def deploy_for_cache(self, cacheLevel: int) -> str:
def deploy_for_cache(self, level: int) -> types.states.State:
"""
Deploys an service instance for cache
"""
self._init_queue_for_deployment(cacheLevel == self.L2_CACHE)
self._init_queue_for_deployment(level == self.L2_CACHE)
return self._execute_queue()
def _init_queue_for_deployment(self, forLevel2: bool = False) -> None:
@ -229,7 +229,7 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
def _push_back_op(self, op: Operation) -> None:
self._queue.append(op)
def _error(self, reason: typing.Any) -> str:
def _error(self, reason: typing.Any) -> types.states.State:
logger.debug('Setting error state, reason: %s', reason)
self.do_log(log.LogLevel.ERROR, reason)
@ -248,17 +248,17 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
self._queue = [Operation.ERROR]
self._reason = str(reason)
return State.ERROR
return types.states.State.ERROR
def _execute_queue(self) -> str:
def _execute_queue(self) -> types.states.State:
self.__debug('executeQueue')
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[Operation, typing.Optional[collections.abc.Callable[[], str]]] = {
Operation.CREATE: self._create,
@ -281,32 +281,32 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
operation()
return State.RUNNING
return types.states.State.RUNNING
except Exception as e:
return self._error(e)
# Queue execution methods
def _retry(self) -> str:
def _retry(self) -> types.states.State:
"""
Used to retry an operation
In fact, this will not be never invoked, unless we push it twice, because
check_state method will "pop" first item when a check operation returns State.FINISHED
check_state method will "pop" first item when a check operation returns types.states.State.FINISHED
At executeQueue this return value will be ignored, and it will only be used at check_state
"""
return State.FINISHED
return types.states.State.FINISHED
def _wait(self) -> str:
def _wait(self) -> types.states.State:
"""
Executes opWait, it simply waits something "external" to end
"""
return State.RUNNING
return types.states.State.RUNNING
def _create(self) -> str:
"""
Deploys a machine from template for user/cache
"""
templateId = self.publication().getTemplateId()
template_id = self.publication().getTemplateId()
name = self.get_name()
if name == consts.NO_MORE_NAMES:
raise Exception(
@ -318,11 +318,11 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
) # oVirt don't let us to create machines with more than 15 chars!!!
comments = 'UDS Linked clone'
self._task = self.service().start_deploy_from_template(name, comments, templateId)
if self._task is None:
self._task = self.service().start_deploy_from_template(name, comments, template_id)
if not self._task:
raise Exception('Can\'t create machine')
return State.RUNNING
return types.states.State.RUNNING
def _remove(self) -> str:
"""
@ -336,7 +336,7 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
else:
self.service().remove_machine(self._vmid)
return State.RUNNING
return types.states.State.RUNNING
def _start_machine(self) -> str:
"""
@ -349,7 +349,7 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
else:
self._task = ''
return State.RUNNING
return types.states.State.RUNNING
def _stop_machine(self) -> str:
"""
@ -362,14 +362,14 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
else:
self._task = ''
return State.RUNNING
return types.states.State.RUNNING
def _wait_suspend(self) -> str:
"""
Before suspending, wait for machine to have the SUSPEND feature
"""
self._task = ''
return State.RUNNING
return types.states.State.RUNNING
def _suspend_machine(self) -> str:
"""
@ -382,7 +382,7 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
else:
self._task = ''
return State.RUNNING
return types.states.State.RUNNING
def _configure(self):
"""
@ -390,7 +390,7 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
"""
self.service().configure_machine(self._vmid, self.get_unique_id())
return State.RUNNING
return types.states.State.RUNNING
def _provision(self):
"""
@ -398,68 +398,68 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
"""
self.service().provision_machine(self._vmid, False) # Let's try this in "sync" mode, this must be fast enough
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def _create_checker(self):
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
state = self.service().check_task_finished(self._task)
if state[0]: # Finished
self._vmid = state[1]
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
def _start_checker(self):
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
if self.service().check_task_finished(self._task)[0]:
return State.FINISHED
return State.RUNNING
return types.states.State.FINISHED
return types.states.State.RUNNING
def _stop_checker(self):
def _stop_checker(self) -> types.states.State:
"""
Checks if machine has stoped
"""
if self.service().check_task_finished(self._task)[0]:
return State.FINISHED
return State.RUNNING
return types.states.State.FINISHED
return types.states.State.RUNNING
def _wait_suspend_checker(self):
def _wait_suspend_checker(self) -> types.states.State:
if self.service().can_suspend_machine(self._vmid) is True:
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
def _suspend_checker(self):
def _suspend_checker(self) -> types.states.State:
"""
Check if the machine has suspended
"""
if self.service().check_task_finished(self._task)[0]:
return State.FINISHED
return State.RUNNING
return types.states.State.FINISHED
return types.states.State.RUNNING
def removed_checker(self):
def removed_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""
return State.FINISHED
return types.states.State.FINISHED
def _configure_checker(self):
def _configure_checker(self) -> types.states.State:
"""
Checks if change mac operation has finished.
Changing nic configuration es 1-step operation, so when we check it here, it is already done
"""
return State.FINISHED
return types.states.State.FINISHED
def _provision_checker(self):
return State.FINISHED
def _provision_checker(self) -> types.states.State:
return types.states.State.FINISHED
def check_state(self) -> str:
def check_state(self) -> types.states.State:
"""
Check what operation is going on, and acts acordly to it
"""
@ -467,12 +467,12 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
op = self._get_current_op()
if op == Operation.ERROR:
return State.ERROR
return types.states.State.ERROR
if op == Operation.FINISH:
return State.FINISHED
return types.states.State.FINISHED
fncs: dict[int, typing.Optional[collections.abc.Callable[[], str]]] = {
fncs: dict[int, typing.Optional[collections.abc.Callable[[], types.states.State]]] = {
Operation.CREATE: self._create_checker,
Operation.RETRY: self._retry,
Operation.WAIT: self._wait,
@ -486,13 +486,13 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
}
try:
chkFnc: typing.Optional[collections.abc.Callable[[], str]] = fncs.get(op, None)
chkFnc: typing.Optional[collections.abc.Callable[[], types.states.State]] = fncs.get(op, None)
if chkFnc is None:
return self._error('Unknown operation found at check queue ({})'.format(op))
state = chkFnc()
if state == State.FINISHED:
if state == types.states.State.FINISHED:
self._pop_current_op() # Remove runing op
return self._execute_queue()
@ -500,14 +500,14 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
except Exception as e:
return self._error(e)
def move_to_cache(self, newLevel: int) -> str:
def move_to_cache(self, level: int) -> types.states.State:
"""
Moves machines between cache levels
"""
if Operation.REMOVE in self._queue:
return State.RUNNING
return types.states.State.RUNNING
if newLevel == self.L1_CACHE:
if level == self.L1_CACHE:
self._queue = [Operation.START, Operation.FINISH]
else:
self._queue = [Operation.START, Operation.SUSPEND, Operation.FINISH]
@ -517,14 +517,14 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
def error_reason(self) -> str:
return self._reason
def destroy(self) -> str:
def destroy(self) -> types.states.State:
self.__debug('destroy')
# If executing something, wait until finished to remove it
# We simply replace the execution queue
op = self._get_current_op()
if op == Operation.ERROR:
return State.FINISHED
return types.states.State.FINISHED
if op == Operation.FINISH or op == Operation.WAIT:
self._queue = [Operation.STOP, Operation.REMOVE, Operation.FINISH]
@ -532,9 +532,9 @@ class XenLinkedDeployment(services.UserService, autoserializable.AutoSerializabl
self._queue = [op, Operation.STOP, Operation.REMOVE, Operation.FINISH]
# Do not execute anything.here, just continue normally
return State.RUNNING
return types.states.State.RUNNING
def cancel(self) -> str:
def cancel(self) -> types.states.State:
return self.destroy()
@staticmethod

View File

@ -30,13 +30,11 @@
"""
Author: Adolfo Gómez, dkmaster at dkmon dot com
"""
import pickle # nosec: controled data
import enum
import logging
import typing
import collections.abc
from uds.core import services
from uds.core import types
from uds.core.services.specializations.fixed_machine.fixed_userservice import FixedUserService, Operation
from uds.core.types.states import State
from uds.core.util import log, autoserializable
@ -69,9 +67,9 @@ class XenFixedUserService(FixedUserService, autoserializable.AutoSerializable):
def service(self) -> 'service_fixed.XenFixedService':
return typing.cast('service_fixed.XenFixedService', super().service())
def set_ready(self) -> str:
def set_ready(self) -> types.states.State:
if self.cache.get('ready') == '1':
return State.FINISHED
return types.states.State.FINISHED
try:
state = self.service().get_machine_power_state(self._vmid)
@ -86,14 +84,14 @@ class XenFixedUserService(FixedUserService, autoserializable.AutoSerializable):
self.do_log(log.LogLevel.ERROR, 'Error setting machine state: {}'.format(e))
# return self.__error('Machine is not available anymore')
return State.FINISHED
return types.states.State.FINISHED
def reset(self) -> None:
if self._vmid:
self.service().reset_machine(self._vmid) # Reset in sync
def process_ready_from_os_manager(self, data: typing.Any) -> str:
return State.FINISHED
def process_ready_from_os_manager(self, data: typing.Any) -> types.states.State:
return types.states.State.FINISHED
def error(self, reason: str) -> str:
return self._error(reason)
@ -118,44 +116,44 @@ class XenFixedUserService(FixedUserService, autoserializable.AutoSerializable):
self._task = self.service().stop_machine(self._vmid) or ''
# Check methods
def _check_task_finished(self) -> str:
def _check_task_finished(self) -> types.states.State:
if self._task == '':
return State.FINISHED
return types.states.State.FINISHED
try:
finished, per = self.service().check_task_finished(self._task)
finished, _per = self.service().check_task_finished(self._task)
except xen_client.XenFailure:
return State.RUNNING # Try again later
return types.states.State.RUNNING # Try again later
except Exception as e: # Failed for some other reason
if isinstance(e.args[0], dict) and 'error_connection' in e.args[0]:
return State.RUNNING # Try again later
return types.states.State.RUNNING # Try again later
raise e
if finished:
return State.FINISHED
return types.states.State.FINISHED
return State.RUNNING
return types.states.State.RUNNING
# Check methods
def _create_checker(self) -> str:
def _create_checker(self) -> types.states.State:
"""
Checks the state of a deploy for an user or cache
"""
return State.FINISHED
return types.states.State.FINISHED
def _start_checker(self) -> str:
def _start_checker(self) -> types.states.State:
"""
Checks if machine has started
"""
return self._check_task_finished()
def _stop_checker(self) -> str:
def _stop_checker(self) -> types.states.State:
"""
Checks if machine has stoped
"""
return self._check_task_finished()
def _removed_checker(self) -> str:
def _removed_checker(self) -> types.states.State:
"""
Checks if a machine has been removed
"""