Refactoring models (without DB modification) to better type checking

This commit is contained in:
Adolfo Gómez García 2020-11-12 12:13:35 +01:00
parent 52f524eb61
commit b8e7dc07c3
41 changed files with 1181 additions and 390 deletions

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -32,17 +32,14 @@
""" """
import logging import logging
# Utility imports
from .util import getSqlDatetime, getSqlDatetimeAsUnix, NEVER, NEVER_UNIX
# Imports all models so they are available for migrations
# Permissions # Permissions
from .permissions import Permissions from .permissions import Permissions
# Utility
from .util import (
getSqlDatetime,
getSqlDatetimeAsUnix,
NEVER,
NEVER_UNIX
)
# Services # Services
from .provider import Provider from .provider import Provider
from .service import Service from .service import Service

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 # Not imported at runtime, just for type checking
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from uds.models import UserService from uds.models import UserService, AccountUsage
class Account(UUIDModel, TaggingMixin): # type: ignore class Account(UUIDModel, TaggingMixin): # type: ignore
""" """
Account storing on DB model Account storing on DB model
""" """
name = models.CharField(max_length=128, unique=False, db_index=True) name = models.CharField(max_length=128, unique=False, db_index=True)
time_mark = models.DateTimeField(default=NEVER) time_mark = models.DateTimeField(default=NEVER)
comments = models.CharField(max_length=256) 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: def startUsageAccounting(self, userService: 'UserService') -> None:
if hasattr(userService, 'accounting'): # Already has an account if hasattr(userService, 'accounting'): # Already has an account
return None return None
@ -65,18 +70,20 @@ class Account(UUIDModel, TaggingMixin): # type: ignore
else: else:
userName = '??????' userName = '??????'
userUuid = '00000000-0000-0000-0000-000000000000' userUuid = '00000000-0000-0000-0000-000000000000'
return self.usages.create(
self.usages.create(
user_service=userService, user_service=userService,
user_name=userName, user_name=userName,
user_uuid=userUuid, user_uuid=userUuid,
pool_name=userService.deployed_service.name, pool_name=userService.deployed_service.name,
pool_uuid=userService.deployed_service.uuid, pool_uuid=userService.deployed_service.uuid,
start=start, start=start,
end=start end=start,
) )
def stopUsageAccounting(self, userService: 'UserService') -> None: 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 return
tmp = userService.accounting tmp = userService.accounting
@ -88,6 +95,7 @@ class Account(UUIDModel, TaggingMixin): # type: ignore
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_accounts' db_table = 'uds_accounts'
app_label = 'uds' app_label = 'uds'

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import typing
import logging import logging
from django.db import models from django.db import models
@ -39,6 +40,10 @@ from .account import Account
from .user_service import UserService from .user_service import UserService
from .util import NEVER 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__) logger = logging.getLogger(__name__)
@ -46,21 +51,35 @@ class AccountUsage(UUIDModel):
""" """
AccountUsage storing on DB model AccountUsage storing on DB model
This is intended for small images (i will limit them to 128x128), so storing at db is fine 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_name = models.CharField(max_length=128, db_index=True, default='')
user_uuid = models.CharField(max_length=50, 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_name = models.CharField(max_length=128, db_index=True, default='')
pool_uuid = models.CharField(max_length=50, db_index=True, default='') pool_uuid = models.CharField(max_length=50, db_index=True, default='')
start = models.DateTimeField(default=NEVER) start = models.DateTimeField(default=NEVER)
end = 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) user_service: 'models.OneToOneField[AccountUsage, UserService]' = (
account: Account = models.ForeignKey(Account, related_name='usages', on_delete=models.CASCADE) 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: class Meta:
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_acc_usage' db_table = 'uds_acc_usage'
app_label = 'uds' app_label = 'uds'
@ -93,4 +112,6 @@ class AccountUsage(UUIDModel):
return secondsToTimeString(self.elapsed_seconds_timemark) return secondsToTimeString(self.elapsed_seconds_timemark)
def __str__(self): 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
)

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -30,10 +30,12 @@
''' '''
from django.db import models from django.db import models
class ActorToken(models.Model): class ActorToken(models.Model):
""" """
UDS Actors tokens on DB UDS Actors tokens on DB
""" """
username = models.CharField(max_length=128) username = models.CharField(max_length=128)
ip_from = models.CharField(max_length=128) ip_from = models.CharField(max_length=128)
ip = 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) token = models.CharField(max_length=48, db_index=True, unique=True)
stamp = models.DateTimeField() # Date creation or validation of this entry stamp = models.DateTimeField() # Date creation or validation of this entry
# "fake" declarations for type checking
objects: 'models.BaseManager[ActorToken]'
def __str__(self): def __str__(self):
return '<ActorToken {} created on {} by {} from {}/{}>'.format(self.token, self.stamp, self.username, self.hostname, self.ip_from) return '<ActorToken {} created on {} by {} from {}/{}>'.format(
self.token, self.stamp, self.username, self.hostname, self.ip_from
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 # Not imported at runtime, just for type checking
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from .user import User from uds.models import User, Group
from django.db.models import QuerySet # pylint: disable=ungrouped-imports
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -64,10 +63,16 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
small_name = models.CharField(max_length=32, default='', db_index=True) small_name = models.CharField(max_length=32, default='', db_index=True)
visible = models.BooleanField(default=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 class Meta(ManagedObjectModel.Meta): # pylint: disable=too-few-public-methods
""" """
Meta class to declare default order Meta class to declare default order
""" """
ordering = ('name',) ordering = ('name',)
app_label = 'uds' app_label = 'uds'
@ -87,7 +92,9 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
Raises: Raises:
""" """
if self.id is None: if self.id is None:
return auths.Authenticator(self, environment.Environment.getTempEnv(), values) return auths.Authenticator(
self, environment.Environment.getTempEnv(), values
)
auType = self.getType() auType = self.getType()
env = self.getEnvironment() 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" # 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 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. Used to get or create a new user at database associated with this authenticator.
@ -139,8 +148,17 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
""" """
user: 'User' user: 'User'
realName = realName if realName is None else username 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}) user, _ = self.users.get_or_create(
if (user.real_name.strip() == '' or user.name.strip() == user.real_name.strip()) and realName != user.real_name: 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.real_name = realName
user.save(update_fields=['real_name']) user.save(update_fields=['real_name'])
@ -169,14 +187,14 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
return falseIfNotExists return falseIfNotExists
@staticmethod @staticmethod
def all() -> 'QuerySet': def all() -> 'models.QuerySet[Authenticator]':
""" """
Returns all authenticators ordered by priority Returns all authenticators ordered by priority
""" """
return Authenticator.objects.all().order_by('priority') return Authenticator.objects.all().order_by('priority')
@staticmethod @staticmethod
def getByTag(tag=None) -> typing.List['Authenticator']: def getByTag(tag=None) -> typing.Iterable['Authenticator']:
""" """
Gets authenticator by tag name. Gets authenticator by tag name.
Special tag name "disabled" is used to exclude customAuth Special tag name "disabled" is used to exclude customAuth
@ -184,7 +202,9 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
from uds.core.util.config import GlobalConfig from uds.core.util.config import GlobalConfig
if tag is not None: 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: if not authsList:
authsList = Authenticator.objects.all().order_by('priority', 'name') authsList = Authenticator.objects.all().order_by('priority', 'name')
# If disallow global login (use all auths), get just the first 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: else:
authsList = Authenticator.objects.all().order_by('priority', 'name') 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 @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs) -> None:
""" """
Used to invoke the Service class "Destroy" before deleting it from database. 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. :note: If destroy raises an exception, the deletion is not taken.
""" """
from uds.core.util.permissions import clean from uds.core.util.permissions import clean
toDelete = kwargs['instance'] toDelete = kwargs['instance']
logger.debug('Before delete auth %s', toDelete) logger.debug('Before delete auth %s', toDelete)
@ -228,4 +251,4 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
# Connects a pre deletion signal to Authenticator # Connects a pre deletion signal to Authenticator
signals.pre_delete.connect(Authenticator.beforeDelete, sender=Authenticator) models.signals.pre_delete.connect(Authenticator.beforeDelete, sender=Authenticator)

View File

@ -45,16 +45,23 @@ class Cache(models.Model):
""" """
General caching model. This model is managed via uds.core.util.cache.Cache class General caching model. This model is managed via uds.core.util.cache.Cache class
""" """
owner = models.CharField(max_length=128, db_index=True) owner = models.CharField(max_length=128, db_index=True)
key = models.CharField(max_length=64, primary_key=True) key = models.CharField(max_length=64, primary_key=True)
value = models.TextField(default='') 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 validity = models.IntegerField(default=60) # Validity of this entry, in seconds
# "fake" relations declarations for type checking
objects: 'models.BaseManager[Cache]'
class Meta: class Meta:
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_utility_cache' db_table = 'uds_utility_cache'
app_label = 'uds' app_label = 'uds'

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# Copyright (c) 2016-2020 Virtual Cable S.L. # Copyright (c) 2016-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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='') comments = models.CharField(max_length=256, default='')
modified = models.DateTimeField(auto_now=True) modified = models.DateTimeField(auto_now=True)
# Sobmodels # "fake" declarations for type checking
# "fake" relations declarations for type checking objects: 'models.BaseManager[Calendar]'
rules: 'models.QuerySet[CalendarRule]' rules: 'models.QuerySet[CalendarRule]'
calendaraction_set: 'models.QuerySet[CalendarAction]' calendaraction_set: 'models.QuerySet[CalendarAction]'
@ -60,10 +60,17 @@ class Calendar(UUIDModel, TaggingMixin):
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_calendar' db_table = 'uds_calendar'
app_label = 'uds' 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') logger.debug('Saving calendar')
res = UUIDModel.save(self, force_insert, force_update, using, update_fields) res = UUIDModel.save(self, force_insert, force_update, using, update_fields)
@ -78,4 +85,6 @@ class Calendar(UUIDModel, TaggingMixin):
return res return res
def __str__(self): 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()
)

View File

