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])
280 """ Returns a copy of the traces
283 return copy.deepcopy(cls._traces.values())
287 """ Returns the description of the type of Resource
293 def get_backend(cls):
294 """ Returns the identified of the backend (i.e. testbed, environment)
301 def get_global(cls, name):
302 """ Returns the value of a global attribute
303 Global attribute meaning an attribute for
304 all the resources from a rtype
306 :param name: Name of the attribute
310 global_attr = cls._attributes[name]
311 return global_attr.value
314 def set_global(cls, name, value):
315 """ Set value for a global attribute
317 :param name: Name of the attribute
319 :param name: Value of the attribute
322 global_attr = cls._attributes[name]
323 global_attr.value = value
326 def __init__(self, ec, guid):
327 super(ResourceManager, self).__init__(self.get_rtype())
330 self._ec = weakref.ref(ec)
331 self._connections = set()
332 self._conditions = dict()
334 # the resource instance gets a copy of all attributes
335 self._attrs = copy.deepcopy(self._attributes)
337 # the resource instance gets a copy of all traces
338 self._trcs = copy.deepcopy(self._traces)
340 # Each resource is placed on a deployment group by the EC
342 self.deployment_group = None
344 self._start_time = None
345 self._stop_time = None
346 self._discover_time = None
347 self._provision_time = None
348 self._ready_time = None
349 self._release_time = None
350 self._failed_time = None
352 self._state = ResourceState.NEW
354 # instance lock to synchronize exclusive state change methods (such
355 # as deploy and release methods), in order to prevent them from being
356 # executed at the same time
357 self._release_lock = threading.Lock()
361 """ Returns the global unique identifier of the RM """
366 """ Returns the Experiment Controller of the RM """
370 def connections(self):
371 """ Returns the set of guids of connected RMs """
372 return self._connections
375 def conditions(self):
376 """ Returns the conditions to which the RM is subjected to.
378 This method returns a dictionary of conditions lists indexed by
382 return self._conditions
385 def start_time(self):
386 """ Returns the start time of the RM as a timestamp """
387 return self._start_time
391 """ Returns the stop time of the RM as a timestamp """
392 return self._stop_time
395 def discover_time(self):
396 """ Returns the discover time of the RM as a timestamp """
397 return self._discover_time
400 def provision_time(self):
401 """ Returns the provision time of the RM as a timestamp """
402 return self._provision_time
405 def ready_time(self):
406 """ Returns the deployment time of the RM as a timestamp """
407 return self._ready_time
410 def release_time(self):
411 """ Returns the release time of the RM as a timestamp """
412 return self._release_time
415 def failed_time(self):
416 """ Returns the time failure occured for the RM as a timestamp """
417 return self._failed_time
421 """ Get the current state of the RM """
424 def log_message(self, msg):
425 """ Returns the log message formatted with added information.
427 :param msg: text message
432 return " %s guid %d - %s " % (self._rtype, self.guid, msg)
434 def register_connection(self, guid):
435 """ Registers a connection to the RM identified by guid
437 This method should not be overriden. Specific functionality
438 should be added in the do_connect method.
440 :param guid: Global unique identified of the RM to connect to
444 if self.valid_connection(guid):
445 self.do_connect(guid)
446 self._connections.add(guid)
448 def unregister_connection(self, guid):
449 """ Removes a registered connection to the RM identified by guid
451 This method should not be overriden. Specific functionality
452 should be added in the do_disconnect method.
454 :param guid: Global unique identified of the RM to connect to
458 if guid in self._connections:
459 self.do_disconnect(guid)
460 self._connections.remove(guid)
464 """ Performs resource discovery.
466 This method is responsible for selecting an individual resource
467 matching user requirements.
469 This method should not be overriden directly. Specific functionality
470 should be added in the do_discover method.
473 with self._release_lock:
474 if self._state != ResourceState.RELEASED:
479 """ Performs resource provisioning.
481 This method is responsible for provisioning one resource.
482 After this method has been successfully invoked, the resource
483 should be accessible/controllable by the RM.
485 This method should not be overriden directly. Specific functionality
486 should be added in the do_provision method.
489 with self._release_lock:
490 if self._state != ResourceState.RELEASED:
495 """ Starts the RM (e.g. launch remote process).
497 There is no standard start behavior. Some RMs will not need to perform
498 any actions upon start.
500 This method should not be overriden directly. Specific functionality
501 should be added in the do_start method.
505 if not self.state in [ResourceState.READY, ResourceState.STOPPED]:
506 self.error("Wrong state %s for start" % self.state)
509 with self._release_lock:
510 if self._state != ResourceState.RELEASED:
515 """ Interrupts the RM, stopping any tasks the RM was performing.
517 There is no standard stop behavior. Some RMs will not need to perform
518 any actions upon stop.
520 This method should not be overriden directly. Specific functionality
521 should be added in the do_stop method.
524 if not self.state in [ResourceState.STARTED]:
525 self.error("Wrong state %s for stop" % self.state)
528 with self._release_lock:
533 """ Execute all steps required for the RM to reach the state READY.
535 This method is responsible for deploying the resource (and invoking
536 the discover and provision methods).
538 This method should not be overriden directly. Specific functionality
539 should be added in the do_deploy method.
542 if self.state > ResourceState.READY:
543 self.error("Wrong state %s for deploy" % self.state)
546 with self._release_lock:
547 if self._state != ResourceState.RELEASED:
551 """ Perform actions to free resources used by the RM.
553 This method is responsible for releasing resources that were
554 used during the experiment by the RM.
556 This method should not be overriden directly. Specific functionality
557 should be added in the do_release method.
560 with self._release_lock:
565 err = traceback.format_exc()
571 """ Sets the RM to state FAILED.
573 This method should not be overriden directly. Specific functionality
574 should be added in the do_fail method.
577 with self._release_lock:
578 if self._state != ResourceState.RELEASED:
581 def set(self, name, value):
582 """ Set the value of the attribute
584 :param name: Name of the attribute
586 :param name: Value of the attribute
589 attr = self._attrs[name]
594 """ Returns the value of the attribute
596 :param name: Name of the attribute
600 attr = self._attrs[name]
601 if attr.has_flag(Flags.Global):
602 self.warning( "Attribute %s is global. Use get_global instead." % name)
606 def has_changed(self, name):
607 """ Returns the True is the value of the attribute
608 has been modified by the user.
610 :param name: Name of the attribute
614 attr = self._attrs[name]
615 return attr.has_changed()
617 def has_flag(self, name, flag):
618 """ Returns true if the attribute has the flag 'flag'
620 :param flag: Flag to be checked
623 attr = self._attrs[name]
624 return attr.has_flag(flag)
626 def has_attribute(self, name):
627 """ Returns true if the RM has an attribute with name
629 :param name: name of the attribute
632 return name in self._attrs
634 def enable_trace(self, name):
635 """ Explicitly enable trace generation
637 :param name: Name of the trace
640 trace = self._trcs[name]
643 def trace_enabled(self, name):
644 """Returns True if trace is enables
646 :param name: Name of the trace
649 trace = self._trcs[name]
652 def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
653 """ Get information on collected trace
655 :param name: Name of the trace
658 :param attr: Can be one of:
659 - TraceAttr.ALL (complete trace content),
660 - TraceAttr.STREAM (block in bytes to read starting at offset),
661 - TraceAttr.PATH (full path to the trace file),
662 - TraceAttr.SIZE (size of trace file).
665 :param block: Number of bytes to retrieve from trace, when attr is TraceAttr.STREAM
668 :param offset: Number of 'blocks' to skip, when attr is TraceAttr.STREAM
675 def register_condition(self, action, group, state, time = None):
676 """ Registers a condition on the resource manager to allow execution
677 of 'action' only after 'time' has elapsed from the moment all resources
678 in 'group' reached state 'state'
680 :param action: Action to restrict to condition (either 'START' or 'STOP')
682 :param group: Group of RMs to wait for (list of guids)
683 :type group: int or list of int
684 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
686 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
691 if not action in self.conditions:
692 self._conditions[action] = list()
694 conditions = self.conditions.get(action)
696 # For each condition to register a tuple of (group, state, time) is
697 # added to the 'action' list
698 if not isinstance(group, list):
701 conditions.append((group, state, time))
703 def unregister_condition(self, group, action = None):
704 """ Removed conditions for a certain group of guids
706 :param action: Action to restrict to condition (either 'START', 'STOP' or 'READY')
709 :param group: Group of RMs to wait for (list of guids)
710 :type group: int or list of int
713 # For each condition a tuple of (group, state, time) is
714 # added to the 'action' list
715 if not isinstance(group, list):
718 for act, conditions in self.conditions.iteritems():
719 if action and act != action:
722 for condition in list(conditions):
723 (grp, state, time) = condition
725 # If there is an intersection between grp and group,
726 # then remove intersected elements
727 intsec = set(group).intersection(set(grp))
729 idx = conditions.index(condition)
731 newgrp.difference_update(intsec)
732 conditions[idx] = (newgrp, state, time)
734 def get_connected(self, rtype = None):
735 """ Returns the list of RM with the type 'rtype'
737 :param rtype: Type of the RM we look for
739 :return: list of guid
742 rclass = ResourceFactory.get_resource_type(rtype)
743 for guid in self.connections:
744 rm = self.ec.get_resource(guid)
745 if not rtype or isinstance(rm, rclass):
750 def _needs_reschedule(self, group, state, time):
751 """ Internal method that verify if 'time' has elapsed since
752 all elements in 'group' have reached state 'state'.
754 :param group: Group of RMs to wait for (list of guids)
755 :type group: int or list of int
756 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
758 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
761 .. note : time should be written like "2s" or "3m" with s for seconds, m for minutes, h for hours, ...
762 If for example, you need to wait 2min 30sec, time could be "150s" or "2.5m".
763 For the moment, 2m30s is not a correct syntax.
767 delay = reschedule_delay
769 # check state and time elapsed on all RMs
771 rm = self.ec.get_resource(guid)
773 # If one of the RMs this resource needs to wait for has FAILED
774 # and is critical we raise an exception
775 if rm.state == ResourceState.FAILED:
776 if not rm.get('critical'):
778 msg = "Resource can not wait for FAILED RM %d. Setting Resource to FAILED"
779 raise RuntimeError, msg
781 # If the RM state is lower than the requested state we must
782 # reschedule (e.g. if RM is READY but we required STARTED).
787 # If there is a time restriction, we must verify the
788 # restriction is satisfied
790 if state == ResourceState.DISCOVERED:
792 if state == ResourceState.PROVISIONED:
793 t = rm.provision_time
794 elif state == ResourceState.READY:
796 elif state == ResourceState.STARTED:
798 elif state == ResourceState.STOPPED:
800 elif state == ResourceState.RELEASED:
805 # time already elapsed since RM changed state
806 waited = "%fs" % tdiffsec(tnow(), t)
809 wait = tdiffsec(stabsformat(time), stabsformat(waited))
816 return reschedule, delay
818 def set_with_conditions(self, name, value, group, state, time):
819 """ Set value 'value' on attribute with name 'name' when 'time'
820 has elapsed since all elements in 'group' have reached state
823 :param name: Name of the attribute to set
825 :param name: Value of the attribute to set
827 :param group: Group of RMs to wait for (list of guids)
828 :type group: int or list of int
829 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
831 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
836 delay = reschedule_delay
838 ## evaluate if set conditions are met
840 # only can set with conditions after the RM is started
841 if self.state != ResourceState.STARTED:
844 reschedule, delay = self._needs_reschedule(group, state, time)
847 callback = functools.partial(self.set_with_conditions,
848 name, value, group, state, time)
849 self.ec.schedule(delay, callback)
851 self.set(name, value)
853 def start_with_conditions(self):
854 """ Starts RM when all the conditions in self.conditions for
855 action 'START' are satisfied.
858 #import pdb;pdb.set_trace()
861 delay = reschedule_delay
864 ## evaluate if conditions to start are met
868 # Can only start when RM is either STOPPED or READY
869 if self.state not in [ResourceState.STOPPED, ResourceState.READY]:
871 self.debug("---- RESCHEDULING START ---- state %s " % self.state )
873 start_conditions = self.conditions.get(ResourceAction.START, [])
875 self.debug("---- START CONDITIONS ---- %s" % start_conditions)
877 # Verify all start conditions are met
878 for (group, state, time) in start_conditions:
879 # Uncomment for debug
882 rm = self.ec.get_resource(guid)
883 unmet.append((guid, rm._state))
885 self.debug("---- WAITED STATES ---- %s" % unmet )
887 reschedule, delay = self._needs_reschedule(group, state, time)
892 self.ec.schedule(delay, self.start_with_conditions)
894 self.debug("----- STARTING ---- ")
897 def stop_with_conditions(self):
898 """ Stops RM when all the conditions in self.conditions for
899 action 'STOP' are satisfied.
903 delay = reschedule_delay
905 ## evaluate if conditions to stop are met
909 # only can stop when RM is STARTED
910 if self.state != ResourceState.STARTED:
912 self.debug("---- RESCHEDULING STOP ---- state %s " % self.state )
914 self.debug(" ---- STOP CONDITIONS ---- %s" %
915 self.conditions.get(ResourceAction.STOP))
917 stop_conditions = self.conditions.get(ResourceAction.STOP, [])
918 for (group, state, time) in stop_conditions:
919 reschedule, delay = self._needs_reschedule(group, state, time)
924 callback = functools.partial(self.stop_with_conditions)
925 self.ec.schedule(delay, callback)
927 self.debug(" ----- STOPPING ---- ")
930 def deploy_with_conditions(self):
931 """ Deploy RM when all the conditions in self.conditions for
932 action 'READY' are satisfied.
936 delay = reschedule_delay
938 ## evaluate if conditions to deploy are met
942 # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED
943 if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED,
944 ResourceState.PROVISIONED]:
946 self.debug("---- RESCHEDULING DEPLOY ---- state %s " % self.state )
948 deploy_conditions = self.conditions.get(ResourceAction.DEPLOY, [])
950 self.debug("---- DEPLOY CONDITIONS ---- %s" % deploy_conditions)
952 # Verify all start conditions are met
953 for (group, state, time) in deploy_conditions:
954 # Uncomment for debug
957 # rm = self.ec.get_resource(guid)
958 # unmet.append((guid, rm._state))
960 #self.debug("---- WAITED STATES ---- %s" % unmet )
962 reschedule, delay = self._needs_reschedule(group, state, time)
967 self.ec.schedule(delay, self.deploy_with_conditions)
969 self.debug("----- DEPLOYING ---- ")
972 def do_connect(self, guid):
973 """ Performs actions that need to be taken upon associating RMs.
974 This method should be redefined when necessary in child classes.
978 def do_disconnect(self, guid):
979 """ Performs actions that need to be taken upon disassociating RMs.
980 This method should be redefined when necessary in child classes.
984 def valid_connection(self, guid):
985 """Checks whether a connection with the other RM
987 This method need to be redefined by each new Resource Manager.
989 :param guid: Guid of the current Resource Manager
997 def do_discover(self):
998 self.set_discovered()
1000 def do_provision(self):
1001 self.set_provisioned()
1009 def do_deploy(self):
1012 def do_release(self):
1018 def set_started(self, time = None):
1019 """ Mark ResourceManager as STARTED """
1020 self.set_state(ResourceState.STARTED, "_start_time", time)
1021 self.debug("----- STARTED ---- ")
1023 def set_stopped(self, time = None):
1024 """ Mark ResourceManager as STOPPED """
1025 self.set_state(ResourceState.STOPPED, "_stop_time", time)
1026 self.debug("----- STOPPED ---- ")
1028 def set_ready(self, time = None):
1029 """ Mark ResourceManager as READY """
1030 self.set_state(ResourceState.READY, "_ready_time", time)
1031 self.debug("----- READY ---- ")
1033 def set_released(self, time = None):
1034 """ Mark ResourceManager as REALEASED """
1035 self.set_state(ResourceState.RELEASED, "_release_time", time)
1036 self.debug("----- RELEASED ---- ")
1038 def set_failed(self, time = None):
1039 """ Mark ResourceManager as FAILED """
1040 self.set_state(ResourceState.FAILED, "_failed_time", time)
1041 self.debug("----- FAILED ---- ")
1043 def set_discovered(self, time = None):
1044 """ Mark ResourceManager as DISCOVERED """
1045 self.set_state(ResourceState.DISCOVERED, "_discover_time", time)
1046 self.debug("----- DISCOVERED ---- ")
1048 def set_provisioned(self, time = None):
1049 """ Mark ResourceManager as PROVISIONED """
1050 self.set_state(ResourceState.PROVISIONED, "_provision_time", time)
1051 self.debug("----- PROVISIONED ---- ")
1053 def set_state(self, state, state_time_attr, time = None):
1054 """ Set the state of the RM while keeping a trace of the time """
1056 # Ensure that RM state will not change after released
1057 if self._state == ResourceState.RELEASED:
1060 time = time or tnow()
1061 self.set_state_time(state, state_time_attr, time)
1063 def set_state_time(self, state, state_time_attr, time):
1064 """ Set the time for the RM state change """
1065 setattr(self, state_time_attr, time)
1068 class ResourceFactory(object):
1069 _resource_types = dict()
1072 def resource_types(cls):
1073 """Return the type of the Class"""
1074 return cls._resource_types
1077 def get_resource_type(cls, rtype):
1078 """Return the type of the Class"""
1079 return cls._resource_types.get(rtype)
1082 def register_type(cls, rclass):
1083 """Register a new Ressource Manager"""
1084 cls._resource_types[rclass.get_rtype()] = rclass
1087 def create(cls, rtype, ec, guid):
1088 """Create a new instance of a Ressource Manager"""
1089 rclass = cls._resource_types[rtype]
1090 return rclass(ec, guid)
1092 def populate_factory():
1093 """Register all the possible RM that exists in the current version of Nepi.
1095 # Once the factory is populated, don't repopulate
1096 if not ResourceFactory.resource_types():
1097 for rclass in find_types():
1098 ResourceFactory.register_type(rclass)
1101 """Look into the different folders to find all the
1102 availables Resources Managers
1104 search_path = os.environ.get("NEPI_SEARCH_PATH", "")
1105 search_path = set(search_path.split(" "))
1108 import nepi.resources
1109 path = os.path.dirname(nepi.resources.__file__)
1110 search_path.add(path)
1114 for importer, modname, ispkg in pkgutil.walk_packages(search_path,
1115 prefix = "nepi.resources."):
1117 loader = importer.find_module(modname)
1120 # Notice: Repeated calls to load_module will act as a reload of the module
1121 if modname in sys.modules:
1122 module = sys.modules.get(modname)
1124 module = loader.load_module(modname)
1126 for attrname in dir(module):
1127 if attrname.startswith("_"):
1130 attr = getattr(module, attrname)
1132 if attr == ResourceManager:
1135 if not inspect.isclass(attr):
1138 if issubclass(attr, ResourceManager):
1141 if not modname in sys.modules:
1142 sys.modules[modname] = module
1147 err = traceback.format_exc()
1148 logger = logging.getLogger("Resource.find_types()")
1149 logger.error("Error while loading Resource Managers %s" % err)