Added metapools capacity of show grouped pools transports

This commit is contained in:
Adolfo Gómez García 2021-04-22 14:44:48 +02:00
parent ce73d4e29f
commit e9a719a2eb
11 changed files with 154 additions and 42 deletions

View File

@ -63,7 +63,7 @@ class MetaPools(ModelHandler):
} }
save_fields = ['name', 'short_name', 'comments', 'tags', save_fields = ['name', 'short_name', 'comments', 'tags',
'image_id', 'servicesPoolGroup_id', 'visible', 'policy', 'calendar_message'] 'image_id', 'servicesPoolGroup_id', 'visible', 'policy', 'calendar_message', 'transport_grouping']
table_title = _('Meta Pools') table_title = _('Meta Pools')
table_fields = [ table_fields = [
@ -74,6 +74,7 @@ class MetaPools(ModelHandler):
{'user_services_in_preparation': {'title': _('In Preparation')}}, {'user_services_in_preparation': {'title': _('In Preparation')}},
{'visible': {'title': _('Visible'), 'type': 'callback'}}, {'visible': {'title': _('Visible'), 'type': 'callback'}},
{'pool_group_name': {'title': _('Pool Group')}}, {'pool_group_name': {'title': _('Pool Group')}},
{'label': {'title': _('Label')}},
{'tags': {'title': _('tags'), 'visible': False}}, {'tags': {'title': _('tags'), 'visible': False}},
] ]
@ -113,6 +114,7 @@ class MetaPools(ModelHandler):
'fallbackAccess': item.fallbackAccess, 'fallbackAccess': item.fallbackAccess,
'permission': permissions.getEffectivePermission(self._user, item), 'permission': permissions.getEffectivePermission(self._user, item),
'calendar_message': item.calendar_message, 'calendar_message': item.calendar_message,
'transport_grouping': item.transport_grouping
} }
return val return val
@ -135,7 +137,7 @@ class MetaPools(ModelHandler):
'tooltip': ugettext('Image assocciated with this service'), 'tooltip': ugettext('Image assocciated with this service'),
'type': gui.InputField.IMAGECHOICE_TYPE, 'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 120, 'order': 120,
'tab': ugettext('Display'), 'tab': gui.DISPLAY_TAB,
}, { }, {
'name': 'servicesPoolGroup_id', 'name': 'servicesPoolGroup_id',
'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all()]), 'values': [gui.choiceImage(-1, _('Default'), DEFAULT_THUMB_BASE64)] + gui.sortedChoices([gui.choiceImage(v.uuid, v.name, v.thumb64) for v in ServicePoolGroup.objects.all()]),
@ -143,7 +145,7 @@ class MetaPools(ModelHandler):
'tooltip': ugettext('Pool group for this pool (for pool classify on display)'), 'tooltip': ugettext('Pool group for this pool (for pool classify on display)'),
'type': gui.InputField.IMAGECHOICE_TYPE, 'type': gui.InputField.IMAGECHOICE_TYPE,
'order': 121, 'order': 121,
'tab': ugettext('Display'), 'tab': gui.DISPLAY_TAB,
}, { }, {
'name': 'visible', 'name': 'visible',
'value': True, 'value': True,
@ -151,7 +153,7 @@ class MetaPools(ModelHandler):
'tooltip': ugettext('If active, metapool will be visible for users'), 'tooltip': ugettext('If active, metapool will be visible for users'),
'type': gui.InputField.CHECKBOX_TYPE, 'type': gui.InputField.CHECKBOX_TYPE,
'order': 123, 'order': 123,
'tab': ugettext('Display'), 'tab': gui.DISPLAY_TAB,
}, { }, {
'name': 'calendar_message', 'name': 'calendar_message',
'value': '', 'value': '',
@ -159,7 +161,15 @@ class MetaPools(ModelHandler):
'tooltip': ugettext('Custom message to be shown to users if access is limited by calendar rules.'), 'tooltip': ugettext('Custom message to be shown to users if access is limited by calendar rules.'),
'type': gui.InputField.TEXT_TYPE, 'type': gui.InputField.TEXT_TYPE,
'order': 124, 'order': 124,
'tab': ugettext('Display'), 'tab': gui.DISPLAY_TAB,
}, {
'name': 'transport_grouping',
'values': [gui.choiceItem(k, str(v)) for k, v in MetaPool.TRANSPORT_SELECT.items()],
'label': ugettext('Transport Selection'),
'tooltip': ugettext('Transport selection policy'),
'type': gui.InputField.CHOICE_TYPE,
'order': 125,
'tab': gui.DISPLAY_TAB
}]: }]:
self.addField(localGUI, field) self.addField(localGUI, field)

