still making both branches closer
[nepi.git] / src / nepi / execution / resource.py
index 0eebc6f..4394130 100644 (file)
@@ -3,9 +3,8 @@
 #    Copyright (C) 2013 INRIA
 #
 #    This program is free software: you can redistribute it and/or modify
-#    it under the terms of the GNU General Public License as published by
-#    the Free Software Foundation, either version 3 of the License, or
-#    (at your option) any later version.
+#    it under the terms of the GNU General Public License version 2 as
+#    published by the Free Software Foundation;
 #
 #    This program is distributed in the hope that it will be useful,
 #    but WITHOUT ANY WARRANTY; without even the implied warranty of
@@ -19,6 +18,7 @@
 
 from nepi.util.timefuncs import tnow, tdiff, tdiffsec, stabsformat
 from nepi.util.logger import Logger
+from nepi.execution.attribute import Attribute, Flags, Types
 from nepi.execution.trace import TraceAttr
 
 import copy
@@ -26,10 +26,10 @@ import functools
 import logging
 import os
 import pkgutil
+import sys
+import threading
 import weakref
 
-reschedule_delay = "1s"
-
 class ResourceAction:
     """ Action that a user can order to a Resource Manager
    
@@ -44,47 +44,104 @@ class ResourceState:
     """
     NEW = 0
     DISCOVERED = 1
-    PROVISIONED = 2
-    READY = 3
-    STARTED = 4
-    STOPPED = 5
-    FINISHED = 6
+    RESERVED = 2
+    PROVISIONED = 3
+    READY = 4
+    STARTED = 5
+    STOPPED = 6
     FAILED = 7
     RELEASED = 8
 
 ResourceState2str = dict({
     ResourceState.NEW : "NEW",
     ResourceState.DISCOVERED : "DISCOVERED",
+    ResourceState.RESERVED : "RESERVED",
     ResourceState.PROVISIONED : "PROVISIONED",
     ResourceState.READY : "READY",
     ResourceState.STARTED : "STARTED",
     ResourceState.STOPPED : "STOPPED",
-    ResourceState.FINISHED : "FINISHED",
     ResourceState.FAILED : "FAILED",
     ResourceState.RELEASED : "RELEASED",
     })
 
 def clsinit(cls):
     """ Initializes template information (i.e. attributes and traces)
-    for the ResourceManager class
-    """
+    on classes derived from the ResourceManager class.
+
+    It is used as a decorator in the class declaration as follows:
+
+        @clsinit
+        class MyResourceManager(ResourceManager):
+        
+            ...
+
+     """
+
     cls._clsinit()
     return cls
 
 def clsinit_copy(cls):
     """ Initializes template information (i.e. attributes and traces)
-    for the ResourceManager class, inheriting attributes and traces
-    from the parent class
+    on classes derived from the ResourceManager class.
+    It differs from the clsinit method in that it forces inheritance
+    of attributes and traces from the parent class.
+
+    It is used as a decorator in the class declaration as follows:
+
+        @clsinit
+        class MyResourceManager(ResourceManager):
+        
+            ...
+
+
+    clsinit_copy should be prefered to clsinit when creating new
+    ResourceManager child classes.
+
     """
+    
     cls._clsinit_copy()
     return cls
 
-# Decorator to invoke class initialization method
+def failtrap(func):
+    """ Decorator function for instance methods that should set the 
+    RM state to FAILED when an error is raised. The methods that must be
+    decorated are: discover, reserved, provision, deploy, start, stop.
+
+    """
+    def wrapped(self, *args, **kwargs):
+        try:
+            return func(self, *args, **kwargs)
+        except:
+            self.fail()
+            
+            import traceback
+            err = traceback.format_exc()
+            logger = Logger(self._rtype)
+            logger.error(err)
+            logger.error("SETTING guid %d to state FAILED" % self.guid)
+            raise
+    
+    return wrapped
+
 @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 platform (e.g. PlanetLab, 
+    OMF, etc).
+
+    The ResourceManager instances are responsible for interacting with
+    and controlling concrete (physical or virtual) resources in the 
+    experimental platforms.
+    
+    """
     _rtype = "Resource"
     _attributes = None
     _traces = None
+    _help = None
+    _platform = None
+    _reschedule_delay = "0.5s"
 
     @classmethod
     def _register_attribute(cls, attr):
