mirror of
https://github.com/dkmstr/openuds.git
synced 2025-03-12 04:58:34 +03:00
Added more tests
* Fixed pytest to not look for classes * Added 'destroy_after' property to user service for convenience * Small cosmetic fixes
This commit is contained in:
parent
b05c5b1396
commit
5c6303fec0
@ -1,4 +1,6 @@
|
||||
[pytest]
|
||||
DJANGO_SETTINGS_MODULE = server.settings
|
||||
python_files = tests.py test_*.py *_tests.py
|
||||
# Do not look for classes (all test clasess are descendant of unittest.TestCase), some of which are named Test* but are not tests (e.g. TestProvider)
|
||||
python_classes =
|
||||
addopts = --cov --cov-report html --cov-config=coverage.ini -n 12
|
||||
|
@ -66,7 +66,7 @@ class PermissionsTests(UDSTransactionTestCase):
|
||||
self.staffs = authenticators_fixtures.createUsers(
|
||||
self.authenticator, is_staff=True, groups=self.groups
|
||||
)
|
||||
self.userService = services_fixtures.createSingleTestingUserServiceStructure(
|
||||
self.userService = services_fixtures.createOneCacheTestingUserService(
|
||||
services_fixtures.createProvider(),
|
||||
self.users[0],
|
||||
list(self.users[0].groups.all()),
|
||||
|
@ -49,7 +49,7 @@ class AssignedAndUnusedTests(UDSTransactionTestCase):
|
||||
config.GlobalConfig.CHECK_UNUSED_TIME.set('600')
|
||||
AssignedAndUnused.setup()
|
||||
# All created user services has "in_use" to False, os_state and state to USABLE
|
||||
self.userServices = fixtures_services.createUserServiceForTesting(count=32)
|
||||
self.userServices = fixtures_services.createCacheTestingUserServices(count=32)
|
||||
|
||||
def test_assigned_unused(self):
|
||||
for us in self.userServices: # Update state date to now
|
||||
|
@ -53,7 +53,7 @@ class HangedCleanerTests(UDSTransactionTestCase):
|
||||
config.GlobalConfig.MAX_REMOVAL_TIME.set(MAX_INIT)
|
||||
HangedCleaner.setup()
|
||||
# All created user services has "in_use" to False, os_state and state to USABLE
|
||||
self.userServices = fixtures_services.createUserServiceForTesting(
|
||||
self.userServices = fixtures_services.createCacheTestingUserServices(
|
||||
count=TEST_SERVICES
|
||||
)
|
||||
|
||||
|
@ -37,23 +37,36 @@ from uds.core.util.state import State
|
||||
from uds.core.workers.servicepools_cache_updater import ServiceCacheUpdater
|
||||
from uds.core.environment import Environment
|
||||
|
||||
from uds.services.Test.provider import TestProvider
|
||||
from uds.services.Test.service import TestServiceCache, TestServiceNoCache
|
||||
|
||||
from ...utils.test import UDSTransactionTestCase
|
||||
from ...fixtures import services as services_fixtures
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ServiceCacheUpdaterTests(UDSTransactionTestCase):
|
||||
servicePool: 'models.ServicePool'
|
||||
|
||||
def setUp(self) -> None:
|
||||
# Default values for max
|
||||
TestProvider.maxPreparingServices = 1000
|
||||
TestProvider.maxRemovingServices = 1000
|
||||
TestServiceCache.maxDeployed = 1000
|
||||
TestServiceNoCache.maxDeployed = 1000
|
||||
|
||||
ServiceCacheUpdater.setup()
|
||||
userService = services_fixtures.createUserServiceForTesting()[0]
|
||||
userService = services_fixtures.createCacheTestingUserServices()[0]
|
||||
self.servicePool = userService.deployed_service
|
||||
userService.delete() # empty all
|
||||
|
||||
def numberOfRemovingOrCanced(self) -> int:
|
||||
return self.servicePool.userServices.filter(state__in=[State.REMOVABLE, State.CANCELED]).count()
|
||||
return self.servicePool.userServices.filter(
|
||||
state__in=[State.REMOVABLE, State.CANCELED]
|
||||
).count()
|
||||
|
||||
def runCacheUpdater(self, times: int) -> int:
|
||||
for _ in range(times):
|
||||
@ -62,17 +75,32 @@ class ServiceCacheUpdaterTests(UDSTransactionTestCase):
|
||||
# Test user service will cancel automatically so it will not get in "removable" state (on remove start, it will tell it has been removed)
|
||||
return self.servicePool.userServices.count() - self.numberOfRemovingOrCanced()
|
||||
|
||||
def setCache(self, initial: typing.Optional[int] = None, cache: typing.Optional[int] = None, cache2: typing.Optional[int] = None, max: typing.Optional[int] = None) -> None:
|
||||
self.servicePool.initial_srvs = self.servicePool.initial_srvs if initial is None else initial
|
||||
self.servicePool.cache_l1_srvs = self.servicePool.cache_l1_srvs if cache is None else cache
|
||||
self.servicePool.cache_l2_srvs = self.servicePool.cache_l2_srvs if cache2 is None else cache2
|
||||
def setCache(
|
||||
self,
|
||||
initial: typing.Optional[int] = None,
|
||||
cache: typing.Optional[int] = None,
|
||||
cache2: typing.Optional[int] = None,
|
||||
max: typing.Optional[int] = None,
|
||||
) -> None:
|
||||
self.servicePool.initial_srvs = (
|
||||
self.servicePool.initial_srvs if initial is None else initial
|
||||
)
|
||||
self.servicePool.cache_l1_srvs = (
|
||||
self.servicePool.cache_l1_srvs if cache is None else cache
|
||||
)
|
||||
self.servicePool.cache_l2_srvs = (
|
||||
self.servicePool.cache_l2_srvs if cache2 is None else cache2
|
||||
)
|
||||
self.servicePool.max_srvs = self.servicePool.max_srvs if max is None else max
|
||||
self.servicePool.save()
|
||||
|
||||
def test_initial(self) -> None:
|
||||
self.setCache(initial=100, cache=10, max=500)
|
||||
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.initial_srvs + 10), self.servicePool.initial_srvs)
|
||||
self.assertEqual(
|
||||
self.runCacheUpdater(self.servicePool.initial_srvs + 10),
|
||||
self.servicePool.initial_srvs,
|
||||
)
|
||||
|
||||
def test_remove(self) -> None:
|
||||
self.setCache(initial=100, cache=110, max=500)
|
||||
@ -83,26 +111,35 @@ class ServiceCacheUpdaterTests(UDSTransactionTestCase):
|
||||
mustDelete = self.servicePool.cache_l1_srvs - self.servicePool.initial_srvs
|
||||
|
||||
self.setCache(cache=10)
|
||||
self.assertEqual(self.runCacheUpdater(mustDelete), self.servicePool.initial_srvs)
|
||||
self.assertEqual(
|
||||
self.runCacheUpdater(mustDelete), self.servicePool.initial_srvs
|
||||
)
|
||||
|
||||
self.assertEqual(self.numberOfRemovingOrCanced(), mustDelete)
|
||||
|
||||
def test_max(self) -> None:
|
||||
self.setCache(initial=100, cache=10, max=50)
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.initial_srvs + 10), self.servicePool.max_srvs)
|
||||
self.assertEqual(
|
||||
self.runCacheUpdater(self.servicePool.initial_srvs + 10),
|
||||
self.servicePool.max_srvs,
|
||||
)
|
||||
|
||||
self.setCache(cache=200)
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.initial_srvs + 10), self.servicePool.max_srvs)
|
||||
self.assertEqual(
|
||||
self.runCacheUpdater(self.servicePool.initial_srvs + 10),
|
||||
self.servicePool.max_srvs,
|
||||
)
|
||||
|
||||
def test_cache(self) -> None:
|
||||
self.setCache(initial=10, cache=100, max=500)
|
||||
|
||||
# Try to "overcreate" cache elements (must create 100, that is "cache" (bigger than initial))
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.cache_l1_srvs + 10), self.servicePool.cache_l1_srvs)
|
||||
self.assertEqual(
|
||||
self.runCacheUpdater(self.servicePool.cache_l1_srvs + 10),
|
||||
self.servicePool.cache_l1_srvs,
|
||||
)
|
||||
|
||||
def test_provider_preparing_limits(self) -> None:
|
||||
from uds.services.Test.provider import TestProvider
|
||||
|
||||
TestProvider.maxPreparingServices = 10
|
||||
self.setCache(initial=100, cache=10, max=50)
|
||||
|
||||
@ -117,8 +154,6 @@ class ServiceCacheUpdaterTests(UDSTransactionTestCase):
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.cache_l1_srvs + 10), 1)
|
||||
|
||||
def test_provider_removing_limits(self) -> None:
|
||||
from uds.services.Test.provider import TestProvider
|
||||
|
||||
TestProvider.maxRemovingServices = 10
|
||||
self.setCache(initial=0, cache=50, max=50)
|
||||
|
||||
@ -131,4 +166,17 @@ class ServiceCacheUpdaterTests(UDSTransactionTestCase):
|
||||
# Execute updater, must remove 10 elements (maxRemovingServices)
|
||||
self.assertEqual(self.runCacheUpdater(10), 40)
|
||||
|
||||
def test_service_max_deployed(self) -> None:
|
||||
TestServiceCache.maxDeployed = 10
|
||||
|
||||
self.setCache(initial=100, cache=10, max=50)
|
||||
|
||||
# Try to "overcreate" cache elements but provider limits it to 10
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.cache_l1_srvs + 10), 10)
|
||||
|
||||
# Delete all userServices
|
||||
self.servicePool.userServices.all().delete()
|
||||
|
||||
# Now, set provider limit to 0. Minumum aceptable is 1, so 1 will be created
|
||||
TestServiceCache.maxDeployed = 0
|
||||
self.assertEqual(self.runCacheUpdater(self.servicePool.cache_l1_srvs + 10), 1)
|
||||
|
100
server/src/tests/core/workers/test_stuck_cleaner.py
Normal file
100
server/src/tests/core/workers/test_stuck_cleaner.py
Normal file
@ -0,0 +1,100 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2022 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
# are permitted provided that the following conditions are met:
|
||||
#
|
||||
# * Redistributions of source code must retain the above copyright notice,
|
||||
# this list of conditions and the following disclaimer.
|
||||
# * 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.U. nor the names of its contributors
|
||||
# may be used to endorse or promote products derived from this software
|
||||
# without specific prior written permission.
|
||||
#
|
||||
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
||||
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
||||
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
||||
# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
||||
# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
import typing
|
||||
import logging
|
||||
import datetime
|
||||
|
||||
from ...utils.test import UDSTransactionTestCase
|
||||
from ...fixtures import services as services_fixtures
|
||||
|
||||
from uds.models import UserService
|
||||
from uds.core.util.state import State
|
||||
from uds.core.workers.stuck_cleaner import StuckCleaner
|
||||
from uds.core.environment import Environment
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class StuckCleanerTests(UDSTransactionTestCase):
|
||||
userServices: typing.List['models.UserService']
|
||||
|
||||
def setUp(self) -> None:
|
||||
StuckCleaner.setup()
|
||||
|
||||
self.userServices = services_fixtures.createCacheTestingUserServices(count=128)
|
||||
# Set state date of all to 2 days ago
|
||||
for i, us in enumerate(self.userServices):
|
||||
us.state_date = datetime.datetime.now() - datetime.timedelta(days=2)
|
||||
# one fourth has to_be_removed property set and state to State.PREPARING
|
||||
if i % 4 == 0:
|
||||
us.destroy_after = True # this is a property, not a field
|
||||
us.state = State.PREPARING
|
||||
us.os_state = State.PREPARING
|
||||
# Other fourth has state to State.CANCELING
|
||||
elif i % 4 == 1:
|
||||
us.state = State.CANCELING
|
||||
us.os_state = State.PREPARING
|
||||
# Other fourth has state to State.REMOVING
|
||||
elif i % 4 == 2:
|
||||
us.state = State.REMOVING
|
||||
us.os_state = State.USABLE
|
||||
# Other fourth has state to State.USABLE (not stuck)
|
||||
elif i % 4 == 3:
|
||||
us.state = State.USABLE
|
||||
us.os_state = State.USABLE
|
||||
|
||||
us.save(update_fields=['state_date', 'state', 'os_state'])
|
||||
|
||||
def test_worker_outdated(self):
|
||||
count = UserService.objects.count()
|
||||
cleaner = StuckCleaner(Environment.getTempEnv())
|
||||
cleaner.run()
|
||||
self.assertEqual(
|
||||
UserService.objects.count(), count // 4
|
||||
) # 3/4 of user services should be removed
|
||||
|
||||
def test_worker_not_outdated(self):
|
||||
# Fix state_date to be less than 1 day for all user services
|
||||
for us in self.userServices:
|
||||
us.state_date = datetime.datetime.now() - datetime.timedelta(
|
||||
hours=23, minutes=59
|
||||
)
|
||||
us.save(update_fields=['state_date'])
|
||||
count = UserService.objects.count()
|
||||
cleaner = StuckCleaner(Environment.getTempEnv())
|
||||
cleaner.run()
|
||||
self.assertEqual(
|
||||
UserService.objects.count(), count
|
||||
) # No service should be removed, because they are not outdated
|
4
server/src/tests/fixtures/osmanagers.py
vendored
4
server/src/tests/fixtures/osmanagers.py
vendored
@ -69,8 +69,8 @@ def createServices(
|
||||
"""
|
||||
Creates a number of services
|
||||
"""
|
||||
from uds.services.Test.service import ServiceTestCache, ServiceTestNoCache
|
||||
service_type = ServiceTestCache if type_of_service == 'cache' else ServiceTestNoCache
|
||||
from uds.services.Test.service import TestServiceCache, TestServiceNoCache
|
||||
service_type = TestServiceCache if type_of_service == 'cache' else TestServiceNoCache
|
||||
|
||||
services = []
|
||||
for i in range(number_of_services):
|
||||
|
12
server/src/tests/fixtures/services.py
vendored
12
server/src/tests/fixtures/services.py
vendored
@ -62,21 +62,21 @@ def createProvider() -> models.Provider:
|
||||
return provider
|
||||
|
||||
|
||||
def createSingleTestingUserServiceStructure(
|
||||
def createOneCacheTestingUserService(
|
||||
provider: 'models.Provider',
|
||||
user: 'models.User',
|
||||
groups: typing.List['models.Group'],
|
||||
type_: typing.Union[typing.Literal['managed'], typing.Literal['unmanaged']],
|
||||
) -> 'models.UserService':
|
||||
|
||||
from uds.services.Test.service import ServiceTestCache, ServiceTestNoCache
|
||||
from uds.services.Test.service import TestServiceCache, TestServiceNoCache
|
||||
from uds.osmanagers.Test import TestOSManager
|
||||
from uds.transports.Test import TestTransport
|
||||
|
||||
service: 'models.Service' = provider.services.create(
|
||||
name='Service {}'.format(glob['service_id']),
|
||||
data_type=ServiceTestCache.typeType,
|
||||
data=ServiceTestCache(
|
||||
data_type=TestServiceCache.typeType,
|
||||
data=TestServiceCache(
|
||||
environment.Environment(str(glob['service_id'])), provider.getInstance()
|
||||
).serialize(),
|
||||
token=generators.random_string(16) + str(glob['service_id']),
|
||||
@ -155,7 +155,7 @@ def createSingleTestingUserServiceStructure(
|
||||
return user_service
|
||||
|
||||
|
||||
def createUserServiceForTesting(
|
||||
def createCacheTestingUserServices(
|
||||
count: int = 1,
|
||||
type_: typing.Union[
|
||||
typing.Literal['managed'], typing.Literal['unmanaged']
|
||||
@ -169,7 +169,7 @@ def createUserServiceForTesting(
|
||||
groups = authenticators.createGroups(auth, 3)
|
||||
user = authenticators.createUsers(auth, 1, groups=groups)[0]
|
||||
user_services.append(
|
||||
createSingleTestingUserServiceStructure(
|
||||
createOneCacheTestingUserService(
|
||||
createProvider(), user, groups, type_
|
||||
)
|
||||
)
|
||||
|
@ -1,6 +1,7 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
#
|
||||
# Copyright (c) 2022 Virtual Cable S.L.
|
||||
# Copyright (c) 2022 Virtual Cable S.L.U.
|
||||
# All rights reserved.
|
||||
#
|
||||
# Redistribution and use in source and binary forms, with or without modification,
|
||||
@ -11,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.
|
||||
#
|
||||
@ -28,12 +29,23 @@
|
||||
"""
|
||||
@author: Adolfo Gómez, dkmaster at dkmon dot com
|
||||
"""
|
||||
from django.test import TestCase
|
||||
import typing
|
||||
import logging
|
||||
|
||||
|
||||
from ..fixtures import notifiers as notifiers_fixtures
|
||||
from uds.core import messaging
|
||||
|
||||
class TestEmailNotifier(TestCase):
|
||||
from ..fixtures import notifiers as notifiers_fixtures
|
||||
from ..utils.test import UDSTransactionTestCase
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class TestEmailNotifier(UDSTransactionTestCase):
|
||||
"""
|
||||
Test Email Notifier
|
||||
"""
|
||||
|
@ -35,7 +35,10 @@ import typing
|
||||
from uds import models
|
||||
|
||||
from .. import test, generators, rest, constants
|
||||
from ... import fixtures
|
||||
from ...fixtures import (
|
||||
authenticators as authenticators_fixtures,
|
||||
services as services_fixtures,
|
||||
)
|
||||
|
||||
from uds.REST.handlers import AUTH_TOKEN_HEADER
|
||||
|
||||
@ -56,31 +59,31 @@ class RESTTestCase(test.UDSTransactionTestCase):
|
||||
def setUp(self) -> None:
|
||||
# Set up data for REST Test cases
|
||||
# First, the authenticator related
|
||||
self.auth = fixtures.authenticators.createAuthenticator()
|
||||
self.groups = fixtures.authenticators.createGroups(
|
||||
self.auth = authenticators_fixtures.createAuthenticator()
|
||||
self.groups = authenticators_fixtures.createGroups(
|
||||
self.auth, NUMBER_OF_ITEMS_TO_CREATE
|
||||
)
|
||||
# Create some users, one admin, one staff and one user
|
||||
self.admins = fixtures.authenticators.createUsers(
|
||||
self.admins = authenticators_fixtures.createUsers(
|
||||
self.auth,
|
||||
number_of_users=NUMBER_OF_ITEMS_TO_CREATE,
|
||||
is_admin=True,
|
||||
groups=self.groups,
|
||||
)
|
||||
self.staffs = fixtures.authenticators.createUsers(
|
||||
self.staffs = authenticators_fixtures.createUsers(
|
||||
self.auth,
|
||||
number_of_users=NUMBER_OF_ITEMS_TO_CREATE,
|
||||
is_staff=True,
|
||||
groups=self.groups,
|
||||
)
|
||||
self.plain_users = fixtures.authenticators.createUsers(
|
||||
self.plain_users = authenticators_fixtures.createUsers(
|
||||
self.auth, number_of_users=NUMBER_OF_ITEMS_TO_CREATE, groups=self.groups
|
||||
)
|
||||
|
||||
self.provider = fixtures.services.createProvider()
|
||||
self.provider = services_fixtures.createProvider()
|
||||
|
||||
self.user_service_managed = (
|
||||
fixtures.services.createSingleTestingUserServiceStructure(
|
||||
services_fixtures.createOneCacheTestingUserService(
|
||||
self.provider,
|
||||
self.admins[0],
|
||||
self.groups,
|
||||
@ -88,7 +91,7 @@ class RESTTestCase(test.UDSTransactionTestCase):
|
||||
)
|
||||
)
|
||||
self.user_service_unmanaged = (
|
||||
fixtures.services.createSingleTestingUserServiceStructure(
|
||||
services_fixtures.createOneCacheTestingUserService(
|
||||
self.provider,
|
||||
self.admins[0],
|
||||
self.groups,
|
||||
|
@ -76,7 +76,7 @@ class AssignedService(DetailHandler):
|
||||
'friendly_name': item.friendly_name,
|
||||
'state': item.state
|
||||
if not (props.get('destroy_after') and item.state == State.PREPARING)
|
||||
else State.CANCELING,
|
||||
else State.CANCELING, # Destroy after means that we need to cancel AFTER finishing preparing, but not before...
|
||||
'os_state': item.os_state,
|
||||
'state_date': item.state_date,
|
||||
'creation_date': item.creation_date,
|
||||
@ -208,7 +208,7 @@ class AssignedService(DetailHandler):
|
||||
user = models.User.objects.get(uuid=processUuid(fields['user_id']))
|
||||
|
||||
logStr = 'Changing ownership of service from {} to {} by {}'.format(
|
||||
userService.user.pretty_name, user.pretty_name, self._user.pretty_name
|
||||
userService.user, user.pretty_name, self._user.pretty_name
|
||||
)
|
||||
|
||||
# If there is another service that has this same owner, raise an exception
|
||||
|
@ -88,8 +88,8 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
return Q(cache_level=level) & self.getStateFilter(servicePool.service)
|
||||
|
||||
@staticmethod
|
||||
def getStateFilter(servicePool: ServicePool) -> Q:
|
||||
if servicePool.service.oldMaxAccountingMethod: # If no limits and accounting method is not old one
|
||||
def getStateFilter(service: 'models.Service') -> Q:
|
||||
if service.oldMaxAccountingMethod: # If no limits and accounting method is not old one
|
||||
# Valid states are: PREPARING, USABLE
|
||||
states = [State.PREPARING, State.USABLE]
|
||||
else: # New accounting method selected
|
||||
@ -97,6 +97,18 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
return Q(state__in=states)
|
||||
|
||||
def _checkMaxDeployedReached(self, servicePool: ServicePool) -> None:
|
||||
"""
|
||||
Checks if maxDeployed for the service has been reached, and, if so,
|
||||
raises an exception that no more services of this kind can be reached
|
||||
"""
|
||||
if self.maximumUserServicesDeployed(servicePool.service):
|
||||
raise MaxServicesReachedError(
|
||||
_('Maximum number of user services reached for this {}').format(
|
||||
servicePool
|
||||
)
|
||||
)
|
||||
|
||||
def getExistingUserServices(self, service: 'models.Service') -> int:
|
||||
"""
|
||||
Returns the number of running user services for this service
|
||||
"""
|
||||
@ -113,24 +125,12 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
if serviceInstance.maxDeployed == services.Service.UNLIMITED:
|
||||
return False
|
||||
|
||||
numberOfServices = servicePool.userServices.filter(
|
||||
self.getStateFilter(servicePool)
|
||||
).count()
|
||||
if self.getExistingUserServices(service) >= (serviceInstance.maxDeployed or 1):
|
||||
logger.debug('Maximum number of user services reached for this service: {}'.format(service.name))
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def __checkMaxDeployedReached(self, servicePool: ServicePool) -> None:
|
||||
"""
|
||||
Checks if maxDeployed for the service has been reached, and, if so,
|
||||
raises an exception that no more services of this kind can be reached
|
||||
"""
|
||||
if self.maximumUserServicesDeployed(servicePool.service):
|
||||
raise MaxServicesReachedError(
|
||||
_('Maximum number of user services reached for this {}').format(
|
||||
servicePool
|
||||
)
|
||||
)
|
||||
|
||||
def _createCacheAtDb(
|
||||
self, publication: ServicePoolPublication, cacheLevel: int
|
||||
) -> UserService:
|
||||
@ -217,7 +217,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
Creates a new assigned deployed service for the current publication (if any) of service pool and user indicated
|
||||
"""
|
||||
# First, honor maxPreparingServices
|
||||
if self.canInitiateServiceFromDeployedService(servicePool) is False:
|
||||
if self.canGrowServicePool(servicePool) is False:
|
||||
# Cannot create new
|
||||
logger.info(
|
||||
'Too many preparing services. Creation of assigned service denied by max preparing services parameter. (login storm with insufficient cache?).'
|
||||
@ -338,7 +338,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
not userServiceInstance.supportsCancel()
|
||||
): # Does not supports cancel, but destroy, so mark it for "later" destroy
|
||||
# State is kept, just mark it for destroy after finished preparing
|
||||
userService.setProperty('destroy_after', 'y')
|
||||
userService.destroy_after = True
|
||||
else:
|
||||
userService.setState(State.CANCELING)
|
||||
# We simply notify service that it should cancel operation
|
||||
@ -595,7 +595,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
return False
|
||||
return True
|
||||
|
||||
def canInitiateServiceFromDeployedService(self, servicePool: ServicePool) -> bool:
|
||||
def canGrowServicePool(self, servicePool: ServicePool) -> bool:
|
||||
"""
|
||||
Checks if we can start a new service
|
||||
"""
|
||||
@ -1004,7 +1004,7 @@ class UserServiceManager(metaclass=singleton.Singleton):
|
||||
sortPools = [(p.pool.usage(), p.pool) for p in poolMembers]
|
||||
else:
|
||||
sortPools = [
|
||||
(random.randint(0, 10000), p.pool) for p in poolMembers
|
||||
(random.randint(0, 10000), p.pool) for p in poolMembers # nosec: just a suffle, not a crypto (to get a round robin-like behavior)
|
||||
] # Just shuffle them
|
||||
|
||||
# Sort pools related to policy now, and xtract only pools, not sort keys
|
||||
|
@ -156,8 +156,8 @@ class UpdateFromPreparing(StateUpdater):
|
||||
return state
|
||||
|
||||
def finish(self):
|
||||
if self.userService.getProperty('destroy_after'): # Marked for destroyal
|
||||
self.userService.setProperty('destroy_after', '') # Cleanup..
|
||||
if self.userService.destroy_after: # Marked for destroyal
|
||||
del self.userService.destroy_after # Cleanup..
|
||||
self.save(State.REMOVABLE) # And start removing it
|
||||
return
|
||||
|
||||
@ -311,6 +311,6 @@ class UserServiceOpChecker(DelayedTask):
|
||||
log.doLog(uService, log.ERROR, 'Exception: {}'.format(e), log.INTERNAL)
|
||||
try:
|
||||
uService.setState(State.ERROR)
|
||||
uService.save(update_fields=['data', 'state', 'state_date'])
|
||||
uService.save(update_fields=['data'])
|
||||
except Exception:
|
||||
logger.error('Can\'t update state of uService object')
|
||||
|
@ -90,9 +90,7 @@ class HangedCleaner(Job):
|
||||
logger.debug('Searching for hanged services for %s', servicePool)
|
||||
us: UserService
|
||||
for us in servicePool.userServices.filter(flt):
|
||||
if us.getProperty(
|
||||
'destroy_after'
|
||||
): # It's waiting for removal, skip this very specific case
|
||||
if us.destroy_after: # It's waiting for removal, skip this very specific case
|
||||
continue
|
||||
logger.debug('Found hanged service %s', us)
|
||||
if (
|
||||
|
@ -179,12 +179,9 @@ class ServiceCacheUpdater(Job):
|
||||
continue
|
||||
|
||||
# If this service don't allows more starting user services, continue
|
||||
if (
|
||||
userServiceManager().canInitiateServiceFromDeployedService(servicePool)
|
||||
is False
|
||||
):
|
||||
if not userServiceManager().canGrowServicePool(servicePool):
|
||||
logger.debug(
|
||||
'This provider has the max allowed starting services running: %s',
|
||||
'This pool cannot grow rithg now: %s',
|
||||
servicePool,
|
||||
)
|
||||
continue
|
||||
|
@ -53,7 +53,7 @@ class StuckCleaner(Job):
|
||||
We keep it in a new place to "control" more specific thins
|
||||
"""
|
||||
|
||||
frecuency = 3601 * 8 # Executes Once a day
|
||||
frecuency = 3601 * 8 # Executes every 8 hours
|
||||
friendly_name = 'Stuck States cleaner'
|
||||
|
||||
def run(self) -> None:
|
||||
@ -84,6 +84,8 @@ class StuckCleaner(Job):
|
||||
# Info states are removed on UserServiceCleaner and VALID_STATES are ok, or if "hanged", checked on "HangedCleaner"
|
||||
def stuckUserServices(servicePool: ServicePool) -> typing.Iterable[UserService]:
|
||||
q = servicePool.userServices.filter(state_date__lt=since_state)
|
||||
# Get all that are not in valid or info states, AND the ones that are "PREPARING" with
|
||||
# "destroy_after" property set (exists) (that means that are waiting to be destroyed after initializations)
|
||||
yield from q.exclude(state__in=State.INFO_STATES + State.VALID_STATES)
|
||||
yield from q.filter(state=State.PREPARING, properties__name='destroy_after')
|
||||
|
||||
|
@ -140,6 +140,27 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
|
||||
"""
|
||||
return "{}\\{}".format(self.deployed_service.name, self.friendly_name)
|
||||
|
||||
@property
|
||||
def destroy_after(self) -> bool:
|
||||
"""
|
||||
Returns True if this service is to be removed
|
||||
"""
|
||||
return self.getProperty('to_be_removed', 'n') == 'y'
|
||||
|
||||
@destroy_after.setter
|
||||
def destroy_after(self, value: bool) -> None:
|
||||
"""
|
||||
Sets the to_be_removed property
|
||||
"""
|
||||
self.setProperty('destroy_after', 'y' if value else 'n')
|
||||
|
||||
@destroy_after.deleter
|
||||
def destroy_after(self) -> None:
|
||||
"""
|
||||
Removes the to_be_removed property
|
||||
"""
|
||||
self.deleteProperty('destroy_after')
|
||||
|
||||
def getEnvironment(self) -> Environment:
|
||||
"""
|
||||
Returns an environment valid for the record this object represents.
|
||||
@ -571,6 +592,12 @@ class UserService(UUIDModel): # pylint: disable=too-many-public-methods
|
||||
prop.value = propValue or ''
|
||||
prop.save()
|
||||
|
||||
def deleteProperty(self, propName: str) -> None:
|
||||
try:
|
||||
self.properties.get(name=propName).delete()
|
||||
except Exception: # nosec: we don't care if it does not exists
|
||||
pass
|
||||
|
||||
def setCommsUrl(self, commsUrl: typing.Optional[str] = None) -> None:
|
||||
self.setProperty('comms_url', commsUrl)
|
||||
|
||||
|
@ -42,7 +42,7 @@ from . import service
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from uds import models
|
||||
from .service import ServiceTestNoCache, ServiceTestCache
|
||||
from .service import TestServiceNoCache, TestServiceCache
|
||||
from .publication import TestPublication
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@ -73,7 +73,7 @@ class TestUserDeployment(services.UserDeployment):
|
||||
# : Recheck every five seconds by default (for task methods)
|
||||
suggestedTime = 5
|
||||
|
||||
def service(self) -> typing.Union['ServiceTestNoCache', 'ServiceTestCache']:
|
||||
def service(self) -> typing.Union['TestServiceNoCache', 'TestServiceCache']:
|
||||
return typing.cast('ServiceTestNoCache', super().service())
|
||||
|
||||
def getName(self) -> str:
|
||||
|
@ -40,7 +40,7 @@ import typing
|
||||
from django.utils.translation import gettext_noop as _
|
||||
from uds.core import services
|
||||
from uds.core import module
|
||||
from .service import ServiceTestNoCache, ServiceTestCache
|
||||
from .service import TestServiceNoCache, TestServiceCache
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
@ -57,7 +57,7 @@ class TestProvider(services.ServiceProvider):
|
||||
|
||||
"""
|
||||
# : What kind of services we offer, this are classes inherited from Service
|
||||
offers = [ServiceTestNoCache, ServiceTestCache]
|
||||
offers = [TestServiceNoCache, TestServiceCache]
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using gettext_noop)
|
||||
|
@ -44,7 +44,7 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
# Not imported at runtime, just for type checking
|
||||
if typing.TYPE_CHECKING:
|
||||
from .service import ServiceTestNoCache, ServiceTestCache
|
||||
from .service import TestServiceNoCache, TestServiceCache
|
||||
|
||||
|
||||
class TestPublication(services.Publication):
|
||||
|
@ -48,53 +48,27 @@ if typing.TYPE_CHECKING:
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ServiceTestNoCache(services.Service):
|
||||
class TestServiceNoCache(services.Service):
|
||||
"""
|
||||
Basic testing service without cache and no publication OFC
|
||||
"""
|
||||
# : Name to show the administrator. This string will be translated BEFORE
|
||||
# : sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using gettext_noop)
|
||||
typeName = _('Testing Service no cache')
|
||||
# : Type used internally to identify this provider
|
||||
typeType = 'TestService1'
|
||||
# : Description shown at administration interface for this provider
|
||||
typeDescription = _('Testing (and dummy) service with no cache')
|
||||
# : Icon file used as icon for this provider. This string will be translated
|
||||
# : BEFORE sending it to administration interface, so don't forget to
|
||||
# : mark it as _ (using gettext_noop)
|
||||
iconFile = 'service.png'
|
||||
|
||||
# Functional related data
|
||||
|
||||
# : If the service provides more than 1 "deployed user" (-1 = no limit,
|
||||
# : 0 = ???? (do not use it!!!), N = max number to deploy
|
||||
maxDeployed = -1
|
||||
# : If we need to generate "cache" for this service, so users can access the
|
||||
# : provided services faster. Is usesCache is True, you will need also
|
||||
# : set publicationType, do take care about that!
|
||||
maxDeployed = 1000 # A big number for testing purposes
|
||||
usesCache = False
|
||||
# : Tooltip shown to user when this item is pointed at admin interface, none
|
||||
# : because we don't use it
|
||||
cacheTooltip = _('None')
|
||||
# : If we need to generate a "Level 2" cache for this service (i.e., L1
|
||||
# : could be running machines and L2 suspended machines)
|
||||
usesCache_L2 = False
|
||||
# : Tooltip shown to user when this item is pointed at admin interface, None
|
||||
# : also because we don't use it
|
||||
cacheTooltip_L2 = _('None')
|
||||
|
||||
# : If the service needs a s.o. manager (managers are related to agents
|
||||
# : provided by services itselfs, i.e. virtual machines with actors)
|
||||
needsManager = False
|
||||
# : If true, the system can't do an automatic assignation of a deployed user
|
||||
# : service from this service
|
||||
mustAssignManually = False
|
||||
|
||||
# : Types of publications (preparated data for deploys)
|
||||
# : In our case, we do no need a publication, so this is None
|
||||
publicationType = None
|
||||
# : Types of deploys (services in cache and/or assigned to users)
|
||||
deployedType = TestUserDeployment
|
||||
|
||||
def parent(self) -> 'TestProvider':
|
||||
@ -106,7 +80,7 @@ class ServiceTestNoCache(services.Service):
|
||||
def getBaseName(self) -> str:
|
||||
return self.parent().getName()
|
||||
|
||||
class ServiceTestCache(services.Service):
|
||||
class TestServiceCache(services.Service):
|
||||
"""
|
||||
A simple testging service WITH cache and publication OFC
|
||||
"""
|
||||
@ -117,7 +91,7 @@ class ServiceTestCache(services.Service):
|
||||
iconFile = 'provider.png' # : We reuse provider icon here :-), it's just for testing purpuoses
|
||||
|
||||
# Functional related data
|
||||
maxDeployed = -1
|
||||
maxDeployed = 1000 # A big number for testing
|
||||
usesCache = True
|
||||
cacheTooltip = _('L1 cache for dummy elements')
|
||||
usesCache_L2 = True
|
||||
|
Loading…
x
Reference in New Issue
Block a user