View File

@ -36,6 +36,7 @@ import typing
from django.utils.translation import ugettext_lazy as _, ugettext from django.utils.translation import ugettext_lazy as _, ugettext
from uds.models import Transport, Network, ServicePool from uds.models import Transport, Network, ServicePool
from uds.core import transports from uds.core import transports
from uds.core.ui import gui
from uds.core.util import permissions from uds.core.util import permissions
from uds.core.util import os_detector as OsDetector from uds.core.util import os_detector as OsDetector
@ -49,7 +50,7 @@ logger = logging.getLogger(__name__)
class Transports(ModelHandler): class Transports(ModelHandler):
model = Transport model = Transport
save_fields = ['name', 'comments', 'tags', 'priority', 'nets_positive', 'allowed_oss'] save_fields = ['name', 'comments', 'tags', 'priority', 'nets_positive', 'allowed_oss', 'label']
table_title = _('Transports') table_title = _('Transports')
table_fields = [ table_fields = [
@ -107,6 +108,16 @@ class Transports(ModelHandler):
'type': 'multichoice', 'type': 'multichoice',
'order': 103 'order': 103
}) })
field = self.addField(field, {
'name': 'label',
'length': 32,
'value': '',
'label': ugettext('Label'),
'tooltip': ugettext('Metapool transport label (only used on metapool transports grouping)'),
'type': 'text',
'order': 201,
'tab': gui.ADVANCED_TAB
})
return field return field
@ -120,6 +131,7 @@ class Transports(ModelHandler):
'comments': item.comments, 'comments': item.comments,
'priority': item.priority, 'priority': item.priority,
'nets_positive': item.nets_positive, 'nets_positive': item.nets_positive,
'label': item.label,
'networks': [{'id': n.uuid} for n in item.networks.all()], 'networks': [{'id': n.uuid} for n in item.networks.all()],
'allowed_oss': [{'id': x} for x in item.allowed_oss.split(',')] if item.allowed_oss != '' else [], 'allowed_oss': [{'id': x} for x in item.allowed_oss.split(',')] if item.allowed_oss != '' else [],
'pools': pools, 'pools': pools,

View File

