2 # NEPI, a framework to manage network experiments
3 # Copyright (C) 2013 INRIA
5 # This program is free software: you can redistribute it and/or modify
6 # it under the terms of the GNU General Public License as published by
7 # the Free Software Foundation, either version 3 of the License, or
8 # (at your option) any later version.
10 # This program is distributed in the hope that it will be useful,
11 # but WITHOUT ANY WARRANTY; without even the implied warranty of
12 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 # GNU General Public License for more details.
15 # You should have received a copy of the GNU General Public License
16 # along with this program. If not, see <http://www.gnu.org/licenses/>.
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
20 from nepi.util.timefuncs import tnow, tdiff, tdiffsec, stabsformat
21 from nepi.util.logger import Logger
22 from nepi.execution.attribute import Attribute, Flags, Types
23 from nepi.execution.trace import TraceAttr
34 reschedule_delay = "1s"
37 """ Action that a user can order to a Resource Manager
45 """ State of a Resource Manager
57 ResourceState2str = dict({
58 ResourceState.NEW : "NEW",
59 ResourceState.DISCOVERED : "DISCOVERED",
60 ResourceState.PROVISIONED : "PROVISIONED",
61 ResourceState.READY : "READY",
62 ResourceState.STARTED : "STARTED",
63 ResourceState.STOPPED : "STOPPED",
64 ResourceState.FAILED : "FAILED",
65 ResourceState.RELEASED : "RELEASED",
69 """ Initializes template information (i.e. attributes and traces)
70 on classes derived from the ResourceManager class.
72 It is used as a decorator in the class declaration as follows:
75 class MyResourceManager(ResourceManager):
84 def clsinit_copy(cls):
85 """ Initializes template information (i.e. attributes and traces)
86 on classes direved from the ResourceManager class.
87 It differs from the clsinit method in that it forces inheritance
88 of attributes and traces from the parent class.
90 It is used as a decorator in the class declaration as follows:
93 class MyResourceManager(ResourceManager):
98 clsinit_copy should be prefered to clsinit when creating new
99 ResourceManager child classes.
107 """ Decorator function for instance methods that should set the
108 RM state to FAILED when an error is raised. The methods that must be
109 decorated are: discover, provision, deploy, start, stop.
112 def wrapped(self, *args, **kwargs):
114 return func(self, *args, **kwargs)
117 err = traceback.format_exc()
119 self.debug("SETTING guid %d to state FAILED" % self.guid)
126 class ResourceManager(Logger):
127 """ Base clase for all ResourceManagers.
129 A ResourceManger is specific to a resource type (e.g. Node,
130 Switch, Application, etc) on a specific backend (e.g. PlanetLab,
133 The ResourceManager instances are responsible for interacting with
134 and controlling concrete (physical or virtual) resources in the
135 experimental backends.
145 def _register_attribute(cls, attr):
146 """ Resource subclasses will invoke this method to add a
151 cls._attributes[attr.name] = attr
154 def _remove_attribute(cls, name):
155 """ Resource subclasses will invoke this method to remove a
160 del cls._attributes[name]
163 def _register_trace(cls, trace):
164 """ Resource subclasses will invoke this method to add a
169 cls._traces[trace.name] = trace
172 def _remove_trace(cls, name):
173 """ Resource subclasses will invoke this method to remove a
178 del cls._traces[name]
181 def _register_attributes(cls):
182 """ Resource subclasses will invoke this method to register
185 This method should be overriden in the RMs that define
189 critical = Attribute("critical",
190 "Defines whether the resource is critical. "
191 "A failure on a critical resource will interrupt "
195 flags = Flags.Design)
196 hard_release = Attribute("hardRelease",
197 "Forces removal of all result files and directories associated "
198 "to the RM upon resource release. After release the RM will "
199 "be removed from the EC and the results will not longer be "
203 flags = Flags.Design)
205 cls._register_attribute(critical)
206 cls._register_attribute(hard_release)
209 def _register_traces(cls):
210 """ Resource subclasses will invoke this method to register
213 This method should be overriden in the RMs that define traces.
221 """ ResourceManager classes have different attributes and traces.
222 Attribute and traces are stored in 'class attribute' dictionaries.
223 When a new ResourceManager class is created, the _clsinit method is
224 called to create a new instance of those dictionaries and initialize
227 The _clsinit method is called by the clsinit decorator method.
231 # static template for resource attributes
232 cls._attributes = dict()
233 cls._register_attributes()
235 # static template for resource traces
237 cls._register_traces()
240 def _clsinit_copy(cls):
241 """ Same as _clsinit, except that after creating new instances of the
242 dictionaries it copies all the attributes and traces from the parent
245 The _clsinit_copy method is called by the clsinit_copy decorator method.
248 # static template for resource attributes
249 cls._attributes = copy.deepcopy(cls._attributes)
250 cls._register_attributes()
252 # static template for resource traces
253 cls._traces = copy.deepcopy(cls._traces)
254 cls._register_traces()
258 """ Returns the type of the Resource Manager
264 def get_attributes(cls):
265 """ Returns a copy of the attributes
268 return copy.deepcopy(cls._attributes.values())
271 def get_attribute(cls, name):
272 """ Returns a copy of the attribute with name 'name'
275 return copy.deepcopy(cls._attributes[name])
279 """ Returns a copy of the traces
282 return copy.deepcopy(cls._traces.values())
286 """ Returns the description of the type of Resource
292 def get_backend(cls):
293 """ Returns the identified of the backend (i.e. testbed, environment)
300 def get_global(cls, name):
301 """ Returns the value of a global attribute
302 Global attribute meaning an attribute for
303 all the resources from a rtype
305 :param name: Name of the attribute
309 global_attr = cls._attributes[name]
310 return global_attr.value
313 def set_global(cls, name, value):
314 """ Set value for a global attribute
316 :param name: Name of the attribute
318 :param name: Value of the attribute
321 global_attr = cls._attributes[name]
322 global_attr.value = value
325 def __init__(self, ec, guid):
326 super(ResourceManager, self).__init__(self.get_rtype())
329 self._ec = weakref.ref(ec)
330 self._connections = set()
331 self._conditions = dict()
333 # the resource instance gets a copy of all attributes
334 self._attrs = copy.deepcopy(self._attributes)
336 # the resource instance gets a copy of all traces
337 self._trcs = copy.deepcopy(self._traces)
339 # Each resource is placed on a deployment group by the EC
341 self.deployment_group = None
343 self._start_time = None
344 self._stop_time = None
345 self._discover_time = None
346 self._provision_time = None
347 self._ready_time = None
348 self._release_time = None
349 self._failed_time = None
351 self._state = ResourceState.NEW
353 # instance lock to synchronize exclusive state change methods (such
354 # as deploy and release methods), in order to prevent them from being
355 # executed at the same time and corrupt internal resource state
356 self._release_lock = threading.Lock()
360 """ Returns the global unique identifier of the RM """
365 """ Returns the Experiment Controller of the RM """
369 def connections(self):
370 """ Returns the set of guids of connected RMs """
371 return self._connections
374 def conditions(self):
375 """ Returns the conditions to which the RM is subjected to.
377 This method returns a dictionary of conditions lists indexed by
381 return self._conditions
384 def start_time(self):
385 """ Returns the start time of the RM as a timestamp """
386 return self._start_time
390 """ Returns the stop time of the RM as a timestamp """
391 return self._stop_time
394 def discover_time(self):
395 """ Returns the discover time of the RM as a timestamp """
396 return self._discover_time
399 def provision_time(self):
400 """ Returns the provision time of the RM as a timestamp """
401 return self._provision_time
404 def ready_time(self):
405 """ Returns the deployment time of the RM as a timestamp """
406 return self._ready_time
409 def release_time(self):
410 """ Returns the release time of the RM as a timestamp """
411 return self._release_time
414 def failed_time(self):
415 """ Returns the time failure occured for the RM as a timestamp """
416 return self._failed_time
420 """ Get the current state of the RM """
423 def log_message(self, msg):
424 """ Returns the log message formatted with added information.
426 :param msg: text message
431 return " %s guid %d - %s " % (self._rtype, self.guid, msg)
433 def register_connection(self, guid):
434 """ Registers a connection to the RM identified by guid
436 This method should not be overriden. Specific functionality
437 should be added in the do_connect method.
439 :param guid: Global unique identified of the RM to connect to
443 if self.valid_connection(guid):
444 self.do_connect(guid)
445 self._connections.add(guid)
447 def unregister_connection(self, guid):
448 """ Removes a registered connection to the RM identified by guid
450 This method should not be overriden. Specific functionality
451 should be added in the do_disconnect method.
453 :param guid: Global unique identified of the RM to connect to
457 if guid in self._connections:
458 self.do_disconnect(guid)
459 self._connections.remove(guid)
463 """ Performs resource discovery.
465 This method is responsible for selecting an individual resource
466 matching user requirements.
468 This method should not be overriden directly. Specific functionality
469 should be added in the do_discover method.
472 with self._release_lock:
473 if self._state != ResourceState.RELEASED:
478 """ Performs resource provisioning.
480 This method is responsible for provisioning one resource.
481 After this method has been successfully invoked, the resource
482 should be accessible/controllable by the RM.
484 This method should not be overriden directly. Specific functionality
485 should be added in the do_provision method.
488 with self._release_lock:
489 if self._state != ResourceState.RELEASED:
494 """ Starts the RM (e.g. launch remote process).
496 There is no standard start behavior. Some RMs will not need to perform
497 any actions upon start.
499 This method should not be overriden directly. Specific functionality
500 should be added in the do_start method.
504 if not self.state in [ResourceState.READY, ResourceState.STOPPED]:
505 self.error("Wrong state %s for start" % self.state)
508 with self._release_lock:
509 if self._state != ResourceState.RELEASED:
514 """ Interrupts the RM, stopping any tasks the RM was performing.
516 There is no standard stop behavior. Some RMs will not need to perform
517 any actions upon stop.
519 This method should not be overriden directly. Specific functionality
520 should be added in the do_stop method.
523 if not self.state in [ResourceState.STARTED]:
524 self.error("Wrong state %s for stop" % self.state)
527 with self._release_lock:
532 """ Execute all steps required for the RM to reach the state READY.
534 This method is responsible for deploying the resource (and invoking
535 the discover and provision methods).
537 This method should not be overriden directly. Specific functionality
538 should be added in the do_deploy method.
541 if self.state > ResourceState.READY:
542 self.error("Wrong state %s for deploy" % self.state)
545 with self._release_lock:
546 if self._state != ResourceState.RELEASED:
550 """ Perform actions to free resources used by the RM.
552 This method is responsible for releasing resources that were
553 used during the experiment by the RM.
555 This method should not be overriden directly. Specific functionality
556 should be added in the do_release method.
559 with self._release_lock:
564 err = traceback.format_exc()
570 """ Sets the RM to state FAILED.
572 This method should not be overriden directly. Specific functionality
573 should be added in the do_fail method.
576 with self._release_lock:
577 if self._state != ResourceState.RELEASED:
580 def set(self, name, value):
581 """ Set the value of the attribute
583 :param name: Name of the attribute
585 :param name: Value of the attribute
588 attr = self._attrs[name]
593 """ Returns the value of the attribute
595 :param name: Name of the attribute
599 attr = self._attrs[name]
602 A.Q. Commenting due to performance impact
603 if attr.has_flag(Flags.Global):
604 self.warning( "Attribute %s is global. Use get_global instead." % name)
609 def has_changed(self, name):
610 """ Returns the True is the value of the attribute
611 has been modified by the user.
613 :param name: Name of the attribute
617 attr = self._attrs[name]
618 return attr.has_changed()
620 def has_flag(self, name, flag):
621 """ Returns true if the attribute has the flag 'flag'
623 :param flag: Flag to be checked
626 attr = self._attrs[name]
627 return attr.has_flag(flag)
629 def has_attribute(self, name):
630 """ Returns true if the RM has an attribute with name
632 :param name: name of the attribute
635 return name in self._attrs
637 def enable_trace(self, name):
638 """ Explicitly enable trace generation
640 :param name: Name of the trace
643 trace = self._trcs[name]
646 def trace_enabled(self, name):
647 """Returns True if trace is enables
649 :param name: Name of the trace
652 trace = self._trcs[name]
655 def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
656 """ Get information on collected trace
658 :param name: Name of the trace
661 :param attr: Can be one of:
662 - TraceAttr.ALL (complete trace content),
663 - TraceAttr.STREAM (block in bytes to read starting at offset),
664 - TraceAttr.PATH (full path to the trace file),
665 - TraceAttr.SIZE (size of trace file).
668 :param block: Number of bytes to retrieve from trace, when attr is TraceAttr.STREAM
671 :param offset: Number of 'blocks' to skip, when attr is TraceAttr.STREAM
678 def register_condition(self, action, group, state, time = None):
679 """ Registers a condition on the resource manager to allow execution
680 of 'action' only after 'time' has elapsed from the moment all resources
681 in 'group' reached state 'state'
683 :param action: Action to restrict to condition (either 'START' or 'STOP')
685 :param group: Group of RMs to wait for (list of guids)
686 :type group: int or list of int
687 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
689 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
694 if not action in self.conditions:
695 self._conditions[action] = list()
697 conditions = self.conditions.get(action)
699 # For each condition to register a tuple of (group, state, time) is
700 # added to the 'action' list
701 if not isinstance(group, list):
704 conditions.append((group, state, time))
706 def unregister_condition(self, group, action = None):
707 """ Removed conditions for a certain group of guids
709 :param action: Action to restrict to condition (either 'START', 'STOP' or 'READY')
712 :param group: Group of RMs to wait for (list of guids)
713 :type group: int or list of int
716 # For each condition a tuple of (group, state, time) is
717 # added to the 'action' list
718 if not isinstance(group, list):
721 for act, conditions in self.conditions.iteritems():
722 if action and act != action:
725 for condition in list(conditions):
726 (grp, state, time) = condition
728 # If there is an intersection between grp and group,
729 # then remove intersected elements
730 intsec = set(group).intersection(set(grp))
732 idx = conditions.index(condition)
734 newgrp.difference_update(intsec)
735 conditions[idx] = (newgrp, state, time)
737 def get_connected(self, rtype = None):
738 """ Returns the list of RM with the type 'rtype'
740 :param rtype: Type of the RM we look for
742 :return: list of guid
745 rclass = ResourceFactory.get_resource_type(rtype)
746 for guid in self.connections:
747 rm = self.ec.get_resource(guid)
748 if not rtype or isinstance(rm, rclass):
752 def is_rm_instance(self, rtype):
753 """ Returns True if the RM is instance of 'rtype'
755 :param rtype: Type of the RM we look for
759 rclass = ResourceFactory.get_resource_type(rtype)
760 if isinstance(self, rclass):
765 def _needs_reschedule(self, group, state, time):
766 """ Internal method that verify if 'time' has elapsed since
767 all elements in 'group' have reached state 'state'.
769 :param group: Group of RMs to wait for (list of guids)
770 :type group: int or list of int
771 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
773 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
776 .. note : time should be written like "2s" or "3m" with s for seconds, m for minutes, h for hours, ...
777 If for example, you need to wait 2min 30sec, time could be "150s" or "2.5m".
778 For the moment, 2m30s is not a correct syntax.
782 delay = reschedule_delay
784 # check state and time elapsed on all RMs
786 rm = self.ec.get_resource(guid)
788 # If one of the RMs this resource needs to wait for has FAILED
789 # and is critical we raise an exception
790 if rm.state == ResourceState.FAILED:
791 if not rm.get('critical'):
793 msg = "Resource can not wait for FAILED RM %d. Setting Resource to FAILED"
794 raise RuntimeError, msg
796 # If the RM state is lower than the requested state we must
797 # reschedule (e.g. if RM is READY but we required STARTED).
802 # If there is a time restriction, we must verify the
803 # restriction is satisfied
805 if state == ResourceState.DISCOVERED:
807 if state == ResourceState.PROVISIONED:
808 t = rm.provision_time
809 elif state == ResourceState.READY:
811 elif state == ResourceState.STARTED:
813 elif state == ResourceState.STOPPED:
815 elif state == ResourceState.RELEASED:
820 # time already elapsed since RM changed state
821 waited = "%fs" % tdiffsec(tnow(), t)
824 wait = tdiffsec(stabsformat(time), stabsformat(waited))
831 return reschedule, delay
833 def set_with_conditions(self, name, value, group, state, time):
834 """ Set value 'value' on attribute with name 'name' when 'time'
835 has elapsed since all elements in 'group' have reached state
838 :param name: Name of the attribute to set
840 :param name: Value of the attribute to set
842 :param group: Group of RMs to wait for (list of guids)
843 :type group: int or list of int
844 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
846 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
851 delay = reschedule_delay
853 ## evaluate if set conditions are met
855 # only can set with conditions after the RM is started
856 if self.state != ResourceState.STARTED:
859 reschedule, delay = self._needs_reschedule(group, state, time)
862 callback = functools.partial(self.set_with_conditions,
863 name, value, group, state, time)
864 self.ec.schedule(delay, callback)
866 self.set(name, value)
868 def start_with_conditions(self):
869 """ Starts RM when all the conditions in self.conditions for
870 action 'START' are satisfied.
873 #import pdb;pdb.set_trace()
876 delay = reschedule_delay
879 ## evaluate if conditions to start are met
883 # Can only start when RM is either STOPPED or READY
884 if self.state not in [ResourceState.STOPPED, ResourceState.READY]:
886 self.debug("---- RESCHEDULING START ---- state %s " % self.state )
888 start_conditions = self.conditions.get(ResourceAction.START, [])
890 self.debug("---- START CONDITIONS ---- %s" % start_conditions)
892 # Verify all start conditions are met
893 for (group, state, time) in start_conditions:
894 # Uncomment for debug
897 # rm = self.ec.get_resource(guid)
898 # unmet.append((guid, rm._state))
900 #self.debug("---- WAITED STATES ---- %s" % unmet )
902 reschedule, delay = self._needs_reschedule(group, state, time)
907 self.ec.schedule(delay, self.start_with_conditions)
909 self.debug("----- STARTING ---- ")
912 def stop_with_conditions(self):
913 """ Stops RM when all the conditions in self.conditions for
914 action 'STOP' are satisfied.
918 delay = reschedule_delay
920 ## evaluate if conditions to stop are met
924 # only can stop when RM is STARTED
925 if self.state != ResourceState.STARTED:
927 self.debug("---- RESCHEDULING STOP ---- state %s " % self.state )
929 self.debug(" ---- STOP CONDITIONS ---- %s" %
930 self.conditions.get(ResourceAction.STOP))
932 stop_conditions = self.conditions.get(ResourceAction.STOP, [])
933 for (group, state, time) in stop_conditions:
934 reschedule, delay = self._needs_reschedule(group, state, time)
939 callback = functools.partial(self.stop_with_conditions)
940 self.ec.schedule(delay, callback)
942 self.debug(" ----- STOPPING ---- ")
945 def deploy_with_conditions(self):
946 """ Deploy RM when all the conditions in self.conditions for
947 action 'READY' are satisfied.
951 delay = reschedule_delay
953 ## evaluate if conditions to deploy are met
957 # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED
958 if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED,
959 ResourceState.PROVISIONED]:
960 #### XXX: A.Q. IT SHOULD FAIL IF DEPLOY IS CALLED IN OTHER STATES!
962 self.debug("---- RESCHEDULING DEPLOY ---- state %s " % self.state )
964 deploy_conditions = self.conditions.get(ResourceAction.DEPLOY, [])
966 self.debug("---- DEPLOY CONDITIONS ---- %s" % deploy_conditions)
968 # Verify all start conditions are met
969 for (group, state, time) in deploy_conditions:
970 # Uncomment for debug
973 # rm = self.ec.get_resource(guid)
974 # unmet.append((guid, rm._state))
976 #self.debug("---- WAITED STATES ---- %s" % unmet )
978 reschedule, delay = self._needs_reschedule(group, state, time)
983 self.ec.schedule(delay, self.deploy_with_conditions)
985 self.debug("----- DEPLOYING ---- ")
988 def do_connect(self, guid):
989 """ Performs actions that need to be taken upon associating RMs.
990 This method should be redefined when necessary in child classes.
994 def do_disconnect(self, guid):
995 """ Performs actions that need to be taken upon disassociating RMs.
996 This method should be redefined when necessary in child classes.
1000 def valid_connection(self, guid):
1001 """Checks whether a connection with the other RM
1003 This method need to be redefined by each new Resource Manager.
1005 :param guid: Guid of the current Resource Manager
1013 def do_discover(self):
1014 self.set_discovered()
1016 def do_provision(self):
1017 self.set_provisioned()
1025 def do_deploy(self):
1028 def do_release(self):
1033 self.ec.inform_failure(self.guid)
1035 def set_started(self, time = None):
1036 """ Mark ResourceManager as STARTED """
1037 self.set_state(ResourceState.STARTED, "_start_time", time)
1038 self.debug("----- STARTED ---- ")
1040 def set_stopped(self, time = None):
1041 """ Mark ResourceManager as STOPPED """
1042 self.set_state(ResourceState.STOPPED, "_stop_time", time)
1043 self.debug("----- STOPPED ---- ")
1045 def set_ready(self, time = None):
1046 """ Mark ResourceManager as READY """
1047 self.set_state(ResourceState.READY, "_ready_time", time)
1048 self.debug("----- READY ---- ")
1050 def set_released(self, time = None):
1051 """ Mark ResourceManager as REALEASED """
1052 self.set_state(ResourceState.RELEASED, "_release_time", time)
1053 self.debug("----- RELEASED ---- ")
1055 def set_failed(self, time = None):
1056 """ Mark ResourceManager as FAILED """
1057 self.set_state(ResourceState.FAILED, "_failed_time", time)
1058 self.debug("----- FAILED ---- ")
1060 def set_discovered(self, time = None):
1061 """ Mark ResourceManager as DISCOVERED """
1062 self.set_state(ResourceState.DISCOVERED, "_discover_time", time)
1063 self.debug("----- DISCOVERED ---- ")
1065 def set_provisioned(self, time = None):
1066 """ Mark ResourceManager as PROVISIONED """
1067 self.set_state(ResourceState.PROVISIONED, "_provision_time", time)
1068 self.debug("----- PROVISIONED ---- ")
1070 def set_state(self, state, state_time_attr, time = None):
1071 """ Set the state of the RM while keeping a trace of the time """
1073 # Ensure that RM state will not change after released
1074 if self._state == ResourceState.RELEASED:
1077 time = time or tnow()
1078 self.set_state_time(state, state_time_attr, time)
1080 def set_state_time(self, state, state_time_attr, time):
1081 """ Set the time for the RM state change """
1082 setattr(self, state_time_attr, time)
1085 class ResourceFactory(object):
1086 _resource_types = dict()
1089 def resource_types(cls):
1090 """Return the type of the Class"""
1091 return cls._resource_types
1094 def get_resource_type(cls, rtype):
1095 """Return the type of the Class"""
1096 return cls._resource_types.get(rtype)
1099 def register_type(cls, rclass):
1100 """Register a new Ressource Manager"""
1101 cls._resource_types[rclass.get_rtype()] = rclass
1104 def create(cls, rtype, ec, guid):
1105 """Create a new instance of a Ressource Manager"""
1106 rclass = cls._resource_types[rtype]
1107 return rclass(ec, guid)
1109 def populate_factory():
1110 """Find and rgister all available RMs
1112 # Once the factory is populated, don't repopulate
1113 if not ResourceFactory.resource_types():
1114 for rclass in find_types():
1115 ResourceFactory.register_type(rclass)
1118 """Look into the different folders to find all the
1119 availables Resources Managers
1121 search_path = os.environ.get("NEPI_SEARCH_PATH", "")
1122 search_path = set(search_path.split(" "))
1125 import nepi.resources
1126 path = os.path.dirname(nepi.resources.__file__)
1127 search_path.add(path)
1131 for importer, modname, ispkg in pkgutil.walk_packages(search_path,
1132 prefix = "nepi.resources."):
1134 loader = importer.find_module(modname)
1137 # Notice: Repeated calls to load_module will act as a reload of the module
1138 if modname in sys.modules:
1139 module = sys.modules.get(modname)
1141 module = loader.load_module(modname)
1143 for attrname in dir(module):
1144 if attrname.startswith("_"):
1147 attr = getattr(module, attrname)
1149 if attr == ResourceManager:
1152 if not inspect.isclass(attr):
1155 if issubclass(attr, ResourceManager):
1158 if not modname in sys.modules:
1159 sys.modules[modname] = module
1164 err = traceback.format_exc()
1165 logger = logging.getLogger("Resource.find_types()")
1166 logger.error("Error while loading Resource Managers %s" % err)