forked from shaba/openuds
Fixed multy phisical machines service to add a "custom" maximum duration for assignation
This commit is contained in:
parent
f4e953c9c9
commit
91d2398ade
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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']
|
||||
|
Loading…
Reference in New Issue
Block a user