@@ -92,6 +149,7 @@ class ResourceManager(Logger):
         resource attribute
 
         """
+        
         cls._attributes[attr.name] = attr
 
     @classmethod
@@ -100,6 +158,7 @@ class ResourceManager(Logger):
         resource attribute
 
         """
+        
         del cls._attributes[name]
 
     @classmethod
@@ -108,6 +167,7 @@ class ResourceManager(Logger):
         resource trace
 
         """
+        
         cls._traces[trace.name] = trace
 
     @classmethod
@@ -116,34 +176,60 @@ class ResourceManager(Logger):
         resource trace
 
         """
+        
         del cls._traces[name]
 
     @classmethod
     def _register_attributes(cls):
         """ Resource subclasses will invoke this method to register
-        resource attributes
+        resource attributes.
 
-        """
-        pass
+        This method should be overriden in the RMs that define
+        attributes.
 
+        """
+        critical = Attribute("critical", 
+                "Defines whether the resource is critical. "
+                "A failure on a critical resource will interrupt "
+                "the experiment. ",
+                type = Types.Bool,
+                default = True,
+                flags = Flags.Design)
+        hard_release = Attribute("hardRelease", 
+                "Forces removal of all result files and directories associated "
+                "to the RM upon resource release. After release the RM will "
+                "be removed from the EC and the results will not longer be "
+                "accessible",
+                type = Types.Bool,
+                default = False,
+                flags = Flags.Design)
+
+        cls._register_attribute(critical)
+        cls._register_attribute(hard_release)
+        
     @classmethod
     def _register_traces(cls):
         """ Resource subclasses will invoke this method to register
         resource traces
 
+        This method should be overridden in the RMs that define traces.
+        
         """
+        
         pass
 
     @classmethod
     def _clsinit(cls):
-        """ ResourceManager child classes have different attributes and traces.
-        Since the templates that hold the information of attributes and traces
-        are 'class attribute' dictionaries, initially they all point to the 
-        parent class ResourceManager instances of those dictionaries. 
-        In order to make these templates independent from the parent's one,
-        it is necessary re-initialize the corresponding dictionaries. 
-        This is the objective of the _clsinit method
+        """ ResourceManager classes have different attributes and traces.
+        Attribute and traces are stored in 'class attribute' dictionaries.
+        When a new ResourceManager class is created, the _clsinit method is 
+        called to create a new instance of those dictionaries and initialize 
+        them.
+        
+        The _clsinit method is called by the clsinit decorator method.
+        
         """
+        
         # static template for resource attributes
         cls._attributes = dict()
         cls._register_attributes()
@@ -154,8 +240,12 @@ class ResourceManager(Logger):
 
     @classmethod
     def _clsinit_copy(cls):
