Adding help and background class atrributes to ResourceManager
[nepi.git] / src / nepi / execution / resource.py
index 8cb3673..a9087ae 100644 (file)
@@ -26,6 +26,7 @@ import functools
 import logging
 import os
 import pkgutil
+import sys
 import weakref
 
 reschedule_delay = "1s"
@@ -82,9 +83,22 @@ def clsinit_copy(cls):
 # Decorator to invoke class initialization method
 @clsinit
 class ResourceManager(Logger):
+    """ Base clase for all ResourceManagers. 
+    
+    A ResourceManger is specific to a resource type (e.g. Node, 
+    Switch, Application, etc) on a specific backend (e.g. PlanetLab, 
+    OMF, etc).
+
+    The ResourceManager instances are responsible for interacting with
+    and controlling concrete (physical or virtual) resources in the 
+    experimental backends.
+    
+    """
     _rtype = "Resource"
     _attributes = None
     _traces = None
+    _help = None
+    _backend = None
 
     @classmethod
     def _register_attribute(cls, attr):
@@ -186,6 +200,21 @@ class ResourceManager(Logger):
         """
         return copy.deepcopy(cls._traces.values())
 
+    @classmethod
+    def get_help(cls):
+        """ Returns the description of the type of Resource
+
+        """
+        return cls._help
+
+    @classmethod
+    def get_backend(cls):
+        """ Returns the identified of the backend (i.e. testbed, environment)
+        for the Resource
+
+        """
+        return cls._backend
+
     def __init__(self, ec, guid):
         super(ResourceManager, self).__init__(self.rtype())
         
@@ -200,7 +229,9 @@ class ResourceManager(Logger):
         # the resource instance gets a copy of all traces
         self._trcs = copy.deepcopy(self._traces)
 
-        self._state = ResourceState.NEW
+        # Each resource is placed on a deployment group by the EC
+        # during deployment
+        self.deployment_group = None
 
         self._start_time = None
         self._stop_time = None
@@ -211,6 +242,8 @@ class ResourceManager(Logger):
         self._finish_time = None
         self._failed_time = None
 
+        self._state = ResourceState.NEW
+
     @property
     def guid(self):
         """ Returns the global unique identifier of the RM """
@@ -314,9 +347,8 @@ class ResourceManager(Logger):
         This  method is resposible for selecting an individual resource
         matching user requirements.
         This method should be redefined when necessary in child classes.
-        """ 
-        self._discover_time = tnow()
-        self._state = ResourceState.DISCOVERED
+        """
+        self.set_discovered()
 
     def provision(self):
         """ Performs resource provisioning.
@@ -325,9 +357,8 @@ class ResourceManager(Logger):
         After this method has been successfully invoked, the resource
         should be acccesible/controllable by the RM.
         This method should be redefined when necessary in child classes.
-        """ 
-        self._provision_time = tnow()
-        self._state = ResourceState.PROVISIONED
+        """
+        self.set_provisioned()
 
     def start(self):
         """ Starts the resource.
@@ -335,12 +366,11 @@ class ResourceManager(Logger):
         There is no generic start behavior for all resources.
         This method should be redefined when necessary in child classes.
         """
-        if not self._state in [ResourceState.READY, ResourceState.STOPPED]:
+        if not self.state in [ResourceState.READY, ResourceState.STOPPED]:
             self.error("Wrong state %s for start" % self.state)
             return
 
-        self._start_time = tnow()
-        self._state = ResourceState.STARTED
+        self.set_started()
 
     def stop(self):
         """ Stops the resource.
@@ -348,12 +378,31 @@ class ResourceManager(Logger):
         There is no generic stop behavior for all resources.
         This method should be redefined when necessary in child classes.
         """
-        if not self._state in [ResourceState.STARTED]:
+        if not self.state in [ResourceState.STARTED]:
             self.error("Wrong state %s for stop" % self.state)
             return
+        
+        self.set_stopped()
 
-        self._stop_time = tnow()
-        self._state = ResourceState.STOPPED
+    def deploy(self):
+        """ Execute all steps required for the RM to reach the state READY
+
+        """
+        if self.state > ResourceState.READY:
+            self.error("Wrong state %s for deploy" % self.state)
+            return
+
+        self.debug("----- READY ---- ")
+        self.set_ready()
+
+    def release(self):
+        self.set_released()
+
+    def finish(self):
+        self.set_finished()
+    def fail(self):
+        self.set_failed()
 
     def set(self, name, value):
         """ Set the value of the attribute
@@ -426,7 +475,7 @@ class ResourceManager(Logger):
         :type action: str
         :param group: Group of RMs to wait for (list of guids)
         :type group: int or list of int
-        :param state: State to wait for on all RM in group. (either 'STARTED' or 'STOPPED')
+        :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
         :type state: str
         :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
         :type time: str
@@ -448,7 +497,7 @@ class ResourceManager(Logger):
     def unregister_condition(self, group, action = None):
         """ Removed conditions for a certain group of guids
 
-        :param action: Action to restrict to condition (either 'START' or 'STOP')
+        :param action: Action to restrict to condition (either 'START', 'STOP' or 'READY')
         :type action: str
 
         :param group: Group of RMs to wait for (list of guids)