@ -2,7 +2,7 @@
# Model based on https://github.com/llazzaro/django-scheduler # 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. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -47,15 +47,23 @@ logger = logging.getLogger(__name__)
class CalendarAccess(UUIDModel): class CalendarAccess(UUIDModel):
calendar: 'models.ForeignKey[CalendarAccess, Calendar]' = models.ForeignKey(Calendar, on_delete=models.CASCADE) calendar: 'models.ForeignKey[CalendarAccess, Calendar]' = models.ForeignKey(
service_pool: 'models.ForeignKey[CalendarAccess, ServicePool]' = models.ForeignKey(ServicePool, related_name='calendarAccess', on_delete=models.CASCADE) 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) access = models.CharField(max_length=8, default=states.action.DENY)
priority = models.IntegerField(default=0, db_index=True) priority = models.IntegerField(default=0, db_index=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[CalendarAccess]'
class Meta: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_cal_access' db_table = 'uds_cal_access'
ordering = ('priority',) ordering = ('priority',)
app_label = 'uds' app_label = 'uds'
@ -66,14 +74,20 @@ class CalendarAccess(UUIDModel):
class CalendarAccessMeta(UUIDModel): class CalendarAccessMeta(UUIDModel):
calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE) 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) access = models.CharField(max_length=8, default=states.action.DENY)
priority = models.IntegerField(default=0, db_index=True) priority = models.IntegerField(default=0, db_index=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[CalendarAccessMeta]'
class Meta: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_cal_maccess' db_table = 'uds_cal_maccess'
ordering = ('priority',) ordering = ('priority',)
app_label = 'uds' app_label = 'uds'

View File

@ -2,7 +2,7 @@
# Model based on https://github.com/llazzaro/django-scheduler # 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. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 .service_pool import ServicePool
from .transport import Transport from .transport import Transport
from .authenticator import Authenticator from .authenticator import Authenticator
# from django.utils.translation import ugettext_lazy as _, ugettext # from django.utils.translation import ugettext_lazy as _, ugettext
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
# Current posible actions # Current posible actions
# Each line describes:
# #
CALENDAR_ACTION_PUBLISH: typing.Dict[str, typing.Any] = {'id': 'PUBLISH', 'description': _('Publish'), 'params': ()} CALENDAR_ACTION_PUBLISH: typing.Dict[str, typing.Any] = {
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'},)} 'id': 'PUBLISH',
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'},)} 'description': _('Publish'),
CALENDAR_ACTION_INITIAL: typing.Dict[str, typing.Any] = {'id': 'INITIAL', 'description': _('Set initial services'), 'params': ({'type': 'numeric', 'name': 'size', 'description': _('Initial services'), 'default': '1'},)} 'params': (),
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_CACHE_L1: typing.Dict[str, typing.Any] = {
CALENDAR_ACTION_DEL_TRANSPORT: typing.Dict[str, typing.Any] = {'id': 'REMOVE_TRANSPORT', 'description': _('Remove a transport'), 'params': ({'type': 'transport', 'name': 'transport', 'description': _('Trasport'), 'default': ''},)} 'id': 'CACHEL1',
CALENDAR_ACTION_ADD_GROUP: typing.Dict[str, typing.Any] = {'id': 'ADD_GROUP', 'description': _('Add a group'), 'params': ({'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''},)} 'description': _('Set cache size'),
CALENDAR_ACTION_DEL_GROUP: typing.Dict[str, typing.Any] = {'id': 'REMOVE_GROUP', 'description': _('Remove a group'), 'params': ({'type': 'group', 'name': 'group', 'description': _('Group'), 'default': ''},)} 'params': (
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': ()} '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_DICT: typing.Dict[str, typing.Dict] = {
CALENDAR_ACTION_PUBLISH, CALENDAR_ACTION_CACHE_L1, c['id']: c
CALENDAR_ACTION_CACHE_L2, CALENDAR_ACTION_INITIAL, for c in (
CALENDAR_ACTION_PUBLISH,
CALENDAR_ACTION_CACHE_L1,
CALENDAR_ACTION_CACHE_L2,
CALENDAR_ACTION_INITIAL,
CALENDAR_ACTION_MAX, CALENDAR_ACTION_MAX,
CALENDAR_ACTION_ADD_TRANSPORT, CALENDAR_ACTION_DEL_TRANSPORT, CALENDAR_ACTION_ADD_TRANSPORT,
CALENDAR_ACTION_ADD_GROUP, CALENDAR_ACTION_DEL_GROUP, CALENDAR_ACTION_DEL_TRANSPORT,
CALENDAR_ACTION_ADD_GROUP,
CALENDAR_ACTION_DEL_GROUP,
CALENDAR_ACTION_IGNORE_UNUSED, CALENDAR_ACTION_IGNORE_UNUSED,
CALENDAR_ACTION_REMOVE_USERSERVICES CALENDAR_ACTION_REMOVE_USERSERVICES,
)} )
}
class CalendarAction(UUIDModel): class CalendarAction(UUIDModel):
calendar: Calendar = models.ForeignKey(Calendar, on_delete=models.CASCADE) calendar: 'models.ForeignKey[CalendarAction, Calendar]' = models.ForeignKey(
service_pool: ServicePool = models.ForeignKey(ServicePool, on_delete=models.CASCADE) 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='') 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 events_offset = models.IntegerField(default=0) # In minutes
params = models.CharField(max_length=1024, default='') params = models.CharField(max_length=1024, default='')
# Not to be edited, just to be used as indicators for executions # 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) last_execution = models.DateTimeField(
next_execution = models.DateTimeField(default=None, db_index=True, null=True, blank=True) 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: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_cal_action' db_table = 'uds_cal_action'
app_label = 'uds' app_label = 'uds'
@ -140,7 +258,9 @@ class CalendarAction(UUIDModel):
logger.exception('error') logger.exception('error')
return '(invalid action)' 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 """Executes the calendar action
Keyword Arguments: Keyword Arguments:
@ -149,7 +269,10 @@ class CalendarAction(UUIDModel):
logger.debug('Executing action') logger.debug('Executing action')
# If restrained pool, skip this execution (will rery later, not updated) # If restrained pool, skip this execution (will rery later, not updated)
if not self.service_pool.isUsable(): 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 return
self.last_execution = getSqlDatetime() self.last_execution = getSqlDatetime()
@ -182,11 +305,19 @@ class CalendarAction(UUIDModel):
self.service_pool.ignores_unused = params['state'] in ('true', '1', True) self.service_pool.ignores_unused = params['state'] in ('true', '1', True)
elif CALENDAR_ACTION_REMOVE_USERSERVICES['id'] == self.action: elif CALENDAR_ACTION_REMOVE_USERSERVICES['id'] == self.action:
# 1.- Remove usable assigned services (Ignore "creating ones", just for created) # 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() userService.remove()
else: else:
caTransports = (CALENDAR_ACTION_ADD_TRANSPORT['id'], CALENDAR_ACTION_DEL_TRANSPORT['id']) caTransports = (
caGroups = (CALENDAR_ACTION_ADD_GROUP['id'], CALENDAR_ACTION_DEL_GROUP['id']) 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: if self.action in caTransports:
try: try:
t = Transport.objects.get(uuid=params['transport']) t = Transport.objects.get(uuid=params['transport'])
@ -196,7 +327,9 @@ class CalendarAction(UUIDModel):
self.service_pool.transports.remove(t) self.service_pool.transports.remove(t)
executed = True executed = True
except Exception: 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 saveServicePool = False
elif self.action in caGroups: elif self.action in caGroups:
try: try:
@ -208,20 +341,27 @@ class CalendarAction(UUIDModel):
self.service_pool.assignedGroups.remove(grp) self.service_pool.assignedGroups.remove(grp)
executed = True executed = True
except Exception: 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 saveServicePool = False
if executed: if executed:
try: try:
self.service_pool.log( self.service_pool.log(
'Executed action {} [{}]'.format( '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: except Exception:
# Avoid invalid ACTIONS errors on log # 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 # On save, will regenerate nextExecution
if save: if save:
@ -230,9 +370,13 @@ class CalendarAction(UUIDModel):
if saveServicePool: if saveServicePool:
self.service_pool.save() 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() 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: if possibleNext:
self.next_execution = possibleNext + self.offset self.next_execution = possibleNext + self.offset
else: else:
@ -242,5 +386,9 @@ class CalendarAction(UUIDModel):
def __str__(self): def __str__(self):
return 'Calendar of {}, last_execution = {}, next execution = {}, action = {}, params = {}'.format( 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,
) )

View File

@ -54,7 +54,7 @@ freqs: typing.Tuple[typing.Tuple[str, str], ...] = (
('MONTHLY', _('Monthly')), ('MONTHLY', _('Monthly')),
('WEEKLY', _('Weekly')), ('WEEKLY', _('Weekly')),
('DAILY', _('Daily')), ('DAILY', _('Daily')),
(WEEKDAYS, _('Weekdays')) (WEEKDAYS, _('Weekdays')),
) )
frq_to_rrl: typing.Dict[str, int] = { frq_to_rrl: typing.Dict[str, int] = {
@ -82,10 +82,18 @@ dunit_to_mins: typing.Dict[str, int] = {
'MINUTES': 1, 'MINUTES': 1,
'HOURS': 60, 'HOURS': 60,
'DAYS': 60 * 24, '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): class CalendarRule(UUIDModel):
@ -95,16 +103,24 @@ class CalendarRule(UUIDModel):
start = models.DateTimeField() start = models.DateTimeField()
end = models.DateField(null=True, blank=True) end = models.DateField(null=True, blank=True)
frequency = models.CharField(choices=freqs, max_length=32) 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 = models.IntegerField(default=0) # Duration in minutes
duration_unit = models.CharField(choices=dunits, default='MINUTES', max_length=32) 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: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_calendar_rules' db_table = 'uds_calendar_rules'
app_label = 'uds' app_label = 'uds'
@ -112,7 +128,10 @@ class CalendarRule(UUIDModel):
if self.interval == 0: # Fix 0 intervals if self.interval == 0: # Fix 0 intervals
self.interval = 1 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: if self.frequency == WEEKDAYS:
dw = [] dw = []
@ -122,13 +141,21 @@ class CalendarRule(UUIDModel):
dw.append(weekdays[i]) dw.append(weekdays[i])
l >>= 1 l >>= 1
return rules.rrule(rules.DAILY, byweekday=dw, dtstart=self.start, until=end) 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: def as_rrule_end(self) -> rules.rrule:
if self.interval == 0: # Fix 0 intervals if self.interval == 0: # Fix 0 intervals
self.interval = 1 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: if self.frequency == WEEKDAYS:
dw = [] dw = []
@ -137,8 +164,19 @@ class CalendarRule(UUIDModel):
if l & 1 == 1: if l & 1 == 1:
dw.append(weekdays[i]) dw.append(weekdays[i])
l >>= 1 l >>= 1
return rules.rrule(rules.DAILY, byweekday=dw, dtstart=self.start + datetime.timedelta(minutes=self.duration_as_minutes), until=end) return rules.rrule(
return rules.rrule(frq_to_rrl[self.frequency], interval=self.interval, dtstart=self.start + datetime.timedelta(minutes=self.duration_as_minutes), until=end) 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 @property
def frequency_as_minutes(self) -> int: def frequency_as_minutes(self) -> int:
@ -150,7 +188,9 @@ class CalendarRule(UUIDModel):
def duration_as_minutes(self) -> int: def duration_as_minutes(self) -> int:
return dunit_to_mins.get(self.duration_unit, 1) * self.duration 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...') logger.debug('Saving...')
self.calendar.modified = getSqlDatetime() self.calendar.modified = getSqlDatetime()
@ -160,4 +200,11 @@ class CalendarRule(UUIDModel):
return res return res
def __str__(self): 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,
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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. General configuration values model. Used to store global and specific modules configuration values.
This model is managed via uds.core.util.config.Config class This model is managed via uds.core.util.config.Config class
""" """
section = models.CharField(max_length=128, db_index=True) section = models.CharField(max_length=128, db_index=True)
key = models.CharField(max_length=64, db_index=True) key = models.CharField(max_length=64, db_index=True)
value = models.TextField(default='') value = models.TextField(default='')
@ -50,10 +51,14 @@ class Config(models.Model):
long = models.BooleanField(default=False) long = models.BooleanField(default=False)
field_type = models.IntegerField(default=-1) field_type = models.IntegerField(default=-1)
# "fake" declarations for type checking
objects: 'models.BaseManager[Config]'
class Meta: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
db_table = 'uds_configuration' db_table = 'uds_configuration'
unique_together = (('section', 'key'),) unique_together = (('section', 'key'),)
app_label = 'uds' app_label = 'uds'

View File

@ -2,7 +2,7 @@
# Model based on https://github.com/llazzaro/django-scheduler # 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. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import codecs
import logging import logging
import typing
from django.db import models from django.db import models
from uds.core.util import encoders
from .uuid_model import UUIDModel from .uuid_model import UUIDModel
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
class DBFile(UUIDModel): 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) name = models.CharField(max_length=255, primary_key=True)
content = models.TextField(blank=True) content = models.TextField(blank=True)
size = models.IntegerField(default=0) size = models.IntegerField(default=0)
created = models.DateTimeField() created = models.DateTimeField()
modified = models.DateTimeField() modified = models.DateTimeField()
# "fake" declarations for type checking
objects: 'models.BaseManager[DBFile]'
@property @property
def data(self) -> bytes: def data(self) -> bytes:
try: 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: except Exception:
logger.error('DBFile %s has errors and cannot be used', self.name) logger.error('DBFile %s has errors and cannot be used', self.name)
try: try:
@ -65,7 +70,9 @@ class DBFile(UUIDModel):
@data.setter @data.setter
def data(self, value: bytes): def data(self, value: bytes):
self.size = len(value) 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): def __str__(self) -> str:
return 'File: {} {} {} {}'.format(self.name, self.size, self.created, self.modified) return 'File: {} {} {} {}'.format(
self.name, self.size, self.created, self.modified
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2020 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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_delay = models.PositiveIntegerField()
execution_time = models.DateTimeField(db_index=True) execution_time = models.DateTimeField(db_index=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[DelayedTask]'
class Meta: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 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) name = models.CharField(max_length=128, db_index=True)
state = models.CharField(max_length=1, default=State.ACTIVE, db_index=True) state = models.CharField(max_length=1, default=State.ACTIVE, db_index=True)
comments = models.CharField(max_length=256, default='') comments = models.CharField(max_length=256, default='')
@ -65,10 +67,14 @@ class Group(UUIDModel):
groups = models.ManyToManyField('self', symmetrical=False) groups = models.ManyToManyField('self', symmetrical=False)
created = models.DateTimeField(default=getSqlDatetime, blank=True) created = models.DateTimeField(default=getSqlDatetime, blank=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[Group]'
class Meta: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
unique_together = (("manager", "name"),) unique_together = (("manager", "name"),)
ordering = ('name',) ordering = ('name',)
app_label = 'uds' app_label = 'uds'
@ -87,12 +93,16 @@ class Group(UUIDModel):
def __str__(self) -> str: def __str__(self) -> str:
if self.is_meta: 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 @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs) -> None:
""" """
Used to invoke the Service class "Destroy" before deleting it from database. Used to invoke the Service class "Destroy" before deleting it from database.

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 .. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com
""" """
import io import io
import codecs
import logging import logging
import typing import typing
@ -38,8 +39,6 @@ import PIL.Image
from django.db import models from django.db import models
from django.http import HttpResponse from django.http import HttpResponse
from uds.core.util import encoders
from .uuid_model import UUIDModel from .uuid_model import UUIDModel
from .util import getSqlDatetime 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 This is intended for small images (i will limit them to 128x128), so storing at db is fine
""" """
MAX_IMAGE_SIZE = (128, 128) MAX_IMAGE_SIZE = (128, 128)
THUMBNAIL_SIZE = (48, 48) THUMBNAIL_SIZE = (48, 48)
name = models.CharField(max_length=128, unique=True, db_index=True) 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 data = models.BinaryField() # Image storage
thumb = models.BinaryField() # Thumbnail, very small thumb = models.BinaryField() # Thumbnail, very small
width = models.IntegerField(default=0) width = models.IntegerField(default=0)
height = models.IntegerField(default=0) height = models.IntegerField(default=0)
# "fake" declarations for type checking
objects: 'models.BaseManager[Image]'
class Meta: class Meta:
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_images' db_table = 'uds_images'
app_label = 'uds' app_label = 'uds'
@staticmethod @staticmethod
def encode64(data: bytes) -> str: 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 @staticmethod
def decode64(data64: str) -> bytes: def decode64(data64: str) -> bytes:
return typing.cast(bytes, encoders.decode(data64, 'base64')) return codecs.decode(data64.encode(), 'base64')
@staticmethod @staticmethod
def prepareForDb(data: bytes) -> bytes: def prepareForDb(data: bytes) -> bytes:
@ -168,12 +174,16 @@ class Image(UUIDModel):
def thumbnailResponse(self) -> HttpResponse: def thumbnailResponse(self) -> HttpResponse:
return HttpResponse(self.thumb, content_type='image/png') 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() self.stamp = getSqlDatetime()
return super().save(force_insert, force_update, using, update_fields) return super().save(force_insert, force_update, using, update_fields)
def __str__(self): 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 @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs):

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) level = models.PositiveIntegerField(default=0, db_index=True)
data = models.CharField(max_length=255, default='') data = models.CharField(max_length=255, default='')
# "fake" declarations for type checking
objects: 'models.BaseManager[Log]'
class Meta: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_log' db_table = 'uds_log'
app_label = 'uds' app_label = 'uds'
def __str__(self) -> str: 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,
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 Base abstract model for models that are top level Managed Objects
(such as Authenticator, Transport, OSManager, Provider, Service ...) (such as Authenticator, Transport, OSManager, Provider, Service ...)
""" """
name = models.CharField(max_length=128, unique=False, db_index=True) name = models.CharField(max_length=128, unique=False, db_index=True)
data_type = models.CharField(max_length=128) data_type = models.CharField(max_length=128)
data = models.TextField(default='') data = models.TextField(default='')
@ -60,6 +61,7 @@ class ManagedObjectModel(UUIDModel):
""" """
Defines this is an abstract class Defines this is an abstract class
""" """
abstract = True abstract = True
def getEnvironment(self) -> Environment: def getEnvironment(self) -> Environment:
@ -79,7 +81,9 @@ class ManagedObjectModel(UUIDModel):
self._cachedInstance = None # Ensures returns correct value on getInstance 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. Instantiates the object this record contains.
@ -95,7 +99,6 @@ class ManagedObjectModel(UUIDModel):
Can be overriden Can be overriden
""" """
if self._cachedInstance and values is None: 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 return self._cachedInstance
klass = self.getType() klass = self.getType()
@ -112,7 +115,9 @@ class ManagedObjectModel(UUIDModel):
Returns the type of self (as python type) Returns the type of self (as python type)
Must be overriden!!! 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: def isOfType(self, type_: str) -> bool:
""" """

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2018-2019 Virtual Cable S.L. # Copyright (c) 2018-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 from uds.models import User, CalendarAccessMeta
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -63,6 +62,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
""" """
A meta pool is a pool that has pool members A meta pool is a pool that has pool members
""" """
# Type of pool selection for meta pool # Type of pool selection for meta pool
ROUND_ROBIN_POOL = 0 ROUND_ROBIN_POOL = 0
PRIORITY_POOL = 1 PRIORITY_POOL = 1
@ -78,9 +78,23 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
short_name = models.CharField(max_length=32, default='') short_name = models.CharField(max_length=32, default='')
comments = models.CharField(max_length=256, default='') comments = models.CharField(max_length=256, default='')
visible = models.BooleanField(default=True) visible = models.BooleanField(default=True)
image = models.ForeignKey(Image, null=True, blank=True, related_name='metaPools', on_delete=models.SET_NULL) image = models.ForeignKey(
servicesPoolGroup = models.ForeignKey(ServicePoolGroup, null=True, blank=True, related_name='metaPools', on_delete=models.SET_NULL) Image,
assignedGroups = models.ManyToManyField(Group, related_name='metaPools', db_table='uds__meta_grps') 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 # Message if access denied
calendar_message = models.CharField(default='', max_length=256) calendar_message = models.CharField(default='', max_length=256)
@ -90,7 +104,8 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
# Pool selection policy # Pool selection policy
policy = models.SmallIntegerField(default=0) policy = models.SmallIntegerField(default=0)
# "fake" relations declarations for type checking # "fake" declarations for type checking
objects: 'models.BaseManager[MetaPool]'
calendarAccess: 'models.QuerySet[CalendarAccessMeta]' calendarAccess: 'models.QuerySet[CalendarAccessMeta]'
members: 'models.QuerySet[MetaPoolMember]' 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 Meta class to declare the name of the table at database
""" """
db_table = 'uds__pool_meta' db_table = 'uds__pool_meta'
app_label = 'uds' app_label = 'uds'
@ -115,7 +131,9 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
maintenance += 1 maintenance += 1
return total == maintenance 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) 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 @property
def visual_name(self) -> str: 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(): if self.short_name.strip():
return self.short_name return self.short_name
return self.name return self.name
@staticmethod @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. Return deployed services with publications for the groups requested.
@ -153,7 +175,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
meta = MetaPool.objects.filter( meta = MetaPool.objects.filter(
assignedGroups__in=groups, assignedGroups__in=groups,
assignedGroups__state=states.group.ACTIVE, assignedGroups__state=states.group.ACTIVE,
visible=True visible=True,
).prefetch_related( ).prefetch_related(
'servicesPoolGroup', 'servicesPoolGroup',
'servicesPoolGroup__image', 'servicesPoolGroup__image',
@ -168,7 +190,7 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
'calendarAccess', 'calendarAccess',
'calendarAccess__calendar', 'calendarAccess__calendar',
'calendarAccess__calendar__rules', 'calendarAccess__calendar__rules',
'image' 'image',
) )
if user: if user:
meta = meta.annotate( meta = meta.annotate(
@ -177,8 +199,8 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
filter=models.Q( filter=models.Q(
members__pool__userServices__user=user, members__pool__userServices__user=user,
members__pool__userServices__in_use=True, 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?) # 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. :note: If destroy raises an exception, the deletion is not taken.
""" """
from uds.core.util.permissions import clean from uds.core.util.permissions import clean
toDelete = kwargs['instance'] toDelete = kwargs['instance']
# Clears related logs # Clears related logs
@ -215,8 +238,12 @@ signals.pre_delete.connect(MetaPool.beforeDelete, sender=MetaPool)
class MetaPoolMember(UUIDModel): class MetaPoolMember(UUIDModel):
pool: 'models.ForeignKey[MetaPoolMember, ServicePool]' = models.ForeignKey(ServicePool, related_name='memberOfMeta', on_delete=models.CASCADE) pool: 'models.ForeignKey[MetaPoolMember, ServicePool]' = models.ForeignKey(
meta_pool: 'models.ForeignKey[MetaPoolMember, MetaPool]' = models.ForeignKey(MetaPool, related_name='members', on_delete=models.CASCADE) 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) priority = models.PositiveIntegerField(default=0)
enabled = models.BooleanField(default=True) enabled = models.BooleanField(default=True)
@ -224,8 +251,11 @@ class MetaPoolMember(UUIDModel):
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds__meta_pool_member' db_table = 'uds__meta_pool_member'
app_label = 'uds' app_label = 'uds'
def __str__(self) -> str: 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
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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..) 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) name = models.CharField(max_length=64, unique=True)
net_start = models.BigIntegerField(db_index=True) net_start = models.BigIntegerField(db_index=True)
net_end = models.BigIntegerField(db_index=True) net_end = models.BigIntegerField(db_index=True)
net_string = models.CharField(max_length=128, default='') 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): class Meta(UUIDModel.Meta):
""" """
Meta class to declare default order Meta class to declare default order
""" """
ordering = ('name',) ordering = ('name',)
app_label = 'uds' app_label = 'uds'
@ -82,7 +88,9 @@ class Network(UUIDModel, TaggingMixin): # type: ignore
netEnd: Network end netEnd: Network end
""" """
nr = net.networkFromString(netRange) 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 @property
def netStart(self) -> str: def netStart(self) -> str:
@ -123,11 +131,17 @@ class Network(UUIDModel, TaggingMixin): # type: ignore
self.save() self.save()
def __str__(self) -> str: 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 @staticmethod
def beforeDelete(sender, **kwargs) -> None: def beforeDelete(sender, **kwargs) -> None:
from uds.core.util.permissions import clean from uds.core.util.permissions import clean
toDelete = kwargs['instance'] toDelete = kwargs['instance']
logger.debug('Before delete auth %s', toDelete) logger.debug('Before delete auth %s', toDelete)
@ -135,5 +149,6 @@ class Network(UUIDModel, TaggingMixin): # type: ignore
# Clears related permissions # Clears related permissions
clean(toDelete) clean(toDelete)
# Connects a pre deletion signal to Authenticator # Connects a pre deletion signal to Authenticator
models.signals.pre_delete.connect(Network.beforeDelete, sender=Network) models.signals.pre_delete.connect(Network.beforeDelete, sender=Network)

View File

@ -51,17 +51,21 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore
An OS Manager represents a manager for responding requests for agents inside services. 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]' deployedServices: 'models.QuerySet[ServicePool]'
class Meta(ManagedObjectModel.Meta): class Meta(ManagedObjectModel.Meta):
""" """
Meta class to declare default order Meta class to declare default order
""" """
ordering = ('name',) ordering = ('name',)
app_label = 'uds' 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)) return typing.cast('osmanagers.OSManager', super().getInstance(values=values))
def getType(self) -> typing.Type['osmanagers.OSManager']: 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) :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) # 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) type_ = osmanagers.factory().lookup(self.data_type)
if type_: if type_:
@ -100,7 +104,7 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore
self.delete() self.delete()
return True return True
def __str__(self): def __str__(self) -> str:
return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id) return "{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
@staticmethod @staticmethod
@ -115,7 +119,9 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore
""" """
toDelete = kwargs['instance'] toDelete = kwargs['instance']
if toDelete.deployedServices.count() > 0: 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 # Only tries to get instance if data is not empty
if toDelete.data != '': if toDelete.data != '':
s = toDelete.getInstance() s = toDelete.getInstance()

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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. An OS Manager represents a manager for responding requests for agents inside services.
""" """
# Allowed permissions # Allowed permissions
PERMISSION_NONE = 0 PERMISSION_NONE = 0
PERMISSION_READ = 32 PERMISSION_READ = 32
@ -56,23 +57,42 @@ class Permissions(UUIDModel):
PERMISSION_ALL = 96 PERMISSION_ALL = 96
created = models.DateTimeField(db_index=True) 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) user = models.ForeignKey(
group = models.ForeignKey(Group, on_delete=models.CASCADE, related_name='permissions', null=True, blank=True, default=None) 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_type = models.SmallIntegerField(default=-1, db_index=True)
object_id = models.IntegerField(default=None, db_index=True, null=True, blank=True) object_id = models.IntegerField(default=None, db_index=True, null=True, blank=True)
permission = models.SmallIntegerField(default=PERMISSION_NONE, db_index=True) permission = models.SmallIntegerField(default=PERMISSION_NONE, db_index=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[Permissions]'
@staticmethod @staticmethod
def permissionAsString(perm: int) -> str: def permissionAsString(perm: int) -> str:
return { return {
Permissions.PERMISSION_NONE: _('None'), Permissions.PERMISSION_NONE: _('None'),
Permissions.PERMISSION_READ: _('Read'), Permissions.PERMISSION_READ: _('Read'),
Permissions.PERMISSION_MANAGEMENT: _('Manage'), Permissions.PERMISSION_MANAGEMENT: _('Manage'),
Permissions.PERMISSION_ALL: _('All') Permissions.PERMISSION_ALL: _('All'),
}.get(perm, _('None')) }.get(perm, _('None'))
@staticmethod @staticmethod
@ -104,13 +124,22 @@ class Permissions(UUIDModel):
q = Q(group=group) q = Q(group=group)
try: 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.permission = permission
existing.save() existing.save()
return existing return existing
except Exception: # Does not exists except Exception: # Does not exists
return Permissions.objects.create(created=getSqlDatetime(), ends=None, user=user, group=group, return Permissions.objects.create(
object_type=object_type, object_id=object_id, permission=permission) created=getSqlDatetime(),
ends=None,
user=user,
group=group,
object_type=object_type,
object_id=object_id,
permission=permission,
)
@staticmethod @staticmethod
def getPermissions(**kwargs) -> int: def getPermissions(**kwargs) -> int:
@ -141,7 +170,7 @@ class Permissions(UUIDModel):
perm = Permissions.objects.filter( perm = Permissions.objects.filter(
Q(object_type=object_type), Q(object_type=object_type),
Q(object_id=None) | Q(object_id=object_id), Q(object_id=None) | Q(object_id=object_id),
q q,
).order_by('-permission')[0] ).order_by('-permission')[0]
logger.debug('Got permission %s', perm) logger.debug('Got permission %s', perm)
return perm.permission return perm.permission
@ -157,7 +186,9 @@ class Permissions(UUIDModel):
@staticmethod @staticmethod
def cleanPermissions(object_type, object_id) -> None: 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 @staticmethod
def cleanUserPermissions(user) -> None: def cleanUserPermissions(user) -> None:
@ -173,5 +204,10 @@ class Permissions(UUIDModel):
def __str__(self) -> str: def __str__(self) -> str:
return 'Permission {}, user {} group {} object_type {} object_id {} permission {}'.format( 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),
) )

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) 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) maintenance_mode = models.BooleanField(default=False, db_index=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[Provider]'
class Meta(ManagedObjectModel.Meta): class Meta(ManagedObjectModel.Meta):
""" """
Meta class to declare default order Meta class to declare default order
""" """
ordering = ('name',) ordering = ('name',)
app_label = 'uds' app_label = 'uds'
def getType(self) -> typing.Type['services.ServiceProvider']: def getType(self) -> typing.Type['services.ServiceProvider']:
''' """
Get the type of the object this record represents. Get the type of the object this record represents.
The type is Python type, it obtains this type from ServiceProviderFactory and associated record field. The type is Python type, it obtains this type from ServiceProviderFactory and associated record field.
Returns: Returns:
The python type for this record object The python type for this record object
''' """
from uds.core import services # pylint: disable=redefined-outer-name from uds.core import services # pylint: disable=redefined-outer-name
type_ = services.factory().lookup(self.data_type) type_ = services.factory().lookup(self.data_type)
if type_: if type_:
return 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': def getInstance(
prov: services.ServiceProvider = typing.cast('services.ServiceProvider', super().getInstance(values=values)) self, values: typing.Optional[typing.Dict[str, str]] = None
) -> 'services.ServiceProvider':
prov: services.ServiceProvider = typing.cast(
'services.ServiceProvider', super().getInstance(values=values)
)
# Set uuid # Set uuid
prov.setUuid(self.uuid) prov.setUuid(self.uuid)
return prov return prov

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 Proxy DB model
""" """
name = models.CharField(max_length=128, unique=False, db_index=True) name = models.CharField(max_length=128, unique=False, db_index=True)
comments = models.CharField(max_length=256) comments = models.CharField(max_length=256)
@ -55,16 +56,22 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore
ssl = models.BooleanField(default=True) ssl = models.BooleanField(default=True)
check_cert = models.BooleanField(default=False) check_cert = models.BooleanField(default=False)
# "fake" declarations for type checking
objects: 'models.BaseManager[Proxy]'
class Meta: class Meta:
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_proxies' db_table = 'uds_proxies'
app_label = 'uds' app_label = 'uds'
@property @property
def url(self) -> str: 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 @property
def proxyRequestUrl(self) -> str: def proxyRequestUrl(self) -> str:
@ -74,10 +81,10 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore
def testServerUrl(self) -> str: def testServerUrl(self) -> str:
return self.url + "/testServer" return self.url + "/testServer"
def doProxyRequest(self, url, data: typing.Optional[typing.Any] = None, timeout: int = 5) -> requests.Response: def doProxyRequest(
d = { self, url, data: typing.Optional[typing.Any] = None, timeout: int = 5
'url': url ) -> requests.Response:
} d = {'url': url}
if data is not None: if data is not None:
d['data'] = data d['data'] = data
@ -86,17 +93,15 @@ class Proxy(UUIDModel, TaggingMixin): # type: ignore
data=json.dumps(d), data=json.dumps(d),
headers={'content-type': 'application/json'}, headers={'content-type': 'application/json'},
verify=self.check_cert, verify=self.check_cert,
timeout=timeout timeout=timeout,
) )
def doTestServer(self, ip: str, port: typing.Union[str, int], timeout=5) -> bool: def doTestServer(self, ip: str, port: typing.Union[str, int], timeout=5) -> bool:
try: try:
url = self.testServerUrl + '?host={}&port={}&timeout={}'.format(ip, port, timeout) url = self.testServerUrl + '?host={}&port={}&timeout={}'.format(
r = requests.get( ip, port, timeout
url,
verify=self.check_cert,
timeout=timeout
) )
r = requests.get(url, verify=self.check_cert, timeout=timeout)
if r.status_code == 302: # Proxy returns "Found" for a success test if r.status_code == 302: # Proxy returns "Found" for a success test
return True return True
# Else returns 404 # Else returns 404

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -34,7 +34,6 @@ import logging
import typing import typing
from django.db import models from django.db import models
from django.db.models import signals
from uds.core.util.state import State from uds.core.util.state import State
from uds.core.environment import Environment 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) state = models.CharField(max_length=1, default=State.FOR_EXECUTE, db_index=True)
# primary key id declaration (for type checking) # 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: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
app_label = 'uds' app_label = 'uds'
def getEnvironment(self) -> Environment: def getEnvironment(self) -> Environment:
@ -99,7 +102,7 @@ class Scheduler(models.Model):
return None return None
@staticmethod @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs) -> None:
""" """
Used to remove environment for sheduled task Used to remove environment for sheduled task
""" """
@ -107,9 +110,11 @@ class Scheduler(models.Model):
logger.debug('Deleting sheduled task %s', toDelete) logger.debug('Deleting sheduled task %s', toDelete)
toDelete.getEnvironment().clearRelatedData() toDelete.getEnvironment().clearRelatedData()
def __str__(self): def __str__(self) -> str:
return 'Scheduled task {}, every {}, last execution at {}, state = {}'.format(self.name, self.frecuency, self.last_execution, self.state) 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 # Connects a pre deletion signal to Scheduler
signals.pre_delete.connect(Scheduler.beforeDelete, sender=Scheduler) models.signals.pre_delete.connect(Scheduler.beforeDelete, sender=Scheduler)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 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). 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 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 _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 Meta class to declare default order and unique multiple field index
""" """
ordering = ('name',) ordering = ('name',)
unique_together = (("provider", "name"),) unique_together = (("provider", "name"),)
app_label = 'uds' app_label = 'uds'
def getEnvironment(self): def getEnvironment(self) -> Environment:
""" """
Returns an environment valid for the record this object represents 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( return Environment.getEnvForTableElement(
self._meta.verbose_name, self._meta.verbose_name,
self.id, self.id,
@ -89,10 +97,10 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore
'mac': unique.UniqueMacGenerator, 'mac': unique.UniqueMacGenerator,
'name': unique.UniqueNameGenerator, 'name': unique.UniqueNameGenerator,
'id': unique.UniqueGIDGenerator, '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. 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) obj = sType(self.getEnvironment(), prov, values, uuid=self.uuid)
self.deserialize(obj, values) self.deserialize(obj, values)
else: 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 self._cachedInstance = obj
@ -140,22 +152,28 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore
if type_: if type_:
return 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: def isInMaintenance(self) -> bool:
# orphaned services? # orphaned services?
return self.provider.isInMaintenance() if self.provider else True return self.provider.isInMaintenance() if self.provider else True
def testServer(self, host: str, port: typing.Union[str, int], timeout: int = 4) -> bool: def testServer(
if self.proxy is not None: self, host: str, port: typing.Union[str, int], timeout: int = 4
) -> bool:
if self.proxy:
return self.proxy.doTestServer(host, port, timeout) return self.proxy.doTestServer(host, port, timeout)
return connection.testServer(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) return '{} of type {} (id:{})'.format(self.name, self.data_type, self.id)
@staticmethod @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs) -> None:
""" """
Used to invoke the Service class "Destroy" before deleting it from database. 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. :note: If destroy raises an exception, the deletion is not taken.
""" """
from uds.core.util.permissions import clean from uds.core.util.permissions import clean
toDelete = kwargs['instance'] toDelete = kwargs['instance']
logger.debug('Before delete service %s', toDelete) logger.debug('Before delete service %s', toDelete)
@ -180,5 +199,6 @@ class Service(ManagedObjectModel, TaggingMixin): # type: ignore
# Clears related permissions # Clears related permissions
clean(toDelete) clean(toDelete)
# : Connects a pre deletion signal to Service # : Connects a pre deletion signal to Service
models.signals.pre_delete.connect(Service.beforeDelete, sender=Service) models.signals.pre_delete.connect(Service.beforeDelete, sender=Service)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 # Not imported at runtime, just for type checking
if typing.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__) 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..) 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='') name = models.CharField(max_length=128, default='')
short_name = models.CharField(max_length=32, default='') short_name = models.CharField(max_length=32, default='')
comments = models.CharField(max_length=256, 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) service: 'models.ForeignKey[ServicePool, Service]' = models.ForeignKey(
osmanager: 'models.ForeignKey[ServicePool, OSManager]' = models.ForeignKey(OSManager, null=True, blank=True, related_name='deployedServices', on_delete=models.CASCADE) Service,
transports = models.ManyToManyField(Transport, related_name='deployedServices', db_table='uds__ds_trans') null=True,
assignedGroups = models.ManyToManyField(Group, related_name='deployedServices', db_table='uds__ds_grps') blank=True,
state = models.CharField(max_length=1, default=states.servicePool.ACTIVE, db_index=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) state_date = models.DateTimeField(default=NEVER)
show_transports = models.BooleanField(default=True) show_transports = models.BooleanField(default=True)
visible = 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) 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 # Message if access denied
calendar_message = models.CharField(default='', max_length=256) calendar_message = models.CharField(default='', max_length=256)
# Default fallback action for access # Default fallback action for access
fallbackAccess = models.CharField(default=states.action.ALLOW, max_length=8) fallbackAccess = models.CharField(default=states.action.ALLOW, max_length=8)
# actionsCalendars = models.ManyToManyField(Calendar, related_name='actionsSP', through='CalendarAction')
# Usage accounting # 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) initial_srvs = models.PositiveIntegerField(default=0)
cache_l1_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) max_srvs = models.PositiveIntegerField(default=0)
current_pub_revision = models.PositiveIntegerField(default=1) 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]' publications: 'models.QuerySet[ServicePoolPublication]'
memberOfMeta: 'models.QuerySet[MetaPoolMember]' memberOfMeta: 'models.QuerySet[MetaPoolMember]'
userServices: 'models.QuerySet[UserService]' userServices: 'models.QuerySet[UserService]'
calendarAccess: 'models.QuerySet[CalendarAccess]' calendarAccess: 'models.QuerySet[CalendarAccess]'
# Meta service related
# meta_pools = models.ManyToManyField('self', symmetrical=False)
class Meta(UUIDModel.Meta): class Meta(UUIDModel.Meta):
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds__deployed_service' db_table = 'uds__deployed_service'
app_label = 'uds' app_label = 'uds'
@ -125,9 +170,6 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
""" """
Returns an environment valid for the record this object represents Returns an environment valid for the record this object represents
""" """
a = self.image
return Environment.getEnvForTableElement(self._meta.verbose_name, self.id) return Environment.getEnvForTableElement(self._meta.verbose_name, self.id)
def activePublication(self) -> typing.Optional['ServicePoolPublication']: 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. None if there is no valid publication for this deployed service.
""" """
try: try:
return typing.cast(ServicePoolPublication, self.publications.filter(state=states.publication.USABLE)[0]) return self.publications.filter(state=states.publication.USABLE)[0]
except Exception: except Exception:
return None return None
@ -161,36 +203,49 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return username, password return username, password
@staticmethod @staticmethod
def getRestrainedsQuerySet() -> models.QuerySet: def getRestrainedsQuerySet() -> 'models.QuerySet[ServicePool]':
from uds.models.user_service import UserService # pylint: disable=redefined-outer-name from uds.models.user_service import (
UserService,
) # pylint: disable=redefined-outer-name
from uds.core.util.config import GlobalConfig from uds.core.util.config import GlobalConfig
from django.db.models import Count from django.db.models import Count
if GlobalConfig.RESTRAINT_TIME.getInt() <= 0: 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() min_ = GlobalConfig.RESTRAINT_COUNT.getInt()
res = [] res = []
for v in UserService.objects.filter( for v in (
UserService.objects.filter(
state=states.userService.ERROR, state_date__gt=date 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_: if v['how_many'] >= min_:
res.append(v['deployed_service']) res.append(v['deployed_service'])
return ServicePool.objects.filter(pk__in=res) return ServicePool.objects.filter(pk__in=res)
@staticmethod @staticmethod
def getRestraineds() -> typing.Iterator['ServicePool']: def getRestraineds() -> typing.Iterator['ServicePool']:
return typing.cast(typing.Iterator['ServicePool'], ServicePool.getRestrainedsQuerySet().iterator()) return ServicePool.getRestrainedsQuerySet().iterator()
@property @property
def is_meta(self) -> bool: def owned_by_meta(self) -> bool:
return self.memberOfMeta.count() > 0 return self.memberOfMeta.count() > 0
@property @property
def visual_name(self) -> str: 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() != '': if self.short_name is not None and str(self.short_name).strip() != '':
return str(self.short_name) return str(self.short_name)
return str(self.name) return str(self.name)
@ -214,8 +269,15 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
if GlobalConfig.RESTRAINT_TIME.getInt() <= 0: if GlobalConfig.RESTRAINT_TIME.getInt() <= 0:
return False # Do not perform any restraint check if we set the globalconfig to 0 (or less) 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()) date = typing.cast(datetime, getSqlDatetime()) - timedelta(
if self.userServices.filter(state=states.userService.ERROR, state_date__gt=date).count() >= GlobalConfig.RESTRAINT_COUNT.getInt(): 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 True
return False return False
@ -227,7 +289,11 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return self.visible return self.visible
def isUsable(self) -> bool: 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() activePub: typing.Optional['ServicePoolPublication'] = self.activePublication()
@ -241,7 +307,12 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
# Return the date # Return the date
try: 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: if activePub and found.publication and activePub.id != found.publication.id:
ret = self.recoverValue('toBeReplacedIn') ret = self.recoverValue('toBeReplacedIn')
if ret: if ret:
@ -268,7 +339,9 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return access == states.action.ALLOW 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 """Gets the deadline for an access on chkDateTime in seconds
Keyword Arguments: Keyword Arguments:
@ -286,7 +359,10 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
deadLine = None deadLine = None
for ac in self.calendarAccess.all(): 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) nextE = CalendarChecker(ac.calendar).nextEvent(chkDateTime, False)
if deadLine is None or deadLine > nextE: if deadLine is None or deadLine > nextE:
deadLine = nextE deadLine = nextE
@ -350,8 +426,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
def removed(self) -> None: def removed(self) -> None:
""" """
Mark the deployed service as removed. Mark the deployed service as removed.
Basically, deletes the user service
A background worker will check for removed deloyed services and clean database of them.
""" """
# self.transports.clear() # self.transports.clear()
# self.assignedGroups.clear() # self.assignedGroups.clear()
@ -360,7 +435,11 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
# self.setState(State.REMOVED) # self.setState(State.REMOVED)
self.delete() 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. Used when a new publication is finished.
@ -379,15 +458,23 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
userService: 'UserService' userService: 'UserService'
if activePub is None: 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 return
for nonActivePub in self.publications.exclude(id=activePub.id): 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() userService.cancel()
with transaction.atomic(): 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: 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: def validateGroups(self, groups: typing.Iterable['Group']) -> None:
""" """
@ -395,6 +482,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
raise an InvalidUserException if fails check raise an InvalidUserException if fails check
""" """
from uds.core import auths from uds.core import auths
if not set(groups) & set(self.assignedGroups.all()): if not set(groups) & set(self.assignedGroups.all()):
raise auths.exceptions.InvalidUserException() 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 Ensures that, if this service has publications, that a publication is active
raises an IvalidServiceException if check fails 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() raise InvalidServiceException()
def validateTransport(self, transport) -> None: def validateTransport(self, transport) -> None:
@ -419,9 +510,6 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
Args: Args:
user: User (db record) to check if has access to this deployed service user: User (db record) to check if has access to this deployed service
Returns:
True if has access
Raises: Raises:
InvalidUserException() if user do not has access to this deployed service InvalidUserException() if user do not has access to this deployed service
@ -436,7 +524,9 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
self.validatePublication() self.validatePublication()
@staticmethod @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. Return deployed services with publications for the groups requested.
@ -447,17 +537,33 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
List of accesible deployed services List of accesible deployed services
""" """
from uds.core import 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 # Get services that HAS publications
query = ( query = (
ServicePool.objects.filter( ServicePool.objects.filter(
assignedGroups__in=groups, assignedGroups__in=groups,
assignedGroups__state=states.group.ACTIVE, assignedGroups__state=states.group.ACTIVE,
state=states.servicePool.ACTIVE, state=states.servicePool.ACTIVE,
visible=True 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( .prefetch_related(
'transports', 'transports',
'transports__networks', 'transports__networks',
@ -471,22 +577,28 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
'calendarAccess', 'calendarAccess',
'calendarAccess__calendar', 'calendarAccess__calendar',
'calendarAccess__calendar__rules', 'calendarAccess__calendar__rules',
'image' 'image',
) )
) )
if user: # Optimize loading if there is some assgned service.. if user: # Optimize loading if there is some assgned service..
query = query.annotate( query = query.annotate(
number_in_use=models.Count( number_in_use=models.Count(
'userServices', filter=models.Q( 'userServices',
filter=models.Q(
userServices__user=user, userServices__user=user,
userServices__in_use=True, userServices__in_use=True,
userServices__state__in=states.userService.USABLE userServices__state__in=states.userService.USABLE,
) ),
) )
) )
servicePool: 'ServicePool' 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: 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. No check is done, it simply redirects the request to PublicationManager, where checks are done.
""" """
from uds.core.managers import publicationManager from uds.core.managers import publicationManager
publicationManager().publish(self, changeLog) publicationManager().publish(self, changeLog)
def unpublish(self) -> None: def unpublish(self) -> None:
@ -507,7 +620,7 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
if pub: if pub:
pub.unpublish() pub.unpublish()
def cachedUserServices(self) -> 'models.QuerySet': def cachedUserServices(self) -> 'models.QuerySet[UserService]':
""" """
':rtype uds.models.user_service.UserService' ':rtype uds.models.user_service.UserService'
Utility method to access the cached user services (level 1 and 2) 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) 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 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) 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. Utility method to locate invalid assigned user services.
@ -549,7 +662,11 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
return 0 return 0
if cachedValue == -1: 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 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. :note: If destroy raises an exception, the deletion is not taken.
""" """
from uds.core.util.permissions import clean from uds.core.util.permissions import clean
toDelete: 'ServicePool' = kwargs['instance'] toDelete: 'ServicePool' = kwargs['instance']
logger.debug('Deleting Service Pool %s', toDelete) logger.debug('Deleting Service Pool %s', toDelete)
@ -589,7 +707,13 @@ class ServicePool(UUIDModel, TaggingMixin): # type: ignore
def __str__(self): def __str__(self):
return 'Deployed service {}({}) with {} as initial, {} as L1 cache, {} as L2 cache, {} as max'.format( 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 # Connects a pre deletion signal to Authenticator

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) 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) 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): class Meta(UUIDModel.Meta):
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
@ -62,11 +65,11 @@ class ServicePoolGroup(UUIDModel):
db_table = 'uds__pools_groups' db_table = 'uds__pools_groups'
app_label = 'uds' app_label = 'uds'
def __str__(self): def __str__(self) -> str:
return 'Service Pool group {}({}): {}'.format(self.name, self.comments, self.image.name) return 'Service Pool group {}({}): {}'.format(self.name, self.comments, self.image.name)
@property @property
def as_dict(self) -> typing.Dict[str, typing.Any]: def as_dict(self) -> typing.MutableMapping[str, typing.Any]:
return { return {
'id': self.uuid, 'id': self.uuid,
'name': self.name, 'name': self.name,
@ -81,4 +84,9 @@ class ServicePoolGroup(UUIDModel):
@staticmethod @staticmethod
def default() -> 'ServicePoolGroup': 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) return ServicePoolGroup(name=_('General'), comments='', priority=-10000)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -36,6 +36,7 @@ import typing
from django.db import models from django.db import models
from uds.core.managers import publicationManager
from uds.core.util.state import State from uds.core.util.state import State
from uds.core.environment import Environment from uds.core.environment import Environment
from uds.core.util import log from uds.core.util import log
@ -53,26 +54,43 @@ logger = logging.getLogger(__name__)
class ServicePoolPublicationChangelog(models.Model): 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() stamp = models.DateTimeField()
revision = models.PositiveIntegerField(default=1) revision = models.PositiveIntegerField(default=1)
log = models.TextField(default='') log = models.TextField(default='')
# "fake" declarations for type checking
objects: 'models.BaseManager[ServicePoolPublicationChangelog]'
class Meta(UUIDModel.Meta): class Meta(UUIDModel.Meta):
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
db_table = 'uds__deployed_service_pub_cl' db_table = 'uds__deployed_service_pub_cl'
app_label = 'uds' app_label = 'uds'
def __str__(self): def __str__(self) -> str:
return 'Revision log for publication {}, rev {}: {}'.format(self.publication.name, self.revision, self.log) return 'Revision log for publication {}, rev {}: {}'.format(
self.publication.name, self.revision, self.log
)
class ServicePoolPublication(UUIDModel): 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) 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) publish_date = models.DateTimeField(db_index=True)
# data_type = models.CharField(max_length=128) # The data type is specified by the service itself # data_type = models.CharField(max_length=128) # The data type is specified by the service itself
data = models.TextField(default='') data = models.TextField(default='')
@ -87,7 +105,8 @@ class ServicePoolPublication(UUIDModel):
state_date = models.DateTimeField() state_date = models.DateTimeField()
revision = models.PositiveIntegerField(default=1) revision = models.PositiveIntegerField(default=1)
# "fake" relations declarations for type checking # "fake" declarations for type checking
objects: 'models.BaseManager[ServicePoolPublication]'
userServices: 'models.QuerySet[UserService]' userServices: 'models.QuerySet[UserService]'
@ -95,6 +114,7 @@ class ServicePoolPublication(UUIDModel):
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
db_table = 'uds__deployed_service_pub' db_table = 'uds__deployed_service_pub'
ordering = ('publish_date',) ordering = ('publish_date',)
app_label = 'uds' app_label = 'uds'
@ -128,7 +148,11 @@ class ServicePoolPublication(UUIDModel):
# a service that needs publication but do not have # a service that needs publication but do not have
if serviceInstance.publicationType is None: 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( publication = serviceInstance.publicationType(
self.getEnvironment(), self.getEnvironment(),
@ -136,7 +160,8 @@ class ServicePoolPublication(UUIDModel):
osManager=osManagerInstance, osManager=osManagerInstance,
revision=self.revision, revision=self.revision,
dsName=self.deployed_service.name, dsName=self.deployed_service.name,
uuid=self.uuid, dbPublication=self uuid=self.uuid,
dbPublication=self,
) )
# Only invokes deserialization if data has something. '' is nothing # Only invokes deserialization if data has something. '' is nothing
if self.data: 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. No check is done, it simply redirects the request to PublicationManager, where checks are done.
""" """
from uds.core.managers import publicationManager
publicationManager().unpublish(self) publicationManager().unpublish(self)
def cancel(self): def cancel(self):
""" """
Invoques the cancelation of this publication Invoques the cancelation of this publication
""" """
from uds.core.managers import publicationManager
publicationManager().cancel(self) publicationManager().cancel(self)
@staticmethod @staticmethod
@ -207,8 +232,13 @@ class ServicePoolPublication(UUIDModel):
logger.debug('Deleted publication %s', toDelete) logger.debug('Deleted publication %s', toDelete)
def __str__(self): def __str__(self) -> str:
return 'Publication {}, rev {}, state {}'.format(self.deployed_service.name, self.revision, State.toString(self.state)) return 'Publication {}, rev {}, state {}'.format(
self.deployed_service.name, self.revision, State.toString(self.state)
)
# Connects a pre deletion signal to Authenticator # 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
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -46,7 +46,7 @@ logger = logging.getLogger(__name__)
class StatsCounters(models.Model): 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) 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) stamp = models.IntegerField(db_index=True, default=0)
value = 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: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_stats_c' db_table = 'uds_stats_c'
app_label = 'uds' app_label = 'uds'
@staticmethod @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) 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 since = int(since) if since else NEVER_UNIX
to = int(to) if to else getSqlDatetimeAsUnix() 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') max_intervals = kwargs.get('max_intervals')
@ -106,9 +114,13 @@ class StatsCounters(models.Model):
q = StatsCounters.objects.filter(stamp__gte=since, stamp__lte=to) q = StatsCounters.objects.filter(stamp__gte=since, stamp__lte=to)
else: else:
if isinstance(owner_id, (list, tuple, types.GeneratorType)): 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: 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)): if isinstance(owner_type, (list, tuple, types.GeneratorType)):
q = q.filter(owner_type__in=owner_type) q = q.filter(owner_type__in=owner_type)
@ -120,11 +132,11 @@ class StatsCounters(models.Model):
last = q.order_by('stamp').reverse()[0].stamp last = q.order_by('stamp').reverse()[0].stamp
interval = int((last - first) / (max_intervals - 1)) 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( filt += ' AND stamp>={since} AND stamp<={to} GROUP BY {stampValue} ORDER BY stamp'.format(
since=since, since=since, to=to, stampValue=stampValue
to=to,
stampValue=stampValue
) )
if limit: if limit:
@ -138,15 +150,22 @@ class StatsCounters(models.Model):
# fnc = getSqlFnc('MAX' if kwargs.get('use_max', False) else 'AVG') # fnc = getSqlFnc('MAX' if kwargs.get('use_max', False) else 'AVG')
query = ( query = (
'SELECT -1 as id,-1 as owner_id,-1 as owner_type,-1 as counter_type, ' + stampValue + '*{}'.format(interval) + ' AS stamp, ' + 'SELECT -1 as id,-1 as owner_id,-1 as owner_type,-1 as counter_type, '
'{} AS value ' + stampValue
+ '*{}'.format(interval)
+ ' AS stamp, '
+ '{} AS value '
'FROM {} WHERE {}' 'FROM {} WHERE {}'
).format(fnc, StatsCounters._meta.db_table, filt) ).format(fnc, StatsCounters._meta.db_table, filt)
logger.debug('Stats query: %s', query) logger.debug('Stats query: %s', query)
# We use result as an iterator # 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): 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
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -45,7 +45,7 @@ logger = logging.getLogger(__name__)
class StatsEvents(models.Model): 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) owner_id = models.IntegerField(db_index=True, default=0)
@ -59,15 +59,23 @@ class StatsEvents(models.Model):
fld3 = models.CharField(max_length=128, default='') fld3 = models.CharField(max_length=128, default='')
fld4 = models.CharField(max_length=128, default='') fld4 = models.CharField(max_length=128, default='')
# "fake" declarations for type checking
objects: 'models.BaseManager[StatsEvents]'
class Meta: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_stats_e' db_table = 'uds_stats_e'
app_label = 'uds' app_label = 'uds'
@staticmethod @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) 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 return self.fld4
def __str__(self): 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,
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -47,7 +47,8 @@ class Storage(models.Model):
data = models.TextField(default='') data = models.TextField(default='')
attr1 = models.CharField(max_length=64, db_index=True, null=True, blank=True, default=None) 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: class Meta:
""" """
@ -55,5 +56,5 @@ class Storage(models.Model):
""" """
app_label = 'uds' app_label = 'uds'
def __str__(self): def __str__(self) -> str:
return '{} {} > str= {}, {}'.format(self.owner, self.key, self.data, '/'.join([self.attr1])) return '{} {} > str= {}, {}'.format(self.owner, self.key, self.data, '/'.join([self.attr1]))

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) tag = models.CharField(max_length=32, db_index=True, unique=True)
# "fake" declarations for type checking
objects: 'models.BaseManager[Tag]'
class Meta: class Meta:
""" """
Meta class to declare db table Meta class to declare db table
""" """
db_table = 'uds_tag' db_table = 'uds_tag'
app_label = 'uds' app_label = 'uds'

View File

@ -1,6 +1,6 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -50,16 +50,24 @@ class TicketStore(UUIDModel):
""" """
Tickets storing on DB Tickets storing on DB
""" """
DEFAULT_VALIDITY = 60 DEFAULT_VALIDITY = 60
MAX_VALIDITY = 60 * 60 * 12 MAX_VALIDITY = 60 * 60 * 12
# Cleanup will purge all elements that have been created MAX_VALIDITY ago # Cleanup will purge all elements that have been created MAX_VALIDITY ago
owner = models.CharField(null=True, blank=True, default=None, max_length=8) owner = models.CharField(null=True, blank=True, default=None, max_length=8)
stamp = models.DateTimeField() # Date creation or validation of this entry 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 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): class InvalidTicket(Exception):
pass pass
@ -68,6 +76,7 @@ class TicketStore(UUIDModel):
""" """
Meta class to declare the name of the table at database Meta class to declare the name of the table at database
""" """
db_table = 'uds_tickets' db_table = 'uds_tickets'
app_label = 'uds' app_label = 'uds'
@ -84,7 +93,7 @@ class TicketStore(UUIDModel):
validatorFnc: typing.Optional[ValidatorType] = None, validatorFnc: typing.Optional[ValidatorType] = None,
validity: int = DEFAULT_VALIDITY, validity: int = DEFAULT_VALIDITY,
owner: typing.Optional[str] = None, owner: typing.Optional[str] = None,
secure: bool = False secure: bool = False,
) -> str: ) -> str:
""" """
validity is in seconds validity is in seconds
@ -94,7 +103,13 @@ class TicketStore(UUIDModel):
if secure: if secure:
pass 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 @staticmethod
def store( def store(
@ -103,7 +118,7 @@ class TicketStore(UUIDModel):
validatorFnc: typing.Optional[ValidatorType] = None, validatorFnc: typing.Optional[ValidatorType] = None,
validity: int = DEFAULT_VALIDITY, validity: int = DEFAULT_VALIDITY,
owner: typing.Optional[str] = None, owner: typing.Optional[str] = None,
secure: bool = False secure: bool = False,
) -> None: ) -> None:
""" """
Stores an ticketstore. If one with this uuid already exists, replaces it. Else, creates a new one Stores an ticketstore. If one with this uuid already exists, replaces it. Else, creates a new one
@ -122,14 +137,20 @@ class TicketStore(UUIDModel):
t.owner = owner t.owner = owner
t.save() t.save()
except TicketStore.DoesNotExist: 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 @staticmethod
def get( def get(
uuid: str, uuid: str,
invalidate: bool = True, invalidate: bool = True,
owner: typing.Optional[str] = None, owner: typing.Optional[str] = None,
secure: bool = False secure: bool = False,
) -> typing.Any: ) -> typing.Any:
try: try:
t = TicketStore.objects.get(uuid=uuid, owner=owner) t = TicketStore.objects.get(uuid=uuid, owner=owner)
@ -159,30 +180,43 @@ class TicketStore(UUIDModel):
raise TicketStore.InvalidTicket('Does not exists') raise TicketStore.InvalidTicket('Does not exists')
@staticmethod @staticmethod
def revalidate(uuid, validity=None, owner=None): def revalidate(
uuid: str,
validity: typing.Optional[int] = None,
owner: typing.Optional[str] = None,
):
try: try:
t = TicketStore.objects.get(uuid=uuid, owner=owner) t = TicketStore.objects.get(uuid=uuid, owner=owner)
t.stamp = getSqlDatetime() t.stamp = getSqlDatetime()
if validity is not None: if validity:
t.validity = validity t.validity = validity
t.save() t.save(update_fields=['validity', 'stamp'])
except TicketStore.DoesNotExist: except TicketStore.DoesNotExist:
raise Exception('Does not exists') raise Exception('Does not exists')
@staticmethod @staticmethod
def cleanup(): def cleanup() -> None:
from datetime import timedelta
now = getSqlDatetime() now = getSqlDatetime()
for v in TicketStore.objects.all(): 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() v.delete()
cleanSince = now - datetime.timedelta(seconds=TicketStore.MAX_VALIDITY) 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() TicketStore.objects.filter(stamp__lt=cleanSince).delete()
def __str__(self): def __str__(self) -> str:
if self.validator is not None: if self.validator:
validator = pickle.loads(self.validator) validator = pickle.loads(self.validator)
else: else:
validator = None 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),
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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 # "fake" relations declarations for type checking
networks: 'models.QuerySet[Network]' networks: 'models.QuerySet[Network]'
# "fake" declarations for type checking
objects: 'models.BaseManager[Transport]'
class Meta(ManagedObjectModel.Meta): class Meta(ManagedObjectModel.Meta):
""" """
@ -132,7 +134,7 @@ class Transport(ManagedObjectModel, TaggingMixin):
return '{} of type {} (id:{})'.format(self.name, self.data_type, self.id) return '{} of type {} (id:{})'.format(self.name, self.data_type, self.id)
@staticmethod @staticmethod
def beforeDelete(sender, **kwargs): def beforeDelete(sender, **kwargs) -> None:
""" """
Used to invoke the Service class "Destroy" before deleting it from database. Used to invoke the Service class "Destroy" before deleting it from database.

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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... Unique ID Database. Used to store unique names, unique macs, etc...
Managed via uds.core.util.unique_id_generator.unique_id_generator Managed via uds.core.util.unique_id_generator.unique_id_generator
""" """
owner = models.CharField(max_length=128, db_index=True, default='') owner = models.CharField(max_length=128, db_index=True, default='')
basename = models.CharField(max_length=32, db_index=True) basename = models.CharField(max_length=32, db_index=True)
seq = models.BigIntegerField(db_index=True) seq = models.BigIntegerField(db_index=True)
assigned = models.BooleanField(db_index=True, default=True) assigned = models.BooleanField(db_index=True, default=True)
stamp = models.IntegerField(db_index=True, default=0) stamp = models.IntegerField(db_index=True, default=0)
# "fake" declarations for type checking
objects: 'models.BaseManager[UniqueId]'
class Meta: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
unique_together = (('basename', 'seq'),) unique_together = (('basename', 'seq'),)
ordering = ('-seq',) ordering = ('-seq',)
app_label = 'uds' app_label = 'uds'
def __str__(self) -> str: 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
)

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) parent = models.CharField(max_length=50, default=None, null=True)
created = models.DateTimeField(default=getSqlDatetime, blank=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]' groups: 'models.QuerySet[Group]'
class Meta(UUIDModel.Meta): class Meta(UUIDModel.Meta):

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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) value = models.CharField(max_length=128, db_index=True)
user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='preferences') user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='preferences')
# "fake" declarations for type checking
objects: 'models.BaseManager[UserPreference]'
class Meta: class Meta:
app_label = 'uds' app_label = 'uds'

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # Redistribution and use in source and binary forms, with or without modification,
@ -52,7 +52,13 @@ from .util import getSqlDatetime
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from uds.core import osmanagers from uds.core import osmanagers
from uds.core import services 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__) 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 # 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 # through publication, but queries are much more simple
deployed_service: 'models.ForeignKey[UserService, ServicePool]' = models.ForeignKey(ServicePool, on_delete=models.CASCADE, related_name='userServices') deployed_service: 'models.ForeignKey[UserService, ServicePool]' = models.ForeignKey(
publication: 'models.ForeignKey[UserService, ServicePoolPublication]' = models.ForeignKey( ServicePool, on_delete=models.CASCADE, related_name='userServices'
ServicePoolPublication, on_delete=models.CASCADE, null=True, blank=True, 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='') 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 # 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 state = models.CharField(
os_state = models.CharField(max_length=1, default=State.PREPARING) # The valid values for this field are PREPARE and USABLE 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) state_date = models.DateTimeField(db_index=True)
creation_date = models.DateTimeField(db_index=True) creation_date = models.DateTimeField(db_index=True)
data = models.TextField(default='') data = models.TextField(default='')
user: 'models.ForeignKey[UserService, User]' = models.ForeignKey( 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 = models.BooleanField(default=False)
in_use_date = models.DateTimeField(default=NEVER) 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_hostname = models.CharField(max_length=64, default='')
src_ip = models.CharField(max_length=15, 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]' properties: 'models.QuerySet[UserServiceProperty]'
accounting: 'AccountUsage'
class Meta(UUIDModel.Meta): class Meta(UUIDModel.Meta):
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
db_table = 'uds__user_service' db_table = 'uds__user_service'
ordering = ('creation_date',) ordering = ('creation_date',)
app_label = 'uds' app_label = 'uds'
index_together = ( index_together = ('deployed_service', 'cache_level', 'state')
'deployed_service',
'cache_level',
'state'
)
@property @property
def name(self) -> str: def name(self) -> str:
@ -130,7 +160,7 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
'mac': unique.UniqueMacGenerator, 'mac': unique.UniqueMacGenerator,
'name': unique.UniqueNameGenerator, 'name': unique.UniqueNameGenerator,
'id': unique.UniqueGIDGenerator, 'id': unique.UniqueGIDGenerator,
} },
) )
def getInstance(self) -> 'services.UserDeployment': def getInstance(self) -> 'services.UserDeployment':
@ -164,16 +194,33 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
except Exception: except Exception:
# The publication to witch this item points to, does not exists # The publication to witch this item points to, does not exists
self.publication = None 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: if serviceInstance.deployedType is None:
raise Exception('Class {0} needs deployedType but it is not defined!!!'.format(serviceInstance.__class__.__name__)) raise Exception(
us = serviceInstance.deployedType(self.getEnvironment(), service=serviceInstance, 'Class {0} needs deployedType but it is not defined!!!'.format(
publication=publicationInstance, osmanager=osmanagerInstance, dbservice=self) 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: if self.data != '' and self.data is not None:
try: try:
us.unserialize(self.data) us.unserialize(self.data)
except Exception: 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 return us
def updateData(self, userServiceInstance: 'services.UserDeployment'): def updateData(self, userServiceInstance: 'services.UserDeployment'):
@ -285,7 +332,9 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
""" """
return self.deployed_service.transformsUserOrPasswordForService() 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 Before accessing a service by a transport, we can request
the service to "transform" the username & password that the transport 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: if serviceInstance.needsManager is False or not servicePool.osmanager:
return (username, password) 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: 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. :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 from uds.core.managers import userServiceManager
self.in_use = inUse self.in_use = inUse
self.in_use_date = getSqlDatetime() self.in_use_date = getSqlDatetime()
self.save(update_fields=['in_use', 'in_use_date']) 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 # 1.- If do not have any accounter associated, do nothing
# 2.- If called but not accounting, do nothing # 2.- If called but not accounting, do nothing
# 3.- If called and accounting, stop accounting # 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 return
self.deployed_service.account.stopUsageAccounting(self) 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 # Call to isReady of the instance
from uds.core.managers import userServiceManager from uds.core.managers import userServiceManager
return userServiceManager().isReady(self) return userServiceManager().isReady(self)
def isInMaintenance(self) -> bool: 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. Asks the UserServiceManager to cancel the current operation of this user deployed service.
""" """
from uds.core.managers import userServiceManager from uds.core.managers import userServiceManager
userServiceManager().cancel(self) userServiceManager().cancel(self)
def removeOrCancel(self) -> None: 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 cacheLevel: New cache level to put object in
""" """
from uds.core.managers import userServiceManager from uds.core.managers import userServiceManager
userServiceManager().moveToLevel(self, cacheLevel) 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: try:
val = self.properties.get(name=propName).value val = self.properties.get(name=propName).value
return val or default # Empty string is null 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 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 # Utility for logging
def log(self, message: str, level: int = log.INFO) -> None: 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): def __str__(self):
return "User service {}, unique_id {}, cache_level {}, user {}, name {}, state {}:{}".format( return "User service {}, unique_id {}, cache_level {}, user {}, name {}, state {}:{}".format(
self.name, self.unique_id, self.cache_level, self.user, self.friendly_name, self.name,
State.toString(self.state), State.toString(self.os_state) self.unique_id,
self.cache_level,
self.user,
self.friendly_name,
State.toString(self.state),
State.toString(self.os_state),
) )
@staticmethod @staticmethod

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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. Properties for User Service.
The value field is a Text field, so we can put whatever we want in it 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) name = models.CharField(max_length=128, db_index=True)
value = models.TextField(default='') 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: class Meta:
""" """
Meta class to declare default order and unique multiple field index Meta class to declare default order and unique multiple field index
""" """
db_table = 'uds__user_service_property' db_table = 'uds__user_service_property'
unique_together = (('name', 'user_service'),) unique_together = (('name', 'user_service'),)
app_label = 'uds' app_label = 'uds'
def __str__(self) -> str: 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
)

View File

@ -75,7 +75,7 @@ def getSqlDatetime() -> datetime:
def getSqlDatetimeAsUnix() -> int: def getSqlDatetimeAsUnix() -> int:
return int(mktime(getSqlDatetime().timetuple())) return int(mktime(getSqlDatetime().timetuple()))
def getSqlFnc(fncName): def getSqlFnc(fncName: str) -> str:
""" """
Convert different sql functions for different platforms Convert different sql functions for different platforms
""" """

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2012-2019 Virtual Cable S.L. # Copyright (c) 2012-2020 Virtual Cable S.L.U.
# All rights reserved. # All rights reserved.
# #
# Redistribution and use in source and binary forms, with or without modification, # 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__) logger = logging.getLogger(__name__)
class UUIDModel(models.Model): class UUIDModel(models.Model):
""" """
Base abstract model for models that require an uuid Base abstract model for models that require an uuid
""" """
uuid = models.CharField(max_length=50, default=None, null=True, unique=True) uuid = models.CharField(max_length=50, default=None, null=True, unique=True)
# Automatic field from Model without a defined specific primary_key # Automatic field from Model without a defined specific primary_key
@ -55,13 +57,17 @@ class UUIDModel(models.Model):
return generateUuid() return generateUuid()
# Override default save to add uuid # 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: if not self.uuid:
self.uuid = self.genUuid() self.uuid = self.genUuid()
elif self.uuid != self.uuid.lower(): 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) 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) return 'Object of class {} with uuid {}'.format(self.__class__, self.uuid)