# -*- 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 ''' from uds.core.services import UserDeployment from uds.core.util.State import State import logging logger = logging.getLogger(__name__) class SampleUserDeploymentOne(UserDeployment): ''' This class generates the user consumable elements of the service tree. 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.UniqueMacGenerator.UniqueNameGenerator` and py:class:`uds.core.util.UniqueNameGenerator.UniqueNameGenerator` This first sample do not uses cache. To see one with cache, see SampleUserDeploymentTwo. The main difference are the "...Cache".." methods, that here are not needed. 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 Serializble 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 SampleServiceOne, or whenever you trie to access a service of SampleServiceOne, you will get an excetion that says that you havent included the deployedType. ''' #: Recheck every five seconds by default (for task methods) suggestedTime = 5 # Serializable needed methods def marshal(self): ''' Does nothing right here, we will use envoronment storage in this sample ''' return '' def unmarshal(self, str_): ''' Does nothing here also, all data are keeped at environment storage ''' pass def getName(self): ''' We override this to return a name to display. Default inplementation (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 uniques 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) ''' name = self.storage().readData('name') if name is None: name = self.nameGenerator().get( self.service().getBaseName() + '-' + self.service().getColour(), 3 ) # Store value for persistence self.storage().saveData('name', name) return name def setIp(self, ip): ''' 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.storage().saveData('ip', str(ip)) def getUniqueId(self): ''' 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. ''' mac = self.storage().readData('mac') if mac is None: mac = self.macGenerator().get( '00:00:00:00:00:00-00:FF:FF:FF:FF:FF' ) self.storage().saveData('mac', mac) return mac def getIp(self): ''' 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 ''' ip = self.storage().readData('ip') if ip is None: ip = '192.168.0.34' # Sample IP for testing purposses only return ip def setReady(self): ''' This is a task method. As that, the expected 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 accesible, it will return "State.FINISHED". If the machine is not accesible (has ben 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): ''' 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.storage().saveData('count', '0') # random fail if random.randint(0, 9) == 9: self.storage().saveData('error', 'Random error at deployForUser :-)') return State.ERROR return State.RUNNING def checkState(self): ''' 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 publication 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 ''' import random count = int(self.storage().readData('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 count >= 5: return State.FINISHED # random fail if random.randint(0, 9) == 9: self.storage().saveData('error', 'Random error at checkState :-)') return State.ERROR self.storage().saveData('count', str(count)) return State.RUNNING def finish(self): ''' Invoked when the core notices that the deployment of a service has finished. (No matter wether it is for cache or for an user) This gives the oportunity 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) ''' # Note that this is not really needed, is just a sample of storage use self.storage().remove('count') def assignToUser(self, user): ''' This method is invoked whenever a cache item gets assigned to an user. This gives the User Deployment an oportunity to do whatever actions are required so the service puts at a correct state for using by a service. In our sample, the service is always ready, so this does nothing. This is not a task method. All level 1 cache items can be diretly assigned to an user with no more work needed, but, if something is needed, here you can do whatever you need ''' pass def userLoggedIn(self, user): ''' 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 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 store the value at storage, but never get used, just an example self.storage().saveData('user', user) def userLoggedOut(self, user): ''' 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): ''' 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). ''' return self.storage().readData('error') or 'No error' def destroy(self): ''' 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): ''' 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