-        """ Same as _clsinit, except that it also inherits all attributes and traces
-        from the parent class.
+        """ Same as _clsinit, except that after creating new instances of the
+        dictionaries it copies all the attributes and traces from the parent 
+        class.
+        
+        The _clsinit_copy method is called by the clsinit_copy decorator method.
+        
         """
         # static template for resource attributes
         cls._attributes = copy.deepcopy(cls._attributes)
@@ -166,7 +256,7 @@ class ResourceManager(Logger):
         cls._register_traces()
 
     @classmethod
-    def rtype(cls):
+    def get_rtype(cls):
         """ Returns the type of the Resource Manager
 
         """
@@ -177,17 +267,65 @@ class ResourceManager(Logger):
         """ Returns a copy of the attributes
 
         """
-        return copy.deepcopy(cls._attributes.values())
+        return copy.deepcopy(list(cls._attributes.values()))
+
+    @classmethod
+    def get_attribute(cls, name):
+        """ Returns a copy of the attribute with name 'name'
+
+        """
+        return copy.deepcopy(cls._attributes[name])
 
     @classmethod
     def get_traces(cls):
         """ Returns a copy of the traces
 
         """
-        return copy.deepcopy(cls._traces.values())
+        return copy.deepcopy(list(cls._traces.values()))
+
+    @classmethod
+    def get_help(cls):
+        """ Returns the description of the type of Resource
+
+        """
+        return cls._help
+
+    @classmethod
+    def get_platform(cls):
+        """ Returns the identified of the platform (i.e. testbed type)
+        for the Resource
+
+        """
+        return cls._platform
+
+    @classmethod
+    def get_global(cls, name):
+        """ Returns the value of a global attribute
+            Global attribute meaning an attribute for 
+            all the resources from a rtype
+
+        :param name: Name of the attribute
+        :type name: str
+        :rtype: str
+        """
+        global_attr = cls._attributes[name]
+        return global_attr.value
+
+    @classmethod
+    def set_global(cls, name, value):
+        """ Set value for a global attribute
+
+        :param name: Name of the attribute
+        :type name: str
+        :param name: Value of the attribute
+        :type name: str
+        """
+        global_attr = cls._attributes[name]
+        global_attr.value = value
+        return value
 
     def __init__(self, ec, guid):
-        super(ResourceManager, self).__init__(self.rtype())
+        super(ResourceManager, self).__init__(self.get_rtype())
         
         self._guid = guid
         self._ec = weakref.ref(ec)
@@ -200,17 +338,26 @@ 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
         self._discover_time = None
+        self._reserved_time = None
         self._provision_time = None
         self._ready_time = None
         self._release_time = None
-        self._finish_time = None
         self._failed_time = None
 
+        self._state = ResourceState.NEW
+
+        # instance lock to synchronize exclusive state change methods (such
+        # as deploy and release methods), in order to prevent them from being 
+        # executed at the same time and corrupt internal resource state
+        self._release_lock = threading.Lock()
+
     @property
     def guid(self):
         """ Returns the global unique identifier of the RM """
@@ -218,142 +365,264 @@ class ResourceManager(Logger):
 
     @property
     def ec(self):
-        """ Returns the Experiment Controller """
+        """ Returns the Experiment Controller of the RM """
         return self._ec()
 
     @property
     def connections(self):
-        """ Returns the set of guids of connected RMs"""
+        """ Returns the set of guids of connected RMs """
         return self._connections
 
     @property
     def conditions(self):
         """ Returns the conditions to which the RM is subjected to.
         
-        The object returned by this method is a dictionary indexed by
-        ResourceAction."""
+        This method returns a dictionary of conditions lists indexed by
+        a ResourceAction.
+        
+        """
         return self._conditions
 
     @property
     def start_time(self):
-        """ Returns the start time of the RM as a timestamp"""
+        """ Returns the start time of the RM as a timestamp """
         return self._start_time
 
     @property
     def stop_time(self):
-        """ Returns the stop time of the RM as a timestamp"""
+        """ Returns the stop time of the RM as a timestamp """
         return self._stop_time
 
     @property
     def discover_time(self):
-        """ Returns the time discovering was finished for the RM as a timestamp"""
+        """ Returns the discover time of the RM as a timestamp """
         return self._discover_time
 
+    @property
+    def reserved_time(self):
+        """ Returns the reserved time of the RM as a timestamp """
+        return self._reserved_time
+
     @property
     def provision_time(self):
-        """ Returns the time provisioning was finished for the RM as a timestamp"""
+        """ Returns the provision time of the RM as a timestamp """
         return self._provision_time
 
     @property
     def ready_time(self):
-        """ Returns the time deployment was finished for the RM as a timestamp"""
+        """ Returns the deployment time of the RM as a timestamp """
         return self._ready_time
 
     @property
     def release_time(self):
-        """ Returns the release time of the RM as a timestamp"""
+        """ Returns the release time of the RM as a timestamp """
         return self._release_time
 
-    @property
-    def finish_time(self):
-        """ Returns the finalization time of the RM as a timestamp"""
-        return self._finish_time
-
     @property
     def failed_time(self):
-        """ Returns the time failure occured for the RM as a timestamp"""
+        """ Returns the time failure occurred for the RM as a timestamp """
         return self._failed_time
 
     @property
     def state(self):
-        """ Get the state of the current RM """
+        """ Get the current state of the RM """
         return self._state
 
+    @property
+    def reschedule_delay(self):
+        """ Returns default reschedule delay """
+        return self._reschedule_delay
+
     def log_message(self, msg):
         """ Returns the log message formatted with added information.
 
         :param msg: text message
         :type msg: str
         :rtype: str
+
         """
