Refactoring models (without DB modification) to better type checking

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

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# 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

View File

@ -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'

View File

@ -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
)

View File

@ -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
)

View File

@ -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)

View File

@ -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'

View File

@ -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()
)

View File

@ -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'

View File

@ -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,
)

View File

@ -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,
)

View File

@ -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'

View File

@ -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
)

View File

@ -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

View File

@ -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.

View File

@ -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):

View File

@ -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,
)

View File

@ -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:
"""

View File

@ -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
)

View File

@ -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)

View File

@ -51,17 +51,21 @@ class OSManager(ManagedObjectModel, TaggingMixin): # type: ignore
An OS Manager represents a manager for responding requests for agents inside services.
"""
# "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()

View File

@ -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),
)

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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)

View File

@ -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

View File

@ -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)

View File

@ -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
)

View File

@ -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
)

View File

@ -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,
)

View File

@ -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]))

View File

@ -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'

View File

@ -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),
)

View File

@ -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.

View File

@ -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
)

View File

@ -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):

View File

@ -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'

View File

@ -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

View File

@ -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
)

View File

@ -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
"""

View File

@ -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)