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