diff --git a/server/requirements.txt b/server/requirements.txt
index def9f25d8..b10a2f151 100644
--- a/server/requirements.txt
+++ b/server/requirements.txt
@@ -13,7 +13,6 @@ pyOpenSSL==16.2.0
mysqlclient==1.3.12
python-ldap==3.0.0b4
paramiko==2.3.1
-python-dateutil
defusedxml==0.5.0
python-dateutil==2.6.1
requests==2.18.4
diff --git a/server/src/server/urls.py b/server/src/server/urls.py
index 574d0271a..5b13368b5 100644
--- a/server/src/server/urls.py
+++ b/server/src/server/urls.py
@@ -2,7 +2,9 @@
"""
Url patterns for UDS project (Django)
"""
-from django.conf.urls import include, url
+from django.conf.urls import include
+from django.urls import path
+
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
@@ -10,5 +12,5 @@ from django.conf.urls import include, url
urlpatterns = [
- url(r'^', include('uds.urls')),
+ path('', include('uds.urls')),
]
diff --git a/server/src/uds/core/auths/auth.py b/server/src/uds/core/auths/auth.py
index 2a7c7bb3d..3dac5ff2b 100644
--- a/server/src/uds/core/auths/auth.py
+++ b/server/src/uds/core/auths/auth.py
@@ -54,7 +54,7 @@ from uds.models import User
import logging
import six
-__updated__ = '2018-01-05'
+__updated__ = '2018-02-26'
logger = logging.getLogger(__name__)
authLogger = logging.getLogger('authLog')
diff --git a/server/src/uds/core/managers/PublicationManager.py b/server/src/uds/core/managers/PublicationManager.py
index 6050d19ad..b15d21c13 100644
--- a/server/src/uds/core/managers/PublicationManager.py
+++ b/server/src/uds/core/managers/PublicationManager.py
@@ -132,8 +132,14 @@ class PublicationFinishChecker(DelayedTask):
for old in servicePoolPub.deployed_service.publications.filter(state=State.USABLE):
old.state = State.REMOVABLE
old.save()
- pc = PublicationOldMachinesCleaner(old.id)
- pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
+
+ osm = servicePoolPub.deployed_service.osmanager
+ # If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
+ doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
+
+ if doPublicationCleanup:
+ pc = PublicationOldMachinesCleaner(old.id)
+ pc.register(GlobalConfig.SESSION_EXPIRE_TIME.getInt(True) * 3600, 'pclean-' + str(old.id), True)
servicePoolPub.setState(State.USABLE)
servicePoolPub.deployed_service.markOldUserServicesAsRemovables(servicePoolPub)
@@ -205,7 +211,7 @@ class PublicationManager(object):
"""
Initiates the publication of a service pool, or raises an exception if this cannot be done
:param servicePool: Service pool object (db object)
- :param changeLog: if not Noe, store change log string on "change log" table
+ :param changeLog: if not None, store change log string on "change log" table
"""
if servicePool.publications.filter(state__in=State.PUBLISH_STATES).count() > 0:
raise PublishException(_('Already publishing. Wait for previous publication to finish and try again'))
diff --git a/server/src/uds/core/managers/UserServiceManager.py b/server/src/uds/core/managers/UserServiceManager.py
index c9f89ffd4..7fd613f38 100644
--- a/server/src/uds/core/managers/UserServiceManager.py
+++ b/server/src/uds/core/managers/UserServiceManager.py
@@ -51,8 +51,6 @@ import requests
import json
import logging
-__updated__ = '2018-02-16'
-
logger = logging.getLogger(__name__)
traceLogger = logging.getLogger('traceLog')
@@ -488,18 +486,20 @@ class UserServiceManager(object):
This method is used by UserService when a request for setInUse(False) is made
This checks that the service can continue existing or not
"""
- remove = False
- # uService = UserService.objects.get(id=uService.id)
- with transaction.atomic():
- uService = UserService.objects.select_for_update().get(id=uService.id)
- if uService.publication is None:
- return
- if uService.publication.id != uService.deployed_service.activePublication().id:
- logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
- remove = True
+ osm = uService.deployed_service.osmanager
+ # If os manager says "machine is persistent", do not tray to delete "previous version" assigned machines
+ doPublicationCleanup = True if osm is None else not osm.getInstance().isPersistent()
- if remove:
- uService.remove()
+ if doPublicationCleanup:
+ remove = False
+ with transaction.atomic():
+ uService = UserService.objects.select_for_update().get(id=uService.id)
+ if uService.publication not is None and uService.publication.id != uService.deployed_service.activePublication().id:
+ logger.debug('Old revision of user service, marking as removable: {0}'.format(uService))
+ remove = True
+
+ if remove:
+ uService.remove()
def notifyReadyFromOsManager(self, uService, data):
try:
diff --git a/server/src/uds/core/osmanagers/BaseOsManager.py b/server/src/uds/core/osmanagers/BaseOsManager.py
index 96755a493..4b56a5fe4 100644
--- a/server/src/uds/core/osmanagers/BaseOsManager.py
+++ b/server/src/uds/core/osmanagers/BaseOsManager.py
@@ -42,7 +42,7 @@ from uds.core import Module
import six
-__updated__ = '2017-10-02'
+__updated__ = '2018-03-02'
STORAGE_KEY = 'osmk'
@@ -260,6 +260,13 @@ class OSManager(Module):
if save:
userService.save()
+ def isPersistent(self):
+ '''
+ When a publication if finished, old assigned machines will be removed if this value is True.
+ Defaults to False
+ '''
+ return False
+
def __str__(self):
return "Base OS Manager"
diff --git a/server/src/uds/models/Authenticator.py b/server/src/uds/models/Authenticator.py
index c16713f02..6a7cba2ef 100644
--- a/server/src/uds/models/Authenticator.py
+++ b/server/src/uds/models/Authenticator.py
@@ -48,7 +48,7 @@ import logging
logger = logging.getLogger(__name__)
-__updated__ = '2016-02-26'
+__updated__ = '2018-02-26'
@python_2_unicode_compatible
@@ -176,6 +176,28 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
"""
return Authenticator.objects.all().order_by('priority')
+ @staticmethod
+ def getByTag(tag=None):
+ '''
+ Gets authenticator by tag name.
+ Special tag name "disabled" is used to exclude customAuth
+ '''
+ from uds.core.util.Config import GlobalConfig
+
+ auths = []
+ if tag is not None:
+ auths = Authenticator.objects.filter(small_name=tag).order_by('priority', 'name')
+ if auths.count() == 0:
+ auths = Authenticator.objects.all().order_by('priority', 'name')
+ # If disallow global login (use all auths), get just the first by priority/name
+ if GlobalConfig.DISALLOW_GLOBAL_LOGIN.getBool(False) is True:
+ auths = auths[0:1]
+ logger.debug(auths)
+ else:
+ auths = Authenticator.objects.all().order_by('priority', 'name')
+
+ return [auth for auth in auths if auth.getType().isCustom() is False or tag != 'disabled']
+
@staticmethod
def beforeDelete(sender, **kwargs):
"""
@@ -206,5 +228,6 @@ class Authenticator(ManagedObjectModel, TaggingMixin):
def __str__(self):
return u"{0} of type {1} (id:{2})".format(self.name, self.data_type, self.id)
+
# Connects a pre deletion signal to Authenticator
signals.pre_delete.connect(Authenticator.beforeDelete, sender=Authenticator)
diff --git a/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py b/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py
index 2d3dc8898..d71777631 100644
--- a/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py
+++ b/server/src/uds/osmanagers/LinuxOsManager/LinuxOsManager.py
@@ -201,6 +201,9 @@ class LinuxOsManager(osmanagers.OSManager):
if self._onLogout == 'remove':
userService.release()
+ def isPersistent(self):
+ return not self._onLogout == 'remove'
+
def checkState(self, service):
logger.debug('Checking state for service {0}'.format(service))
return State.RUNNING
diff --git a/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py b/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py
index b193005d7..39ab04a90 100644
--- a/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py
+++ b/server/src/uds/osmanagers/WindowsOsManager/WindowsOsManager.py
@@ -244,6 +244,9 @@ class WindowsOsManager(osmanagers.OSManager):
if self._onLogout == 'remove':
userService.release()
+ def isPersistent(self):
+ return not self._onLogout == 'remove'
+
def checkState(self, service):
logger.debug('Checking state for service {0}'.format(service))
return State.RUNNING
diff --git a/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py b/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py
index 63ea2099e..1bc56b923 100644
--- a/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py
+++ b/server/src/uds/services/OpenStack/openStack/UDSOpenStackClient.py
@@ -33,33 +33,26 @@
# pylint: disable=maybe-no-member,protected-access
from django.utils.translation import ugettext as _
-from uds.core.util.Cache import Cache
-
import logging
import requests
import json
import dateutil.parser
-import hashlib
import six
-
-__updated__ = '2017-11-13'
+__updated__ = '2018-03-02'
logger = logging.getLogger(__name__)
# Required: Authentication v3
-
-# This is a vary basic implementation for what we need from openstack
+# This is an implementation for what we need from openstack
# This does not includes (nor it is intention) full API implementation, just the parts we need
-# Theese are related to auth, compute & network basically
-
-# In case we Cache time for endpoints. This is more likely to not change never, so we will tray to keep it as long as we can (1 hour for example?)
-# ENDPOINTS_TIMEOUT = 1 * 3600
+# These are related to auth, compute & network basically
# Do not verify SSL conections right now
VERIFY_SSL = False
+
# Helpers
def ensureResponseIsValid(response, errMsg=None):
if response.ok is False:
@@ -96,28 +89,30 @@ def getRecurringUrlJson(url, headers, key, params=None, errMsg=None, timeout=10)
# Decorators
def authRequired(func):
+
def ensurer(obj, *args, **kwargs):
obj.ensureAuthenticated()
try:
return func(obj, *args, **kwargs)
except Exception as e:
logger.error('Got error {} for openstack'.format(e))
- obj._cleanCache() # On any request error, force next time auth
raise
+
return ensurer
+
def authProjectRequired(func):
+
def ensurer(obj, *args, **kwargs):
if obj._projectId is None:
raise Exception('Need a project for method {}'.format(func))
obj.ensureAuthenticated()
return func(obj, *args, **kwargs)
+
return ensurer
class Client(object):
- cache = Cache('uds-openstack')
-
PUBLIC = 'public'
PRIVATE = 'private'
INTERNAL = 'url'
@@ -138,18 +133,6 @@ class Client(object):
self._authUrl = 'http{}://{}:{}/'.format('s' if useSSL else '', host, port)
- # Generates a hash for auth + credentials
- h = hashlib.md5()
- h.update(six.binary_type(host))
- h.update(six.binary_type(port))
- h.update(six.binary_type(domain))
- h.update(six.binary_type(username))
- h.update(six.binary_type(password))
- h.update(six.binary_type(useSSL))
- h.update(six.binary_type(projectId))
- h.update(six.binary_type(region))
- self._cacheKey = h.hexdigest()
-
def _getEndpointFor(self, type_): # If no region is indicatad, first endpoint is returned
for i in self._catalog:
if i['type'] == type_:
@@ -164,38 +147,7 @@ class Client(object):
return headers
- def _getFromCache(self):
- cached = self.cache.get(self._cacheKey)
- if cached is not None:
- self._authenticated = True
- self._tokenId = cached['tokenId']
- # Extract the token id
- self._userId = cached['userId']
- self._projectId = cached['projectId']
- self._catalog = cached['catalog']
-
- return True
-
- return False
-
- def _saveToCache(self, validity=600):
- self.cache.put(self._cacheKey,
- {
- 'tokenId': self._tokenId,
- 'userId': self._userId,
- 'projectId': self._projectId,
- 'catalog': self._catalog
- },
- validity - 60) # We substract some seconds to allow some time desynchronization
-
- def _clearCache(self):
- self.cache.remove(self._cacheKey)
-
def authPassword(self):
- # If cached data exists, use it as auth
- if self._getFromCache() is True:
- return
-
data = {
'auth': {
'identity': {
@@ -239,20 +191,16 @@ class Client(object):
self._userId = token['user']['id']
validity = (dateutil.parser.parse(token['expires_at']).replace(tzinfo=None) - dateutil.parser.parse(token['issued_at']).replace(tzinfo=None)).seconds - 60
-
logger.debug('The token {} will be valid for {}'.format(self._tokenId, validity))
- # Now, if endpoints are present (only if tenant was specified), store & cache them
+ # Now, if endpoints are present (only if tenant was specified), store them
if self._projectId is not None:
self._catalog = token['catalog']
- self._saveToCache(validity)
-
def ensureAuthenticated(self):
if self._authenticated is False:
self.authPassword()
-
@authRequired
def listProjects(self):
return getRecurringUrlJson(self._authUrl + 'v3/users/{user_id}/projects'.format(user_id=self._userId),
@@ -261,7 +209,6 @@ class Client(object):
errMsg='List Projects',
timeout=self._timeout)
-
@authRequired
def listRegions(self):
return getRecurringUrlJson(self._authUrl + 'v3/regions/',
@@ -270,7 +217,6 @@ class Client(object):
errMsg='List Regions',
timeout=self._timeout)
-
@authProjectRequired
def listServers(self, detail=False, params=None):
path = '/servers/' + 'detail' if detail is True else ''
@@ -281,7 +227,6 @@ class Client(object):
errMsg='List Vms',
timeout=self._timeout)
-
@authProjectRequired
def listImages(self):
return getRecurringUrlJson(self._getEndpointFor('image') + '/v2/images?status=active',
@@ -290,7 +235,6 @@ class Client(object):
errMsg='List Images',
timeout=self._timeout)
-
@authProjectRequired
def listVolumeTypes(self):
return getRecurringUrlJson(self._getEndpointFor('volumev2') + '/types',
@@ -299,7 +243,6 @@ class Client(object):
errMsg='List Volume Types',
timeout=self._timeout)
-
@authProjectRequired
def listVolumes(self):
# self._getEndpointFor('volumev2') + '/volumes'
@@ -309,7 +252,6 @@ class Client(object):
errMsg='List Volumes',
timeout=self._timeout)
-
@authProjectRequired
def listVolumeSnapshots(self, volumeId=None):
for s in getRecurringUrlJson(self._getEndpointFor('volumev2') + '/snapshots',
@@ -320,7 +262,6 @@ class Client(object):
if volumeId is None or s['volume_id'] == volumeId:
yield s
-
@authProjectRequired
def listAvailabilityZones(self):
for az in getRecurringUrlJson(self._getEndpointFor('compute') + '/os-availability-zone',
@@ -331,7 +272,6 @@ class Client(object):
if az['zoneState']['available'] is True:
yield az['zoneName']
-
@authProjectRequired
def listFlavors(self):
return getRecurringUrlJson(self._getEndpointFor('compute') + '/flavors',
@@ -340,7 +280,6 @@ class Client(object):
errMsg='List Flavors',
timeout=self._timeout)
-
@authProjectRequired
def listNetworks(self):
return getRecurringUrlJson(self._getEndpointFor('network') + '/v2.0/networks',
@@ -372,7 +311,6 @@ class Client(object):
errMsg='List security groups',
timeout=self._timeout)
-
@authProjectRequired
def getServer(self, serverId):
r = requests.get(self._getEndpointFor('compute') + '/servers/{server_id}'.format(server_id=serverId),
@@ -396,7 +334,6 @@ class Client(object):
return v
-
@authProjectRequired
def getSnapshot(self, snapshotId):
"""
@@ -414,7 +351,6 @@ class Client(object):
return v
-
@authProjectRequired
def updateSnapshot(self, snapshotId, name=None, description=None):
data = { 'snapshot': {} }
@@ -436,7 +372,6 @@ class Client(object):
return v
-
@authProjectRequired
def createVolumeSnapshot(self, volumeId, name, description=None):
description = 'UDS Snapshot' if description is None else description
@@ -461,7 +396,6 @@ class Client(object):
return r.json()['snapshot']
-
@authProjectRequired
def createVolumeFromSnapshot(self, snapshotId, name, description=None):
description = 'UDS Volume' if description is None else description
@@ -520,7 +454,6 @@ class Client(object):
return r.json()['server']
-
@authProjectRequired
def deleteServer(self, serverId):
r = requests.post(self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
@@ -533,7 +466,6 @@ class Client(object):
# This does not returns anything
-
@authProjectRequired
def deleteSnapshot(self, snapshotId):
r = requests.delete(self._getEndpointFor('volumev2') + '/snapshots/{snapshot_id}'.format(snapshot_id=snapshotId),
@@ -545,7 +477,6 @@ class Client(object):
# Does not returns a message body
-
@authProjectRequired
def startServer(self, serverId):
r = requests.post(self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
@@ -558,7 +489,6 @@ class Client(object):
# This does not returns anything
-
@authProjectRequired
def stopServer(self, serverId):
r = requests.post(self._getEndpointFor('compute') + '/servers/{server_id}/action'.format(server_id=serverId),
@@ -589,7 +519,6 @@ class Client(object):
ensureResponseIsValid(r, 'Resuming server')
-
def testConnection(self):
# First, ensure requested api is supported
# We need api version 3.2 or greater
diff --git a/server/src/uds/templates/uds/html5/login.html b/server/src/uds/templates/uds/html5/login.html
index 86b04eae6..77febe2c7 100644
--- a/server/src/uds/templates/uds/html5/login.html
+++ b/server/src/uds/templates/uds/html5/login.html
@@ -1,9 +1,11 @@
{% extends "uds/html5/templates/base.html" %}
-{% load i18n static html5 %}
+{% load i18n static html5 uds %}
{% block title %}{% trans 'Welcome to UDS' %}{% endblock %}
{% block js %}
+{% javascript_auths authenticators %}
+
+