-        return " %s guid: %d - %s " % (self._rtype, self.guid, msg)
+        return " %s guid %d - %s " % (self._rtype, self.guid, msg)
 
     def register_connection(self, guid):
         """ Registers a connection to the RM identified by guid
 
+        This method should not be overridden. Specific functionality
+        should be added in the do_connect method.
+
         :param guid: Global unique identified of the RM to connect to
         :type guid: int
+
         """
         if self.valid_connection(guid):
-            self.connect(guid)
+            self.do_connect(guid)
             self._connections.add(guid)
 
     def unregister_connection(self, guid):
         """ Removes a registered connection to the RM identified by guid
+        
+        This method should not be overridden. Specific functionality
+        should be added in the do_disconnect method.
 
         :param guid: Global unique identified of the RM to connect to
         :type guid: int
+
         """
         if guid in self._connections:
-            self.disconnect(guid)
+            self.do_disconnect(guid)
             self._connections.remove(guid)
 
+    @failtrap
     def discover(self):
         """ Performs resource discovery.
+        
+        This  method is responsible for selecting an individual resource
+        matching user requirements.
+
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_discover method.
+
+        """
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_discover()
 
-        This  method is resposible for selecting an individual resource
+    @failtrap
+    def reserve(self):
+        """ Performs resource reserve.
+        
+        This  method is responsible for reserving an individual resource
         matching user requirements.
-        This method should be redefined when necessary in child classes.
-        """ 
-        self._discover_time = tnow()
-        self._state = ResourceState.DISCOVERED
 
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_reserved method.
+
+        """
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_reserve()
+
+    @failtrap
     def provision(self):
         """ Performs resource provisioning.
 
-        This  method is resposible for provisioning one resource.
+        This  method is responsible for provisioning one resource.
         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
+        should be accessible/controllable by the RM.
+
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_provision method.
 
+        """
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_provision()
+
+    @failtrap
+    def configure(self):
+        """ Performs resource configuration.
+
+        This  method is responsible for configuring one resource.
+        After this method has been successfully invoked, the resource
+        should be set up to start the experimentation.
+
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_configure method.
+
+        """
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_configure()
+
+    @failtrap
     def start(self):
-        """ Starts the resource.
-        
-        There is no generic start behavior for all resources.
-        This method should be redefined when necessary in child classes.
+        """ Starts the RM (e.g. launch remote process).
+    
+        There is no standard start behavior. Some RMs will not need to perform
+        any actions upon start.
+
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_start method.
+
         """
-        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
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_start()
 
+    @failtrap
     def stop(self):
-        """ Stops the resource.
-        
-        There is no generic stop behavior for all resources.
-        This method should be redefined when necessary in child classes.
+        """ Interrupts the RM, stopping any tasks the RM was performing.
+     
+        There is no standard stop behavior. Some RMs will not need to perform
+        any actions upon stop.
+    
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_stop method.
+      
         """
-        if not self._state in [ResourceState.STARTED]:
+        if not self.state in [ResourceState.STARTED]:
             self.error("Wrong state %s for stop" % self.state)
             return
+        
+        with self._release_lock:
+            self.do_stop()
+
+    @failtrap
+    def deploy(self):
+        """ Execute all steps required for the RM to reach the state READY.
+
+        This method is responsible for deploying the resource (and invoking 
+        the discover and provision methods).
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_deploy method.
+       
+        """
+        if self.state > ResourceState.READY:
+            self.error("Wrong state %s for deploy" % self.state)
+            return
+
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_deploy()
+
+    def release(self):
+        """ Perform actions to free resources used by the RM.
+  
+        This  method is responsible for releasing resources that were
+        used during the experiment by the RM.
+
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_release method.
+      
+        """
+        with self._release_lock:
+            try:
+                self.do_release()
+            except:
+                self.set_released()
+
+                import traceback
+                err = traceback.format_exc()
+                msg = " %s guid %d ----- FAILED TO RELEASE ----- \n %s " % (
+                        self._rtype, self.guid, err)
+                logger = Logger(self._rtype)
+                logger.debug(msg)
+
+    def fail(self):
+        """ Sets the RM to state FAILED.
 
-        self._stop_time = tnow()
-        self._state = ResourceState.STOPPED
+        This method should not be overridden directly. Specific functionality
+        should be added in the do_fail method.
+
+        """
+        with self._release_lock:
+            if self._state != ResourceState.RELEASED:
+                self.do_fail()
 
     def set(self, name, value):
         """ Set the value of the attribute
