Fixed multy phisical machines service to add a "custom" maximum duration for assignation

This commit is contained in:
Adolfo Gómez García 2021-07-21 13:59:12 +02:00
parent f4e953c9c9
commit 91d2398ade
3 changed files with 77 additions and 21 deletions

View File

@ -1,7 +1,7 @@
# -*- coding: utf-8 -*-
#
# Copyright (c) 2012-2019 Virtual Cable S.L.
# Copyright (c) 2012-2021 Virtual Cable S.L.U.
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without modification,
@ -12,7 +12,7 @@
# * Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# * Neither the name of Virtual Cable S.L. nor the names of its contributors
# * Neither the name of Virtual Cable S.L.U. nor the names of its contributors
# may be used to endorse or promote products derived from this software
# without specific prior written permission.
#
@ -38,7 +38,10 @@ from uds.core.ui import gui
from uds.core import auths
if typing.TYPE_CHECKING:
from django.http import HttpRequest, HttpResponse # pylint: disable=ungrouped-imports
from django.http import (
HttpRequest,
HttpResponse,
) # pylint: disable=ungrouped-imports
logger = logging.getLogger(__name__)
@ -125,7 +128,9 @@ class SampleAuth(auths.Authenticator):
# unserialization, and at this point all will be default values
# so self.groups.value will be []
if values and len(self.groups.value) < 2:
raise auths.Authenticator.ValidationException(_('We need more than two groups!'))
raise auths.Authenticator.ValidationException(
_('We need more than two groups!')
)
def searchUsers(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
"""
@ -137,7 +142,13 @@ class SampleAuth(auths.Authenticator):
facility for users. In our case, we will simply return a list of users
(array of dictionaries with ids and names) with the pattern plus 1..10
"""
return [{'id': '{0}-{1}'.format(pattern, a), 'name': '{0} number {1}'.format(pattern, a)} for a in range(1, 10)]
return [
{
'id': '{0}-{1}'.format(pattern, a),
'name': '{0} number {1}'.format(pattern, a),
}
for a in range(1, 10)
]
def searchGroups(self, pattern: str) -> typing.Iterable[typing.Dict[str, str]]:
"""
@ -154,7 +165,9 @@ class SampleAuth(auths.Authenticator):
res.append({'id': g, 'name': ''})
return res
def authenticate(self, username: str, credentials: str, groupsManager: 'auths.GroupsManager') -> bool:
def authenticate(
self, username: str, credentials: str, groupsManager: 'auths.GroupsManager'
) -> bool:
"""
This method is invoked by UDS whenever it needs an user to be authenticated.
It is used from web interface, but also from administration interface to
@ -196,7 +209,9 @@ class SampleAuth(auths.Authenticator):
:note: groupsManager is an in/out parameter
"""
if username != credentials: # All users with same username and password are allowed
if (
username != credentials
): # All users with same username and password are allowed
return False
# Now the tricky part. We will make this user belong to groups that contains at leat
@ -247,11 +262,17 @@ class SampleAuth(auths.Authenticator):
# I know, this is a bit ugly, but this is just a sample :-)
res = '<p>Login name: <input id="logname" type="text"/></p>'
res += '<p><a href="" onclick="window.location.replace(\'' + self.callbackUrl() + '?user='
res += (
'<p><a href="" onclick="window.location.replace(\''
+ self.callbackUrl()
+ '?user='
)
res += '\' + $(\'#logname\').val()); return false;">Login</a></p>'
return res
def authCallback(self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager') -> typing.Optional[str]:
def authCallback(
self, parameters: typing.Dict[str, typing.Any], gm: 'auths.GroupsManager'
) -> typing.Optional[str]:
"""
We provide this as a sample of callback for an user.
We will accept all petitions that has "user" parameter
@ -286,6 +307,7 @@ class SampleAuth(auths.Authenticator):
Here, we will set the state to "Inactive" and realName to the same as username, but twice :-)
"""
from uds.core.util.state import State
usrData['real_name'] = usrData['name'] + ' ' + usrData['name']
usrData['state'] = State.INACTIVE

View File

@ -97,6 +97,19 @@ class IPMachinesService(IPServiceBase):
tab=gui.ADVANCED_TAB,
)
maxSessionForMachine = gui.NumericField(
length=3,
label=_('Max session per machine'),
defvalue='0',
order=3,
tooltip=_('Maximum session duration before UDS thinks this machine got locked and releases it (hours). 0 means "never".'),
minValue=0,
required=True,
tab=gui.ADVANCED_TAB,
)
# Description of service
typeName = _('Static Multiple IP')
typeType = 'IPMachinesService'
@ -120,6 +133,7 @@ class IPMachinesService(IPServiceBase):
_token: str = ''
_port: int = 0
_skipTimeOnFailure: int = 0
_maxSessionForMachine: int = 0
def initialize(self, values: 'Module.ValuesType') -> None:
if values is None:
@ -141,7 +155,7 @@ class IPMachinesService(IPServiceBase):
d = self.storage.readData('ips')
old_ips = pickle.loads(d) if d and isinstance(d, bytes) else []
# dissapeared ones
dissapeared = set(i.split('~')[0] for i in old_ips) - set(i.split('~')[0] for i in self._ips)
dissapeared = set(IPServiceBase.getIp(i.split('~')[0]) for i in old_ips) - set(i.split('~')[0] for i in self._ips)
with transaction.atomic():
for removable in dissapeared:
self.storage.remove(removable)
@ -149,6 +163,7 @@ class IPMachinesService(IPServiceBase):
self._token = self.token.value.strip()
self._port = self.port.value
self._skipTimeOnFailure = self.skipTimeOnFailure.num()
self._maxSessionForMachine = self.maxSessionForMachine.num()
def getToken(self):
return self._token or None
@ -161,16 +176,18 @@ class IPMachinesService(IPServiceBase):
'token': self._token,
'port': str(self._port),
'skipTimeOnFailure': str(self._skipTimeOnFailure),
'maxSessionForMachine': str(self._maxSessionForMachine),
}
def marshal(self) -> bytes:
self.storage.saveData('ips', pickle.dumps(self._ips))
return b'\0'.join(
[
b'v4',
b'v5',
self._token.encode(),
str(self._port).encode(),
str(self._skipTimeOnFailure).encode(),
str(self._maxSessionForMachine).encode(),
]
)
@ -186,24 +203,41 @@ class IPMachinesService(IPServiceBase):
self._ips = []
if values[0] != b'v1':
self._token = values[1].decode()
if values[0] in (b'v3', b'v4'):
if values[0] in (b'v3', b'v4', b'v5'):
self._port = int(values[2].decode())
if values[0] == b'v4':
if values[0] in (b'v4', b'v5'):
self._skipTimeOnFailure = int(values[3].decode())
if values[0] == b'v5':
self._maxSessionForMachine = int(values[4].decode())
# Sets maximum services for this
self.maxDeployed = len(self._ips)
def canBeUsed(self, locked: typing.Optional[int], now: int) -> int:
# If _maxSessionForMachine is 0, it can be used only if not locked
# (that is locked is None)
if self._maxSessionForMachine <= 0:
return bool(locked)
if not isinstance(locked, int): # May have "old" data, that was the IP repeated
return False
if not locked or locked < now - self._maxSessionForMachine * 3600:
return True
return False
def getUnassignedMachine(self) -> typing.Optional[str]:
# Search first unassigned machine
try:
now = getSqlDatetimeAsUnix()
consideredFreeTime = now - config.GlobalConfig.SESSION_EXPIRE_TIME.getInt(force=False) * 3600
for ip in self._ips:
theIP = IPServiceBase.getIp(ip)
theMAC = IPServiceBase.getMac(ip)
locked = self.storage.getPickle(theIP)
if not locked or locked < consideredFreeTime:
if self.canBeUsed(locked, now):
if self._port > 0 and self._skipTimeOnFailure > 0 and self.cache.get('port{}'.format(theIP)):
continue # The check failed not so long ago, skip it...
self.storage.putPickle(theIP, now)
@ -246,9 +280,7 @@ class IPMachinesService(IPServiceBase):
def unassignMachine(self, ip: str) -> None:
try:
if ';' in ip:
ip = ip.split(';')[0] # ; means that HAS an attached MAC
self.storage.remove(ip)
self.storage.remove(IPServiceBase.getIp(ip))
except Exception:
logger.exception("Exception at getUnassignedMachine")
@ -271,8 +303,10 @@ class IPMachinesService(IPServiceBase):
theIP = IPServiceBase.getIp(assignableId)
theMAC = IPServiceBase.getMac(assignableId)
if self.storage.readData(theIP) is None:
self.storage.saveData(theIP, theIP)
now = getSqlDatetimeAsUnix()
locked = self.storage.getPickle(theIP)
if self.canBeUsed(locked, now):
self.storage.saveData(theIP, now)
if theMAC:
theIP += ';' + theMAC
return userServiceInstance.assign(theIP)

View File

@ -86,7 +86,7 @@ def authCallback(request: HttpRequest, authName: str) -> HttpResponse:
return errors.exceptionView(request, e)
def authCallback_stage2(request: ExtendedHttpRequestWithUser, ticketId: str) -> HttpResponse:
def authCallback_stage2(request: 'ExtendedHttpRequestWithUser', ticketId: str) -> HttpResponse:
try:
ticket = TicketStore.get(ticketId)
params: typing.Dict[str, typing.Any] = ticket['params']