moving towards reservable nodes
[nodemanager.git] / plugins / reservation.py
index 21e3e55..ddb974b 100644 (file)
 # $Id$
 # $URL$
 #
-# NodeManager plugin - first step of handling omf_controlled slices
+# NodeManager plugin - first step of handling reservable nodes
 
 """
-Overwrites the 'resctl' tag of slivers controlled by OMF so sm.py does the right thing
+Manages running slices when reservation_policy is 'lease_or_idle' or 'lease_or_shared'
 """
 
+import time
+import threading
+
 import logger
 
 priority = 45
-# this instructs nodemanager that we want to use the latest known data when the plc link is down
+
+# this instructs nodemanager that we want to use the latest known data in case the plc link is down
 persistent_data = True
 
+# of course things would be simpler if node manager was to create one instance of the plugins 
+# instead of blindly caling functions in the module...
+
+##############################
+# rough implementation for a singleton class
+def Singleton (klass,*args,**kwds):
+    if not hasattr(klass,'_instance'):
+        klass._instance=klass(*args,**kwds)
+    return klass._instance
+
 def start(options, conf):
-    logger.log("reservation: plugin starting up...")
+    return Singleton(reservation).start(options,conf)
 
 def GetSlivers(data, conf = None, plc = None):
+    return Singleton(reservation).GetSlivers(data, conf, plc)
+
+##############################
+class reservation:
+
+    def __init__ (self):
+        # the last snapshot of data exposed by GetSlivers
+        self.data = None
+        # this is a dict mapping a raounded timestamp to the corr. Timer object
+        self.timers = {}
+    # the granularity is set in the API (initial value is 15 minutes)
+    # and it used to round all leases start/until times
+    # changing this dynamically can have some weird effects of course..
+    def granularity (self):
+        try:
+            return self.data['lease_granularity']
+        # in case we'd try to access this before it's populated..
+        except:
+            return 60*60
+
+    # round to granularity
+    def round_time (self, time):
+        granularity=self.granularity()
+        return ((int(time)+granularity/2)/granularity)*granularity
+
+    def clear_timers (self):
+        for timer in self.timers.values():
+            timer.cancel()
+        self.timers={}
+
+    def clear_timer (self,timestamp):
+        round=self.round_time(timestamp)
+        if self.timers.has_key(round):
+            timer=self.timers[round]
+            timer.cancel()
+            del self.timers[round]
+
+    def sync_timers_from_leases (self):
+        self.clear_timers()
+        for lease in self.data['leases']:
+            self.ensure_timer(lease['t_from'])
+            self.ensure_timer(lease['t_until'])
+
+    def ensure_timer(self, timestamp):
+        now=time.time()
+        # forget about past events
+        if timestamp < now: return
+        round=self.round_time(timestamp)
+        if self.timers.has_key(round): return
+        def this_closure ():
+            self.round_time_callback (round)
+        timer=threading.Timer(timestamp-now,this_closure)
+        self.timers[round]=timer
+        timer.start()
+
+    def round_time_callback (self, time_arg):
+        now=time.time()
+        round_now=self.round_time(now)
+        logger.log('reservation.round_time_callback now=%f round_now=%d arg=%d...'%(now,round_now,time_arg))
+        leases_text="leases=%r"%self.data['leases']
+        logger.log(leases_text)
+
+    def show_time (self, timestamp):
+        return time.strftime ("%Y-%m-%d %H:%M %Z",time.gmtime(timestamp))
+
+    ####################
+    def start(self,options,conf):
+        logger.log("reservation: plugin performing dummy start...")
+
+    # this method is entirely about making sure that we have events scheduled 
+    # at the <granularity> intervals where there is a lease that starts or ends
+    def GetSlivers (self, data, conf=None, plc=None):
+    
+        # check we're using a compliant GetSlivers
+        if 'reservation_policy' not in data: 
+            logger.log_missing_data("reservation.GetSlivers",'reservation_policy')
+            return
+        reservation_policy=data['reservation_policy']
+        if 'leases' not in data: 
+            logger.log_missing_data("reservation.GetSlivers",'leases')
+            return
+    
+
+        # store data locally
+        # since we've asked for persistent_data, we should not get an empty data here
+        if data: self.data = data
 
-    if 'reservation_policy' not in data: 
-        logger.log_missing_data("reservation.GetSlivers",'reservation_policy')
-        return
-    reservation_policy=data['reservation_policy']
-
-    if 'leases' not in data: 
-        logger.log_missing_data("reservation.GetSlivers",'leases')
-        return
-
-    if reservation_policy in ['lease_or_idle','lease_or_shared']:
-        logger.log( 'reservation.GetSlivers - scaffolding...')
-    elif reservation_policy == 'none':
-        return
-    else:
-        logger.log("reservation: ignoring -- unexpected value for reservation_policy %r"%reservation_policy)
-        return
+        # regular nodes are not affected
+        if reservation_policy == 'none':
+            return
+        elif reservation_policy not in ['lease_or_idle','lease_or_shared']:
+            logger.log("reservation: ignoring -- unexpected value for reservation_policy %r"%reservation_policy)
+            return
+        # at this point we have reservation_policy in ['lease_or_idle','lease_or_shared']
+        # we make no difference for now
+        logger.verbose('reservation.GetSlivers : reservable node -- listing timers ')
+        
+        self.sync_timers_from_leases()
+        for timestamp in self.timers.keys():
+            logger.verbose('TIMER armed for %s'%self.show_time(timestamp))
+           
+        logger.verbose('reservation.GetSlivers : end listing timers')
+