@@ -365,6 +634,7 @@ class ResourceManager(Logger):
         """
         attr = self._attrs[name]
         attr.value = value
+        return value
 
     def get(self, name):
         """ Returns the value of the attribute
@@ -374,8 +644,43 @@ class ResourceManager(Logger):
         :rtype: str
         """
         attr = self._attrs[name]
+
+        """
+        A.Q. Commenting due to performance impact
+        if attr.has_flag(Flags.Global):
+            self.warning( "Attribute %s is global. Use get_global instead." % name)
+        """
+            
         return attr.value
 
+    def has_changed(self, name):
+        """ Returns the True is the value of the attribute
+            has been modified by the user.
+
+        :param name: Name of the attribute
+        :type name: str
+        :rtype: str
+        """
+        attr = self._attrs[name]
+        return attr.has_changed
+
+    def has_flag(self, name, flag):
+        """ Returns true if the attribute has the flag 'flag'
+
+        :param flag: Flag to be checked
+        :type flag: Flags
+        """
+        attr = self._attrs[name]
+        return attr.has_flag(flag)
+
+    def has_attribute(self, name):
+        """ Returns true if the RM has an attribute with name
+
+        :param name: name of the attribute
+        :type name: string
+        """
+        return name in self._attrs
+
     def enable_trace(self, name):
         """ Explicitly enable trace generation
 
@@ -426,7 +731,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 +753,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)
@@ -460,7 +765,7 @@ class ResourceManager(Logger):
         if not isinstance(group, list):
             group = [group]
 
-        for act, conditions in self.conditions.iteritems():
+        for act, conditions in self.conditions.items():
             if action and act != action:
                 continue
 
@@ -491,13 +796,26 @@ class ResourceManager(Logger):
                 connected.append(rm)
         return connected
 
+    def is_rm_instance(self, rtype):
+        """ Returns True if the RM is instance of 'rtype'
+
+        :param rtype: Type of the RM we look for
+        :type rtype: str
+        :return: True|False
+        """
+        rclass = ResourceFactory.get_resource_type(rtype)
+        if isinstance(self, rclass):
+            return True
+        return False
+
+    @failtrap
     def _needs_reschedule(self, group, state, time):
         """ Internal method that verify if 'time' has elapsed since 
         all elements in 'group' have reached state 'state'.
 
         :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
@@ -508,11 +826,20 @@ class ResourceManager(Logger):
 
         """
         reschedule = False
-        delay = reschedule_delay 
+        delay = self.reschedule_delay 
 
         # check state and time elapsed on all RMs
         for guid in group:
             rm = self.ec.get_resource(guid)
+            
+            # If one of the RMs this resource needs to wait for has FAILED
+            # and is critical we raise an exception
+            if rm.state == ResourceState.FAILED:
+                if not rm.get('critical'):
+                    continue
+                msg = "Resource can not wait for FAILED RM %d. Setting Resource to FAILED"
+                raise RuntimeError(msg)
+
             # If the RM state is lower than the requested state we must
             # reschedule (e.g. if RM is READY but we required STARTED).
             if rm.state < state:
@@ -524,7 +851,9 @@ class ResourceManager(Logger):
             if time:
                 if state == ResourceState.DISCOVERED:
                     t = rm.discover_time
-                if state == ResourceState.PROVISIONED:
+                elif state == ResourceState.RESERVED:
+                    t = rm.reserved_time
+                elif state == ResourceState.PROVISIONED:
                     t = rm.provision_time
                 elif state == ResourceState.READY:
                     t = rm.ready_time
@@ -532,8 +861,9 @@ class ResourceManager(Logger):
                     t = rm.start_time
                 elif state == ResourceState.STOPPED:
                     t = rm.stop_time
+                elif state == ResourceState.RELEASED:
+                    t = rm.release_time
                 else:
-                    # Only keep time information for START and STOP
                     break
 
                 # time already elapsed since RM changed state
@@ -567,7 +897,7 @@ class ResourceManager(Logger):
         """
 
         reschedule = False
-        delay = reschedule_delay 
+        delay = self.reschedule_delay 
 
         ## evaluate if set conditions are met
 
@@ -589,12 +919,17 @@ class ResourceManager(Logger):
         action 'START' are satisfied.
 
         """