@@ -497,7 +546,7 @@ class ResourceManager(Logger):
 
         :param group: Group of RMs to wait for (list of guids)
         :type group: int or list of int
-        :param state: State to wait for on all RM in group. (either 'STARTED' or 'STOPPED')
+        :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
         :type state: str
         :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
         :type time: str
@@ -533,7 +582,6 @@ class ResourceManager(Logger):
                 elif state == ResourceState.STOPPED:
                     t = rm.stop_time
                 else:
-                    # Only keep time information for START and STOP
                     break
 
                 # time already elapsed since RM changed state
@@ -653,38 +701,46 @@ class ResourceManager(Logger):
             self.debug(" ----- STOPPING ---- ") 
             self.stop()
 
-    def deploy(self):
-        """ Execute all steps required for the RM to reach the state READY
+    def deploy_with_conditions(self):
+        """ Deploy RM when all the conditions in self.conditions for
+        action 'READY' are satisfied.
 
         """
-        if self._state > ResourceState.READY:
-            self.error("Wrong state %s for deploy" % self.state)
-            return
-
-        self.debug("----- READY ---- ")
-        self._ready_time = tnow()
-        self._state = ResourceState.READY
-
-    def release(self):
-        """Release any resources used by this RM
+        reschedule = False
+        delay = reschedule_delay 
 
-        """
-        self._release_time = tnow()
-        self._state = ResourceState.RELEASED
+        ## evaluate if set conditions are met
 
-    def finish(self):
-        """ Mark ResourceManager as FINISHED
+        # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED 
+        if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED, 
+                ResourceState.PROVISIONED]:
+            reschedule = True
+            self.debug("---- RESCHEDULING DEPLOY ---- state %s " % self.state )
+        else:
+            deploy_conditions = self.conditions.get(ResourceAction.DEPLOY, [])
+            
+            self.debug("---- DEPLOY CONDITIONS ---- %s" % deploy_conditions) 
+            
+            # Verify all start conditions are met
+            for (group, state, time) in deploy_conditions:
+                # Uncomment for debug
+                #unmet = []
+                #for guid in group:
+                #    rm = self.ec.get_resource(guid)
+                #    unmet.append((guid, rm._state))
+                #
+                #self.debug("---- WAITED STATES ---- %s" % unmet )
 
-        """
-        self._finish_time = tnow()
-        self._state = ResourceState.FINISHED
+                reschedule, delay = self._needs_reschedule(group, state, time)
+                if reschedule:
+                    break
 
-    def fail(self):
-        """ Mark ResourceManager as FAILED
+        if reschedule:
+            self.ec.schedule(delay, self.deploy_with_conditions)
+        else:
+            self.debug("----- STARTING ---- ")
+            self.deploy()
 
-        """
-        self._failed_time = tnow()
-        self._state = ResourceState.FAILED
 
     def connect(self, guid):
         """ Performs actions that need to be taken upon associating RMs.
@@ -710,6 +766,46 @@ class ResourceManager(Logger):
         """
         # TODO: Validate!
         return True
+    
+    def set_started(self):
+        """ Mark ResourceManager as STARTED """
+        self._start_time = tnow()
+        self._state = ResourceState.STARTED
+        
+    def set_stopped(self):
+        """ Mark ResourceManager as STOPPED """
+        self._stop_time = tnow()
+        self._state = ResourceState.STOPPED
+
+    def set_ready(self):
+        """ Mark ResourceManager as READY """
+        self._ready_time = tnow()
+        self._state = ResourceState.READY
+
+    def set_released(self):
+        """ Mark ResourceManager as REALEASED """
+        self._release_time = tnow()
+        self._state = ResourceState.RELEASED
+
+    def set_finished(self):
+        """ Mark ResourceManager as FINISHED """
+        self._finish_time = tnow()
+        self._state = ResourceState.FINISHED
+
+    def set_failed(self):
+        """ Mark ResourceManager as FAILED """
+        self._failed_time = tnow()
+        self._state = ResourceState.FAILED
+
+    def set_discovered(self):
+        """ Mark ResourceManager as DISCOVERED """
+        self._discover_time = tnow()
+        self._state = ResourceState.DISCOVERED
+
+    def set_provisioned(self):
+        """ Mark ResourceManager as PROVISIONED """
+        self._provision_time = tnow()
+        self._state = ResourceState.PROVISIONED
 
 class ResourceFactory(object):
     _resource_types = dict()
@@ -764,7 +860,10 @@ def find_types():
         
         try:
             # Notice: Repeated calls to load_module will act as a reload of teh module
-            module = loader.load_module(modname)
+            if modname in sys.modules:
+                module = sys.modules.get(modname)
+            else:
+                module = loader.load_module(modname)
 
             for attrname in dir(module):
                 if attrname.startswith("_"):
@@ -780,6 +879,10 @@ def find_types():
 
                 if issubclass(attr, ResourceManager):
                     types.append(attr)
+
+                    if not modname in sys.modules:
+                        sys.modules[modname] = module
+
         except:
             import traceback
             import logging