1
0
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:
Adolfo Gómez García 2022-08-31 00:10:57 +02:00
parent b05c5b1396
commit 5c6303fec0
21 changed files with 273 additions and 110 deletions

View File

@ -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

View File

@ -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()),

View File

@ -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

View File

@ -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
)

View File

@ -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)

View 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

View File

@ -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):

View File

@ -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_
)
)

View File

@ -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
"""

View File

@ -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,

View File

@ -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

View File

@ -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

View File

@ -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')

View File

@ -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 (

View File

@ -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

View File

@ -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')

View File

@ -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)

View File

@ -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:

View File

@ -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)

View File

@ -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):

View File

@ -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