+        #import pdb;pdb.set_trace()
+
         reschedule = False
-        delay = reschedule_delay 
+        delay = self.reschedule_delay 
 
-        ## evaluate if set conditions are met
 
-        # only can start when RM is either STOPPED or READY
+        ## evaluate if conditions to start are met
+        if self.ec.abort:
+            return 
+
+        # Can only start when RM is either STOPPED or READY
         if self.state not in [ResourceState.STOPPED, ResourceState.READY]:
             reschedule = True
             self.debug("---- RESCHEDULING START ---- state %s " % self.state )
@@ -629,13 +964,16 @@ class ResourceManager(Logger):
 
         """
         reschedule = False
-        delay = reschedule_delay 
+        delay = self.reschedule_delay 
 
-        ## evaluate if set conditions are met
+        ## evaluate if conditions to stop are met
+        if self.ec.abort:
+            return 
 
         # only can stop when RM is STARTED
         if self.state != ResourceState.STARTED:
             reschedule = True
+            self.debug("---- RESCHEDULING STOP ---- state %s " % self.state )
         else:
             self.debug(" ---- STOP CONDITIONS ---- %s" % 
                     self.conditions.get(ResourceAction.STOP))
@@ -653,46 +991,56 @@ class ResourceManager(Logger):
             self.debug(" ----- STOPPING ---- ") 
             self.stop()
 
-    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._ready_time = tnow()
-        self._state = ResourceState.READY
-
-    def release(self):
-        """Release any resources used by this RM
+    def deploy_with_conditions(self):
+        """ Deploy RM when all the conditions in self.conditions for
+        action 'READY' are satisfied.
 
         """
-        self._release_time = tnow()
-        self._state = ResourceState.RELEASED
+        reschedule = False
+        delay = self.reschedule_delay 
 
-    def finish(self):
-        """ Mark ResourceManager as FINISHED
+        ## evaluate if conditions to deploy are met
+        if self.ec.abort:
+            return 
 
-        """
-        self._finish_time = tnow()
-        self._state = ResourceState.FINISHED
+        # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED 
+        if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED,
+                ResourceState.RESERVED, ResourceState.PROVISIONED]:
+            #### XXX: A.Q. IT SHOULD FAIL IF DEPLOY IS CALLED IN OTHER STATES!
+            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 )
 
-    def fail(self):
-        """ Mark ResourceManager as FAILED
+                reschedule, delay = self._needs_reschedule(group, state, time)
+                if reschedule:
+                    break
 
-        """
-        self._failed_time = tnow()
-        self._state = ResourceState.FAILED
+        if reschedule:
+            self.ec.schedule(delay, self.deploy_with_conditions)
+        else:
+            self.debug("----- DEPLOYING ---- ")
+            self.deploy()
 
-    def connect(self, guid):
+    def do_connect(self, guid):
         """ Performs actions that need to be taken upon associating RMs.
         This method should be redefined when necessary in child classes.
         """
         pass
 
-    def disconnect(self, guid):
+    def do_disconnect(self, guid):
         """ Performs actions that need to be taken upon disassociating RMs.
         This method should be redefined when necessary in child classes.
         """
@@ -711,6 +1059,95 @@ class ResourceManager(Logger):
         # TODO: Validate!
         return True
 
