From b8e7dc07c363de64a7b83e0e85be579dfacbc4b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Thu, 12 Nov 2020 12:13:35 +0100 Subject: [PATCH] Refactoring models (without DB modification) to better type checking --- server/src/uds/models/__init__.py | 15 +- server/src/uds/models/account.py | 18 +- server/src/uds/models/account_usage.py | 35 ++- server/src/uds/models/actor_token.py | 11 +- server/src/uds/models/authenticator.py | 51 +++- server/src/uds/models/cache.py | 9 +- server/src/uds/models/calendar.py | 19 +- server/src/uds/models/calendar_access.py | 22 +- server/src/uds/models/calendar_action.py | 228 +++++++++++++--- server/src/uds/models/calendar_rule.py | 71 ++++- server/src/uds/models/config.py | 7 +- server/src/uds/models/dbfile.py | 23 +- server/src/uds/models/delayed_task.py | 5 +- server/src/uds/models/group.py | 22 +- server/src/uds/models/image.py | 26 +- server/src/uds/models/log.py | 15 +- server/src/uds/models/managed_object_model.py | 13 +- server/src/uds/models/meta_pool.py | 62 +++-- server/src/uds/models/network.py | 25 +- server/src/uds/models/os_manager.py | 16 +- server/src/uds/models/permissions.py | 58 +++- server/src/uds/models/provider.py | 24 +- server/src/uds/models/proxy.py | 29 +- server/src/uds/models/scheduler.py | 19 +- server/src/uds/models/service.py | 54 ++-- server/src/uds/models/service_pool.py | 256 +++++++++++++----- server/src/uds/models/service_pool_group.py | 14 +- .../uds/models/service_pool_publication.py | 56 +++- server/src/uds/models/stats_counters.py | 47 +++- server/src/uds/models/stats_events.py | 24 +- server/src/uds/models/storage.py | 7 +- server/src/uds/models/tag.py | 6 +- server/src/uds/models/ticket_store.py | 98 ++++--- server/src/uds/models/transport.py | 6 +- server/src/uds/models/unique_id.py | 11 +- server/src/uds/models/user.py | 5 +- server/src/uds/models/user_preference.py | 5 +- server/src/uds/models/user_service.py | 128 +++++++-- .../src/uds/models/user_service_property.py | 15 +- server/src/uds/models/util.py | 2 +- server/src/uds/models/uuid_model.py | 14 +- 41 files changed, 1181 insertions(+), 390 deletions(-) diff --git a/server/src/uds/models/__init__.py b/server/src/uds/models/__init__.py index 653b3c71..3f99bda0 100644 --- a/server/src/uds/models/__init__.py +++ b/server/src/uds/models/__init__.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -32,17 +32,14 @@ """ import logging +# Utility imports +from .util import getSqlDatetime, getSqlDatetimeAsUnix, NEVER, NEVER_UNIX + +# Imports all models so they are available for migrations + # Permissions from .permissions import Permissions -# Utility -from .util import ( - getSqlDatetime, - getSqlDatetimeAsUnix, - NEVER, - NEVER_UNIX -) - # Services from .provider import Provider from .service import Service diff --git a/server/src/uds/models/account.py b/server/src/uds/models/account.py index 2bbf1f0d..767cd4ea 100644 --- a/server/src/uds/models/account.py +++ b/server/src/uds/models/account.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -42,17 +42,22 @@ logger = logging.getLogger(__name__) # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: - from uds.models import UserService + from uds.models import UserService, AccountUsage class Account(UUIDModel, TaggingMixin): # type: ignore """ Account storing on DB model """ + name = models.CharField(max_length=128, unique=False, db_index=True) time_mark = models.DateTimeField(default=NEVER) comments = models.CharField(max_length=256) + # "fake" declarations for type checking + objects: 'models.BaseManager[Account]' + usages: 'models.QuerySet[AccountUsage]' + def startUsageAccounting(self, userService: 'UserService') -> None: if hasattr(userService, 'accounting'): # Already has an account return None @@ -65,18 +70,20 @@ class Account(UUIDModel, TaggingMixin): # type: ignore else: userName = '??????' userUuid = '00000000-0000-0000-0000-000000000000' - return self.usages.create( + + self.usages.create( user_service=userService, user_name=userName, user_uuid=userUuid, pool_name=userService.deployed_service.name, pool_uuid=userService.deployed_service.uuid, start=start, - end=start + end=start, ) def stopUsageAccounting(self, userService: 'UserService') -> None: - if hasattr(userService, 'accounting') is False: + # if one to one does not exists, attr is not there + if not hasattr(userService, 'accounting'): return tmp = userService.accounting @@ -88,6 +95,7 @@ class Account(UUIDModel, TaggingMixin): # type: ignore """ Meta class to declare the name of the table at database """ + db_table = 'uds_accounts' app_label = 'uds' diff --git a/server/src/uds/models/account_usage.py b/server/src/uds/models/account_usage.py index 94f133f9..4207bbcc 100644 --- a/server/src/uds/models/account_usage.py +++ b/server/src/uds/models/account_usage.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -28,6 +28,7 @@ """ .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com """ +import typing import logging from django.db import models @@ -39,6 +40,10 @@ from .account import Account from .user_service import UserService from .util import NEVER +# Not imported at runtime, just for type checking +if typing.TYPE_CHECKING: + from uds.models import UserService, Account + logger = logging.getLogger(__name__) @@ -46,33 +51,47 @@ class AccountUsage(UUIDModel): """ AccountUsage storing on DB model This is intended for small images (i will limit them to 128x128), so storing at db is fine - """ + + # "fake" declarations for type checking + objects: 'models.BaseManager[AccountUsage]' + user_name = models.CharField(max_length=128, db_index=True, default='') user_uuid = models.CharField(max_length=50, db_index=True, default='') pool_name = models.CharField(max_length=128, db_index=True, default='') pool_uuid = models.CharField(max_length=50, db_index=True, default='') start = models.DateTimeField(default=NEVER) end = models.DateTimeField(default=NEVER) - user_service: UserService = models.OneToOneField(UserService, null=True, blank=True, related_name='accounting', on_delete=models.SET_NULL) - account: Account = models.ForeignKey(Account, related_name='usages', on_delete=models.CASCADE) + user_service: 'models.OneToOneField[AccountUsage, UserService]' = ( + models.OneToOneField( + UserService, + null=True, + blank=True, + related_name='accounting', + on_delete=models.SET_NULL, + ) + ) + account: 'models.ForeignKey[AccountUsage, Account]' = models.ForeignKey( + Account, related_name='usages', on_delete=models.CASCADE + ) class Meta: """ Meta class to declare the name of the table at database """ + db_table = 'uds_acc_usage' app_label = 'uds' @property def elapsed_seconds(self) -> int: - if NEVER in (self.end, self.start): + if NEVER in (self.end, self.start): return 0 return (self.end - self.start).total_seconds() @property def elapsed_seconds_timemark(self) -> int: - if NEVER in (self.end, self.start): + if NEVER in (self.end, self.start): return 0 start = self.start @@ -93,4 +112,6 @@ class AccountUsage(UUIDModel): return secondsToTimeString(self.elapsed_seconds_timemark) def __str__(self): - return 'AccountUsage id {}, pool {}, name {}, start {}, end {}'.format(self.id, self.pool_name, self.user_name, self.start, self.end) + return 'AccountUsage id {}, pool {}, name {}, start {}, end {}'.format( + self.id, self.pool_name, self.user_name, self.start, self.end + ) diff --git a/server/src/uds/models/actor_token.py b/server/src/uds/models/actor_token.py index 5ca54be7..ad516f84 100644 --- a/server/src/uds/models/actor_token.py +++ b/server/src/uds/models/actor_token.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -30,10 +30,12 @@ ''' from django.db import models + class ActorToken(models.Model): """ UDS Actors tokens on DB """ + username = models.CharField(max_length=128) ip_from = models.CharField(max_length=128) ip = models.CharField(max_length=128) @@ -47,5 +49,10 @@ class ActorToken(models.Model): token = models.CharField(max_length=48, db_index=True, unique=True) stamp = models.DateTimeField() # Date creation or validation of this entry + # "fake" declarations for type checking + objects: 'models.BaseManager[ActorToken]' + def __str__(self): - return ''.format(self.token, self.stamp, self.username, self.hostname, self.ip_from) + return ''.format( + self.token, self.stamp, self.username, self.hostname, self.ip_from + ) diff --git a/server/src/uds/models/authenticator.py b/server/src/uds/models/authenticator.py index 0cd3e17a..12e1da57 100644 --- a/server/src/uds/models/authenticator.py +++ b/server/src/uds/models/authenticator.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -47,8 +47,7 @@ from .util import NEVER # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: - from .user import User - from django.db.models import QuerySet # pylint: disable=ungrouped-imports + from uds.models import User, Group logger = logging.getLogger(__name__) @@ -64,14 +63,20 @@ class Authenticator(ManagedObjectModel, TaggingMixin): small_name = models.CharField(max_length=32, default='', db_index=True) visible = models.BooleanField(default=True) + # "fake" relations declarations for type checking + objects: 'models.BaseManager[Authenticator]' + users: 'models.QuerySet[User]' + groups: 'models.QuerySet[Group]' + class Meta(ManagedObjectModel.Meta): # pylint: disable=too-few-public-methods """ Meta class to declare default order """ + ordering = ('name',) app_label = 'uds' - def getInstance(self, values=None) -> auths.Authenticator: + def getInstance(self, values=None) -> auths.Authenticator: """ Instantiates the object this record contains. @@ -87,7 +92,9 @@ class Authenticator(ManagedObjectModel, TaggingMixin): Raises: """ if self.id is None: - return auths.Authenticator(self, environment.Environment.getTempEnv(), values) + return auths.Authenticator( + self, environment.Environment.getTempEnv(), values + ) auType = self.getType() env = self.getEnvironment() @@ -109,7 +116,9 @@ class Authenticator(ManagedObjectModel, TaggingMixin): # If type is not registered (should be, but maybe a database inconsistence), consider this a "base empty auth" return auths.factory().lookup(self.data_type) or auths.Authenticator - def getOrCreateUser(self, username: str, realName: typing.Optional[str] = None) -> 'User': + def getOrCreateUser( + self, username: str, realName: typing.Optional[str] = None + ) -> 'User': """ Used to get or create a new user at database associated with this authenticator. @@ -139,8 +148,17 @@ class Authenticator(ManagedObjectModel, TaggingMixin): """ user: 'User' realName = realName if realName is None else username - user, _ = self.users.get_or_create(name=username, defaults={'real_name': realName, 'last_access': NEVER, 'state': State.ACTIVE}) - if (user.real_name.strip() == '' or user.name.strip() == user.real_name.strip()) and realName != user.real_name: + user, _ = self.users.get_or_create( + name=username, + defaults={ + 'real_name': realName, + 'last_access': NEVER, + 'state': State.ACTIVE, + }, + ) + if ( + user.real_name.strip() == '' or user.name.strip() == user.real_name.strip() + ) and realName != user.real_name: user.real_name = realName user.save(update_fields=['real_name']) @@ -169,14 +187,14 @@ class Authenticator(ManagedObjectModel, TaggingMixin): return falseIfNotExists @staticmethod - def all() -> 'QuerySet': + def all() -> 'models.QuerySet[Authenticator]': """ Returns all authenticators ordered by priority """ return Authenticator.objects.all().order_by('priority') @staticmethod - def getByTag(tag=None) -> typing.List['Authenticator']: + def getByTag(tag=None) -> typing.Iterable['Authenticator']: """ Gets authenticator by tag name. Special tag name "disabled" is used to exclude customAuth @@ -184,7 +202,9 @@ class Authenticator(ManagedObjectModel, TaggingMixin): from uds.core.util.config import GlobalConfig if tag is not None: - authsList: 'QuerySet' = Authenticator.objects.filter(small_name=tag).order_by('priority', 'name') + authsList = Authenticator.objects.filter(small_name=tag).order_by( + 'priority', 'name' + ) if not authsList: authsList = Authenticator.objects.all().order_by('priority', 'name') # If disallow global login (use all auths), get just the first by priority/name @@ -194,10 +214,12 @@ class Authenticator(ManagedObjectModel, TaggingMixin): else: authsList = Authenticator.objects.all().order_by('priority', 'name') - return [auth for auth in authsList if auth.getType() and (auth.getType().isCustom() is False or tag != 'disabled')] + for auth in authsList: + if auth.getType() and (not auth.getType().isCustom() or tag != 'disabled'): + yield auth @staticmethod - def beforeDelete(sender, **kwargs): + def beforeDelete(sender, **kwargs) -> None: """ Used to invoke the Service class "Destroy" before deleting it from database. @@ -207,6 +229,7 @@ class Authenticator(ManagedObjectModel, TaggingMixin): :note: If destroy raises an exception, the deletion is not taken. """ from uds.core.util.permissions import clean + toDelete = kwargs['instance'] logger.debug('Before delete auth %s', toDelete) @@ -228,4 +251,4 @@ class Authenticator(ManagedObjectModel, TaggingMixin): # Connects a pre deletion signal to Authenticator -signals.pre_delete.connect(Authenticator.beforeDelete, sender=Authenticator) +models.signals.pre_delete.connect(Authenticator.beforeDelete, sender=Authenticator) diff --git a/server/src/uds/models/cache.py b/server/src/uds/models/cache.py index a7c4d018..7a9d172b 100644 --- a/server/src/uds/models/cache.py +++ b/server/src/uds/models/cache.py @@ -45,16 +45,23 @@ class Cache(models.Model): """ General caching model. This model is managed via uds.core.util.cache.Cache class """ + owner = models.CharField(max_length=128, db_index=True) key = models.CharField(max_length=64, primary_key=True) value = models.TextField(default='') - created = models.DateTimeField() # Date creation or validation of this entry. Set at write time + created = ( + models.DateTimeField() + ) # Date creation or validation of this entry. Set at write time validity = models.IntegerField(default=60) # Validity of this entry, in seconds + # "fake" relations declarations for type checking + objects: 'models.BaseManager[Cache]' + class Meta: """ Meta class to declare the name of the table at database """ + db_table = 'uds_utility_cache' app_label = 'uds' diff --git a/server/src/uds/models/calendar.py b/server/src/uds/models/calendar.py index 37f87aca..768f57af 100644 --- a/server/src/uds/models/calendar.py +++ b/server/src/uds/models/calendar.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2016-2020 Virtual Cable S.L. +# Copyright (c) 2016-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -51,8 +51,8 @@ class Calendar(UUIDModel, TaggingMixin): comments = models.CharField(max_length=256, default='') modified = models.DateTimeField(auto_now=True) - # Sobmodels - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[Calendar]' rules: 'models.QuerySet[CalendarRule]' calendaraction_set: 'models.QuerySet[CalendarAction]' @@ -60,10 +60,17 @@ class Calendar(UUIDModel, TaggingMixin): """ Meta class to declare db table """ + db_table = 'uds_calendar' app_label = 'uds' - def save(self, force_insert: bool = False, force_update: bool = False, using: bool = None, update_fields: bool = None): + def save( + self, + force_insert: bool = False, + force_update: bool = False, + using: bool = None, + update_fields: bool = None, + ): logger.debug('Saving calendar') res = UUIDModel.save(self, force_insert, force_update, using, update_fields) @@ -78,4 +85,6 @@ class Calendar(UUIDModel, TaggingMixin): return res def __str__(self): - return 'Calendar "{}" modified on {} with {} rules'.format(self.name, self.modified, self.rules.count()) + return 'Calendar "{}" modified on {} with {} rules'.format( + self.name, self.modified, self.rules.count() + ) diff --git a/server/src/uds/models/calendar_access.py b/server/src/uds/models/calendar_access.py index 7f77c30b..8e13e9fb 100644 --- a/server/src/uds/models/calendar_access.py +++ b/server/src/uds/models/calendar_access.py @@ -2,7 +2,7 @@ # Model based on https://github.com/llazzaro/django-scheduler # -# Copyright (c) 2016-2020 Virtual Cable S.L. +# Copyright (c) 2016-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -47,15 +47,23 @@ logger = logging.getLogger(__name__) class CalendarAccess(UUIDModel): - calendar: 'models.ForeignKey[CalendarAccess, Calendar]' = models.ForeignKey(Calendar, on_delete=models.CASCADE) - service_pool: 'models.ForeignKey[CalendarAccess, ServicePool]' = models.ForeignKey(ServicePool, related_name='calendarAccess', on_delete=models.CASCADE) + calendar: 'models.ForeignKey[CalendarAccess, Calendar]' = models.ForeignKey( + Calendar, on_delete=models.CASCADE + ) + service_pool: 'models.ForeignKey[CalendarAccess, ServicePool]' = models.ForeignKey( + ServicePool, related_name='calendarAccess', on_delete=models.CASCADE + ) access = models.CharField(max_length=8, default=states.action.DENY) priority = models.IntegerField(default=0, db_index=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[CalendarAccess]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_cal_access' ordering = ('priority',) app_label = 'uds' @@ -66,14 +74,20 @@ class CalendarAccess(UUIDModel): class CalendarAccessMeta(UUIDModel): calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE) - meta_pool = models.ForeignKey(MetaPool, related_name='calendarAccess', on_delete=models.CASCADE) + meta_pool = models.ForeignKey( + MetaPool, related_name='calendarAccess', on_delete=models.CASCADE + ) access = models.CharField(max_length=8, default=states.action.DENY) priority = models.IntegerField(default=0, db_index=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[CalendarAccessMeta]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_cal_maccess' ordering = ('priority',) app_label = 'uds' diff --git a/server/src/uds/models/calendar_action.py b/server/src/uds/models/calendar_action.py index 1aeb62f7..7f68a692 100644 --- a/server/src/uds/models/calendar_action.py +++ b/server/src/uds/models/calendar_action.py @@ -2,7 +2,7 @@ # Model based on https://github.com/llazzaro/django-scheduler # -# Copyright (c) 2016-2019 Virtual Cable S.L. +# Copyright (c) 2016-2020 Virtual Cable S.L. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -50,53 +50,171 @@ from .util import getSqlDatetime from .service_pool import ServicePool from .transport import Transport from .authenticator import Authenticator + # from django.utils.translation import ugettext_lazy as _, ugettext logger = logging.getLogger(__name__) # Current posible actions -# Each line describes: # -CALENDAR_ACTION_PUBLISH: typing.Dict[str, typing.Any] = {'id': 'PUBLISH', 'description': _('Publish'), 'params': ()} -CALENDAR_ACTION_CACHE_L1: typing.Dict[str, typing.Any] = {'id': 'CACHEL1', 'description': _('Set cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache size'), 'default': '1'},)} -CALENDAR_ACTION_CACHE_L2: typing.Dict[str, typing.Any] = {'id': 'CACHEL2', 'description': _('Set L2 cache size'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Cache L2 size'), 'default': '1'},)} -CALENDAR_ACTION_INITIAL: typing.Dict[str, typing.Any] = {'id': 'INITIAL', 'description': _('Set initial services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Initial services'), 'default': '1'},)} -CALENDAR_ACTION_MAX: typing.Dict[str, typing.Any] = {'id': 'MAX', 'description': _('Set maximum number of services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Maximum services'), 'default': '10'},)} -CALENDAR_ACTION_ADD_TRANSPORT: typing.Dict[str, typing.Any] = {'id': 'ADD_TRANSPORT', 'description': _('Add a transport'), 'params': ({'type': 'transport', 'name': 'transport', 'description': _('Transport'), 'default': ''},)} -CALENDAR_ACTION_DEL_TRANSPORT: typing.Dict[str, typing.Any] = {'id': 'REMOVE_TRANSPORT', 'description': _('Remove a transport'), 'params': ({'type': 'transport', 'name': 'transport', 'description': _('Trasport'), 'default': ''},)} -CALENDAR_ACTION_ADD_GROUP: typing.Dict[str, typing.Any] = {'id': 'ADD_GROUP', 'description': _('Add a group'), 'params': ({'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''},)} -CALENDAR_ACTION_DEL_GROUP: typing.Dict[str, typing.Any] = {'id': 'REMOVE_GROUP', 'description': _('Remove a group'), 'params': ({'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''},)} -CALENDAR_ACTION_IGNORE_UNUSED: typing.Dict[str, typing.Any] = {'id': 'IGNORE_UNUSED', 'description': _('Sets the ignore unused'), 'params': ({'type': 'bool', 'name': 'state', 'description': _('Ignore assigned and unused'), 'default': False},)} -CALENDAR_ACTION_REMOVE_USERSERVICES: typing.Dict[str, typing.Any] = {'id': 'REMOVE_USERSERVICES', 'description': _('Remove ALL assigned user service. USE WITH CAUTION!'), 'params': ()} +CALENDAR_ACTION_PUBLISH: typing.Dict[str, typing.Any] = { + 'id': 'PUBLISH', + 'description': _('Publish'), + 'params': (), +} +CALENDAR_ACTION_CACHE_L1: typing.Dict[str, typing.Any] = { + 'id': 'CACHEL1', + 'description': _('Set cache size'), + 'params': ( + { + 'type': 'numeric', + 'name': 'size', + 'description': _('Cache size'), + 'default': '1', + }, + ), +} +CALENDAR_ACTION_CACHE_L2: typing.Dict[str, typing.Any] = { + 'id': 'CACHEL2', + 'description': _('Set L2 cache size'), + 'params': ( + { + 'type': 'numeric', + 'name': 'size', + 'description': _('Cache L2 size'), + 'default': '1', + }, + ), +} +CALENDAR_ACTION_INITIAL: typing.Dict[str, typing.Any] = { + 'id': 'INITIAL', + 'description': _('Set initial services'), + 'params': ( + { + 'type': 'numeric', + 'name': 'size', + 'description': _('Initial services'), + 'default': '1', + }, + ), +} +CALENDAR_ACTION_MAX: typing.Dict[str, typing.Any] = { + 'id': 'MAX', + 'description': _('Set maximum number of services'), + 'params': ( + { + 'type': 'numeric', + 'name': 'size', + 'description': _('Maximum services'), + 'default': '10', + }, + ), +} +CALENDAR_ACTION_ADD_TRANSPORT: typing.Dict[str, typing.Any] = { + 'id': 'ADD_TRANSPORT', + 'description': _('Add a transport'), + 'params': ( + { + 'type': 'transport', + 'name': 'transport', + 'description': _('Transport'), + 'default': '', + }, + ), +} +CALENDAR_ACTION_DEL_TRANSPORT: typing.Dict[str, typing.Any] = { + 'id': 'REMOVE_TRANSPORT', + 'description': _('Remove a transport'), + 'params': ( + { + 'type': 'transport', + 'name': 'transport', + 'description': _('Trasport'), + 'default': '', + }, + ), +} +CALENDAR_ACTION_ADD_GROUP: typing.Dict[str, typing.Any] = { + 'id': 'ADD_GROUP', + 'description': _('Add a group'), + 'params': ( + {'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''}, + ), +} +CALENDAR_ACTION_DEL_GROUP: typing.Dict[str, typing.Any] = { + 'id': 'REMOVE_GROUP', + 'description': _('Remove a group'), + 'params': ( + {'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''}, + ), +} +CALENDAR_ACTION_IGNORE_UNUSED: typing.Dict[str, typing.Any] = { + 'id': 'IGNORE_UNUSED', + 'description': _('Sets the ignore unused'), + 'params': ( + { + 'type': 'bool', + 'name': 'state', + 'description': _('Ignore assigned and unused'), + 'default': False, + }, + ), +} +CALENDAR_ACTION_REMOVE_USERSERVICES: typing.Dict[str, typing.Any] = { + 'id': 'REMOVE_USERSERVICES', + 'description': _('Remove ALL assigned user service. USE WITH CAUTION!'), + 'params': (), +} -CALENDAR_ACTION_DICT: typing.Dict[str, typing.Dict] = { c['id']: c for c in ( - CALENDAR_ACTION_PUBLISH, CALENDAR_ACTION_CACHE_L1, - CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_INITIAL, - CALENDAR_ACTION_MAX, - CALENDAR_ACTION_ADD_TRANSPORT, CALENDAR_ACTION_DEL_TRANSPORT, - CALENDAR_ACTION_ADD_GROUP, CALENDAR_ACTION_DEL_GROUP, - CALENDAR_ACTION_IGNORE_UNUSED, - CALENDAR_ACTION_REMOVE_USERSERVICES -)} +CALENDAR_ACTION_DICT: typing.Dict[str, typing.Dict] = { + c['id']: c + for c in ( + CALENDAR_ACTION_PUBLISH, + CALENDAR_ACTION_CACHE_L1, + CALENDAR_ACTION_CACHE_L2, + CALENDAR_ACTION_INITIAL, + CALENDAR_ACTION_MAX, + CALENDAR_ACTION_ADD_TRANSPORT, + CALENDAR_ACTION_DEL_TRANSPORT, + CALENDAR_ACTION_ADD_GROUP, + CALENDAR_ACTION_DEL_GROUP, + CALENDAR_ACTION_IGNORE_UNUSED, + CALENDAR_ACTION_REMOVE_USERSERVICES, + ) +} class CalendarAction(UUIDModel): - calendar: Calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE) - service_pool: ServicePool = models.ForeignKey(ServicePool, on_delete=models.CASCADE) + calendar: 'models.ForeignKey[CalendarAction, Calendar]' = models.ForeignKey( + Calendar, on_delete=models.CASCADE + ) + service_pool: 'models.ForeignKey[CalendarAction, ServicePool]' = models.ForeignKey( + ServicePool, on_delete=models.CASCADE + ) action = models.CharField(max_length=64, default='') - at_start = models.BooleanField(default=False) # If false, action is done at end of event + at_start = models.BooleanField( + default=False + ) # If false, action is done at end of event events_offset = models.IntegerField(default=0) # In minutes params = models.CharField(max_length=1024, default='') # Not to be edited, just to be used as indicators for executions - last_execution = models.DateTimeField(default=None, db_index=True, null=True, blank=True) - next_execution = models.DateTimeField(default=None, db_index=True, null=True, blank=True) + last_execution = models.DateTimeField( + default=None, db_index=True, null=True, blank=True + ) + next_execution = models.DateTimeField( + default=None, db_index=True, null=True, blank=True + ) + + # "fake" declarations for type checking + objects: 'models.BaseManager[CalendarAction]' class Meta: """ Meta class to declare db table """ + db_table = 'uds_cal_action' app_label = 'uds' @@ -140,7 +258,9 @@ class CalendarAction(UUIDModel): logger.exception('error') return '(invalid action)' - def execute(self, save: bool = True) -> None: # pylint: disable=too-many-branches, too-many-statements + def execute( + self, save: bool = True + ) -> None: # pylint: disable=too-many-branches, too-many-statements """Executes the calendar action Keyword Arguments: @@ -149,7 +269,10 @@ class CalendarAction(UUIDModel): logger.debug('Executing action') # If restrained pool, skip this execution (will rery later, not updated) if not self.service_pool.isUsable(): - logger.info('Execution of task for %s due to contained state (restrained, in maintenance or removing)', self.service_pool.name) + logger.info( + 'Execution of task for %s due to contained state (restrained, in maintenance or removing)', + self.service_pool.name, + ) return self.last_execution = getSqlDatetime() @@ -182,11 +305,19 @@ class CalendarAction(UUIDModel): self.service_pool.ignores_unused = params['state'] in ('true', '1', True) elif CALENDAR_ACTION_REMOVE_USERSERVICES['id'] == self.action: # 1.- Remove usable assigned services (Ignore "creating ones", just for created) - for userService in self.service_pool.assignedUserServices().filter(state=state.State.USABLE): + for userService in self.service_pool.assignedUserServices().filter( + state=state.State.USABLE + ): userService.remove() else: - caTransports = (CALENDAR_ACTION_ADD_TRANSPORT['id'], CALENDAR_ACTION_DEL_TRANSPORT['id']) - caGroups = (CALENDAR_ACTION_ADD_GROUP['id'], CALENDAR_ACTION_DEL_GROUP['id']) + caTransports = ( + CALENDAR_ACTION_ADD_TRANSPORT['id'], + CALENDAR_ACTION_DEL_TRANSPORT['id'], + ) + caGroups = ( + CALENDAR_ACTION_ADD_GROUP['id'], + CALENDAR_ACTION_DEL_GROUP['id'], + ) if self.action in caTransports: try: t = Transport.objects.get(uuid=params['transport']) @@ -196,7 +327,9 @@ class CalendarAction(UUIDModel): self.service_pool.transports.remove(t) executed = True except Exception: - self.service_pool.log('Scheduled action not executed because transport is not available anymore') + self.service_pool.log( + 'Scheduled action not executed because transport is not available anymore' + ) saveServicePool = False elif self.action in caGroups: try: @@ -208,20 +341,27 @@ class CalendarAction(UUIDModel): self.service_pool.assignedGroups.remove(grp) executed = True except Exception: - self.service_pool.log('Scheduled action not executed because group is not available anymore') + self.service_pool.log( + 'Scheduled action not executed because group is not available anymore' + ) saveServicePool = False if executed: try: self.service_pool.log( 'Executed action {} [{}]'.format( - CALENDAR_ACTION_DICT.get(self.action, {})['description'], self.prettyParams + CALENDAR_ACTION_DICT.get(self.action, {})['description'], + self.prettyParams, ), - level=log.INFO + level=log.INFO, ) except Exception: # Avoid invalid ACTIONS errors on log - self.service_pool.log('Action {} is not a valid scheduled action! please, remove it from your list.'.format(self.action)) + self.service_pool.log( + 'Action {} is not a valid scheduled action! please, remove it from your list.'.format( + self.action + ) + ) # On save, will regenerate nextExecution if save: @@ -230,9 +370,13 @@ class CalendarAction(UUIDModel): if saveServicePool: self.service_pool.save() - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + def save( + self, force_insert=False, force_update=False, using=None, update_fields=None + ): lastExecution = self.last_execution or getSqlDatetime() - possibleNext = calendar.CalendarChecker(self.calendar).nextEvent(checkFrom=lastExecution-self.offset, startEvent=self.at_start) + possibleNext = calendar.CalendarChecker(self.calendar).nextEvent( + checkFrom=lastExecution - self.offset, startEvent=self.at_start + ) if possibleNext: self.next_execution = possibleNext + self.offset else: @@ -242,5 +386,9 @@ class CalendarAction(UUIDModel): def __str__(self): return 'Calendar of {}, last_execution = {}, next execution = {}, action = {}, params = {}'.format( - self.service_pool.name, self.last_execution, self.next_execution, self.action, self.params + self.service_pool.name, + self.last_execution, + self.next_execution, + self.action, + self.params, ) diff --git a/server/src/uds/models/calendar_rule.py b/server/src/uds/models/calendar_rule.py index 912228e6..d34ec4f8 100644 --- a/server/src/uds/models/calendar_rule.py +++ b/server/src/uds/models/calendar_rule.py @@ -54,7 +54,7 @@ freqs: typing.Tuple[typing.Tuple[str, str], ...] = ( ('MONTHLY', _('Monthly')), ('WEEKLY', _('Weekly')), ('DAILY', _('Daily')), - (WEEKDAYS, _('Weekdays')) + (WEEKDAYS, _('Weekdays')), ) frq_to_rrl: typing.Dict[str, int] = { @@ -82,10 +82,18 @@ dunit_to_mins: typing.Dict[str, int] = { 'MINUTES': 1, 'HOURS': 60, 'DAYS': 60 * 24, - 'WEEKS': 60 * 24 * 7 + 'WEEKS': 60 * 24 * 7, } -weekdays: typing.Tuple[rules.weekday, ...] = (rules.SU, rules.MO, rules.TU, rules.WE, rules.TH, rules.FR, rules.SA) +weekdays: typing.Tuple[rules.weekday, ...] = ( + rules.SU, + rules.MO, + rules.TU, + rules.WE, + rules.TH, + rules.FR, + rules.SA, +) class CalendarRule(UUIDModel): @@ -95,16 +103,24 @@ class CalendarRule(UUIDModel): start = models.DateTimeField() end = models.DateField(null=True, blank=True) frequency = models.CharField(choices=freqs, max_length=32) - interval = models.IntegerField(default=1) # If interval is for WEEKDAYS, every bit means a day of week (bit 0 = SUN, 1 = MON, ...) + interval = models.IntegerField( + default=1 + ) # If interval is for WEEKDAYS, every bit means a day of week (bit 0 = SUN, 1 = MON, ...) duration = models.IntegerField(default=0) # Duration in minutes duration_unit = models.CharField(choices=dunits, default='MINUTES', max_length=32) - calendar: 'models.ForeignKey[CalendarRule, Calendar]' = models.ForeignKey(Calendar, related_name='rules', on_delete=models.CASCADE) + calendar: 'models.ForeignKey[CalendarRule, Calendar]' = models.ForeignKey( + Calendar, related_name='rules', on_delete=models.CASCADE + ) + + # "fake" declarations for type checking + objects: 'models.BaseManager[CalendarRule]' class Meta: """ Meta class to declare db table """ + db_table = 'uds_calendar_rules' app_label = 'uds' @@ -112,7 +128,10 @@ class CalendarRule(UUIDModel): if self.interval == 0: # Fix 0 intervals self.interval = 1 - end = datetime.datetime.combine(self.end if self.end is not None else datetime.datetime.max.date(), datetime.datetime.max.time()) + end = datetime.datetime.combine( + self.end if self.end is not None else datetime.datetime.max.date(), + datetime.datetime.max.time(), + ) if self.frequency == WEEKDAYS: dw = [] @@ -122,13 +141,21 @@ class CalendarRule(UUIDModel): dw.append(weekdays[i]) l >>= 1 return rules.rrule(rules.DAILY, byweekday=dw, dtstart=self.start, until=end) - return rules.rrule(frq_to_rrl[self.frequency], interval=self.interval, dtstart=self.start, until=end) + return rules.rrule( + frq_to_rrl[self.frequency], + interval=self.interval, + dtstart=self.start, + until=end, + ) def as_rrule_end(self) -> rules.rrule: if self.interval == 0: # Fix 0 intervals self.interval = 1 - end = datetime.datetime.combine(self.end if self.end is not None else datetime.datetime.max.date(), datetime.datetime.max.time()) + end = datetime.datetime.combine( + self.end if self.end is not None else datetime.datetime.max.date(), + datetime.datetime.max.time(), + ) if self.frequency == WEEKDAYS: dw = [] @@ -137,8 +164,19 @@ class CalendarRule(UUIDModel): if l & 1 == 1: dw.append(weekdays[i]) l >>= 1 - return rules.rrule(rules.DAILY, byweekday=dw, dtstart=self.start + datetime.timedelta(minutes=self.duration_as_minutes), until=end) - return rules.rrule(frq_to_rrl[self.frequency], interval=self.interval, dtstart=self.start + datetime.timedelta(minutes=self.duration_as_minutes), until=end) + return rules.rrule( + rules.DAILY, + byweekday=dw, + dtstart=self.start + + datetime.timedelta(minutes=self.duration_as_minutes), + until=end, + ) + return rules.rrule( + frq_to_rrl[self.frequency], + interval=self.interval, + dtstart=self.start + datetime.timedelta(minutes=self.duration_as_minutes), + until=end, + ) @property def frequency_as_minutes(self) -> int: @@ -150,7 +188,9 @@ class CalendarRule(UUIDModel): def duration_as_minutes(self) -> int: return dunit_to_mins.get(self.duration_unit, 1) * self.duration - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + def save( + self, force_insert=False, force_update=False, using=None, update_fields=None + ): logger.debug('Saving...') self.calendar.modified = getSqlDatetime() @@ -160,4 +200,11 @@ class CalendarRule(UUIDModel): return res def __str__(self): - return 'Rule {0}: {1}-{2}, {3}, Interval: {4}, duration: {5}'.format(self.name, self.start, self.end, self.frequency, self.interval, self.duration) + return 'Rule {0}: {1}-{2}, {3}, Interval: {4}, duration: {5}'.format( + self.name, + self.start, + self.end, + self.frequency, + self.interval, + self.duration, + ) diff --git a/server/src/uds/models/config.py b/server/src/uds/models/config.py index 5b75e829..3e8d014c 100644 --- a/server/src/uds/models/config.py +++ b/server/src/uds/models/config.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -43,6 +43,7 @@ class Config(models.Model): General configuration values model. Used to store global and specific modules configuration values. This model is managed via uds.core.util.config.Config class """ + section = models.CharField(max_length=128, db_index=True) key = models.CharField(max_length=64, db_index=True) value = models.TextField(default='') @@ -50,10 +51,14 @@ class Config(models.Model): long = models.BooleanField(default=False) field_type = models.IntegerField(default=-1) + # "fake" declarations for type checking + objects: 'models.BaseManager[Config]' + class Meta: """ Meta class to declare default order and unique multiple field index """ + db_table = 'uds_configuration' unique_together = (('section', 'key'),) app_label = 'uds' diff --git a/server/src/uds/models/dbfile.py b/server/src/uds/models/dbfile.py index 6b611174..b8571ed5 100644 --- a/server/src/uds/models/dbfile.py +++ b/server/src/uds/models/dbfile.py @@ -2,7 +2,7 @@ # Model based on https://github.com/llazzaro/django-scheduler # -# Copyright (c) 2016-2019 Virtual Cable S.L. +# Copyright (c) 2016-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -31,28 +31,33 @@ """ .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com """ +import codecs import logging -import typing from django.db import models -from uds.core.util import encoders from .uuid_model import UUIDModel logger = logging.getLogger(__name__) + class DBFile(UUIDModel): - owner = models.CharField(max_length=32, default='') # Not indexed, used for cleanups only + + # Not indexed, used for cleanups only + owner = models.CharField(max_length=32, default='') name = models.CharField(max_length=255, primary_key=True) content = models.TextField(blank=True) size = models.IntegerField(default=0) created = models.DateTimeField() modified = models.DateTimeField() + # "fake" declarations for type checking + objects: 'models.BaseManager[DBFile]' + @property def data(self) -> bytes: try: - return typing.cast(bytes, encoders.decode(encoders.decode(self.content, 'base64'), 'zip')) + return codecs.decode(codecs.decode(self.content.encode(), 'base64'), 'zip') except Exception: logger.error('DBFile %s has errors and cannot be used', self.name) try: @@ -65,7 +70,9 @@ class DBFile(UUIDModel): @data.setter def data(self, value: bytes): self.size = len(value) - self.content = typing.cast(str, encoders.encode(encoders.encode(value, 'zip'), 'base64', asText=True)) + self.content = codecs.encode(codecs.encode(value, 'zip'), 'base64').decode() - def __str__(self): - return 'File: {} {} {} {}'.format(self.name, self.size, self.created, self.modified) + def __str__(self) -> str: + return 'File: {} {} {} {}'.format( + self.name, self.size, self.created, self.modified + ) diff --git a/server/src/uds/models/delayed_task.py b/server/src/uds/models/delayed_task.py index 5c800412..aa6bf23c 100644 --- a/server/src/uds/models/delayed_task.py +++ b/server/src/uds/models/delayed_task.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2020 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -55,6 +55,9 @@ class DelayedTask(models.Model): execution_delay = models.PositiveIntegerField() execution_time = models.DateTimeField(db_index=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[DelayedTask]' + class Meta: """ Meta class to declare default order and unique multiple field index diff --git a/server/src/uds/models/group.py b/server/src/uds/models/group.py index 468bcbbf..d75dec2d 100644 --- a/server/src/uds/models/group.py +++ b/server/src/uds/models/group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -54,8 +54,10 @@ class Group(UUIDModel): """ This class represents a group, associated with one authenticator """ - # pylint: disable=model-missing-unicode - manager: 'models.ForeignKey[Group, Authenticator]' = UnsavedForeignKey(Authenticator, on_delete=models.CASCADE, related_name='groups') + + manager: 'models.ForeignKey[Group, Authenticator]' = UnsavedForeignKey( + Authenticator, on_delete=models.CASCADE, related_name='groups' + ) name = models.CharField(max_length=128, db_index=True) state = models.CharField(max_length=1, default=State.ACTIVE, db_index=True) comments = models.CharField(max_length=256, default='') @@ -65,10 +67,14 @@ class Group(UUIDModel): groups = models.ManyToManyField('self', symmetrical=False) created = models.DateTimeField(default=getSqlDatetime, blank=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[Group]' + class Meta: """ Meta class to declare default order and unique multiple field index """ + unique_together = (("manager", "name"),) ordering = ('name',) app_label = 'uds' @@ -87,12 +93,16 @@ class Group(UUIDModel): def __str__(self) -> str: if self.is_meta: - return "Meta group {}(id:{}) with groups {}".format(self.name, self.id, list(self.groups.all())) + return "Meta group {}(id:{}) with groups {}".format( + self.name, self.id, list(self.groups.all()) + ) - return "Group {}(id:{}) from auth {}".format(self.name, self.id, self.manager.name) + return "Group {}(id:{}) from auth {}".format( + self.name, self.id, self.manager.name + ) @staticmethod - def beforeDelete(sender, **kwargs): + def beforeDelete(sender, **kwargs) -> None: """ Used to invoke the Service class "Destroy" before deleting it from database. diff --git a/server/src/uds/models/image.py b/server/src/uds/models/image.py index c57b9f90..f1187a88 100644 --- a/server/src/uds/models/image.py +++ b/server/src/uds/models/image.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -29,6 +29,7 @@ .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com """ import io +import codecs import logging import typing @@ -38,8 +39,6 @@ import PIL.Image from django.db import models from django.http import HttpResponse -from uds.core.util import encoders - from .uuid_model import UUIDModel from .util import getSqlDatetime @@ -54,30 +53,37 @@ class Image(UUIDModel): This is intended for small images (i will limit them to 128x128), so storing at db is fine """ + MAX_IMAGE_SIZE = (128, 128) THUMBNAIL_SIZE = (48, 48) name = models.CharField(max_length=128, unique=True, db_index=True) - stamp = models.DateTimeField() # Date creation or validation of this entry. Set at write time + stamp = ( + models.DateTimeField() + ) # Date creation or validation of this entry. Set at write time data = models.BinaryField() # Image storage thumb = models.BinaryField() # Thumbnail, very small width = models.IntegerField(default=0) height = models.IntegerField(default=0) + # "fake" declarations for type checking + objects: 'models.BaseManager[Image]' + class Meta: """ Meta class to declare the name of the table at database """ + db_table = 'uds_images' app_label = 'uds' @staticmethod def encode64(data: bytes) -> str: - return typing.cast(str, encoders.encode(data, 'base64', asText=True)).replace('\n', '') # Removes \n + return codecs.encode(data, 'base64').decode().replace('\n', '') @staticmethod def decode64(data64: str) -> bytes: - return typing.cast(bytes, encoders.decode(data64, 'base64')) + return codecs.decode(data64.encode(), 'base64') @staticmethod def prepareForDb(data: bytes) -> bytes: @@ -168,12 +174,16 @@ class Image(UUIDModel): def thumbnailResponse(self) -> HttpResponse: return HttpResponse(self.thumb, content_type='image/png') - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + def save( + self, force_insert=False, force_update=False, using=None, update_fields=None + ): self.stamp = getSqlDatetime() return super().save(force_insert, force_update, using, update_fields) def __str__(self): - return 'Image id {}, name {}, {} bytes, {} bytes thumb'.format(self.id, self.name, len(self.data), len(self.thumb)) + return 'Image id {}, name {}, {} bytes, {} bytes thumb'.format( + self.id, self.name, len(self.data), len(self.thumb) + ) @staticmethod def beforeDelete(sender, **kwargs): diff --git a/server/src/uds/models/log.py b/server/src/uds/models/log.py index 52c3e41b..e96e7336 100644 --- a/server/src/uds/models/log.py +++ b/server/src/uds/models/log.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -54,12 +54,23 @@ class Log(models.Model): level = models.PositiveIntegerField(default=0, db_index=True) data = models.CharField(max_length=255, default='') + # "fake" declarations for type checking + objects: 'models.BaseManager[Log]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_log' app_label = 'uds' def __str__(self) -> str: - return "Log of {}({}): {} - {} - {} - {}".format(self.owner_type, self.owner_id, self.created, self.source, self.level, self.data) + return "Log of {}({}): {} - {} - {} - {}".format( + self.owner_type, + self.owner_id, + self.created, + self.source, + self.level, + self.data, + ) diff --git a/server/src/uds/models/managed_object_model.py b/server/src/uds/models/managed_object_model.py index efc3a54d..5796366c 100644 --- a/server/src/uds/models/managed_object_model.py +++ b/server/src/uds/models/managed_object_model.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -49,6 +49,7 @@ class ManagedObjectModel(UUIDModel): Base abstract model for models that are top level Managed Objects (such as Authenticator, Transport, OSManager, Provider, Service ...) """ + name = models.CharField(max_length=128, unique=False, db_index=True) data_type = models.CharField(max_length=128) data = models.TextField(default='') @@ -60,6 +61,7 @@ class ManagedObjectModel(UUIDModel): """ Defines this is an abstract class """ + abstract = True def getEnvironment(self) -> Environment: @@ -79,7 +81,9 @@ class ManagedObjectModel(UUIDModel): self._cachedInstance = None # Ensures returns correct value on getInstance - def getInstance(self, values: typing.Optional[typing.Dict[str, str]] = None) -> Module: + def getInstance( + self, values: typing.Optional[typing.Dict[str, str]] = None + ) -> Module: """ Instantiates the object this record contains. @@ -95,7 +99,6 @@ class ManagedObjectModel(UUIDModel): Can be overriden """ if self._cachedInstance and values is None: - # logger.debug('Got cached instance instead of deserializing a new one for {}'.format(self.name)) return self._cachedInstance klass = self.getType() @@ -112,7 +115,9 @@ class ManagedObjectModel(UUIDModel): Returns the type of self (as python type) Must be overriden!!! """ - raise NotImplementedError('getType has not been implemented for {}'.format(self.__class__)) + raise NotImplementedError( + 'getType has not been implemented for {}'.format(self.__class__) + ) def isOfType(self, type_: str) -> bool: """ diff --git a/server/src/uds/models/meta_pool.py b/server/src/uds/models/meta_pool.py index 67ee4b0c..928c2760 100644 --- a/server/src/uds/models/meta_pool.py +++ b/server/src/uds/models/meta_pool.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2018-2019 Virtual Cable S.L. +# Copyright (c) 2018-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -55,7 +55,6 @@ if typing.TYPE_CHECKING: from uds.models import User, CalendarAccessMeta - logger = logging.getLogger(__name__) @@ -63,6 +62,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore """ A meta pool is a pool that has pool members """ + # Type of pool selection for meta pool ROUND_ROBIN_POOL = 0 PRIORITY_POOL = 1 @@ -78,9 +78,23 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore short_name = models.CharField(max_length=32, default='') comments = models.CharField(max_length=256, default='') visible = models.BooleanField(default=True) - image = models.ForeignKey(Image, null=True, blank=True, related_name='metaPools', on_delete=models.SET_NULL) - servicesPoolGroup = models.ForeignKey(ServicePoolGroup, null=True, blank=True, related_name='metaPools', on_delete=models.SET_NULL) - assignedGroups = models.ManyToManyField(Group, related_name='metaPools', db_table='uds__meta_grps') + image = models.ForeignKey( + Image, + null=True, + blank=True, + related_name='metaPools', + on_delete=models.SET_NULL, + ) + servicesPoolGroup = models.ForeignKey( + ServicePoolGroup, + null=True, + blank=True, + related_name='metaPools', + on_delete=models.SET_NULL, + ) + assignedGroups = models.ManyToManyField( + Group, related_name='metaPools', db_table='uds__meta_grps' + ) # Message if access denied calendar_message = models.CharField(default='', max_length=256) @@ -90,7 +104,8 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore # Pool selection policy policy = models.SmallIntegerField(default=0) - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[MetaPool]' calendarAccess: 'models.QuerySet[CalendarAccessMeta]' members: 'models.QuerySet[MetaPoolMember]' @@ -98,6 +113,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore """ Meta class to declare the name of the table at database """ + db_table = 'uds__pool_meta' app_label = 'uds' @@ -115,7 +131,9 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore maintenance += 1 return total == maintenance - def isAccessAllowed(self, chkDateTime: typing.Optional['datetime.datetime'] = None) -> bool: + def isAccessAllowed( + self, chkDateTime: typing.Optional['datetime.datetime'] = None + ) -> bool: """ Checks if the access for a service pool is allowed or not (based esclusively on associated calendars) """ @@ -133,13 +151,17 @@ class MetaPool(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 is not None, self.name + ) if self.short_name.strip(): return self.short_name return self.name @staticmethod - def getForGroups(groups: typing.Iterable['Group'], user: typing.Optional['User'] = None) -> 'QuerySet[MetaPool]': + def getForGroups( + groups: typing.Iterable['Group'], user: typing.Optional['User'] = None + ) -> 'QuerySet[MetaPool]': """ Return deployed services with publications for the groups requested. @@ -153,7 +175,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore meta = MetaPool.objects.filter( assignedGroups__in=groups, assignedGroups__state=states.group.ACTIVE, - visible=True + visible=True, ).prefetch_related( 'servicesPoolGroup', 'servicesPoolGroup__image', @@ -168,7 +190,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore 'calendarAccess', 'calendarAccess__calendar', 'calendarAccess__calendar__rules', - 'image' + 'image', ) if user: meta = meta.annotate( @@ -177,8 +199,8 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore filter=models.Q( members__pool__userServices__user=user, members__pool__userServices__in_use=True, - members__pool__userServices__state__in=states.userService.USABLE - ) + members__pool__userServices__state__in=states.userService.USABLE, + ), ) ) # TODO: Maybe we can exclude non "usable" metapools (all his pools are in maintenance mode?) @@ -196,6 +218,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore :note: If destroy raises an exception, the deletion is not taken. """ from uds.core.util.permissions import clean + toDelete = kwargs['instance'] # Clears related logs @@ -215,8 +238,12 @@ signals.pre_delete.connect(MetaPool.beforeDelete, sender=MetaPool) class MetaPoolMember(UUIDModel): - pool: 'models.ForeignKey[MetaPoolMember, ServicePool]' = models.ForeignKey(ServicePool, related_name='memberOfMeta', on_delete=models.CASCADE) - meta_pool: 'models.ForeignKey[MetaPoolMember, MetaPool]' = models.ForeignKey(MetaPool, related_name='members', on_delete=models.CASCADE) + pool: 'models.ForeignKey[MetaPoolMember, ServicePool]' = models.ForeignKey( + ServicePool, related_name='memberOfMeta', on_delete=models.CASCADE + ) + meta_pool: 'models.ForeignKey[MetaPoolMember, MetaPool]' = models.ForeignKey( + MetaPool, related_name='members', on_delete=models.CASCADE + ) priority = models.PositiveIntegerField(default=0) enabled = models.BooleanField(default=True) @@ -224,8 +251,11 @@ class MetaPoolMember(UUIDModel): """ Meta class to declare the name of the table at database """ + db_table = 'uds__meta_pool_member' app_label = 'uds' def __str__(self) -> str: - return '{}/{} {} {}'.format(self.pool.name, self.meta_pool.name, self.priority, self.enabled) + return '{}/{} {} {}'.format( + self.pool.name, self.meta_pool.name, self.priority, self.enabled + ) diff --git a/server/src/uds/models/network.py b/server/src/uds/models/network.py index 89b35e47..d4d1e59c 100644 --- a/server/src/uds/models/network.py +++ b/server/src/uds/models/network.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -49,17 +49,23 @@ class Network(UUIDModel, TaggingMixin): # type: ignore """ This model is used for keeping information of networks associated with transports (right now, just transports..) """ - # pylint: disable=model-missing-unicode + name = models.CharField(max_length=64, unique=True) net_start = models.BigIntegerField(db_index=True) net_end = models.BigIntegerField(db_index=True) net_string = models.CharField(max_length=128, default='') - transports = models.ManyToManyField(Transport, related_name='networks', db_table='uds_net_trans') + transports = models.ManyToManyField( + Transport, related_name='networks', db_table='uds_net_trans' + ) + + # "fake" declarations for type checking + objects: 'models.BaseManager[Network]' class Meta(UUIDModel.Meta): """ Meta class to declare default order """ + ordering = ('name',) app_label = 'uds' @@ -82,7 +88,9 @@ class Network(UUIDModel, TaggingMixin): # type: ignore netEnd: Network end """ nr = net.networkFromString(netRange) - return Network.objects.create(name=name, net_start=nr[0], net_end=nr[1], net_string=netRange) + return Network.objects.create( + name=name, net_start=nr[0], net_end=nr[1], net_string=netRange + ) @property def netStart(self) -> str: @@ -123,11 +131,17 @@ class Network(UUIDModel, TaggingMixin): # type: ignore self.save() def __str__(self) -> str: - return u'Network {} ({}) from {} to {}'.format(self.name, self.net_string, net.longToIp(self.net_start), net.longToIp(self.net_end)) + return u'Network {} ({}) from {} to {}'.format( + self.name, + self.net_string, + net.longToIp(self.net_start), + net.longToIp(self.net_end), + ) @staticmethod def beforeDelete(sender, **kwargs) -> None: from uds.core.util.permissions import clean + toDelete = kwargs['instance'] logger.debug('Before delete auth %s', toDelete) @@ -135,5 +149,6 @@ class Network(UUIDModel, TaggingMixin): # type: ignore # Clears related permissions clean(toDelete) + # Connects a pre deletion signal to Authenticator models.signals.pre_delete.connect(Network.beforeDelete, sender=Network) diff --git a/server/src/uds/models/os_manager.py b/server/src/uds/models/os_manager.py index 6319dcdf..43f615eb 100644 --- a/server/src/uds/models/os_manager.py +++ b/server/src/uds/models/os_manager.py @@ -51,17 +51,21 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore An OS Manager represents a manager for responding requests for agents inside services. """ - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[OSManager]' deployedServices: 'models.QuerySet[ServicePool]' class Meta(ManagedObjectModel.Meta): """ Meta class to declare default order """ + ordering = ('name',) app_label = 'uds' - def getInstance(self, values: typing.Optional[typing.Dict[str, str]] = None) -> 'osmanagers.OSManager': + def getInstance( + self, values: typing.Optional[typing.Dict[str, str]] = None + ) -> 'osmanagers.OSManager': return typing.cast('osmanagers.OSManager', super().getInstance(values=values)) def getType(self) -> typing.Type['osmanagers.OSManager']: @@ -76,7 +80,7 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore :note: We only need to get info from this, not access specific data (class specific info) """ # We only need to get info from this, not access specific data (class specific info) - from uds.core import osmanagers # pylint: disable=redefined-outer-name + from uds.core import osmanagers type_ = osmanagers.factory().lookup(self.data_type) if type_: @@ -100,7 +104,7 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore self.delete() return True - def __str__(self): + def __str__(self) -> str: return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id) @staticmethod @@ -115,7 +119,9 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore """ toDelete = kwargs['instance'] if toDelete.deployedServices.count() > 0: - raise IntegrityError('Can\'t remove os managers with assigned deployed services') + raise IntegrityError( + 'Can\'t remove os managers with assigned deployed services' + ) # Only tries to get instance if data is not empty if toDelete.data != '': s = toDelete.getInstance() diff --git a/server/src/uds/models/permissions.py b/server/src/uds/models/permissions.py index 0ee4fa0a..d5a22a5b 100644 --- a/server/src/uds/models/permissions.py +++ b/server/src/uds/models/permissions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -49,6 +49,7 @@ class Permissions(UUIDModel): """ An OS Manager represents a manager for responding requests for agents inside services. """ + # Allowed permissions PERMISSION_NONE = 0 PERMISSION_READ = 32 @@ -56,23 +57,42 @@ class Permissions(UUIDModel): PERMISSION_ALL = 96 created = models.DateTimeField(db_index=True) - ends = models.DateTimeField(db_index=True, null=True, blank=True, default=None) # Future "permisions ends at this moment", not assigned right now + ends = models.DateTimeField( + db_index=True, null=True, blank=True, default=None + ) # Future "permisions ends at this moment", not assigned right now - user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='permissions', null=True, blank=True, default=None) - group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='permissions', null=True, blank=True, default=None) + user = models.ForeignKey( + User, + on_delete=models.CASCADE, + related_name='permissions', + null=True, + blank=True, + default=None, + ) + group = models.ForeignKey( + Group, + on_delete=models.CASCADE, + related_name='permissions', + null=True, + blank=True, + default=None, + ) object_type = models.SmallIntegerField(default=-1, db_index=True) object_id = models.IntegerField(default=None, db_index=True, null=True, blank=True) permission = models.SmallIntegerField(default=PERMISSION_NONE, db_index=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[Permissions]' + @staticmethod def permissionAsString(perm: int) -> str: return { Permissions.PERMISSION_NONE: _('None'), Permissions.PERMISSION_READ: _('Read'), Permissions.PERMISSION_MANAGEMENT: _('Manage'), - Permissions.PERMISSION_ALL: _('All') + Permissions.PERMISSION_ALL: _('All'), }.get(perm, _('None')) @staticmethod @@ -104,13 +124,22 @@ class Permissions(UUIDModel): q = Q(group=group) try: - existing = Permissions.objects.filter(q, object_type=object_type, object_id=object_id)[0] + existing = Permissions.objects.filter( + q, object_type=object_type, object_id=object_id + )[0] existing.permission = permission existing.save() return existing except Exception: # Does not exists - return Permissions.objects.create(created=getSqlDatetime(), ends=None, user=user, group=group, - object_type=object_type, object_id=object_id, permission=permission) + return Permissions.objects.create( + created=getSqlDatetime(), + ends=None, + user=user, + group=group, + object_type=object_type, + object_id=object_id, + permission=permission, + ) @staticmethod def getPermissions(**kwargs) -> int: @@ -141,7 +170,7 @@ class Permissions(UUIDModel): perm = Permissions.objects.filter( Q(object_type=object_type), Q(object_id=None) | Q(object_id=object_id), - q + q, ).order_by('-permission')[0] logger.debug('Got permission %s', perm) return perm.permission @@ -157,7 +186,9 @@ class Permissions(UUIDModel): @staticmethod def cleanPermissions(object_type, object_id) -> None: - Permissions.objects.filter(object_type=object_type, object_id=object_id).delete() + Permissions.objects.filter( + object_type=object_type, object_id=object_id + ).delete() @staticmethod def cleanUserPermissions(user) -> None: @@ -173,5 +204,10 @@ class Permissions(UUIDModel): def __str__(self) -> str: return 'Permission {}, user {} group {} object_type {} object_id {} permission {}'.format( - self.uuid, self.user, self.group, self.object_type, self.object_id, Permissions.permissionAsString(self.permission) + self.uuid, + self.user, + self.group, + self.object_type, + self.object_id, + Permissions.permissionAsString(self.permission), ) diff --git a/server/src/uds/models/provider.py b/server/src/uds/models/provider.py index ebac1747..34ea4afc 100644 --- a/server/src/uds/models/provider.py +++ b/server/src/uds/models/provider.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -52,33 +52,43 @@ class Provider(ManagedObjectModel, TaggingMixin): # type: ignore A Provider represents the Service provider itself, (i.e. a KVM Server or a Terminal Server) """ - # pylint: disable=model-missing-unicode maintenance_mode = models.BooleanField(default=False, db_index=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[Provider]' + class Meta(ManagedObjectModel.Meta): """ Meta class to declare default order """ + ordering = ('name',) app_label = 'uds' def getType(self) -> typing.Type['services.ServiceProvider']: - ''' + """ Get the type of the object this record represents. The type is Python type, it obtains this type from ServiceProviderFactory and associated record field. Returns: The python type for this record object - ''' + """ from uds.core import services # pylint: disable=redefined-outer-name + type_ = services.factory().lookup(self.data_type) if type_: return type_ - return services.ServiceProvider # Basic Service implementation. Will fail if we try to use it, but will be ok to reference it + return ( + services.ServiceProvider + ) # Basic Service implementation. Will fail if we try to use it, but will be ok to reference it - def getInstance(self, values: typing.Optional[typing.Dict[str, str]] = None) -> 'services.ServiceProvider': - prov: services.ServiceProvider = typing.cast('services.ServiceProvider', super().getInstance(values=values)) + def getInstance( + self, values: typing.Optional[typing.Dict[str, str]] = None + ) -> 'services.ServiceProvider': + prov: services.ServiceProvider = typing.cast( + 'services.ServiceProvider', super().getInstance(values=values) + ) # Set uuid prov.setUuid(self.uuid) return prov diff --git a/server/src/uds/models/proxy.py b/server/src/uds/models/proxy.py index 48872c70..c8d39eff 100644 --- a/server/src/uds/models/proxy.py +++ b/server/src/uds/models/proxy.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -47,6 +47,7 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore """ Proxy DB model """ + name = models.CharField(max_length=128, unique=False, db_index=True) comments = models.CharField(max_length=256) @@ -55,16 +56,22 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore ssl = models.BooleanField(default=True) check_cert = models.BooleanField(default=False) + # "fake" declarations for type checking + objects: 'models.BaseManager[Proxy]' + class Meta: """ Meta class to declare the name of the table at database """ + db_table = 'uds_proxies' app_label = 'uds' @property def url(self) -> str: - return 'http{}://{}:{}'.format('s' if self.ssl is True else '', self.host, self.port) + return 'http{}://{}:{}'.format( + 's' if self.ssl is True else '', self.host, self.port + ) @property def proxyRequestUrl(self) -> str: @@ -74,10 +81,10 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore def testServerUrl(self) -> str: return self.url + "/testServer" - def doProxyRequest(self, url, data: typing.Optional[typing.Any] = None, timeout: int = 5) -> requests.Response: - d = { - 'url': url - } + def doProxyRequest( + self, url, data: typing.Optional[typing.Any] = None, timeout: int = 5 + ) -> requests.Response: + d = {'url': url} if data is not None: d['data'] = data @@ -86,17 +93,15 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore data=json.dumps(d), headers={'content-type': 'application/json'}, verify=self.check_cert, - timeout=timeout + timeout=timeout, ) def doTestServer(self, ip: str, port: typing.Union[str, int], timeout=5) -> bool: try: - url = self.testServerUrl + '?host={}&port={}&timeout={}'.format(ip, port, timeout) - r = requests.get( - url, - verify=self.check_cert, - timeout=timeout + url = self.testServerUrl + '?host={}&port={}&timeout={}'.format( + ip, port, timeout ) + r = requests.get(url, verify=self.check_cert, timeout=timeout) if r.status_code == 302: # Proxy returns "Found" for a success test return True # Else returns 404 diff --git a/server/src/uds/models/scheduler.py b/server/src/uds/models/scheduler.py index 05ed14d3..2d1bd3dc 100644 --- a/server/src/uds/models/scheduler.py +++ b/server/src/uds/models/scheduler.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -34,7 +34,6 @@ import logging import typing from django.db import models -from django.db.models import signals from uds.core.util.state import State from uds.core.environment import Environment @@ -72,12 +71,16 @@ class Scheduler(models.Model): state = models.CharField(max_length=1, default=State.FOR_EXECUTE, db_index=True) # primary key id declaration (for type checking) - id: int + + # "fake" declarations for type checking + objects: 'models.BaseManager[Scheduler]' + id: int # Primary key (Autogenerated by model) class Meta: """ Meta class to declare default order and unique multiple field index """ + app_label = 'uds' def getEnvironment(self) -> Environment: @@ -99,7 +102,7 @@ class Scheduler(models.Model): return None @staticmethod - def beforeDelete(sender, **kwargs): + def beforeDelete(sender, **kwargs) -> None: """ Used to remove environment for sheduled task """ @@ -107,9 +110,11 @@ class Scheduler(models.Model): logger.debug('Deleting sheduled task %s', toDelete) toDelete.getEnvironment().clearRelatedData() - def __str__(self): - return 'Scheduled task {}, every {}, last execution at {}, state = {}'.format(self.name, self.frecuency, self.last_execution, self.state) + def __str__(self) -> str: + return 'Scheduled task {}, every {}, last execution at {}, state = {}'.format( + self.name, self.frecuency, self.last_execution, self.state + ) # Connects a pre deletion signal to Scheduler -signals.pre_delete.connect(Scheduler.beforeDelete, sender=Scheduler) +models.signals.pre_delete.connect(Scheduler.beforeDelete, sender=Scheduler) diff --git a/server/src/uds/models/service.py b/server/src/uds/models/service.py index 42eb75ae..6ffc09ce 100644 --- a/server/src/uds/models/service.py +++ b/server/src/uds/models/service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -58,30 +58,38 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore A Service represents an specidied type of service offered to final users, with it configuration (i.e. a KVM Base Machine for cloning or a Terminal Server configuration). """ - provider: 'models.ForeignKey[Service, Provider]' = models.ForeignKey(Provider, related_name='services', on_delete=models.CASCADE) + + provider: 'models.ForeignKey[Service, Provider]' = models.ForeignKey( + Provider, related_name='services', on_delete=models.CASCADE + ) # Proxy for this service - proxy: 'models.ForeignKey[Service, Proxy]' = models.ForeignKey(Proxy, null=True, blank=True, related_name='services', on_delete=models.CASCADE) + proxy: 'models.ForeignKey[Service, Proxy]' = models.ForeignKey( + Proxy, null=True, blank=True, related_name='services', on_delete=models.CASCADE + ) - token = models.CharField(max_length=32, default=None, null=True, blank=True, unique=True) + token = models.CharField( + max_length=32, default=None, null=True, blank=True, unique=True + ) _cachedInstance: typing.Optional['services.Service'] = None - class Meta(ManagedObjectModel.Meta): # pylint: disable=too-few-public-methods + # "fake" declarations for type checking + objects: 'models.BaseManager[Service]' + + class Meta(ManagedObjectModel.Meta): """ Meta class to declare default order and unique multiple field index """ + ordering = ('name',) unique_together = (("provider", "name"),) app_label = 'uds' - def getEnvironment(self): + def getEnvironment(self) -> Environment: """ Returns an environment valid for the record this object represents """ - # from uds.core.util.unique_mac_generator import UniqueMacGenerator - # from uds.core.util.unique_name_generator import UniqueNameGenerator - return Environment.getEnvForTableElement( self._meta.verbose_name, self.id, @@ -89,10 +97,10 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore 'mac': unique.UniqueMacGenerator, 'name': unique.UniqueNameGenerator, 'id': unique.UniqueGIDGenerator, - } + }, ) - def getInstance(self, values=None) -> 'services.Service': + def getInstance(self, values: typing.Optional[typing.Dict[str, str]] = None) -> 'services.Service': """ Instantiates the object this record contains. @@ -118,7 +126,11 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore obj = sType(self.getEnvironment(), prov, values, uuid=self.uuid) self.deserialize(obj, values) else: - raise Exception('Service type of {} is not recogniced by provider {}'.format(self.data_type, prov)) + raise Exception( + 'Service type of {} is not recogniced by provider {}'.format( + self.data_type, prov + ) + ) self._cachedInstance = obj @@ -140,22 +152,28 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore if type_: return type_ - raise Exception('Service type of {} is not recogniced by provider {}'.format(self.data_type, prov)) + raise Exception( + 'Service type of {} is not recogniced by provider {}'.format( + self.data_type, prov + ) + ) def isInMaintenance(self) -> bool: # orphaned services? return self.provider.isInMaintenance() if self.provider else True - def testServer(self, host: str, port: typing.Union[str, int], timeout: int = 4) -> bool: - if self.proxy is not None: + def testServer( + self, host: str, port: typing.Union[str, int], timeout: int = 4 + ) -> bool: + if self.proxy: return self.proxy.doTestServer(host, port, timeout) return connection.testServer(host, port, timeout) - def __str__(self): + def __str__(self) -> str: return '{} of type {} (id:{})'.format(self.name, self.data_type, self.id) @staticmethod - def beforeDelete(sender, **kwargs): + def beforeDelete(sender, **kwargs) -> None: """ Used to invoke the Service class "Destroy" before deleting it from database. @@ -165,6 +183,7 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore :note: If destroy raises an exception, the deletion is not taken. """ from uds.core.util.permissions import clean + toDelete = kwargs['instance'] logger.debug('Before delete service %s', toDelete) @@ -180,5 +199,6 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore # Clears related permissions clean(toDelete) + # : Connects a pre deletion signal to Service models.signals.pre_delete.connect(Service.beforeDelete, sender=Service) diff --git a/server/src/uds/models/service_pool.py b/server/src/uds/models/service_pool.py index dd692dd7..d579f6ae 100644 --- a/server/src/uds/models/service_pool.py +++ b/server/src/uds/models/service_pool.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -61,7 +61,15 @@ from .util import getSqlDatetime # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: - from uds.models import UserService, ServicePoolPublication, User, Group, Proxy, MetaPoolMember, CalendarAccess + from uds.models import ( + UserService, + ServicePoolPublication, + User, + Group, + Proxy, + MetaPoolMember, + CalendarAccess, + ) logger = logging.getLogger(__name__) @@ -70,14 +78,33 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore """ A deployed service is the Service produced element that is assigned finally to an user (i.e. a Virtual Machine, etc..) """ + name = models.CharField(max_length=128, default='') short_name = models.CharField(max_length=32, default='') comments = models.CharField(max_length=256, default='') - service: 'models.ForeignKey[ServicePool, Service]' = models.ForeignKey(Service, null=True, blank=True, related_name='deployedServices', on_delete=models.CASCADE) - osmanager: 'models.ForeignKey[ServicePool, OSManager]' = models.ForeignKey(OSManager, null=True, blank=True, 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') - state = models.CharField(max_length=1, default=states.servicePool.ACTIVE, db_index=True) + service: 'models.ForeignKey[ServicePool, Service]' = models.ForeignKey( + Service, + null=True, + blank=True, + related_name='deployedServices', + on_delete=models.CASCADE, + ) + osmanager: 'models.ForeignKey[ServicePool, OSManager]' = models.ForeignKey( + OSManager, + null=True, + blank=True, + 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' + ) + state = models.CharField( + max_length=1, default=states.servicePool.ACTIVE, db_index=True + ) state_date = models.DateTimeField(default=NEVER) show_transports = models.BooleanField(default=True) visible = models.BooleanField(default=True) @@ -86,18 +113,37 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore ignores_unused = models.BooleanField(default=False) - image: 'models.ForeignKey[ServicePool, Image]' = models.ForeignKey(Image, null=True, blank=True, related_name='deployedServices', on_delete=models.SET_NULL) + image: 'models.ForeignKey[ServicePool, Image]' = models.ForeignKey( + Image, + null=True, + blank=True, + related_name='deployedServices', + on_delete=models.SET_NULL, + ) - servicesPoolGroup: 'models.ForeignKey[ServicePool, ServicePoolGroup]' = models.ForeignKey(ServicePoolGroup, null=True, blank=True, related_name='servicesPools', on_delete=models.SET_NULL) + servicesPoolGroup: 'models.ForeignKey[ServicePool, ServicePoolGroup]' = ( + models.ForeignKey( + ServicePoolGroup, + null=True, + blank=True, + related_name='servicesPools', + on_delete=models.SET_NULL, + ) + ) # Message if access denied calendar_message = models.CharField(default='', max_length=256) # Default fallback action for access fallbackAccess = models.CharField(default=states.action.ALLOW, max_length=8) - # actionsCalendars = models.ManyToManyField(Calendar, related_name='actionsSP', through='CalendarAction') # Usage accounting - account: 'models.ForeignKey[ServicePool, Account]' = models.ForeignKey(Account, null=True, blank=True, related_name='servicesPools', on_delete=models.CASCADE) + account: 'models.ForeignKey[ServicePool, Account]' = models.ForeignKey( + Account, + null=True, + blank=True, + related_name='servicesPools', + on_delete=models.CASCADE, + ) initial_srvs = models.PositiveIntegerField(default=0) cache_l1_srvs = models.PositiveIntegerField(default=0) @@ -105,19 +151,18 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore max_srvs = models.PositiveIntegerField(default=0) current_pub_revision = models.PositiveIntegerField(default=1) - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[ServicePool]' publications: 'models.QuerySet[ServicePoolPublication]' memberOfMeta: 'models.QuerySet[MetaPoolMember]' userServices: 'models.QuerySet[UserService]' calendarAccess: 'models.QuerySet[CalendarAccess]' - # Meta service related - # meta_pools = models.ManyToManyField('self', symmetrical=False) - class Meta(UUIDModel.Meta): """ Meta class to declare the name of the table at database """ + db_table = 'uds__deployed_service' app_label = 'uds' @@ -125,9 +170,6 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore """ Returns an environment valid for the record this object represents """ - a = self.image - - return Environment.getEnvForTableElement(self._meta.verbose_name, self.id) def activePublication(self) -> typing.Optional['ServicePoolPublication']: @@ -140,7 +182,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore None if there is no valid publication for this deployed service. """ try: - return typing.cast(ServicePoolPublication, self.publications.filter(state=states.publication.USABLE)[0]) + return self.publications.filter(state=states.publication.USABLE)[0] except Exception: return None @@ -161,36 +203,49 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore return username, password @staticmethod - def getRestrainedsQuerySet() -> models.QuerySet: - from uds.models.user_service import UserService # pylint: disable=redefined-outer-name + def getRestrainedsQuerySet() -> 'models.QuerySet[ServicePool]': + from uds.models.user_service import ( + UserService, + ) # pylint: disable=redefined-outer-name from uds.core.util.config import GlobalConfig from django.db.models import Count if GlobalConfig.RESTRAINT_TIME.getInt() <= 0: - return ServicePool.objects.none() # Do not perform any restraint check if we set the globalconfig to 0 (or less) + return ( + ServicePool.objects.none() + ) # Do not perform any restraint check if we set the globalconfig to 0 (or less) - date = typing.cast(datetime, getSqlDatetime()) - timedelta(seconds=GlobalConfig.RESTRAINT_TIME.getInt()) + date = getSqlDatetime() - timedelta( + seconds=GlobalConfig.RESTRAINT_TIME.getInt() + ) min_ = GlobalConfig.RESTRAINT_COUNT.getInt() res = [] - for v in UserService.objects.filter( + for v in ( + UserService.objects.filter( state=states.userService.ERROR, state_date__gt=date - ).values('deployed_service').annotate(how_many=Count('deployed_service')).order_by('deployed_service'): + ) + .values('deployed_service') + .annotate(how_many=Count('deployed_service')) + .order_by('deployed_service') + ): if v['how_many'] >= min_: res.append(v['deployed_service']) return ServicePool.objects.filter(pk__in=res) @staticmethod def getRestraineds() -> typing.Iterator['ServicePool']: - return typing.cast(typing.Iterator['ServicePool'], ServicePool.getRestrainedsQuerySet().iterator()) + return ServicePool.getRestrainedsQuerySet().iterator() @property - def is_meta(self) -> bool: + def owned_by_meta(self) -> bool: return self.memberOfMeta.count() > 0 @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 is not None, self.name + ) if self.short_name is not None and str(self.short_name).strip() != '': return str(self.short_name) return str(self.name) @@ -214,8 +269,15 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore if GlobalConfig.RESTRAINT_TIME.getInt() <= 0: return False # Do not perform any restraint check if we set the globalconfig to 0 (or less) - date = typing.cast(datetime, getSqlDatetime()) - timedelta(seconds=GlobalConfig.RESTRAINT_TIME.getInt()) - if self.userServices.filter(state=states.userService.ERROR, state_date__gt=date).count() >= GlobalConfig.RESTRAINT_COUNT.getInt(): + date = typing.cast(datetime, getSqlDatetime()) - timedelta( + seconds=GlobalConfig.RESTRAINT_TIME.getInt() + ) + if ( + self.userServices.filter( + state=states.userService.ERROR, state_date__gt=date + ).count() + >= GlobalConfig.RESTRAINT_COUNT.getInt() + ): return True return False @@ -227,9 +289,13 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore return self.visible def isUsable(self) -> bool: - return self.state == states.servicePool.ACTIVE and not self.isInMaintenance() and not self.isRestrained() + return ( + self.state == states.servicePool.ACTIVE + and not self.isInMaintenance() + and not self.isRestrained() + ) - def toBeReplaced(self, forUser: 'User') -> typing.Optional[datetime]: + def toBeReplaced(self, forUser: 'User') -> typing.Optional[datetime]: activePub: typing.Optional['ServicePoolPublication'] = self.activePublication() # If no publication or current revision, it's not going to be replaced if activePub is None: @@ -241,7 +307,12 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore # Return the date try: - found = typing.cast('UserService', self.assignedUserServices().filter(user=forUser, state__in=states.userService.VALID_STATES)[0]) + found = typing.cast( + 'UserService', + self.assignedUserServices().filter( + user=forUser, state__in=states.userService.VALID_STATES + )[0], + ) if activePub and found.publication and activePub.id != found.publication.id: ret = self.recoverValue('toBeReplacedIn') if ret: @@ -261,14 +332,16 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore access = self.fallbackAccess # Let's see if we can access by current datetime - for ac in sorted(self.calendarAccess.all(), key=lambda x:x.priority): + for ac in sorted(self.calendarAccess.all(), key=lambda x: x.priority): if CalendarChecker(ac.calendar).check(chkDateTime) is True: access = ac.access break # Stops on first rule match found return access == states.action.ALLOW - def getDeadline(self, chkDateTime: typing.Optional[datetime] = None) -> typing.Optional[int]: + def getDeadline( + self, chkDateTime: typing.Optional[datetime] = None + ) -> typing.Optional[int]: """Gets the deadline for an access on chkDateTime in seconds Keyword Arguments: @@ -286,7 +359,10 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore deadLine = None for ac in self.calendarAccess.all(): - if ac.access == states.action.ALLOW and self.fallbackAccess == states.action.DENY: + if ( + ac.access == states.action.ALLOW + and self.fallbackAccess == states.action.DENY + ): nextE = CalendarChecker(ac.calendar).nextEvent(chkDateTime, False) if deadLine is None or deadLine > nextE: deadLine = nextE @@ -350,8 +426,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore def removed(self) -> None: """ Mark the deployed service as removed. - - A background worker will check for removed deloyed services and clean database of them. + Basically, deletes the user service """ # self.transports.clear() # self.assignedGroups.clear() @@ -360,7 +435,11 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore # self.setState(State.REMOVED) self.delete() - def markOldUserServicesAsRemovables(self, activePub: typing.Optional['ServicePoolPublication'], skipAssigned: bool = False): + def markOldUserServicesAsRemovables( + self, + activePub: typing.Optional['ServicePoolPublication'], + skipAssigned: bool = False, + ): """ Used when a new publication is finished. @@ -379,15 +458,23 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore userService: 'UserService' if activePub is None: - logger.error('No active publication, don\'t know what to erase!!! (ds = %s)', self) + logger.error( + 'No active publication, don\'t know what to erase!!! (ds = %s)', self + ) return for nonActivePub in self.publications.exclude(id=activePub.id): - for userService in nonActivePub.userServices.filter(state=states.userService.PREPARING): + for userService in nonActivePub.userServices.filter( + state=states.userService.PREPARING + ): userService.cancel() with transaction.atomic(): - nonActivePub.userServices.exclude(cache_level=0).filter(state=states.userService.USABLE).update(state=states.userService.REMOVABLE, state_date=now) + nonActivePub.userServices.exclude(cache_level=0).filter( + state=states.userService.USABLE + ).update(state=states.userService.REMOVABLE, state_date=now) if not skipAssigned: - nonActivePub.userServices.filter(cache_level=0, state=states.userService.USABLE, in_use=False).update(state=states.userService.REMOVABLE, state_date=now) + nonActivePub.userServices.filter( + cache_level=0, state=states.userService.USABLE, in_use=False + ).update(state=states.userService.REMOVABLE, state_date=now) def validateGroups(self, groups: typing.Iterable['Group']) -> None: """ @@ -395,6 +482,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore raise an InvalidUserException if fails check """ from uds.core import auths + if not set(groups) & set(self.assignedGroups.all()): raise auths.exceptions.InvalidUserException() @@ -403,7 +491,10 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore Ensures that, if this service has publications, that a publication is active raises an IvalidServiceException if check fails """ - if self.activePublication() is None and self.service.getType().publicationType is not None: + if ( + self.activePublication() is None + and self.service.getType().publicationType is not None + ): raise InvalidServiceException() def validateTransport(self, transport) -> None: @@ -419,9 +510,6 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore Args: user: User (db record) to check if has access to this deployed service - Returns: - True if has access - Raises: InvalidUserException() if user do not has access to this deployed service @@ -436,8 +524,10 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore self.validatePublication() @staticmethod - def getDeployedServicesForGroups(groups: typing.Iterable['Group'], user: typing.Optional['User'] = None) -> typing.List['ServicePool']: - """ + def getDeployedServicesForGroups( + groups: typing.Iterable['Group'], user: typing.Optional['User'] = None + ) -> typing.Iterable['ServicePool']: + """ Return deployed services with publications for the groups requested. Args: @@ -447,17 +537,33 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore List of accesible deployed services """ from uds.core import services - servicesNotNeedingPub = [t.type() for t in services.factory().servicesThatDoNotNeedPublication()] + + servicesNotNeedingPub = [ + t.type() for t in services.factory().servicesThatDoNotNeedPublication() + ] # Get services that HAS publications query = ( ServicePool.objects.filter( - assignedGroups__in=groups, - assignedGroups__state=states.group.ACTIVE, - state=states.servicePool.ACTIVE, - visible=True + assignedGroups__in=groups, + assignedGroups__state=states.group.ACTIVE, + state=states.servicePool.ACTIVE, + visible=True, + ) + .annotate( + pubs_active=models.Count( + 'publications', + filter=models.Q(publications__state=states.publication.USABLE), + ) + ) + .annotate( + usage_count=models.Count( + 'userServices', + filter=models.Q( + userServices__state__in=states.userService.VALID_STATES, + userServices__cache_level=0, + ), + ) ) - .annotate(pubs_active=models.Count('publications', filter=models.Q(publications__state=states.publication.USABLE))) - .annotate(usage_count=models.Count('userServices', filter=models.Q(userServices__state__in=states.userService.VALID_STATES, userServices__cache_level=0))) .prefetch_related( 'transports', 'transports__networks', @@ -471,22 +577,28 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore 'calendarAccess', 'calendarAccess__calendar', 'calendarAccess__calendar__rules', - 'image' + 'image', ) ) if user: # Optimize loading if there is some assgned service.. query = query.annotate( number_in_use=models.Count( - 'userServices', filter=models.Q( + 'userServices', + filter=models.Q( userServices__user=user, userServices__in_use=True, - userServices__state__in=states.userService.USABLE - ) + userServices__state__in=states.userService.USABLE, + ), ) ) servicePool: 'ServicePool' - return [servicePool for servicePool in query if servicePool.pubs_active or servicePool.service.data_type in servicesNotNeedingPub] + for servicePool in query: + if ( + typing.cast(typing.Any, servicePool).pubs_active + or servicePool.service.data_type in servicesNotNeedingPub + ): + yield servicePool def publish(self, changeLog: typing.Optional[str] = None) -> None: """ @@ -495,6 +607,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 publicationManager + publicationManager().publish(self, changeLog) def unpublish(self) -> None: @@ -507,7 +620,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore if pub: pub.unpublish() - def cachedUserServices(self) -> 'models.QuerySet': + def cachedUserServices(self) -> 'models.QuerySet[UserService]': """ ':rtype uds.models.user_service.UserService' Utility method to access the cached user services (level 1 and 2) @@ -517,7 +630,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore """ return self.userServices.exclude(cache_level=0) - def assignedUserServices(self) -> 'models.QuerySet': + def assignedUserServices(self) -> 'models.QuerySet[UserService]': """ Utility method to access the assigned user services @@ -526,7 +639,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore """ return self.userServices.filter(cache_level=0) - def erroneousUserServices(self) -> 'models.QuerySet': + def erroneousUserServices(self) -> 'models.QuerySet[UserService]': """ Utility method to locate invalid assigned user services. @@ -549,7 +662,11 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore return 0 if cachedValue == -1: - cachedValue = self.assignedUserServices().filter(state__in=states.userService.VALID_STATES).count() + cachedValue = ( + self.assignedUserServices() + .filter(state__in=states.userService.VALID_STATES) + .count() + ) return 100 * cachedValue // maxs @@ -576,6 +693,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore :note: If destroy raises an exception, the deletion is not taken. """ from uds.core.util.permissions import clean + toDelete: 'ServicePool' = kwargs['instance'] logger.debug('Deleting Service Pool %s', toDelete) @@ -589,7 +707,13 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore def __str__(self): return 'Deployed service {}({}) with {} as initial, {} as L1 cache, {} as L2 cache, {} as max'.format( - self.name, self.id, self.initial_srvs, self.cache_l1_srvs, self.cache_l2_srvs, self.max_srvs) + self.name, + self.id, + self.initial_srvs, + self.cache_l1_srvs, + self.cache_l2_srvs, + self.max_srvs, + ) # Connects a pre deletion signal to Authenticator diff --git a/server/src/uds/models/service_pool_group.py b/server/src/uds/models/service_pool_group.py index a957605e..86276e47 100644 --- a/server/src/uds/models/service_pool_group.py +++ b/server/src/uds/models/service_pool_group.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -55,6 +55,9 @@ class ServicePoolGroup(UUIDModel): priority = models.IntegerField(default=0, db_index=True) image: 'models.ForeignKey[ServicePoolGroup, Image]' = models.ForeignKey(Image, null=True, blank=True, related_name='servicesPoolsGroup', on_delete=models.SET_NULL) + # "fake" declarations for type checking + objects: 'models.BaseManager[ServicePoolGroup]' + class Meta(UUIDModel.Meta): """ Meta class to declare the name of the table at database @@ -62,11 +65,11 @@ class ServicePoolGroup(UUIDModel): db_table = 'uds__pools_groups' app_label = 'uds' - def __str__(self): + def __str__(self) -> str: return 'Service Pool group {}({}): {}'.format(self.name, self.comments, self.image.name) @property - def as_dict(self) -> typing.Dict[str, typing.Any]: + def as_dict(self) -> typing.MutableMapping[str, typing.Any]: return { 'id': self.uuid, 'name': self.name, @@ -81,4 +84,9 @@ class ServicePoolGroup(UUIDModel): @staticmethod def default() -> 'ServicePoolGroup': + """Returns an "default" service pool group. Used on services agroupation on visualization + + Returns: + [ServicePoolGroup]: Default ServicePoolGroup + """ return ServicePoolGroup(name=_('General'), comments='', priority=-10000) diff --git a/server/src/uds/models/service_pool_publication.py b/server/src/uds/models/service_pool_publication.py index 6752f5d2..a4a5ac3c 100644 --- a/server/src/uds/models/service_pool_publication.py +++ b/server/src/uds/models/service_pool_publication.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -36,6 +36,7 @@ import typing from django.db import models +from uds.core.managers import publicationManager from uds.core.util.state import State from uds.core.environment import Environment from uds.core.util import log @@ -53,26 +54,43 @@ logger = logging.getLogger(__name__) class ServicePoolPublicationChangelog(models.Model): - publication: 'models.ForeignKey[ServicePoolPublication, ServicePool]' = models.ForeignKey(ServicePool, on_delete=models.CASCADE, related_name='changelog') + # This should be "servicePool" + publication: 'models.ForeignKey[ServicePoolPublicationChangelog, ServicePool]' = ( + models.ForeignKey( + ServicePool, on_delete=models.CASCADE, related_name='changelog' + ) + ) stamp = models.DateTimeField() revision = models.PositiveIntegerField(default=1) log = models.TextField(default='') + # "fake" declarations for type checking + objects: 'models.BaseManager[ServicePoolPublicationChangelog]' + class Meta(UUIDModel.Meta): """ Meta class to declare default order and unique multiple field index """ + db_table = 'uds__deployed_service_pub_cl' app_label = 'uds' - def __str__(self): - return 'Revision log for publication {}, rev {}: {}'.format(self.publication.name, self.revision, self.log) + def __str__(self) -> str: + return 'Revision log for publication {}, rev {}: {}'.format( + self.publication.name, self.revision, self.log + ) + class ServicePoolPublication(UUIDModel): """ A deployed service publication keep track of data needed by services that needs "preparation". (i.e. Virtual machine --> base machine --> children of base machines) """ - deployed_service: 'models.ForeignKey[ServicePoolPublication, ServicePool]' = models.ForeignKey(ServicePool, on_delete=models.CASCADE, related_name='publications') + + deployed_service: 'models.ForeignKey[ServicePoolPublication, ServicePool]' = ( + models.ForeignKey( + ServicePool, on_delete=models.CASCADE, related_name='publications' + ) + ) publish_date = models.DateTimeField(db_index=True) # data_type = models.CharField(max_length=128) # The data type is specified by the service itself data = models.TextField(default='') @@ -87,7 +105,8 @@ class ServicePoolPublication(UUIDModel): state_date = models.DateTimeField() revision = models.PositiveIntegerField(default=1) - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[ServicePoolPublication]' userServices: 'models.QuerySet[UserService]' @@ -95,6 +114,7 @@ class ServicePoolPublication(UUIDModel): """ Meta class to declare default order and unique multiple field index """ + db_table = 'uds__deployed_service_pub' ordering = ('publish_date',) app_label = 'uds' @@ -128,7 +148,11 @@ class ServicePoolPublication(UUIDModel): # a service that needs publication but do not have if serviceInstance.publicationType is None: - raise Exception('Class {} do not have defined publicationType but needs to be published!!!'.format(serviceInstance.__class__)) + raise Exception( + 'Class {} do not have defined publicationType but needs to be published!!!'.format( + serviceInstance.__class__ + ) + ) publication = serviceInstance.publicationType( self.getEnvironment(), @@ -136,7 +160,8 @@ class ServicePoolPublication(UUIDModel): osManager=osManagerInstance, revision=self.revision, dsName=self.deployed_service.name, - uuid=self.uuid, dbPublication=self + uuid=self.uuid, + dbPublication=self, ) # Only invokes deserialization if data has something. '' is nothing if self.data: @@ -175,14 +200,14 @@ class ServicePoolPublication(UUIDModel): No check is done, it simply redirects the request to PublicationManager, where checks are done. """ - from uds.core.managers import publicationManager + publicationManager().unpublish(self) def cancel(self): """ Invoques the cancelation of this publication """ - from uds.core.managers import publicationManager + publicationManager().cancel(self) @staticmethod @@ -207,8 +232,13 @@ class ServicePoolPublication(UUIDModel): logger.debug('Deleted publication %s', toDelete) - def __str__(self): - return 'Publication {}, rev {}, state {}'.format(self.deployed_service.name, self.revision, State.toString(self.state)) + def __str__(self) -> str: + return 'Publication {}, rev {}, state {}'.format( + self.deployed_service.name, self.revision, State.toString(self.state) + ) + # Connects a pre deletion signal to Authenticator -models.signals.pre_delete.connect(ServicePoolPublication.beforeDelete, sender=ServicePoolPublication) +models.signals.pre_delete.connect( + ServicePoolPublication.beforeDelete, sender=ServicePoolPublication +) diff --git a/server/src/uds/models/stats_counters.py b/server/src/uds/models/stats_counters.py index 6ab921f8..300bbaab 100644 --- a/server/src/uds/models/stats_counters.py +++ b/server/src/uds/models/stats_counters.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -46,7 +46,7 @@ logger = logging.getLogger(__name__) class StatsCounters(models.Model): """ - Counter statistocs mpdes the counter statistics + Statistics about counters (number of users at a given time, number of services at a time, whatever...) """ owner_id = models.IntegerField(db_index=True, default=0) @@ -55,15 +55,21 @@ class StatsCounters(models.Model): stamp = models.IntegerField(db_index=True, default=0) value = models.IntegerField(db_index=True, default=0) + # "fake" declarations for type checking + objects: 'models.BaseManager[StatsCounters]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_stats_c' app_label = 'uds' @staticmethod - def get_grouped(owner_type: typing.Union[int, typing.Iterable[int]], counter_type: int, **kwargs) -> 'models.QuerySet[StatsCounters]': # pylint: disable=too-many-locals + def get_grouped( + owner_type: typing.Union[int, typing.Iterable[int]], counter_type: int, **kwargs + ) -> 'models.QuerySet[StatsCounters]': # pylint: disable=too-many-locals """ Returns the average stats grouped by interval for owner_type and owner_id (optional) @@ -92,7 +98,9 @@ class StatsCounters(models.Model): since = int(since) if since else NEVER_UNIX to = int(to) if to else getSqlDatetimeAsUnix() - interval = int(kwargs.get('interval') or '600') # By default, group items in ten minutes interval (600 seconds) + interval = int( + kwargs.get('interval') or '600' + ) # By default, group items in ten minutes interval (600 seconds) max_intervals = kwargs.get('max_intervals') @@ -106,9 +114,13 @@ class StatsCounters(models.Model): q = StatsCounters.objects.filter(stamp__gte=since, stamp__lte=to) else: if isinstance(owner_id, (list, tuple, types.GeneratorType)): - q = StatsCounters.objects.filter(owner_id__in=owner_id, stamp__gte=since, stamp__lte=to) + q = StatsCounters.objects.filter( + owner_id__in=owner_id, stamp__gte=since, stamp__lte=to + ) else: - q = StatsCounters.objects.filter(owner_id=owner_id, stamp__gte=since, stamp__lte=to) + q = StatsCounters.objects.filter( + owner_id=owner_id, stamp__gte=since, stamp__lte=to + ) if isinstance(owner_type, (list, tuple, types.GeneratorType)): q = q.filter(owner_type__in=owner_type) @@ -120,11 +132,11 @@ class StatsCounters(models.Model): last = q.order_by('stamp').reverse()[0].stamp interval = int((last - first) / (max_intervals - 1)) - stampValue = '{ceil}(stamp/{interval})'.format(ceil=getSqlFnc('CEIL'), interval=interval) + stampValue = '{ceil}(stamp/{interval})'.format( + ceil=getSqlFnc('CEIL'), interval=interval + ) filt += ' AND stamp>={since} AND stamp<={to} GROUP BY {stampValue} ORDER BY stamp'.format( - since=since, - to=to, - stampValue=stampValue + since=since, to=to, stampValue=stampValue ) if limit: @@ -138,15 +150,22 @@ class StatsCounters(models.Model): # fnc = getSqlFnc('MAX' if kwargs.get('use_max', False) else 'AVG') query = ( - 'SELECT -1 as id,-1 as owner_id,-1 as owner_type,-1 as counter_type, ' + stampValue + '*{}'.format(interval) + ' AS stamp, ' + - '{} AS value ' + 'SELECT -1 as id,-1 as owner_id,-1 as owner_type,-1 as counter_type, ' + + stampValue + + '*{}'.format(interval) + + ' AS stamp, ' + + '{} AS value ' 'FROM {} WHERE {}' ).format(fnc, StatsCounters._meta.db_table, filt) logger.debug('Stats query: %s', query) # We use result as an iterator - return typing.cast('models.QuerySet[StatsCounters]', StatsCounters.objects.raw(query)) + return typing.cast( + 'models.QuerySet[StatsCounters]', StatsCounters.objects.raw(query) + ) def __str__(self): - return u"Counter of {}({}): {} - {} - {}".format(self.owner_type, self.owner_id, self.stamp, self.counter_type, self.value) + return u"Counter of {}({}): {} - {} - {}".format( + self.owner_type, self.owner_id, self.stamp, self.counter_type, self.value + ) diff --git a/server/src/uds/models/stats_events.py b/server/src/uds/models/stats_events.py index 5985ede9..cbb39f12 100644 --- a/server/src/uds/models/stats_events.py +++ b/server/src/uds/models/stats_events.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -45,7 +45,7 @@ logger = logging.getLogger(__name__) class StatsEvents(models.Model): """ - Counter statistocs mpdes the counter statistics + Statistics about events (login, logout, whatever...) """ owner_id = models.IntegerField(db_index=True, default=0) @@ -59,15 +59,23 @@ class StatsEvents(models.Model): fld3 = models.CharField(max_length=128, default='') fld4 = models.CharField(max_length=128, default='') + # "fake" declarations for type checking + objects: 'models.BaseManager[StatsEvents]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_stats_e' app_label = 'uds' @staticmethod - def get_stats(owner_type: typing.Union[int, typing.Iterable[int]], event_type: typing.Union[int, typing.Iterable[int]], **kwargs) -> 'models.QuerySet[StatsEvents]': + def get_stats( + owner_type: typing.Union[int, typing.Iterable[int]], + event_type: typing.Union[int, typing.Iterable[int]], + **kwargs + ) -> 'models.QuerySet[StatsEvents]': """ Returns a queryset with the average stats grouped by interval for owner_type and owner_id (optional) @@ -119,4 +127,12 @@ class StatsEvents(models.Model): return self.fld4 def __str__(self): - return 'Log of {}({}): {} - {} - {}, {}, {}'.format(self.owner_type, self.owner_id, self.event_type, self.stamp, self.fld1, self.fld2, self.fld3) + return 'Log of {}({}): {} - {} - {}, {}, {}'.format( + self.owner_type, + self.owner_id, + self.event_type, + self.stamp, + self.fld1, + self.fld2, + self.fld3, + ) diff --git a/server/src/uds/models/storage.py b/server/src/uds/models/storage.py index 9810b51f..fb56ae3c 100644 --- a/server/src/uds/models/storage.py +++ b/server/src/uds/models/storage.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -47,7 +47,8 @@ class Storage(models.Model): data = models.TextField(default='') attr1 = models.CharField(max_length=64, db_index=True, null=True, blank=True, default=None) - # Removed old locking manager, that blocked tables + # "fake" declarations for type checking + objects: 'models.BaseManager[Storage]' class Meta: """ @@ -55,5 +56,5 @@ class Storage(models.Model): """ app_label = 'uds' - def __str__(self): + def __str__(self) -> str: return '{} {} > str= {}, {}'.format(self.owner, self.key, self.data, '/'.join([self.attr1])) diff --git a/server/src/uds/models/tag.py b/server/src/uds/models/tag.py index 8bc7e4f4..64a5e9e5 100644 --- a/server/src/uds/models/tag.py +++ b/server/src/uds/models/tag.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -49,10 +49,14 @@ class Tag(UUIDModel): tag = models.CharField(max_length=32, db_index=True, unique=True) + # "fake" declarations for type checking + objects: 'models.BaseManager[Tag]' + class Meta: """ Meta class to declare db table """ + db_table = 'uds_tag' app_label = 'uds' diff --git a/server/src/uds/models/ticket_store.py b/server/src/uds/models/ticket_store.py index f2e523b4..01df404e 100644 --- a/server/src/uds/models/ticket_store.py +++ b/server/src/uds/models/ticket_store.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -50,16 +50,24 @@ class TicketStore(UUIDModel): """ Tickets storing on DB """ + DEFAULT_VALIDITY = 60 MAX_VALIDITY = 60 * 60 * 12 # Cleanup will purge all elements that have been created MAX_VALIDITY ago owner = models.CharField(null=True, blank=True, default=None, max_length=8) stamp = models.DateTimeField() # Date creation or validation of this entry - validity = models.IntegerField(default=60) # Duration allowed for this ticket to be valid, in seconds + validity = models.IntegerField( + default=60 + ) # Duration allowed for this ticket to be valid, in seconds data = models.BinaryField() # Associated ticket data - validator = models.BinaryField(null=True, blank=True, default=None) # Associated validator for this ticket + validator = models.BinaryField( + null=True, blank=True, default=None + ) # Associated validator for this ticket + + # "fake" declarations for type checking + objects: 'models.BaseManager[TicketStore]' class InvalidTicket(Exception): pass @@ -68,6 +76,7 @@ class TicketStore(UUIDModel): """ Meta class to declare the name of the table at database """ + db_table = 'uds_tickets' app_label = 'uds' @@ -80,12 +89,12 @@ class TicketStore(UUIDModel): @staticmethod def create( - data: typing.Any, - validatorFnc: typing.Optional[ValidatorType] = None, - validity: int = DEFAULT_VALIDITY, - owner: typing.Optional[str]=None, - secure: bool = False - ) -> str: + data: typing.Any, + validatorFnc: typing.Optional[ValidatorType] = None, + validity: int = DEFAULT_VALIDITY, + owner: typing.Optional[str] = None, + secure: bool = False, + ) -> str: """ validity is in seconds """ @@ -94,17 +103,23 @@ class TicketStore(UUIDModel): if secure: pass - return TicketStore.objects.create(stamp=getSqlDatetime(), data=data, validator=validator, validity=validity, owner=owner).uuid + return TicketStore.objects.create( + stamp=getSqlDatetime(), + data=data, + validator=validator, + validity=validity, + owner=owner, + ).uuid @staticmethod def store( - uuid: str, - data: str, - validatorFnc: typing.Optional[ValidatorType] = None, - validity: int = DEFAULT_VALIDITY, - owner: typing.Optional[str] = None, - secure: bool = False - ) -> None: + uuid: str, + data: str, + validatorFnc: typing.Optional[ValidatorType] = None, + validity: int = DEFAULT_VALIDITY, + owner: typing.Optional[str] = None, + secure: bool = False, + ) -> None: """ Stores an ticketstore. If one with this uuid already exists, replaces it. Else, creates a new one validity is in seconds @@ -122,15 +137,21 @@ class TicketStore(UUIDModel): t.owner = owner t.save() except TicketStore.DoesNotExist: - TicketStore.objects.create(uuid=uuid, stamp=getSqlDatetime(), data=pickle.dumps(data), validator=validator, validity=validity) + TicketStore.objects.create( + uuid=uuid, + stamp=getSqlDatetime(), + data=pickle.dumps(data), + validator=validator, + validity=validity, + ) @staticmethod def get( - uuid: str, - invalidate: bool = True, - owner: typing.Optional[str] = None, - secure: bool = False - ) -> typing.Any: + uuid: str, + invalidate: bool = True, + owner: typing.Optional[str] = None, + secure: bool = False, + ) -> typing.Any: try: t = TicketStore.objects.get(uuid=uuid, owner=owner) validity = datetime.timedelta(seconds=t.validity) @@ -159,30 +180,43 @@ class TicketStore(UUIDModel): raise TicketStore.InvalidTicket('Does not exists') @staticmethod - def revalidate(uuid, validity=None, owner=None): + def revalidate( + uuid: str, + validity: typing.Optional[int] = None, + owner: typing.Optional[str] = None, + ): try: t = TicketStore.objects.get(uuid=uuid, owner=owner) t.stamp = getSqlDatetime() - if validity is not None: + if validity: t.validity = validity - t.save() + t.save(update_fields=['validity', 'stamp']) except TicketStore.DoesNotExist: raise Exception('Does not exists') @staticmethod - def cleanup(): - from datetime import timedelta + def cleanup() -> None: now = getSqlDatetime() for v in TicketStore.objects.all(): - if now > v.stamp + timedelta(seconds=v.validity+600): # Delete only really old tickets. Avoid "revalidate" issues + if now > v.stamp + datetime.timedelta( + seconds=v.validity + 600 + ): # Delete only really old tickets. Avoid "revalidate" issues v.delete() cleanSince = now - datetime.timedelta(seconds=TicketStore.MAX_VALIDITY) + # Also remove too long tickets (12 hours is the default) TicketStore.objects.filter(stamp__lt=cleanSince).delete() - def __str__(self): - if self.validator is not None: + def __str__(self) -> str: + if self.validator: validator = pickle.loads(self.validator) else: validator = None - return 'Ticket id: {}, Secure: {}, Stamp: {}, Validity: {}, Validator: {}, Data: {}'.format(self.uuid, self.owner, self.stamp, self.validity, validator, pickle.loads(self.data)) + return 'Ticket id: {}, Secure: {}, Stamp: {}, Validity: {}, Validator: {}, Data: {}'.format( + self.uuid, + self.owner, + self.stamp, + self.validity, + validator, + pickle.loads(self.data), + ) diff --git a/server/src/uds/models/transport.py b/server/src/uds/models/transport.py index 1234e53d..62b6b9c3 100644 --- a/server/src/uds/models/transport.py +++ b/server/src/uds/models/transport.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -65,6 +65,8 @@ class Transport(ManagedObjectModel, TaggingMixin): # "fake" relations declarations for type checking networks: 'models.QuerySet[Network]' + # "fake" declarations for type checking + objects: 'models.BaseManager[Transport]' class Meta(ManagedObjectModel.Meta): """ @@ -132,7 +134,7 @@ class Transport(ManagedObjectModel, TaggingMixin): return '{} of type {} (id:{})'.format(self.name, self.data_type, self.id) @staticmethod - def beforeDelete(sender, **kwargs): + def beforeDelete(sender, **kwargs) -> None: """ Used to invoke the Service class "Destroy" before deleting it from database. diff --git a/server/src/uds/models/unique_id.py b/server/src/uds/models/unique_id.py index c4f4e28b..a847e19d 100644 --- a/server/src/uds/models/unique_id.py +++ b/server/src/uds/models/unique_id.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -42,19 +42,26 @@ class UniqueId(models.Model): Unique ID Database. Used to store unique names, unique macs, etc... Managed via uds.core.util.unique_id_generator.unique_id_generator """ + owner = models.CharField(max_length=128, db_index=True, default='') basename = models.CharField(max_length=32, db_index=True) seq = models.BigIntegerField(db_index=True) assigned = models.BooleanField(db_index=True, default=True) stamp = models.IntegerField(db_index=True, default=0) + # "fake" declarations for type checking + objects: 'models.BaseManager[UniqueId]' + class Meta: """ Meta class to declare default order and unique multiple field index """ + unique_together = (('basename', 'seq'),) ordering = ('-seq',) app_label = 'uds' def __str__(self) -> str: - return u"{0} {1}.{2}, assigned is {3}".format(self.owner, self.basename, self.seq, self.assigned) + return u"{0} {1}.{2}, assigned is {3}".format( + self.owner, self.basename, self.seq, self.assigned + ) diff --git a/server/src/uds/models/user.py b/server/src/uds/models/user.py index 8729a8b6..3daa790f 100644 --- a/server/src/uds/models/user.py +++ b/server/src/uds/models/user.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -70,7 +70,8 @@ class User(UUIDModel): parent = models.CharField(max_length=50, default=None, null=True) created = models.DateTimeField(default=getSqlDatetime, blank=True) - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[User]' groups: 'models.QuerySet[Group]' class Meta(UUIDModel.Meta): diff --git a/server/src/uds/models/user_preference.py b/server/src/uds/models/user_preference.py index 337f0e15..e45210a9 100644 --- a/server/src/uds/models/user_preference.py +++ b/server/src/uds/models/user_preference.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -49,6 +49,9 @@ class UserPreference(models.Model): value = models.CharField(max_length=128, db_index=True) user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='preferences') + # "fake" declarations for type checking + objects: 'models.BaseManager[UserPreference]' + class Meta: app_label = 'uds' diff --git a/server/src/uds/models/user_service.py b/server/src/uds/models/user_service.py index bbd5b0a8..be2ef712 100644 --- a/server/src/uds/models/user_service.py +++ b/server/src/uds/models/user_service.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -52,7 +52,13 @@ from .util import getSqlDatetime if typing.TYPE_CHECKING: from uds.core import osmanagers from uds.core import services - from uds.models import OSManager, ServicePool, ServicePoolPublication, UserServiceProperty + from uds.models import ( + OSManager, + ServicePool, + ServicePoolPublication, + UserServiceProperty, + AccountUsage, + ) logger = logging.getLogger(__name__) @@ -65,44 +71,68 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods # The reference to deployed service is used to accelerate the queries for different methods, in fact its redundant cause we can access to the deployed service # through publication, but queries are much more simple - deployed_service: 'models.ForeignKey[UserService, ServicePool]' = models.ForeignKey(ServicePool, on_delete=models.CASCADE, related_name='userServices') - publication: 'models.ForeignKey[UserService, ServicePoolPublication]' = models.ForeignKey( - ServicePoolPublication, on_delete=models.CASCADE, null=True, blank=True, related_name='userServices') + deployed_service: 'models.ForeignKey[UserService, ServicePool]' = models.ForeignKey( + ServicePool, on_delete=models.CASCADE, related_name='userServices' + ) + publication: 'models.ForeignKey[UserService, ServicePoolPublication]' = ( + models.ForeignKey( + ServicePoolPublication, + on_delete=models.CASCADE, + null=True, + blank=True, + related_name='userServices', + ) + ) - unique_id = models.CharField(max_length=128, default='', db_index=True) # User by agents to locate machine + unique_id = models.CharField( + max_length=128, default='', db_index=True + ) # User by agents to locate machine friendly_name = models.CharField(max_length=128, default='') # We need to keep separated two differents os states so service operations (move beween caches, recover service) do not affects os manager state - state = models.CharField(max_length=1, default=State.PREPARING, db_index=True) # We set index so filters at cache level executes faster - os_state = models.CharField(max_length=1, default=State.PREPARING) # The valid values for this field are PREPARE and USABLE + state = models.CharField( + max_length=1, default=State.PREPARING, db_index=True + ) # We set index so filters at cache level executes faster + os_state = models.CharField( + max_length=1, default=State.PREPARING + ) # The valid values for this field are PREPARE and USABLE state_date = models.DateTimeField(db_index=True) creation_date = models.DateTimeField(db_index=True) data = models.TextField(default='') user: 'models.ForeignKey[UserService, User]' = models.ForeignKey( - User, on_delete=models.CASCADE, related_name='userServices', null=True, blank=True, default=None) + User, + on_delete=models.CASCADE, + related_name='userServices', + null=True, + blank=True, + default=None, + ) in_use = models.BooleanField(default=False) in_use_date = models.DateTimeField(default=NEVER) - cache_level = models.PositiveSmallIntegerField(db_index=True, default=0) # Cache level must be 1 for L1 or 2 for L2, 0 if it is not cached service + cache_level = models.PositiveSmallIntegerField( + db_index=True, default=0 + ) # Cache level must be 1 for L1 or 2 for L2, 0 if it is not cached service src_hostname = models.CharField(max_length=64, default='') src_ip = models.CharField(max_length=15, default='') - cluster_node = models.CharField(max_length=128, default=None, blank=True, null=True, db_index=True) + cluster_node = models.CharField( + max_length=128, default=None, blank=True, null=True, db_index=True + ) - # "fake" relations declarations for type checking + # "fake" declarations for type checking + objects: 'models.BaseManager[UserService]' properties: 'models.QuerySet[UserServiceProperty]' + accounting: 'AccountUsage' class Meta(UUIDModel.Meta): """ Meta class to declare default order and unique multiple field index """ + db_table = 'uds__user_service' ordering = ('creation_date',) app_label = 'uds' - index_together = ( - 'deployed_service', - 'cache_level', - 'state' - ) + index_together = ('deployed_service', 'cache_level', 'state') @property def name(self) -> str: @@ -130,7 +160,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods 'mac': unique.UniqueMacGenerator, 'name': unique.UniqueNameGenerator, 'id': unique.UniqueGIDGenerator, - } + }, ) def getInstance(self) -> 'services.UserDeployment': @@ -164,16 +194,33 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods except Exception: # The publication to witch this item points to, does not exists self.publication = None - logger.exception('Got exception at getInstance of an userService %s (seems that publication does not exists!)', self) + logger.exception( + 'Got exception at getInstance of an userService %s (seems that publication does not exists!)', + self, + ) if serviceInstance.deployedType is None: - raise Exception('Class {0} needs deployedType but it is not defined!!!'.format(serviceInstance.__class__.__name__)) - us = serviceInstance.deployedType(self.getEnvironment(), service=serviceInstance, - publication=publicationInstance, osmanager=osmanagerInstance, dbservice=self) + raise Exception( + 'Class {0} needs deployedType but it is not defined!!!'.format( + serviceInstance.__class__.__name__ + ) + ) + us = serviceInstance.deployedType( + self.getEnvironment(), + service=serviceInstance, + publication=publicationInstance, + osmanager=osmanagerInstance, + dbservice=self, + ) if self.data != '' and self.data is not None: try: us.unserialize(self.data) except Exception: - logger.exception('Error unserializing %s//%s : %s', self.deployed_service.name, self.uuid, self.data) + logger.exception( + 'Error unserializing %s//%s : %s', + self.deployed_service.name, + self.uuid, + self.data, + ) return us def updateData(self, userServiceInstance: 'services.UserDeployment'): @@ -285,7 +332,9 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods """ return self.deployed_service.transformsUserOrPasswordForService() - def processUserPassword(self, username: str, password: str) -> typing.Tuple[str, str]: + def processUserPassword( + self, username: str, password: str + ) -> typing.Tuple[str, str]: """ Before accessing a service by a transport, we can request the service to "transform" the username & password that the transport @@ -309,7 +358,9 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods if serviceInstance.needsManager is False or not servicePool.osmanager: return (username, password) - return servicePool.osmanager.getInstance().processUserPassword(self, username, password) + return servicePool.osmanager.getInstance().processUserPassword( + self, username, password + ) def setState(self, state: str) -> None: """ @@ -363,6 +414,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods :note: If the state is Fase (set to not in use), a check for removal of this deployed service is launched. """ from uds.core.managers import userServiceManager + self.in_use = inUse self.in_use_date = getSqlDatetime() self.save(update_fields=['in_use', 'in_use_date']) @@ -391,7 +443,10 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods # 1.- If do not have any accounter associated, do nothing # 2.- If called but not accounting, do nothing # 3.- If called and accounting, stop accounting - if self.deployed_service.account is None or hasattr(self, 'accounting') is False: + if ( + self.deployed_service.account is None + or hasattr(self, 'accounting') is False + ): return self.deployed_service.account.stopUsageAccounting(self) @@ -414,6 +469,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods """ # Call to isReady of the instance from uds.core.managers import userServiceManager + return userServiceManager().isReady(self) def isInMaintenance(self) -> bool: @@ -436,6 +492,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods Asks the UserServiceManager to cancel the current operation of this user deployed service. """ from uds.core.managers import userServiceManager + userServiceManager().cancel(self) def removeOrCancel(self) -> None: @@ -455,9 +512,12 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods cacheLevel: New cache level to put object in """ from uds.core.managers import userServiceManager + userServiceManager().moveToLevel(self, cacheLevel) - def getProperty(self, propName: str, default: typing.Optional[str] = None) -> typing.Optional[str]: + def getProperty( + self, propName: str, default: typing.Optional[str] = None + ) -> typing.Optional[str]: try: val = self.properties.get(name=propName).value return val or default # Empty string is null @@ -496,7 +556,10 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods """ Returns True if this user service does not needs an publication, or if this deployed service publication is the current one """ - return self.deployed_service.service.getType().publicationType is None or self.publication == self.deployed_service.activePublication() + return ( + self.deployed_service.service.getType().publicationType is None + or self.publication == self.deployed_service.activePublication() + ) # Utility for logging def log(self, message: str, level: int = log.INFO) -> None: @@ -507,8 +570,13 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods def __str__(self): return "User service {}, unique_id {}, cache_level {}, user {}, name {}, state {}:{}".format( - self.name, self.unique_id, self.cache_level, self.user, self.friendly_name, - State.toString(self.state), State.toString(self.os_state) + self.name, + self.unique_id, + self.cache_level, + self.user, + self.friendly_name, + State.toString(self.state), + State.toString(self.os_state), ) @staticmethod diff --git a/server/src/uds/models/user_service_property.py b/server/src/uds/models/user_service_property.py index 67337492..7da63b10 100644 --- a/server/src/uds/models/user_service_property.py +++ b/server/src/uds/models/user_service_property.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -45,17 +45,26 @@ class UserServiceProperty(models.Model): # pylint: disable=too-many-public-meth Properties for User Service. The value field is a Text field, so we can put whatever we want in it """ + name = models.CharField(max_length=128, db_index=True) value = models.TextField(default='') - user_service = models.ForeignKey(UserService, on_delete=models.CASCADE, related_name='properties') + user_service = models.ForeignKey( + UserService, on_delete=models.CASCADE, related_name='properties' + ) + + # "fake" declarations for type checking + objects: 'models.BaseManager[UserServiceProperty]' class Meta: """ Meta class to declare default order and unique multiple field index """ + db_table = 'uds__user_service_property' unique_together = (('name', 'user_service'),) app_label = 'uds' def __str__(self) -> str: - return "Property of {}. {}={}".format(self.user_service.pk, self.name, self.value) + return "Property of {}. {}={}".format( + self.user_service.pk, self.name, self.value + ) diff --git a/server/src/uds/models/util.py b/server/src/uds/models/util.py index eb0ca8b9..62fa5cf5 100644 --- a/server/src/uds/models/util.py +++ b/server/src/uds/models/util.py @@ -75,7 +75,7 @@ def getSqlDatetime() -> datetime: def getSqlDatetimeAsUnix() -> int: return int(mktime(getSqlDatetime().timetuple())) -def getSqlFnc(fncName): +def getSqlFnc(fncName: str) -> str: """ Convert different sql functions for different platforms """ diff --git a/server/src/uds/models/uuid_model.py b/server/src/uds/models/uuid_model.py index d39c70cb..bda771ba 100644 --- a/server/src/uds/models/uuid_model.py +++ b/server/src/uds/models/uuid_model.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- # -# Copyright (c) 2012-2019 Virtual Cable S.L. +# Copyright (c) 2012-2020 Virtual Cable S.L.U. # All rights reserved. # # Redistribution and use in source and binary forms, with or without modification, @@ -39,10 +39,12 @@ from uds.core.util.model import generateUuid logger = logging.getLogger(__name__) + class UUIDModel(models.Model): """ Base abstract model for models that require an uuid """ + uuid = models.CharField(max_length=50, default=None, null=True, unique=True) # Automatic field from Model without a defined specific primary_key @@ -55,13 +57,17 @@ class UUIDModel(models.Model): return generateUuid() # Override default save to add uuid - def save(self, force_insert=False, force_update=False, using=None, update_fields=None): + def save( + self, force_insert=False, force_update=False, using=None, update_fields=None + ): if not self.uuid: self.uuid = self.genUuid() elif self.uuid != self.uuid.lower(): - self.uuid = self.uuid.lower() # If we modify uuid elsewhere, ensure that it's stored in lower case + self.uuid = ( + self.uuid.lower() + ) # If we modify uuid elsewhere, ensure that it's stored in lower case return models.Model.save(self, force_insert, force_update, using, update_fields) - def __str__(self): + def __str__(self) -> str: return 'Object of class {} with uuid {}'.format(self.__class__, self.uuid)