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
35 """ Action that a user can order to a Resource Manager
43 """ State of a Resource Manager
56 ResourceState2str = dict({
57 ResourceState.NEW : "NEW",
58 ResourceState.DISCOVERED : "DISCOVERED",
59 ResourceState.RESERVED : "RESERVED",
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 derived 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, reserved, provision, deploy, start, stop.
112 def wrapped(self, *args, **kwargs):
114 return func(self, *args, **kwargs)
119 err = traceback.format_exc()
120 logger = Logger(self._rtype)
122 logger.error("SETTING guid %d to state FAILED" % self.guid)
128 class ResourceManager(Logger):
129 """ Base clase for all ResourceManagers.
131 A ResourceManger is specific to a resource type (e.g. Node,
132 Switch, Application, etc) on a specific platform (e.g. PlanetLab,
135 The ResourceManager instances are responsible for interacting with
136 and controlling concrete (physical or virtual) resources in the
137 experimental platforms.
145 _reschedule_delay = "0.5s"
148 def _register_attribute(cls, attr):
149 """ Resource subclasses will invoke this method to add a
154 cls._attributes[attr.name] = attr
157 def _remove_attribute(cls, name):
158 """ Resource subclasses will invoke this method to remove a
163 del cls._attributes[name]
166 def _register_trace(cls, trace):
167 """ Resource subclasses will invoke this method to add a
172 cls._traces[trace.name] = trace
175 def _remove_trace(cls, name):
176 """ Resource subclasses will invoke this method to remove a
181 del cls._traces[name]
184 def _register_attributes(cls):
185 """ Resource subclasses will invoke this method to register
188 This method should be overriden in the RMs that define
192 critical = Attribute("critical",
193 "Defines whether the resource is critical. "
194 "A failure on a critical resource will interrupt "
198 flags = Flags.Design)
199 hard_release = Attribute("hardRelease",
200 "Forces removal of all result files and directories associated "
201 "to the RM upon resource release. After release the RM will "
202 "be removed from the EC and the results will not longer be "
206 flags = Flags.Design)
208 cls._register_attribute(critical)
209 cls._register_attribute(hard_release)
212 def _register_traces(cls):
213 """ Resource subclasses will invoke this method to register
216 This method should be overridden in the RMs that define traces.
224 """ ResourceManager classes have different attributes and traces.
225 Attribute and traces are stored in 'class attribute' dictionaries.
226 When a new ResourceManager class is created, the _clsinit method is
227 called to create a new instance of those dictionaries and initialize
230 The _clsinit method is called by the clsinit decorator method.
234 # static template for resource attributes
235 cls._attributes = dict()
236 cls._register_attributes()
238 # static template for resource traces
240 cls._register_traces()
243 def _clsinit_copy(cls):
244 """ Same as _clsinit, except that after creating new instances of the
245 dictionaries it copies all the attributes and traces from the parent
248 The _clsinit_copy method is called by the clsinit_copy decorator method.
251 # static template for resource attributes
252 cls._attributes = copy.deepcopy(cls._attributes)
253 cls._register_attributes()
255 # static template for resource traces
256 cls._traces = copy.deepcopy(cls._traces)
257 cls._register_traces()
261 """ Returns the type of the Resource Manager
267 def get_attributes(cls):
268 """ Returns a copy of the attributes
271 return copy.deepcopy(cls._attributes.values())
274 def get_attribute(cls, name):
275 """ Returns a copy of the attribute with name 'name'
278 return copy.deepcopy(cls._attributes[name])
282 """ Returns a copy of the traces
285 return copy.deepcopy(cls._traces.values())
289 """ Returns the description of the type of Resource
295 def get_platform(cls):
296 """ Returns the identified of the platform (i.e. testbed type)
303 def get_global(cls, name):
304 """ Returns the value of a global attribute
305 Global attribute meaning an attribute for
306 all the resources from a rtype
308 :param name: Name of the attribute
312 global_attr = cls._attributes[name]
313 return global_attr.value
316 def set_global(cls, name, value):
317 """ Set value for a global attribute
319 :param name: Name of the attribute
321 :param name: Value of the attribute
324 global_attr = cls._attributes[name]
325 global_attr.value = value
328 def __init__(self, ec, guid):
329 super(ResourceManager, self).__init__(self.get_rtype())
332 self._ec = weakref.ref(ec)
333 self._connections = set()
334 self._conditions = dict()
336 # the resource instance gets a copy of all attributes
337 self._attrs = copy.deepcopy(self._attributes)
339 # the resource instance gets a copy of all traces
340 self._trcs = copy.deepcopy(self._traces)
342 # Each resource is placed on a deployment group by the EC
344 self.deployment_group = None
346 self._start_time = None
347 self._stop_time = None
348 self._discover_time = None
349 self._reserved_time = None
350 self._provision_time = None
351 self._ready_time = None
352 self._release_time = None
353 self._failed_time = None
355 self._state = ResourceState.NEW
357 # instance lock to synchronize exclusive state change methods (such
358 # as deploy and release methods), in order to prevent them from being
359 # executed at the same time and corrupt internal resource state
360 self._release_lock = threading.Lock()
364 """ Returns the global unique identifier of the RM """
369 """ Returns the Experiment Controller of the RM """
373 def connections(self):
374 """ Returns the set of guids of connected RMs """
375 return self._connections
378 def conditions(self):
379 """ Returns the conditions to which the RM is subjected to.
381 This method returns a dictionary of conditions lists indexed by
385 return self._conditions
388 def start_time(self):
389 """ Returns the start time of the RM as a timestamp """
390 return self._start_time
394 """ Returns the stop time of the RM as a timestamp """
395 return self._stop_time
398 def discover_time(self):
399 """ Returns the discover time of the RM as a timestamp """
400 return self._discover_time
403 def reserved_time(self):
404 """ Returns the reserved time of the RM as a timestamp """
405 return self._reserved_time
408 def provision_time(self):
409 """ Returns the provision time of the RM as a timestamp """
410 return self._provision_time
413 def ready_time(self):
414 """ Returns the deployment time of the RM as a timestamp """
415 return self._ready_time
418 def release_time(self):
419 """ Returns the release time of the RM as a timestamp """
420 return self._release_time
423 def failed_time(self):
424 """ Returns the time failure occurred for the RM as a timestamp """
425 return self._failed_time
429 """ Get the current state of the RM """
433 def reschedule_delay(self):
434 """ Returns default reschedule delay """
435 return self._reschedule_delay
437 def log_message(self, msg):
438 """ Returns the log message formatted with added information.
440 :param msg: text message
445 return " %s guid %d - %s " % (self._rtype, self.guid, msg)
447 def register_connection(self, guid):
448 """ Registers a connection to the RM identified by guid
450 This method should not be overridden. Specific functionality
451 should be added in the do_connect method.
453 :param guid: Global unique identified of the RM to connect to
457 if self.valid_connection(guid):
458 self.do_connect(guid)
459 self._connections.add(guid)
461 def unregister_connection(self, guid):
462 """ Removes a registered connection to the RM identified by guid
464 This method should not be overridden. Specific functionality
465 should be added in the do_disconnect method.
467 :param guid: Global unique identified of the RM to connect to
471 if guid in self._connections:
472 self.do_disconnect(guid)
473 self._connections.remove(guid)
477 """ Performs resource discovery.
479 This method is responsible for selecting an individual resource
480 matching user requirements.
482 This method should not be overridden directly. Specific functionality
483 should be added in the do_discover method.
486 with self._release_lock:
487 if self._state != ResourceState.RELEASED:
492 """ Performs resource reserve.
494 This method is responsible for reserving an individual resource
495 matching user requirements.
497 This method should not be overridden directly. Specific functionality
498 should be added in the do_reserved method.
501 with self._release_lock:
502 if self._state != ResourceState.RELEASED:
507 """ Performs resource provisioning.
509 This method is responsible for provisioning one resource.
510 After this method has been successfully invoked, the resource
511 should be accessible/controllable by the RM.
513 This method should not be overridden directly. Specific functionality
514 should be added in the do_provision method.
517 with self._release_lock:
518 if self._state != ResourceState.RELEASED:
523 """ Performs resource configuration.
525 This method is responsible for configuring one resource.
526 After this method has been successfully invoked, the resource
527 should be set up to start the experimentation.
529 This method should not be overridden directly. Specific functionality
530 should be added in the do_configure method.
533 with self._release_lock:
534 if self._state != ResourceState.RELEASED:
539 """ Starts the RM (e.g. launch remote process).
541 There is no standard start behavior. Some RMs will not need to perform
542 any actions upon start.
544 This method should not be overridden directly. Specific functionality
545 should be added in the do_start method.
549 if not self.state in [ResourceState.READY, ResourceState.STOPPED]:
550 self.error("Wrong state %s for start" % self.state)
553 with self._release_lock:
554 if self._state != ResourceState.RELEASED:
559 """ Interrupts the RM, stopping any tasks the RM was performing.
561 There is no standard stop behavior. Some RMs will not need to perform
562 any actions upon stop.
564 This method should not be overridden directly. Specific functionality
565 should be added in the do_stop method.
568 if not self.state in [ResourceState.STARTED]:
569 self.error("Wrong state %s for stop" % self.state)
572 with self._release_lock:
577 """ Execute all steps required for the RM to reach the state READY.
579 This method is responsible for deploying the resource (and invoking
580 the discover and provision methods).
582 This method should not be overridden directly. Specific functionality
583 should be added in the do_deploy method.
586 if self.state > ResourceState.READY:
587 self.error("Wrong state %s for deploy" % self.state)
590 with self._release_lock:
591 if self._state != ResourceState.RELEASED:
595 """ Perform actions to free resources used by the RM.
597 This method is responsible for releasing resources that were
598 used during the experiment by the RM.
600 This method should not be overridden directly. Specific functionality
601 should be added in the do_release method.
604 with self._release_lock:
611 err = traceback.format_exc()
612 msg = " %s guid %d ----- FAILED TO RELEASE ----- \n %s " % (
613 self._rtype, self.guid, err)
614 logger = Logger(self._rtype)
618 """ Sets the RM to state FAILED.
620 This method should not be overridden directly. Specific functionality
621 should be added in the do_fail method.
624 with self._release_lock:
625 if self._state != ResourceState.RELEASED:
628 def set(self, name, value):
629 """ Set the value of the attribute
631 :param name: Name of the attribute
633 :param name: Value of the attribute
636 attr = self._attrs[name]
641 """ Returns the value of the attribute
643 :param name: Name of the attribute
647 attr = self._attrs[name]
650 A.Q. Commenting due to performance impact
651 if attr.has_flag(Flags.Global):
652 self.warning( "Attribute %s is global. Use get_global instead." % name)
657 def has_changed(self, name):
658 """ Returns the True is the value of the attribute
659 has been modified by the user.
661 :param name: Name of the attribute
665 attr = self._attrs[name]
666 return attr.has_changed
668 def has_flag(self, name, flag):
669 """ Returns true if the attribute has the flag 'flag'
671 :param flag: Flag to be checked
674 attr = self._attrs[name]
675 return attr.has_flag(flag)
677 def has_attribute(self, name):
678 """ Returns true if the RM has an attribute with name
680 :param name: name of the attribute
683 return name in self._attrs
685 def enable_trace(self, name):
686 """ Explicitly enable trace generation
688 :param name: Name of the trace
691 trace = self._trcs[name]
694 def trace_enabled(self, name):
695 """Returns True if trace is enables
697 :param name: Name of the trace
700 trace = self._trcs[name]
703 def trace(self, name, attr = TraceAttr.ALL, block = 512, offset = 0):
704 """ Get information on collected trace
706 :param name: Name of the trace
709 :param attr: Can be one of:
710 - TraceAttr.ALL (complete trace content),
711 - TraceAttr.STREAM (block in bytes to read starting at offset),
712 - TraceAttr.PATH (full path to the trace file),
713 - TraceAttr.SIZE (size of trace file).
716 :param block: Number of bytes to retrieve from trace, when attr is TraceAttr.STREAM
719 :param offset: Number of 'blocks' to skip, when attr is TraceAttr.STREAM
726 def register_condition(self, action, group, state, time = None):
727 """ Registers a condition on the resource manager to allow execution
728 of 'action' only after 'time' has elapsed from the moment all resources
729 in 'group' reached state 'state'
731 :param action: Action to restrict to condition (either 'START' or 'STOP')
733 :param group: Group of RMs to wait for (list of guids)
734 :type group: int or list of int
735 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
737 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
742 if not action in self.conditions:
743 self._conditions[action] = list()
745 conditions = self.conditions.get(action)
747 # For each condition to register a tuple of (group, state, time) is
748 # added to the 'action' list
749 if not isinstance(group, list):
752 conditions.append((group, state, time))
754 def unregister_condition(self, group, action = None):
755 """ Removed conditions for a certain group of guids
757 :param action: Action to restrict to condition (either 'START', 'STOP' or 'READY')
760 :param group: Group of RMs to wait for (list of guids)
761 :type group: int or list of int
764 # For each condition a tuple of (group, state, time) is
765 # added to the 'action' list
766 if not isinstance(group, list):
769 for act, conditions in self.conditions.iteritems():
770 if action and act != action:
773 for condition in list(conditions):
774 (grp, state, time) = condition
776 # If there is an intersection between grp and group,
777 # then remove intersected elements
778 intsec = set(group).intersection(set(grp))
780 idx = conditions.index(condition)
782 newgrp.difference_update(intsec)
783 conditions[idx] = (newgrp, state, time)
785 def get_connected(self, rtype = None):
786 """ Returns the list of RM with the type 'rtype'
788 :param rtype: Type of the RM we look for
790 :return: list of guid
793 rclass = ResourceFactory.get_resource_type(rtype)
794 for guid in self.connections:
795 rm = self.ec.get_resource(guid)
796 if not rtype or isinstance(rm, rclass):
800 def is_rm_instance(self, rtype):
801 """ Returns True if the RM is instance of 'rtype'
803 :param rtype: Type of the RM we look for
807 rclass = ResourceFactory.get_resource_type(rtype)
808 if isinstance(self, rclass):
813 def _needs_reschedule(self, group, state, time):
814 """ Internal method that verify if 'time' has elapsed since
815 all elements in 'group' have reached state 'state'.
817 :param group: Group of RMs to wait for (list of guids)
818 :type group: int or list of int
819 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
821 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
824 .. note : time should be written like "2s" or "3m" with s for seconds, m for minutes, h for hours, ...
825 If for example, you need to wait 2min 30sec, time could be "150s" or "2.5m".
826 For the moment, 2m30s is not a correct syntax.
830 delay = self.reschedule_delay
832 # check state and time elapsed on all RMs
834 rm = self.ec.get_resource(guid)
836 # If one of the RMs this resource needs to wait for has FAILED
837 # and is critical we raise an exception
838 if rm.state == ResourceState.FAILED:
839 if not rm.get('critical'):
841 msg = "Resource can not wait for FAILED RM %d. Setting Resource to FAILED"
842 raise RuntimeError, msg
844 # If the RM state is lower than the requested state we must
845 # reschedule (e.g. if RM is READY but we required STARTED).
850 # If there is a time restriction, we must verify the
851 # restriction is satisfied
853 if state == ResourceState.DISCOVERED:
855 elif state == ResourceState.RESERVED:
857 elif state == ResourceState.PROVISIONED:
858 t = rm.provision_time
859 elif state == ResourceState.READY:
861 elif state == ResourceState.STARTED:
863 elif state == ResourceState.STOPPED:
865 elif state == ResourceState.RELEASED:
870 # time already elapsed since RM changed state
871 waited = "%fs" % tdiffsec(tnow(), t)
874 wait = tdiffsec(stabsformat(time), stabsformat(waited))
881 return reschedule, delay
883 def set_with_conditions(self, name, value, group, state, time):
884 """ Set value 'value' on attribute with name 'name' when 'time'
885 has elapsed since all elements in 'group' have reached state
888 :param name: Name of the attribute to set
890 :param name: Value of the attribute to set
892 :param group: Group of RMs to wait for (list of guids)
893 :type group: int or list of int
894 :param state: State to wait for on all RM in group. (either 'STARTED', 'STOPPED' or 'READY')
896 :param time: Time to wait after 'state' is reached on all RMs in group. (e.g. '2s')
901 delay = self.reschedule_delay
903 ## evaluate if set conditions are met
905 # only can set with conditions after the RM is started
906 if self.state != ResourceState.STARTED:
909 reschedule, delay = self._needs_reschedule(group, state, time)
912 callback = functools.partial(self.set_with_conditions,
913 name, value, group, state, time)
914 self.ec.schedule(delay, callback)
916 self.set(name, value)
918 def start_with_conditions(self):
919 """ Starts RM when all the conditions in self.conditions for
920 action 'START' are satisfied.
923 #import pdb;pdb.set_trace()
926 delay = self.reschedule_delay
929 ## evaluate if conditions to start are met
933 # Can only start when RM is either STOPPED or READY
934 if self.state not in [ResourceState.STOPPED, ResourceState.READY]:
936 self.debug("---- RESCHEDULING START ---- state %s " % self.state )
938 start_conditions = self.conditions.get(ResourceAction.START, [])
940 self.debug("---- START CONDITIONS ---- %s" % start_conditions)
942 # Verify all start conditions are met
943 for (group, state, time) in start_conditions:
944 # Uncomment for debug
947 # rm = self.ec.get_resource(guid)
948 # unmet.append((guid, rm._state))
950 #self.debug("---- WAITED STATES ---- %s" % unmet )
952 reschedule, delay = self._needs_reschedule(group, state, time)
957 self.ec.schedule(delay, self.start_with_conditions)
959 self.debug("----- STARTING ---- ")
962 def stop_with_conditions(self):
963 """ Stops RM when all the conditions in self.conditions for
964 action 'STOP' are satisfied.
968 delay = self.reschedule_delay
970 ## evaluate if conditions to stop are met
974 # only can stop when RM is STARTED
975 if self.state != ResourceState.STARTED:
977 self.debug("---- RESCHEDULING STOP ---- state %s " % self.state )
979 self.debug(" ---- STOP CONDITIONS ---- %s" %
980 self.conditions.get(ResourceAction.STOP))
982 stop_conditions = self.conditions.get(ResourceAction.STOP, [])
983 for (group, state, time) in stop_conditions:
984 reschedule, delay = self._needs_reschedule(group, state, time)
989 callback = functools.partial(self.stop_with_conditions)
990 self.ec.schedule(delay, callback)
992 self.debug(" ----- STOPPING ---- ")
995 def deploy_with_conditions(self):
996 """ Deploy RM when all the conditions in self.conditions for
997 action 'READY' are satisfied.
1001 delay = self.reschedule_delay
1003 ## evaluate if conditions to deploy are met
1007 # only can deploy when RM is either NEW, DISCOVERED or PROVISIONED
1008 if self.state not in [ResourceState.NEW, ResourceState.DISCOVERED,
1009 ResourceState.RESERVED, ResourceState.PROVISIONED]:
1010 #### XXX: A.Q. IT SHOULD FAIL IF DEPLOY IS CALLED IN OTHER STATES!
1012 self.debug("---- RESCHEDULING DEPLOY ---- state %s " % self.state )
1014 deploy_conditions = self.conditions.get(ResourceAction.DEPLOY, [])
1016 self.debug("---- DEPLOY CONDITIONS ---- %s" % deploy_conditions)
1018 # Verify all start conditions are met
1019 for (group, state, time) in deploy_conditions:
1020 # Uncomment for debug
1023 # rm = self.ec.get_resource(guid)
1024 # unmet.append((guid, rm._state))
1026 #self.debug("---- WAITED STATES ---- %s" % unmet )
1028 reschedule, delay = self._needs_reschedule(group, state, time)
1033 self.ec.schedule(delay, self.deploy_with_conditions)
1035 self.debug("----- DEPLOYING ---- ")
1038 def do_connect(self, guid):
1039 """ Performs actions that need to be taken upon associating RMs.
1040 This method should be redefined when necessary in child classes.
1044 def do_disconnect(self, guid):
1045 """ Performs actions that need to be taken upon disassociating RMs.
1046 This method should be redefined when necessary in child classes.
1050 def valid_connection(self, guid):
1051 """Checks whether a connection with the other RM
1053 This method need to be redefined by each new Resource Manager.
1055 :param guid: Guid of the current Resource Manager
1063 def do_discover(self):
1064 self.set_discovered()
1066 def do_reserve(self):
1069 def do_provision(self):
1070 self.set_provisioned()
1072 def do_configure(self):
1081 def do_deploy(self):
1084 def do_release(self):
1089 self.ec.inform_failure(self.guid)
1091 def set_started(self, time = None):
1092 """ Mark ResourceManager as STARTED """
1093 self.set_state(ResourceState.STARTED, "_start_time", time)
1094 self.debug("----- STARTED ---- ")
1096 def set_stopped(self, time = None):
1097 """ Mark ResourceManager as STOPPED """
1098 self.set_state(ResourceState.STOPPED, "_stop_time", time)
1099 self.debug("----- STOPPED ---- ")
1101 def set_ready(self, time = None):
1102 """ Mark ResourceManager as READY """
1103 self.set_state(ResourceState.READY, "_ready_time", time)
1104 self.debug("----- READY ---- ")
1106 def set_released(self, time = None):
1107 """ Mark ResourceManager as REALEASED """
1108 self.set_state(ResourceState.RELEASED, "_release_time", time)
1110 msg = " %s guid %d ----- RELEASED ----- " % (self._rtype, self.guid)
1111 logger = Logger(self._rtype)
1114 def set_failed(self, time = None):
1115 """ Mark ResourceManager as FAILED """
1116 self.set_state(ResourceState.FAILED, "_failed_time", time)
1118 msg = " %s guid %d ----- FAILED ----- " % (self._rtype, self.guid)
1119 logger = Logger(self._rtype)
1122 def set_discovered(self, time = None):
1123 """ Mark ResourceManager as DISCOVERED """
1124 self.set_state(ResourceState.DISCOVERED, "_discover_time", time)
1125 self.debug("----- DISCOVERED ---- ")
1127 def set_reserved(self, time = None):
1128 """ Mark ResourceManager as RESERVED """
1129 self.set_state(ResourceState.RESERVED, "_reserved_time", time)
1130 self.debug("----- RESERVED ---- ")
1132 def set_provisioned(self, time = None):
1133 """ Mark ResourceManager as PROVISIONED """
1134 self.set_state(ResourceState.PROVISIONED, "_provision_time", time)
1135 self.debug("----- PROVISIONED ---- ")
1137 def set_state(self, state, state_time_attr, time = None):
1138 """ Set the state of the RM while keeping a trace of the time """
1140 # Ensure that RM state will not change after released
1141 if self._state == ResourceState.RELEASED:
1144 time = time or tnow()
1145 self.set_state_time(state, state_time_attr, time)
1147 def set_state_time(self, state, state_time_attr, time):
1148 """ Set the time for the RM state change """
1149 setattr(self, state_time_attr, time)
1152 class ResourceFactory(object):
1153 _resource_types = dict()
1156 def resource_types(cls):
1157 """Return the type of the Class"""
1158 return cls._resource_types
1161 def get_resource_type(cls, rtype):
1162 """Return the type of the Class"""
1163 return cls._resource_types.get(rtype)
1166 def register_type(cls, rclass):
1167 """Register a new Ressource Manager"""
1168 cls._resource_types[rclass.get_rtype()] = rclass
1171 def create(cls, rtype, ec, guid):
1172 """Create a new instance of a Ressource Manager"""
1173 rclass = cls._resource_types[rtype]
1174 return rclass(ec, guid)
1176 def populate_factory():
1177 """Find and rgister all available RMs
1179 # Once the factory is populated, don't repopulate
1180 if not ResourceFactory.resource_types():
1181 for rclass in find_types():
1182 ResourceFactory.register_type(rclass)
1185 """Look into the different folders to find all the
1186 availables Resources Managers
1188 search_path = os.environ.get("NEPI_SEARCH_PATH", "")
1189 search_path = set(search_path.split(" "))
1192 import nepi.resources
1193 path = os.path.dirname(nepi.resources.__file__)
1194 search_path.add(path)
1198 for importer, modname, ispkg in pkgutil.walk_packages(search_path,
1199 prefix = "nepi.resources."):
1201 loader = importer.find_module(modname)
1204 # Notice: Repeated calls to load_module will act as a reload of the module
1205 if modname in sys.modules:
1206 module = sys.modules.get(modname)
1208 module = loader.load_module(modname)
1210 for attrname in dir(module):
1211 if attrname.startswith("_"):
1214 attr = getattr(module, attrname)
1216 if attr == ResourceManager:
1219 if not inspect.isclass(attr):
1222 if issubclass(attr, ResourceManager):
1225 if not modname in sys.modules:
1226 sys.modules[modname] = module
1231 err = traceback.format_exc()
1232 logger = logging.getLogger("Resource.find_types()")
1233 logger.error("Error while loading Resource Managers %s" % err)