+    def do_discover(self):
+        self.set_discovered()
+
+    def do_reserve(self):
+        self.set_reserved()
+
+    def do_provision(self):
+        self.set_provisioned()
+
+    def do_configure(self):
+        pass
+
+    def do_start(self):
+        self.set_started()
+
+    def do_stop(self):
+        self.set_stopped()
+
+    def do_deploy(self):
+        self.set_ready()
+
+    def do_release(self):
+        self.set_released()
+
+    def do_fail(self):
+        self.set_failed()
+        self.ec.inform_failure(self.guid)
+
+    def set_started(self, time = None):
+        """ Mark ResourceManager as STARTED """
+        self.set_state(ResourceState.STARTED, "_start_time", time)
+        self.debug("----- STARTED ---- ")
+
+    def set_stopped(self, time = None):
+        """ Mark ResourceManager as STOPPED """
+        self.set_state(ResourceState.STOPPED, "_stop_time", time)
+        self.debug("----- STOPPED ---- ")
+
+    def set_ready(self, time = None):
+        """ Mark ResourceManager as READY """
+        self.set_state(ResourceState.READY, "_ready_time", time)
+        self.debug("----- READY ---- ")
+
+    def set_released(self, time = None):
+        """ Mark ResourceManager as REALEASED """
+        self.set_state(ResourceState.RELEASED, "_release_time", time)
+
+        msg = " %s guid %d ----- RELEASED ----- " % (self._rtype, self.guid)
+        logger = Logger(self._rtype)
+        logger.debug(msg)
+
+    def set_failed(self, time = None):
+        """ Mark ResourceManager as FAILED """
+        self.set_state(ResourceState.FAILED, "_failed_time", time)
+
+        msg = " %s guid %d ----- FAILED ----- " % (self._rtype, self.guid)
+        logger = Logger(self._rtype)
+        logger.debug(msg)
+
+    def set_discovered(self, time = None):
+        """ Mark ResourceManager as DISCOVERED """
+        self.set_state(ResourceState.DISCOVERED, "_discover_time", time)
+        self.debug("----- DISCOVERED ---- ")
+
+    def set_reserved(self, time = None):
+        """ Mark ResourceManager as RESERVED """
+        self.set_state(ResourceState.RESERVED, "_reserved_time", time)
+        self.debug("----- RESERVED ---- ")
+
+    def set_provisioned(self, time = None):
+        """ Mark ResourceManager as PROVISIONED """
+        self.set_state(ResourceState.PROVISIONED, "_provision_time", time)
+        self.debug("----- PROVISIONED ---- ")
+
+    def set_state(self, state, state_time_attr, time = None):
+        """ Set the state of the RM while keeping a trace of the time """
+
+        # Ensure that RM state will not change after released
+        if self._state == ResourceState.RELEASED:
+            return 
+
+        time = time or tnow()
+        self.set_state_time(state, state_time_attr, time)
+  
+    def set_state_time(self, state, state_time_attr, time):
+        """ Set the time for the RM state change """
+        setattr(self, state_time_attr, time)
+        self._state = state
+
 class ResourceFactory(object):
     _resource_types = dict()
 
@@ -727,7 +1164,7 @@ class ResourceFactory(object):
     @classmethod
     def register_type(cls, rclass):
         """Register a new Ressource Manager"""
-        cls._resource_types[rclass.rtype()] = rclass
+        cls._resource_types[rclass.get_rtype()] = rclass
 
     @classmethod
     def create(cls, rtype, ec, guid):
@@ -736,7 +1173,7 @@ class ResourceFactory(object):
         return rclass(ec, guid)
 
 def populate_factory():
-    """Register all the possible RM that exists in the current version of Nepi.
+    """Find and rgister all available RMs
     """
     # Once the factory is populated, don't repopulate
     if not ResourceFactory.resource_types():
@@ -755,7 +1192,7 @@ def find_types():
     path = os.path.dirname(nepi.resources.__file__)
     search_path.add(path)
 
-    types = []
+    types = set()
 
     for importer, modname, ispkg in pkgutil.walk_packages(search_path, 
             prefix = "nepi.resources."):
@@ -763,8 +1200,11 @@ def find_types():
         loader = importer.find_module(modname)
         
         try:
-            # Notice: Repeated calls to load_module will act as a reload of teh module
-            module = loader.load_module(modname)
+            # Notice: Repeated calls to load_module will act as a reload of the module
+            if modname in sys.modules:
+                module = sys.modules.get(modname)
+            else:
+                module = loader.load_module(modname)
 
             for attrname in dir(module):
                 if attrname.startswith("_"):
@@ -779,14 +1219,17 @@ def find_types():
                     continue
 
                 if issubclass(attr, ResourceManager):
-                    types.append(attr)
+                    types.add(attr)
+
+                    if not modname in sys.modules:
+                        sys.modules[modname] = module
+
         except:
             import traceback
             import logging
             err = traceback.format_exc()
             logger = logging.getLogger("Resource.find_types()")
-            logger.error("Error while lading Resource Managers %s" % err)
+            logger.error("Error while loading Resource Managers %s" % err)
 
     return types
 
-