From 07e1c6214ee4929599e6478111632738d5a2ce48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adolfo=20G=C3=B3mez=20Garc=C3=ADa?= Date: Sun, 14 Aug 2022 23:36:22 +0200 Subject: [PATCH] Added Test Service --- .../Test/{deployment_one.py => deployment.py} | 9 +- .../src/uds/services/Test/deployment_two.py | 445 ------------------ server/src/uds/services/Test/provider.png | Bin 491 -> 18668 bytes server/src/uds/services/Test/provider.py | 1 - server/src/uds/services/Test/service.png | Bin 578 -> 18668 bytes server/src/uds/services/Test/service.py | 13 +- 6 files changed, 10 insertions(+), 458 deletions(-) rename server/src/uds/services/Test/{deployment_one.py => deployment.py} (95%) delete mode 100644 server/src/uds/services/Test/deployment_two.py diff --git a/server/src/uds/services/Test/deployment_one.py b/server/src/uds/services/Test/deployment.py similarity index 95% rename from server/src/uds/services/Test/deployment_one.py rename to server/src/uds/services/Test/deployment.py index 063e9f89..05b8797d 100644 --- a/server/src/uds/services/Test/deployment_one.py +++ b/server/src/uds/services/Test/deployment.py @@ -42,13 +42,13 @@ from . import service # Not imported at runtime, just for type checking if typing.TYPE_CHECKING: from uds import models - from .service import ServiceTestNoCache + from .service import ServiceTestNoCache, ServiceTestCache from .publication import TestPublication logger = logging.getLogger(__name__) -class TestUserDeploymentNoCache(services.UserDeployment): +class TestUserDeployment(services.UserDeployment): """ Simple testing deployment, no cache """ @@ -58,7 +58,6 @@ class TestUserDeploymentNoCache(services.UserDeployment): """ This is the data we will store in the storage """ - count: int = -1 ready: bool = False name: str = '' @@ -70,7 +69,7 @@ class TestUserDeploymentNoCache(services.UserDeployment): # : Recheck every five seconds by default (for task methods) suggestedTime = 5 - def service(self) -> 'ServiceTestNoCache': + def service(self) -> typing.Union['ServiceTestNoCache', 'ServiceTestCache']: return typing.cast('ServiceTestNoCache', super().service()) def getName(self) -> str: @@ -107,7 +106,7 @@ class TestUserDeploymentNoCache(services.UserDeployment): def deployForUser(self, user: 'models.User') -> str: logger.info('Deploying for user %s %s', user, self.data) - self.data.count = 10 + self.data.count = 3 return State.RUNNING def checkState(self) -> str: diff --git a/server/src/uds/services/Test/deployment_two.py b/server/src/uds/services/Test/deployment_two.py deleted file mode 100644 index 040b9715..00000000 --- a/server/src/uds/services/Test/deployment_two.py +++ /dev/null @@ -1,445 +0,0 @@ -# -*- coding: utf-8 -*- - -# -# Copyright (c) 2012 Virtual Cable S.L. -# 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. 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. - -""" -.. moduleauthor:: Adolfo Gómez, dkmaster at dkmon dot com -""" -import codecs -import logging -import typing - -from uds.core import services -from uds.core.util.state import State - -# Not imported at runtime, just for type checking -if typing.TYPE_CHECKING: - from uds import models - from .service import ServiceTestNoCache - from .publication import TestPublication - -logger = logging.getLogger(__name__) - - -class TestUserDeploymentCache(services.UserDeployment): - """ - This class generates the user consumable elements of the service tree. - - This is almost the same as SampleUserDeploymentOne, but differs that this one - uses the publication to get data from it, in a very basic way. - - After creating at administration interface an Deployed Service, UDS will - create consumable services for users using UserDeployment class as - provider of this elements. - - At class instantiation, this will receive an environment with"generator", - that are classes that provides a way to generate unique items. - - The generators provided right now are 'mac' and 'name'. To get more info - about this, look at py:class:`uds.core.util.unique_mac_generator.UniqueNameGenerator` - and py:class:`uds.core.util.unique_name_generator.UniqueNameGenerator` - - As sample also of environment storage usage, wi will use here the provider - storage to keep all our needed info, leaving marshal and unmarshal (needed - by Serializable classes, like this) empty (that is, returns '' first and does - nothing the second one) - - Also Remember, if you don't include this class as the deployedType of the - SampleServiceTwo, or whenever you try to access a service of SampleServiceTwo, - you will get an exception that says that you haven't included the deployedType. - """ - - # : Recheck every five seconds by default (for task methods) - suggestedTime = 2 - - _name: str - _ip: str - _mac: str - _error: str - _count: int - - # Utility overrides for type checking... - def service(self) -> 'ServiceTestNoCache': - return typing.cast('ServiceOne', super().service()) - - def publication(self) -> 'TestPublication': - pub = super().publication() - if pub is None: - raise Exception('No publication for this element!') - return typing.cast('SamplePublication', pub) - - def initialize(self) -> None: - """ - Initialize default attributes values here. We can do whatever we like, - but for this sample this is just right... - """ - self._name = '' - self._ip = '' - self._mac = '' - self._error = '' - self._count = 0 - - # Serializable needed methods - def marshal(self) -> bytes: - """ - Marshal own data, in this sample we will marshal internal needed - attributes. - - In this case, the data will be store with the database record. To - minimize database storage usage, we will "zip" data before returning it. - Anyway, we should keep this data as low as possible, we also have an - storage for loading larger data. - - :note: It's a good idea when providing marshalers, to store a 'version' - beside the values, so we can, at a later stage, treat with old - data for current modules. - """ - data = '\t'.join( - ['v1', self._name, self._ip, self._mac, self._error, str(self._count)] - ) - return codecs.encode(data.encode(), encoding='zip') # type: ignore - - def unmarshal(self, data: bytes) -> None: - """ - We unmarshal the content. - """ - values: typing.List[str] = codecs.decode(data, 'zip').decode().split('\t') # type: ignore - # Data Version check - # If we include some new data at some point in a future, we can - # add "default" values at v1 check, and load new values at 'v2' check. - if values[0] == 'v1': - self._name, self._ip, self._mac, self._error, count = values[1:] - self._count = int(count) - - def getName(self) -> str: - """ - We override this to return a name to display. Default implementation - (in base class), returns getUniqueIde() value - This name will help user to identify elements, and is only used - at administration interface. - - We will use here the environment name provided generator to generate - a name for this element. - - The namaGenerator need two params, the base name and a length for a - numeric incremental part for generating unique names. This are unique for - all UDS names generations, that is, UDS will not generate this name again - until this name is freed, or object is removed, what makes its environment - to also get removed, that makes all unique ids (names and macs right now) - to also get released. - - Every time get method of a generator gets called, the generator creates - a new unique name, so we keep the first generated name cached and don't - generate more names. (Generator are simple utility classes) - """ - if self._name == '': - self._name = self.nameGenerator().get(self.publication().getBaseName(), 3) - # self._name will be stored when object is marshaled - return self._name - - def setIp(self, ip: str) -> None: - """ - In our case, there is no OS manager associated with this, so this method - will never get called, but we put here as sample. - - Whenever an os manager actor notifies the broker the state of the service - (mainly machines), the implementation of that os manager can (an probably will) - need to notify the IP of the deployed service. Remember that UDS treats with - IP services, so will probable needed in every service that you will create. - :note: This IP is the IP of the "consumed service", so the transport can - access it. - """ - self._ip = ip - - def getUniqueId(self) -> str: - """ - Return and unique identifier for this service. - In our case, we will generate a mac name, that can be also as sample - of 'mac' generator use, and probably will get used something like this - at some services. - - The get method of a mac generator takes one param, that is the mac range - to use to get an unused mac. - - The mac generated is not used by anyone, it will not depend on - the range, the generator will take care that this mac is unique - and in the range provided, or it will return None. The ranges - are wide enough to ensure that we always will get a mac address - in this case, but if this is not your case, take into account that - None is a possible return value, and in that case, you should return an - invalid id right now. Every time a task method is invoked, the core - will try to update the value of the unique id using this method, so - that id can change with time. (In fact, it's not unique at database level, - it's unique in the sense that you must return an unique id that can, for - example, be used by os managers to identify this element). - - :note: Normally, getting out of macs in the mac pool is a bad thing... :-) - """ - if self._mac == '': - self._mac = self.macGenerator().get('00:00:00:00:00:00-00:FF:FF:FF:FF:FF') - return self._mac - - def getIp(self) -> str: - """ - We need to implement this method, so we can return the IP for transports - use. If no IP is known for this service, this must return None - - If our sample do not returns an IP, IP transport will never work with - this service. Remember in real cases to return a valid IP address if - the service is accesible and you alredy know that (for example, because - the IP has been assigend via setIp by an os manager) or because - you get it for some other method. - - Storage returns None if key is not stored. - - :note: Keeping the IP address is responsibility of the User Deployment. - Every time the core needs to provide the service to the user, or - show the IP to the administrator, this method will get called - - """ - if self._ip == '': - return '192.168.0.34' # Sample IP for testing purposes only - return self._ip - - def setReady(self) -> str: - """ - This is a task method. As that, the excepted return values are - State values RUNNING, FINISHED or ERROR. - - The method is invoked whenever a machine is provided to an user, right - before presenting it (via transport rendering) to the user. - - This method exist for this kind of situations (i will explain it with a - sample) - - Imagine a Service tree (Provider, Service, ...) for virtual machines. - This machines will get created by the UserDeployment implementation, but, - at some time, the machine can be put at in an state (suspend, shut down) - that will make the transport impossible to connect with it. - - This method, in this case, will check the state of the machine, and if - it is "ready", that is, powered on and accessible, it will return - "State.FINISHED". If the machine is not accessible (has been erased, for - example), it will return "State.ERROR" and store a reason of error so UDS - can ask for it and present this information to the Administrator. - - If the machine powered off, or suspended, or any other state that is not - directly usable but can be put in an usable state, it will return - "State.RUNNING", and core will use checkState to see when the operation - has finished. - - I hope this sample is enough to explain the use of this method.. - """ - - # In our case, the service is always ready - return State.FINISHED - - def deployForUser(self, user: 'models.User') -> str: - """ - Deploys an service instance for an user. - - This is a task method. As that, the excepted return values are - State values RUNNING, FINISHED or ERROR. - - The user parameter is not realy neded, but provided. It indicates the - Database User Object (see py:mod:`uds.modules`) to which this deployed - user service will be assigned to. - - This method will get called whenever a new deployed service for an user - is needed. This will give this class the oportunity to create - a service that is assigned to an user. - - The way of using this method is as follows: - - If the service gets created in "one step", that is, before the return - of this method, the consumable service for the user gets created, it - will return "State.FINISH". - If the service needs more steps (as in this case), we will return - "State.RUNNING", and if it has an error, it wil return "State.ERROR" and - store an error string so administration interface can show it. - - We do not use user for anything, as in most cases will be. - """ - import random - - self._count = 0 - - # random fail - if random.randint(0, 9) == 9: - # Note that we can mark this string as translatable, and return - # it translated at reasonOfError method - self._error = 'Random error at deployForUser :-)' - return State.ERROR - - return State.RUNNING - - def deployForCache(self, cacheLevel: int) -> str: - """ - Deploys a user deployment as cache. - - This is a task method. As that, the expected return values are - State values RUNNING, FINISHED or ERROR. - - In our sample, this will do exactly the same as deploy for user, - except that it will never will give an error. - - See deployForUser for a description of what this method should do. - - :note: deployForCache is invoked whenever a new cache element is needed - for an specific user deployment. It will also indicate for what - cache level (L1, L2) is the deployment - """ - self._count = 0 - return State.RUNNING - - def checkState(self) -> str: - """ - Our deployForUser method will initiate the consumable service deployment, - but will not finish it. - - So in our sample, we will only check if a number reaches 5, and if so - return that we have finished, else we will return that we are working - on it. - - One deployForUser returns State.RUNNING, this task will get called until - checkState returns State.FINISHED. - - Also, we will make the user deployment fail one of every 10 calls to this - method. - - Note: Destroying, canceling and deploying for cache also makes use of - this method, so you must keep the info of that you are checking if you - need it. - - In our case, destroy is 1-step action so this will no get called while - destroying, and cancel will simply invoke destroy. Cache deployment is - exactly as user deployment, except that the core will not assign it to - anyone, and cache moving operations is - """ - import random - - self._count += 1 - # Count is always a valid value, because this method will never get - # called before deployForUser, deployForCache, destroy or cancel. - # In our sample, we only use checkState in case of deployForUser, - # so at first call count will be 0. - if self._count >= 5: - return State.FINISHED - - # random fail - if random.randint(0, 9) == 9: - self._error = 'Random error at checkState :-)' - return State.ERROR - - return State.RUNNING - - def finish(self): - """ - Invoked when the core notices that the deployment of a service has finished. - (No matter whether it is for cache or for an user) - - This gives the opportunity to make something at that moment. - - :note: You can also make these operations at checkState, this is really - not needed, but can be provided (default implementation of base class does - nothing) - """ - # We set count to 0, not needed but for sample purposes - self._count = 0 - - def userLoggedIn(self, username: str) -> None: - """ - This method must be available so os managers can invoke it whenever - an user get logged into a service. - - Default implementation does nothing, so if you are going to do nothing, - you don't need to implement it. - - The responsibility of notifying it is of os manager actor, and it's - directly invoked by os managers (right now, linux os manager and windows - os manager) - - The user provided is just an string, that is provided by actors. - """ - # We store the value at storage, but never get used, just an example - self.storage.saveData('user', username) - - def userLoggedOut(self, username) -> None: - """ - This method must be available so os managers can invoke it whenever - an user get logged out if a service. - - Default implementation does nothing, so if you are going to do nothing, - you don't need to implement it. - - The responability of notifying it is of os manager actor, and it's - directly invoked by os managers (right now, linux os manager and windows - os manager) - - The user provided is just an string, that is provided by actor. - """ - # We do nothing more that remove the user - self.storage.remove('user') - - def reasonOfError(self) -> str: - """ - Returns the reason of the error. - - Remember that the class is responsible of returning this whenever asked - for it, and it will be asked everytime it's needed to be shown to the - user (when the administation asks for it). - - :note: Remember that you can use gettext to translate this error to - user language whenever it is possible. (This one will get invoked - directly from admin interface and, as so, will have translation - environment correctly set up. - """ - return self._error - - def destroy(self) -> str: - """ - This is a task method. As that, the excepted return values are - State values RUNNING, FINISHED or ERROR. - - Invoked for destroying a deployed service - Do whatever needed here, as deleting associated data if needed (i.e. a copy of the machine, snapshots, etc...) - @return: State.FINISHED if no more checks/steps for deployment are needed, State.RUNNING if more steps are needed (steps checked using checkState) - """ - return State.FINISHED - - def cancel(self) -> str: - """ - This is a task method. As that, the excepted return values are - State values RUNNING, FINISHED or ERROR. - - This can be invoked directly by an administration or by the clean up - of the deployed service (indirectly). - When administrator requests it, the cancel is "delayed" and not - invoked directly. - """ - return State.FINISHED diff --git a/server/src/uds/services/Test/provider.png b/server/src/uds/services/Test/provider.png index d2a954d44526a447e6f4e693324450868f7443b4..4d658d7c40cde1617b754831a4b8531918fbea42 100644 GIT binary patch literal 18668 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 QqyPW_07*qoM6N<$f=DO_-T(jq diff --git a/server/src/uds/services/Test/service.py b/server/src/uds/services/Test/service.py index 20d8e2f1..d76b5831 100644 --- a/server/src/uds/services/Test/service.py +++ b/server/src/uds/services/Test/service.py @@ -38,8 +38,7 @@ from uds.core import services from uds.core.ui import gui from .publication import TestPublication -from .deployment_one import TestUserDeploymentNoCache -from .deployment_two import TestUserDeploymentCache +from .deployment import TestUserDeployment # Not imported at runtime, just for type checking @@ -60,7 +59,7 @@ class ServiceTestNoCache(services.Service): # : mark it as _ (using gettext_noop) typeName = _('Testing Service no cache') # : Type used internally to identify this provider - typeType = 'TestServiceNoCache' + 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 @@ -98,7 +97,7 @@ class ServiceTestNoCache(services.Service): # : 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 = TestUserDeploymentNoCache + deployedType = TestUserDeployment def parent(self) -> 'Provider': return typing.cast('Provider', super().parent()) @@ -115,7 +114,7 @@ class ServiceTestCache(services.Service): """ typeName = _('Testing Service WITH cache') - typeType = 'TestingServiceCache' + typeType = 'TestService2' typeDescription = _('Testing (and dummy) service with CACHE and PUBLICATION') iconFile = 'provider.png' # : We reuse provider icon here :-), it's just for testing purpuoses @@ -134,7 +133,7 @@ class ServiceTestCache(services.Service): # : Note that this is a MUST if you indicate that needPublication publicationType = TestPublication # : Types of deploys (services in cache and/or assigned to users) - deployedType = TestUserDeploymentCache + deployedType = TestUserDeployment def parent(self) -> 'Provider': return typing.cast('Provider', super().parent()) @@ -143,4 +142,4 @@ class ServiceTestCache(services.Service): return self.parent().getName() + '{' + self.typeName + '}' def getBaseName(self) -> str: - return self.parent().getName() \ No newline at end of file + return self.parent().getName()