Adding help and background class atrributes to ResourceManager
[nepi.git] / src / nepi / execution / resource.py
1 #
2 #    NEPI, a framework to manage network experiments
3 #    Copyright (C) 2013 INRIA
4 #
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.
9 #
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.
14 #
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/>.
17 #
18 # Author: Alina Quereilhac <alina.quereilhac@inria.fr>
19
20 from nepi.util.timefuncs import tnow, tdiff, tdiffsec, stabsformat
21 from nepi.util.logger import Logger
22 from nepi.execution.trace import TraceAttr
23
24 import copy
25 import functools
26 import logging
27 import os
28 import pkgutil
29 import sys
30 import weakref
31
32 reschedule_delay = "1s"
33
34 class ResourceAction:
35     """ Action that a user can order to a Resource Manager
36    
37     """
38     DEPLOY = 0
39     START = 1
40     STOP = 2
41
42 class ResourceState:
43     """ State of a Resource Manager
44    
45     """
46     NEW = 0
47     DISCOVERED = 1
48     PROVISIONED = 2
49     READY = 3
50     STARTED = 4
51     STOPPED = 5
52     FINISHED = 6
53     FAILED = 7
54     RELEASED = 8
55
56 ResourceState2str = dict({
57     ResourceState.NEW : "NEW",
58     ResourceState.DISCOVERED : "DISCOVERED",
59     ResourceState.PROVISIONED : "PROVISIONED",
60     ResourceState.READY : "READY",
61     ResourceState.STARTED : "STARTED",
62     ResourceState.STOPPED : "STOPPED",
63     ResourceState.FINISHED : "FINISHED",
64     ResourceState.FAILED : "FAILED",
65     ResourceState.RELEASED : "RELEASED",
66     })
67
68 def clsinit(cls):
69     """ Initializes template information (i.e. attributes and traces)
70     for the ResourceManager class
71     """
72     cls._clsinit()
73     return cls
74
75 def clsinit_copy(cls):
76     """ Initializes template information (i.e. attributes and traces)
77     for the ResourceManager class, inheriting attributes and traces
78     from the parent class
79     """
80     cls._clsinit_copy()
81     return cls
82
83 # Decorator to invoke class initialization method
84 @clsinit
85 class ResourceManager(Logger):
86     """ Base clase for all ResourceManagers. 
87     
88     A ResourceManger is specific to a resource type (e.g. Node, 
89     Switch, Application, etc) on a specific backend (e.g. PlanetLab, 
90     OMF, etc).
91
92     The ResourceManager instances are responsible for interacting with
93     and controlling concrete (physical or virtual) resources in the 
94     experimental backends.
95     
96     """
97     _rtype = "Resource"
98     _attributes = None
99     _traces = None
100     _help = None
101     _backend = None
102
103     @classmethod
104     def _register_attribute(cls, attr):
105         """ Resource subclasses will invoke this method to add a 
106         resource attribute
107
108         """
109         cls._attributes[attr.name] = attr
110
111     @classmethod
112     def _remove_attribute(cls, name):
113         """ Resource subclasses will invoke this method to remove a 
114         resource attribute
115
116         """
117         del cls._attributes[name]
118
119     @classmethod
120     def _register_trace(cls, trace):
121         """ Resource subclasses will invoke this method to add a 
122         resource trace
123
124         """
125         cls._traces[trace.name] = trace
126
127     @classmethod
128     def _remove_trace(cls, name):
129         """ Resource subclasses will invoke this method to remove a 
130         resource trace
131
132         """
133         del cls._traces[name]
134
135     @classmethod
136     def _register_attributes(cls):
137         """ Resource subclasses will invoke this method to register
138         resource attributes
139
140         """
141         pass
142
143     @classmethod
144     def _register_traces(cls):
145         """ Resource subclasses will invoke this method to register
146         resource traces
147
148         """
149         pass
150
151     @classmethod
152     def _clsinit(cls):
153         """ ResourceManager child classes have different attributes and traces.
154         Since the templates that hold the information of attributes and traces
155         are 'class attribute' dictionaries, initially they all point to the 
156         parent class ResourceManager instances of those dictionaries. 
157         In order to make these templates independent from the parent's one,
158         it is necessary re-initialize the corresponding dictionaries. 
159         This is the objective of the _clsinit method
160         """
161         # static template for resource attributes
162         cls._attributes = dict()
163         cls._register_attributes()
164
165         # static template for resource traces
166         cls._traces = dict()
167         cls._register_traces()
168
169     @classmethod
170     def _clsinit_copy(cls):
171         """ Same as _clsinit, except that it also inherits all attributes and traces
172         from the parent class.
173         """
174         # static template for resource attributes
175         cls._attributes = copy.deepcopy(cls._attributes)
176         cls._register_attributes()
177
178         # static template for resource traces
179         cls._traces = copy.deepcopy(cls._traces)
180         cls._register_traces()
181
182     @classmethod
183     def rtype(cls):
184         """ Returns the type of the Resource Manager
185
186         """
187         return cls._rtype
188
189     @classmethod
190     def get_attributes(cls):
191         """ Returns a copy of the attributes
192
193         """
194         return copy.deepcopy(cls._attributes.values())
195
196     @classmethod
197     def get_traces(cls):
198         """ Returns a copy of the traces
199
200         """
201         return copy.deepcopy(cls._traces.values())
202
203     @classmethod
204     def get_help(cls):
205         """ Returns the description of the type of Resource
206
207         """
208         return cls._help
209
210     @classmethod
211     def get_backend(cls):
212         """ Returns the identified of the backend (i.e. testbed, environment)
213         for the Resource
214
215         """
216         return cls._backend
217
218     def __init__(self, ec, guid):
219         super(ResourceManager, self).__init__(self.rtype())
220         
221         self._guid = guid
222         self._ec = weakref.ref(ec)
223         self._connections = set()
224         self._conditions = dict() 
225
226         # the resource instance gets a copy of all attributes
227         self._attrs = copy.deepcopy(self._attributes)
228
229         # the resource instance gets a copy of all traces
230         self._trcs = copy.deepcopy(self._traces)
231
232         # Each resource is placed on a deployment group by the EC
233         # during deployment
234         self.deployment_group = None
235
236         self._start_time = None
237         self._stop_time = None
238         self._discover_time = None
239         self._provision_time = None
240         self._ready_time = None
241         self._release_time = None
242         self._finish_time = None
243         self._failed_time = None
244
245         self._state = ResourceState.NEW
246
247     @property
248     def guid(self):
249         """ Returns the global unique identifier of the RM """
250         return self._guid
251
252     @property
253     def ec(self):
254         """ Returns the Experiment Controller """
255         return self._ec()
256
257     @property
258     def connections(self):
259         """ Returns the set of guids of connected RMs"""
260         return self._connections
261
262     @property
263     def conditions(self):
264         """ Returns the conditions to which the RM is subjected to.
265         
266         The object returned by this method is a dictionary indexed by
267         ResourceAction."""
268         return self._conditions
269
270     @property
271     def start_time(self):
272         """ Returns the start time of the RM as a timestamp"""
273         return self._start_time
274
275     @property
276     def stop_time(self):
277         """ Returns the stop time of the RM as a timestamp"""
278         return self._stop_time
279
280     @property
281     def discover_time(self):
282         """ Returns the time discovering was finished for the RM as a timestamp"""
283         return self._discover_time
284
285     @property
286     def provision_time(self):
287         """ Returns the time provisioning was finished for the RM as a timestamp"""
288         return self._provision_time
289
290     @property
291     def ready_time(self):
292         """ Returns the time deployment was finished for the RM as a timestamp"""
293         return self._ready_time
294
295     @property
296     def release_time(self):
297         """ Returns the release time of the RM as a timestamp"""
298         return self._release_time
299
300     @property
301     def finish_time(self):
302         """ Returns the finalization time of the RM as a timestamp"""
303         return self._finish_time
304
305     @property
306     def failed_time(self):
307         """ Returns the time failure occured for the RM as a timestamp"""
308         return self._failed_time
309
310     @property
311     def state(self):
312         """ Get the state of the current RM """
313         return self._state
314
315     def log_message(self, msg):
316         """ Returns the log message formatted with added information.
317
318         :param msg: text message
319         :type msg: str
320         :rtype: str
321         """
322         return " %s guid: %d - %s " % (self._rtype, self.guid, msg)
323
324     def register_connection(self, guid):
325         """ Registers a connection to the RM identified by guid
326
327         :param guid: Global unique identified of the RM to connect to
328         :type guid: int
329         """
330         if self.valid_connection(guid):
331             self.connect(guid)
332             self._connections.add(guid)
333
334     def unregister_connection(self, guid):
335         """ Removes a registered connection to the RM identified by guid
336
337         :param guid: Global unique identified of the RM to connect to
338         :type guid: int
339         """
340         if guid in self._connections:
341             self.disconnect(guid)
342             self._connections.remove(guid)
343
344     def discover(self):
345         """ Performs resource discovery.
346
347         This  method is resposible for selecting an individual resource
348         matching user requirements.
349         This method should be redefined when necessary in child classes.
350         """
351         self.set_discovered()
352
353     def provision(self):
354         """ Performs resource provisioning.
355
356         This  method is resposible for provisioning one resource.
357         After this method has been successfully invoked, the resource
358         should be acccesible/controllable by the RM.
359         This method should be redefined when necessary in child classes.
360         """
361         self.set_provisioned()
362
363     def start(self):
364         """ Starts the resource.
365         
366         There is no generic start behavior for all resources.
367         This method should be redefined when necessary in child classes.
368         """
369         if not self.state in [ResourceState.READY, ResourceState.STOPPED]:
370             self.error("Wrong state %s for start" % self.state)
371             return
372
373         self.set_started()
374
375     def stop(self):
376         """ Stops the resource.
377         
378         There is no generic stop behavior for all resources.
379         This method should be redefined when necessary in child classes.
380         """
381         if not self.state in [ResourceState.STARTED]:
382             self.error("Wrong state %s for stop" % self.state)
383             return
384         
385         self.set_stopped()
386
387     def deploy(self):
388         """ Execute all steps required for the RM to reach the state READY
389
390         """
391         if self.state > ResourceState.READY:
392             self.error("Wrong state %s for deploy" % self.state)
393             return
394
395         self.debug("----- READY ---- ")
396         self.set_ready()
397
398     def release(self):
399         self.set_released()
400
401     def finish(self):
402         self.set_finished()
403  
404     def fail(self):
405         self.set_failed()
406
407     def set(self, name, value):
408         """ Set the value of the attribute
409
410         :param name: Name of the attribute
411         :type name: str
412         :param name: Value of the attribute
413         :type name: str
414         """
415         attr = self._attrs[name]
416         attr.value = value
417
418     def get(self, name):
419         """ Returns the value of the attribute
420
421         :param name: Name of the attribute
422         :type name: str
423         :rtype: str
424         """
425         attr = self._attrs[name]
426         return attr.value
427
428     def enable_trace(self, name):
429         """ Explicitly enable trace generation
430
431         :param name: Name of the trace
432         :type name: str
433         """
434         trace = self._trcs[name]
435         trace.enabled = True
436     
437     def trace_enabled(self, name):
438         """Returns True if trace is enables 
439
440         :param name: Name of the trace
441         :type name: str
442         """
443         trace = self._trcs[name]
444         return trace.enabled
445  
446     def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
447         """ Get information on collected trace
448
449         :param name: Name of the trace
450         :type name: str
451
452         :param attr: Can be one of:
453                          - TraceAttr.ALL (complete trace content), 
454                          - TraceAttr.STREAM (block in bytes to read starting at offset), 
455                          - TraceAttr.PATH (full path to the trace file),
456                          - TraceAttr.SIZE (size of trace file). 
457         :type attr: str
458
459         :param block: Number of bytes to retrieve from trace, when attr is TraceAttr.STREAM 
460         :type name: int
461
462         :param offset: Number of 'blocks' to skip, when attr is TraceAttr.STREAM 
463         :type name: int
464
465         :rtype: str
466         """
467         pass
468
469     def register_condition(self, action, group, state, time = None):
470         """ Registers a condition on the resource manager to allow execution 
471         of 'action' only after 'time' has elapsed from the moment all resources 
472         in 'group' reached state 'state'
473
474         :param action: Action to restrict to condition (either 'START' or 'STOP')
475         :type action: str
476         :param group: Group of RMs to wait for (list of guids)
477         :type group: int or list of int
478         :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
479         :type state: str
480         :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
481         :type time: str
482
483         """
484
485         if not action in self.conditions:
486             self._conditions[action] = list()
487         
488         conditions = self.conditions.get(action)
489
490         # For each condition to register a tuple of (group, state, time) is 
491         # added to the 'action' list
492         if not isinstance(group, list):
493             group = [group]
494
495         conditions.append((group, state, time))
496
497     def unregister_condition(self, group, action = None):
498         """ Removed conditions for a certain group of guids
499
500         :param action: Action to restrict to condition (either 'START', 'STOP' or 'READY')
501         :type action: str
502
503         :param group: Group of RMs to wait for (list of guids)
504         :type group: int or list of int
505
506         """
507         # For each condition a tuple of (group, state, time) is 
508         # added to the 'action' list
509         if not isinstance(group, list):
510             group = [group]
511
512         for act, conditions in self.conditions.iteritems():
513             if action and act != action:
514                 continue
515
516             for condition in list(conditions):
517                 (grp, state, time) = condition
518
519                 # If there is an intersection between grp and group,
520                 # then remove intersected elements
521                 intsec = set(group).intersection(set(grp))
522                 if intsec:
523                     idx = conditions.index(condition)
524                     newgrp = set(grp)
525                     newgrp.difference_update(intsec)
526                     conditions[idx] = (newgrp, state, time)
527                  
528     def get_connected(self, rtype = None):
529         """ Returns the list of RM with the type 'rtype'
530
531         :param rtype: Type of the RM we look for
532         :type rtype: str
533         :return: list of guid
534         """
535         connected = []
536         rclass = ResourceFactory.get_resource_type(rtype)
537         for guid in self.connections:
538             rm = self.ec.get_resource(guid)
539             if not rtype or isinstance(rm, rclass):
540                 connected.append(rm)
541         return connected
542
543     def _needs_reschedule(self, group, state, time):
544         """ Internal method that verify if 'time' has elapsed since 
545         all elements in 'group' have reached state 'state'.
546
547         :param group: Group of RMs to wait for (list of guids)
548         :type group: int or list of int
549         :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
550         :type state: str
551         :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
552         :type time: str
553
554         .. note : time should be written like "2s" or "3m" with s for seconds, m for minutes, h for hours, ...
555         If for example, you need to wait 2min 30sec, time could be "150s" or "2.5m".
556         For the moment, 2m30s is not a correct syntax.
557
558         """
559         reschedule = False
560         delay = reschedule_delay 
561
562         # check state and time elapsed on all RMs
563         for guid in group:
564             rm = self.ec.get_resource(guid)
565             # If the RM state is lower than the requested state we must
566             # reschedule (e.g. if RM is READY but we required STARTED).
567             if rm.state < state:
568                 reschedule = True
569                 break
570
571             # If there is a time restriction, we must verify the
572             # restriction is satisfied 
573             if time:
574                 if state == ResourceState.DISCOVERED:
575                     t = rm.discover_time
576                 if state == ResourceState.PROVISIONED:
577                     t = rm.provision_time
578                 elif state == ResourceState.READY:
579                     t = rm.ready_time
580                 elif state == ResourceState.STARTED:
581                     t = rm.start_time
582                 elif state == ResourceState.STOPPED:
583                     t = rm.stop_time
584                 else:
585                     break
586
587                 # time already elapsed since RM changed state
588                 waited = "%fs" % tdiffsec(tnow(), t)
589
590                 # time still to wait
591                 wait = tdiffsec(stabsformat(time), stabsformat(waited))
592
593                 if wait > 0.001:
594                     reschedule = True
595                     delay = "%fs" % wait
596                     break
597
598         return reschedule, delay
599
600     def set_with_conditions(self, name, value, group, state, time):
601         """ Set value 'value' on attribute with name 'name' when 'time' 
602         has elapsed since all elements in 'group' have reached state
603         'state'
604
605         :param name: Name of the attribute to set
606         :type name: str
607         :param name: Value of the attribute to set
608         :type name: str
609         :param group: Group of RMs to wait for (list of guids)
610         :type group: int or list of int
611         :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
612         :type state: str
613         :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
614         :type time: str
615         """
616
617         reschedule = False
618         delay = reschedule_delay 
619
620         ## evaluate if set conditions are met
621
622         # only can set with conditions after the RM is started
623         if self.state != ResourceState.STARTED:
624             reschedule = True
625         else:
626             reschedule, delay = self._needs_reschedule(group, state, time)
627
628         if reschedule:
629             callback = functools.partial(self.set_with_conditions, 
630                     name, value, group, state, time)
631             self.ec.schedule(delay, callback)
632         else:
633             self.set(name, value)
634
635     def start_with_conditions(self):
636         """ Starts RM when all the conditions in self.conditions for
637         action 'START' are satisfied.
638
639         """
640         reschedule = False
641         delay = reschedule_delay 
642
643         ## evaluate if set conditions are met
644
645         # only can start when RM is either STOPPED or READY
646         if self.state not in [ResourceState.STOPPED, ResourceState.READY]:
647             reschedule = True
648             self.debug("---- RESCHEDULING START ---- state %s " % self.state )
649         else:
650             start_conditions = self.conditions.get(ResourceAction.START, [])
651             
652             self.debug("---- START CONDITIONS ---- %s" % start_conditions) 
653             
654             # Verify all start conditions are met
655             for (group, state, time) in start_conditions:
656                 # Uncomment for debug
657                 #unmet = []
658                 #for guid in group:
659                 #    rm = self.ec.get_resource(guid)
660                 #    unmet.append((guid, rm._state))
661                 #
662                 #self.debug("---- WAITED STATES ---- %s" % unmet )
663
664                 reschedule, delay = self._needs_reschedule(group, state, time)
665                 if reschedule:
666                     break
667
668         if reschedule:
669             self.ec.schedule(delay, self.start_with_conditions)
670         else:
671             self.debug("----- STARTING ---- ")
672             self.start()
673
674     def stop_with_conditions(self):
675         """ Stops RM when all the conditions in self.conditions for
676         action 'STOP' are satisfied.
677
678         """
679         reschedule = False
680         delay = reschedule_delay 
681
682         ## evaluate if set conditions are met
683
684         # only can stop when RM is STARTED
685         if self.state != ResourceState.STARTED:
686             reschedule = True
687         else:
688             self.debug(" ---- STOP CONDITIONS ---- %s" % 
689                     self.conditions.get(ResourceAction.STOP))
690
691             stop_conditions = self.conditions.get(ResourceAction.STOP, []) 
692             for (group, state, time) in stop_conditions:
693                 reschedule, delay = self._needs_reschedule(group, state, time)
694                 if reschedule:
695                     break
696
697         if reschedule:
698             callback = functools.partial(self.stop_with_conditions)
699             self.ec.schedule(delay, callback)
700         else:
701             self.debug(" ----- STOPPING ---- ") 
702             self.stop()
703
704     def deploy_with_conditions(self):
705         """ Deploy RM when all the conditions in self.conditions for
706         action 'READY' are satisfied.
707
708         """
709         reschedule = False
710         delay = reschedule_delay 
711
712         ## evaluate if set conditions are met
713
714         # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED 
715         if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED, 
716                 ResourceState.PROVISIONED]:
717             reschedule = True
718             self.debug("---- RESCHEDULING DEPLOY ---- state %s " % self.state )
719         else:
720             deploy_conditions = self.conditions.get(ResourceAction.DEPLOY, [])
721             
722             self.debug("---- DEPLOY CONDITIONS ---- %s" % deploy_conditions) 
723             
724             # Verify all start conditions are met
725             for (group, state, time) in deploy_conditions:
726                 # Uncomment for debug
727                 #unmet = []
728                 #for guid in group:
729                 #    rm = self.ec.get_resource(guid)
730                 #    unmet.append((guid, rm._state))
731                 #
732                 #self.debug("---- WAITED STATES ---- %s" % unmet )
733
734                 reschedule, delay = self._needs_reschedule(group, state, time)
735                 if reschedule:
736                     break
737
738         if reschedule:
739             self.ec.schedule(delay, self.deploy_with_conditions)
740         else:
741             self.debug("----- STARTING ---- ")
742             self.deploy()
743
744
745     def connect(self, guid):
746         """ Performs actions that need to be taken upon associating RMs.
747         This method should be redefined when necessary in child classes.
748         """
749         pass
750
751     def disconnect(self, guid):
752         """ Performs actions that need to be taken upon disassociating RMs.
753         This method should be redefined when necessary in child classes.
754         """
755         pass
756
757     def valid_connection(self, guid):
758         """Checks whether a connection with the other RM
759         is valid.
760         This method need to be redefined by each new Resource Manager.
761
762         :param guid: Guid of the current Resource Manager
763         :type guid: int
764         :rtype:  Boolean
765
766         """
767         # TODO: Validate!
768         return True
769     
770     def set_started(self):
771         """ Mark ResourceManager as STARTED """
772         self._start_time = tnow()
773         self._state = ResourceState.STARTED
774         
775     def set_stopped(self):
776         """ Mark ResourceManager as STOPPED """
777         self._stop_time = tnow()
778         self._state = ResourceState.STOPPED
779
780     def set_ready(self):
781         """ Mark ResourceManager as READY """
782         self._ready_time = tnow()
783         self._state = ResourceState.READY
784
785     def set_released(self):
786         """ Mark ResourceManager as REALEASED """
787         self._release_time = tnow()
788         self._state = ResourceState.RELEASED
789
790     def set_finished(self):
791         """ Mark ResourceManager as FINISHED """
792         self._finish_time = tnow()
793         self._state = ResourceState.FINISHED
794
795     def set_failed(self):
796         """ Mark ResourceManager as FAILED """
797         self._failed_time = tnow()
798         self._state = ResourceState.FAILED
799
800     def set_discovered(self):
801         """ Mark ResourceManager as DISCOVERED """
802         self._discover_time = tnow()
803         self._state = ResourceState.DISCOVERED
804
805     def set_provisioned(self):
806         """ Mark ResourceManager as PROVISIONED """
807         self._provision_time = tnow()
808         self._state = ResourceState.PROVISIONED
809
810 class ResourceFactory(object):
811     _resource_types = dict()
812
813     @classmethod
814     def resource_types(cls):
815         """Return the type of the Class"""
816         return cls._resource_types
817
818     @classmethod
819     def get_resource_type(cls, rtype):
820         """Return the type of the Class"""
821         return cls._resource_types.get(rtype)
822
823     @classmethod
824     def register_type(cls, rclass):
825         """Register a new Ressource Manager"""
826         cls._resource_types[rclass.rtype()] = rclass
827
828     @classmethod
829     def create(cls, rtype, ec, guid):
830         """Create a new instance of a Ressource Manager"""
831         rclass = cls._resource_types[rtype]
832         return rclass(ec, guid)
833
834 def populate_factory():
835     """Register all the possible RM that exists in the current version of Nepi.
836     """
837     # Once the factory is populated, don't repopulate
838     if not ResourceFactory.resource_types():
839         for rclass in find_types():
840             ResourceFactory.register_type(rclass)
841
842 def find_types():
843     """Look into the different folders to find all the 
844     availables Resources Managers
845     """
846     search_path = os.environ.get("NEPI_SEARCH_PATH", "")
847     search_path = set(search_path.split(" "))
848    
849     import inspect
850     import nepi.resources 
851     path = os.path.dirname(nepi.resources.__file__)
852     search_path.add(path)
853
854     types = []
855
856     for importer, modname, ispkg in pkgutil.walk_packages(search_path, 
857             prefix = "nepi.resources."):
858
859         loader = importer.find_module(modname)
860         
861         try:
862             # Notice: Repeated calls to load_module will act as a reload of teh module
863             if modname in sys.modules:
864                 module = sys.modules.get(modname)
865             else:
866                 module = loader.load_module(modname)
867
868             for attrname in dir(module):
869                 if attrname.startswith("_"):
870                     continue
871
872                 attr = getattr(module, attrname)
873
874                 if attr == ResourceManager:
875                     continue
876
877                 if not inspect.isclass(attr):
878                     continue
879
880                 if issubclass(attr, ResourceManager):
881                     types.append(attr)
882
883                     if not modname in sys.modules:
884                         sys.modules[modname] = module
885
886         except:
887             import traceback
888             import logging
889             err = traceback.format_exc()
890             logger = logging.getLogger("Resource.find_types()")
891             logger.error("Error while loading Resource Managers %s" % err)
892
893     return types
894
895