@ -749,7 +749,7 @@ class UserServiceManager:
Get service info from user service Get service info from user service
""" """
if idService[0] == 'M': # Meta pool if idService[0] == 'M': # Meta pool
return self.getMeta(user, srcIp, os, idService[1:]) return self.getMeta(user, srcIp, os, idService[1:], idTransport)
userService = self.locateUserService(user, idService, create=True) userService = self.locateUserService(user, idService, create=True)
@ -908,6 +908,7 @@ class UserServiceManager:
srcIp: str, srcIp: str,
os: typing.MutableMapping, os: typing.MutableMapping,
idMetaPool: str, idMetaPool: str,
idTransport: str,
clientHostName: typing.Optional[str] = None, clientHostName: typing.Optional[str] = None,
) -> typing.Tuple[ ) -> typing.Tuple[
typing.Optional[str], typing.Optional[str],
@ -953,7 +954,13 @@ class UserServiceManager:
) -> typing.Optional[typing.Tuple[ServicePool, Transport]]: ) -> typing.Optional[typing.Tuple[ServicePool, Transport]]:
found = None found = None
t: Transport t: Transport
for t in pool.transports.all().order_by('priority'): if idTransport == 'meta': # Autoselected:
q = pool.transports.all().order_by('priority')
elif idTransport[:6] == 'LABEL:':
q = pool.transports.filter(label=idTransport[6:])
else:
q = pool.transports.filter(uuid=idTransport)
for t in q:
typeTrans = t.getType() typeTrans = t.getType()
if ( if (
t.getType() t.getType()

View File

@ -0,0 +1,23 @@
# Generated by Django 3.2 on 2021-04-22 13:40
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('uds', '0039_auto_20201111_1329'),
]
operations = [
migrations.AddField(
model_name='metapool',
name='transport_grouping',
field=models.IntegerField(default=0),
),
migrations.AddField(
model_name='transport',
name='label',
field=models.CharField(db_index=True, default='', max_length=32),
),
]

View File

@ -68,12 +68,22 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
PRIORITY_POOL = 1 PRIORITY_POOL = 1
MOST_AVAILABLE_BY_NUMBER = 2 MOST_AVAILABLE_BY_NUMBER = 2
TYPES = { TYPES: typing.Mapping[int, str] = {
ROUND_ROBIN_POOL: _('Evenly distributed'), ROUND_ROBIN_POOL: _('Evenly distributed'),
PRIORITY_POOL: _('Priority'), PRIORITY_POOL: _('Priority'),
MOST_AVAILABLE_BY_NUMBER: _('Greater % available'), MOST_AVAILABLE_BY_NUMBER: _('Greater % available'),
} }
# Type of transport grouping
AUTO_TRANSPORT_SELECT = 0
COMMON_TRANSPORT_SELECT = 1
LABEL_TRANSPORT_SELECT = 2
TRANSPORT_SELECT: typing.Mapping[int, str] = {
AUTO_TRANSPORT_SELECT: _('Automatic selection'),
COMMON_TRANSPORT_SELECT: _('Use only common transports'),
LABEL_TRANSPORT_SELECT: _('Group Transports by label')
}
name = models.CharField(max_length=128, default='') name = models.CharField(max_length=128, default='')
short_name = models.CharField(max_length=32, default='') short_name = models.CharField(max_length=32, default='')
comments = models.CharField(max_length=256, default='') comments = models.CharField(max_length=256, default='')
@ -103,6 +113,8 @@ class MetaPool(UUIDModel, TaggingMixin): # type: ignore
# Pool selection policy # Pool selection policy
policy = models.SmallIntegerField(default=0) policy = models.SmallIntegerField(default=0)
# If use common transports instead of auto select one
transport_grouping = models.IntegerField(default=0)
# "fake" declarations for type checking # "fake" declarations for type checking
objects: 'models.BaseManager[MetaPool]' objects: 'models.BaseManager[MetaPool]'

View File

@ -62,6 +62,8 @@ class Transport(ManagedObjectModel, TaggingMixin):
nets_positive = models.BooleanField(default=False) nets_positive = models.BooleanField(default=False)
# We store allowed oss as a comma-separated list # We store allowed oss as a comma-separated list
allowed_oss = models.CharField(max_length=255, default='') allowed_oss = models.CharField(max_length=255, default='')
# Label, to group transports on meta pools
label = models.CharField(max_length=32, default='', db_index=True)
# "fake" declarations for type checking # "fake" declarations for type checking
objects: 'models.BaseManager[Transport]' objects: 'models.BaseManager[Transport]'

File diff suppressed because one or more lines are too long

View File

@ -93,6 +93,6 @@
</svg> </svg>
</div> </div>
</uds-root> </uds-root>
<script src="/uds/res/admin/runtime.js?stamp=1619085422" defer></script><script src="/uds/res/admin/polyfills-es5.js?stamp=1619085422" nomodule defer></script><script src="/uds/res/admin/polyfills.js?stamp=1619085422" defer></script><script src="/uds/res/admin/main.js?stamp=1619085422" defer></script></body> <script src="/uds/res/admin/runtime.js?stamp=1619092209" defer></script><script src="/uds/res/admin/polyfills-es5.js?stamp=1619092209" nomodule defer></script><script src="/uds/res/admin/polyfills.js?stamp=1619092209" defer></script><script src="/uds/res/admin/main.js?stamp=1619092209" defer></script></body>
</html> </html>

View File

@ -101,12 +101,12 @@ urlpatterns = [
re_path(r'^uds/utility/download/(?P<idDownload>[a-zA-Z0-9-]*)$', uds.web.views.download, name='utility.downloader'), re_path(r'^uds/utility/download/(?P<idDownload>[a-zA-Z0-9-]*)$', uds.web.views.download, name='utility.downloader'),
# WEB API path (not REST api, frontend) # WEB API path (not REST api, frontend)
re_path(r'^uds/webapi/img/transport/(?P<idTrans>[a-zA-Z0-9-]+)$', uds.web.views.transportIcon, name='webapi.transportIcon'), re_path(r'^uds/webapi/img/transport/(?P<idTrans>[a-zA-Z0-9:-]+)$', uds.web.views.transportIcon, name='webapi.transportIcon'),
re_path(r'^uds/webapi/img/gallery/(?P<idImage>[a-zA-Z0-9-]+)$', uds.web.views.image, name='webapi.galleryImage'), re_path(r'^uds/webapi/img/gallery/(?P<idImage>[a-zA-Z0-9-]+)$', uds.web.views.image, name='webapi.galleryImage'),
re_path(r'^uds/webapi/action/(?P<idService>.+)/enable/(?P<idTransport>[a-zA-Z0-9-]+)$', uds.web.views.userServiceEnabler, name='webapi.enabler'), re_path(r'^uds/webapi/action/(?P<idService>.+)/enable/(?P<idTransport>[a-zA-Z0-9:-]+)$', uds.web.views.userServiceEnabler, name='webapi.enabler'),
re_path(r'^uds/webapi/action/(?P<idService>.+)/(?P<actionString>[a-zA-Z0-9-]+)$', uds.web.views.action, name='webapi.action'), re_path(r'^uds/webapi/action/(?P<idService>.+)/(?P<actionString>[a-zA-Z0-9:-]+)$', uds.web.views.action, name='webapi.action'),
# Services list, ... # Services list, ...
path(r'uds/webapi/services', uds.web.views.modern.servicesData, name='webapi.services'), path(r'uds/webapi/services', uds.web.views.modern.servicesData, name='webapi.services'),

View File

@ -101,6 +101,34 @@ def getServicesData(
logger.debug('Checking meta pools: %s', availMetaPools) logger.debug('Checking meta pools: %s', availMetaPools)
services = [] services = []
# Metapool helpers
def transportIterator(member) -> typing.Iterable[Transport]:
for t in member.pool.transports.all():
typeTrans = t.getType()
if (
typeTrans
and t.validForIp(request.ip)
and typeTrans.supportsOs(osName)
and t.validForOs(osName)
):
yield t
def buildMetaTransports(
transports: typing.Iterable[Transport],
isLabel: bool,
) -> typing.List[typing.Mapping[str, typing.Any]]:
idd = lambda i: i.uuid if not isLabel else 'LABEL:' + i.label
return [
{
'id': idd(i),
'name': i.name,
'link': html.udsAccessLink(request, 'M' + meta.uuid, idd(i)),
'priority': 0,
}
for i in transports
]
# Preload all assigned user services for this user # Preload all assigned user services for this user
# Add meta pools data first # Add meta pools data first
for meta in availMetaPools: for meta in availMetaPools:
@ -108,35 +136,48 @@ def getServicesData(
metaTransports: typing.List[typing.Mapping[str, typing.Any]] = [] metaTransports: typing.List[typing.Mapping[str, typing.Any]] = []
in_use = meta.number_in_use > 0 # type: ignore # anotated value in_use = meta.number_in_use > 0 # type: ignore # anotated value
if True: # If meta.use_common_transports inAll: typing.Optional[typing.Set[str]] = None
tmpSet: typing.Set[str]
if (
meta.transport_grouping == MetaPool.COMMON_TRANSPORT_SELECT
): # If meta.use_common_transports
# only keep transports that are in ALL members # only keep transports that are in ALL members
inAll: typing.Optional[typing.Set[str]] = None for member in meta.members.all().order_by('priority'):
for member in meta.members.all(): tmpSet = set()
tmpSet: typing.Set[str] = set() # if first pool, get all its transports and check that are valid
for t in member.pool.transports.all(): for t in transportIterator(member):
if inAll is None: # if first... if inAll is None:
typeTrans = t.getType()
if (
typeTrans
and t.validForIp(request.ip)
and typeTrans.supportsOs(osName)
and t.validForOs(osName)
):
tmpSet.add(t.uuid)
elif t.uuid in inAll:
tmpSet.add(t.uuid) tmpSet.add(t.uuid)
elif t.uuid in inAll: # For subsequent, reduce...
tmpSet.add(t.uuid)
inAll = tmpSet inAll = tmpSet
# tmpSet has ALL common transports # tmpSet has ALL common transports
metaTransports = [ metaTransports = buildMetaTransports(
{ Transport.objects.filter(uuid__in=inAll or []),
'id': i.uuid, isLabel=False
'name': i.name, )
'link': html.udsAccessLink(request, 'M' + meta.uuid, i.uuid), elif meta.transport_grouping == MetaPool.LABEL_TRANSPORT_SELECT:
'priority': 0, ltrans: typing.MutableMapping[str, Transport] = {}
} for member in meta.members.all():
for i in Transport.objects.filter(uuid__in=inAll or []) tmpSet = set()
] # if first pool, get all its transports and check that are valid
for t in transportIterator(member):
if not t.label:
continue
if t.label not in ltrans:
ltrans[t.label] = t
if inAll is None:
tmpSet.add(t.label)
elif t.label in inAll: # For subsequent, reduce...
tmpSet.add(t.label)
inAll = tmpSet
# tmpSet has ALL common transports
metaTransports = buildMetaTransports(
(v for k, v in ltrans.items() if k in (inAll or set())),
isLabel=True
)
else: else:
for member in meta.members.all(): for member in meta.members.all():
# if pool.isInMaintenance(): # if pool.isInMaintenance():
@ -187,7 +228,7 @@ def getServicesData(
'group': group, 'group': group,
'transports': metaTransports, 'transports': metaTransports,
'imageId': meta.image and meta.image.uuid or 'x', 'imageId': meta.image and meta.image.uuid or 'x',
'show_transports': False, 'show_transports': len(metaTransports) > 1,
'allow_users_remove': False, 'allow_users_remove': False,
'allow_users_reset': False, 'allow_users_reset': False,
'maintenance': meta.isInMaintenance(), 'maintenance': meta.isInMaintenance(),
@ -209,7 +250,7 @@ def getServicesData(
use_count = str(sPool.usage_count) # type: ignore # anotated value use_count = str(sPool.usage_count) # type: ignore # anotated value
left_count = str(sPool.max_srvs - sPool.usage_count) # type: ignore # anotated value left_count = str(sPool.max_srvs - sPool.usage_count) # type: ignore # anotated value
trans = [] trans: typing.List[typing.MutableMapping[str, typing.Any]] = []
for t in sorted( for t in sorted(
sPool.transports.all(), key=lambda x: x.priority sPool.transports.all(), key=lambda x: x.priority
): # In memory sort, allows reuse prefetched and not too big array ): # In memory sort, allows reuse prefetched and not too big array

View File

@ -100,7 +100,12 @@ def transportOwnLink(
@cache_page(3600, key_prefix='img', cache='memory') @cache_page(3600, key_prefix='img', cache='memory')
def transportIcon(request: 'ExtendedHttpRequest', idTrans: str) -> HttpResponse: def transportIcon(request: 'ExtendedHttpRequest', idTrans: str) -> HttpResponse:
try: try:
transport: Transport = Transport.objects.get(uuid=processUuid(idTrans)) transport: Transport
if idTrans[:6] == 'LABEL:':
# Get First label
transport = Transport.objects.filter(label=idTrans[6:]).order_by('priority')[0]
else:
transport = Transport.objects.get(uuid=processUuid(idTrans))
return HttpResponse(transport.getInstance().icon(), content_type='image/png') return HttpResponse(transport.getInstance().icon(), content_type='image/png')
except Exception: except Exception:
return HttpResponse(DEFAULT_IMAGE, content_type='image/png') return HttpResponse(DEFAULT_IMAGE, content_type='image/png')