--- /dev/null
+#
+# $Id$
+# $URL$
+# Thierry Parmentelat -- INRIA
+#
+# Utilities for filtering on leases
+#
+
+from types import StringTypes
+from PLC.Faults import *
+from PLC.Filter import Filter
+from PLC.Parameter import Parameter, Mixed
+from PLC.Timestamp import Timestamp
+
+# supersede the generic Filter class to support time intersection
+class LeaseFilter (Filter):
+
+ # general notes on input parameters
+ # int_timestamp: number of seconds since the epoch
+ # str_timestamp: see Timestamp.sql_validate
+ # timeslot: a tuple (from,until), each being either int_timestamp or str_timestamp
+
+ local_fields = { 'alive': Mixed ( Parameter (int, "int_timestamp: leases alive at that time"),
+ Parameter (str, "str_timestamp: leases alive at that time"),
+ Parameter (tuple,"timeslot: the leases alive during this timeslot")),
+ 'clip': Mixed ( Parameter (int, "int_timestamp: leases alive after that time"),
+ Parameter (str, "str_timestamp: leases alive after at that time"),
+ Parameter (tuple,"timeslot: the leases alive during this timeslot")),
+ }
+
+ def __init__(self, fields = {}, filter = {},
+ doc = "Lease filter -- adds the 'alive' and 'clip' capabilities for filtering on leases"):
+ Filter.__init__(self,fields,filter,doc)
+ self.fields.update (LeaseFilter.local_fields)
+
+
+ ## canonical type
+ @staticmethod
+ def quote (timestamp): return Timestamp.cast_long(timestamp)
+
+ ## basic SQL utilities
+ @staticmethod
+ def sql_time_intersect (f1,u1,f2,u2):
+ # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
+ return ("((%(f1)s <= %(f2)s) AND (%(f2)s <= %(u1)s)) " + \
+ "OR ((%(f1)s <= %(u2)s) AND (%(u2)s <= %(u1)s)) " + \
+ "OR ((%(f2)s<=%(f1)s) AND (%(u1)s<=%(u2)s))")%locals()
+
+ @staticmethod
+ def time_in_range (timestamp,f1,u1):
+ return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
+ and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
+
+ @staticmethod
+ def sql_time_in_range (timestamp,f1,u1):
+ # is timestamp in [f1,u1]
+ return "((%(f1)s <= %(timestamp)s) AND (%(timestamp)s <= %(u1)s))"%locals()
+
+ @staticmethod
+ def sql_timeslot_after (f1,u1,mark):
+ # is the lease alive after mark, i.e. u1 >= mark
+ return "(%(u1)s >= %(mark)s)"%locals()
+
+
+ ## hooks for the local fields
+ def sql_alive (self, alive):
+ if isinstance (alive,int) or isinstance (alive, StringTypes):
+ # the lease is alive at that time if from <= alive <= until
+ alive=LeaseFilter.quote(alive)
+ return LeaseFilter.sql_time_in_range(alive,'t_from','t_until')
+ elif isinstance (alive,tuple):
+ (f,u)=alive
+ f=LeaseFilter.quote(f)
+ u=LeaseFilter.quote(u)
+ return LeaseFilter.sql_time_intersect (f,u,'t_from','t_until')
+ else: raise PLCInvalidArgument ("LeaseFilter: alive field %r"%alive)
+
+ def sql_clip (self, clip):
+ if isinstance (clip,int) or isinstance (clip, StringTypes):
+ start=LeaseFilter.quote(clip)
+ return LeaseFilter.sql_timeslot_after('t_from','t_until',start)
+ elif isinstance (clip,tuple):
+ (f,u)=clip
+ f=LeaseFilter.quote(f)
+ u=LeaseFilter.quote(u)
+ return LeaseFilter.sql_time_intersect(f,u,'t_from','t_until')
+ else: raise PLCInvalidArgument ("LeaseFilter: clip field %r"%clip)
+
+
+ ## supersede the generic Filter 'sql' method
+ def sql(self, api, join_with = "AND"):
+ # preserve locally what belongs to us, hide it from the superclass
+ # self.local is a dict local_key : user_value
+ # self.negation is a dict local_key : string
+ self.local={}
+ self.negation={}
+ for (k,v) in LeaseFilter.local_fields.items():
+ if self.has_key(k):
+ self.local[k]=self[k]
+ del self[k]
+ self.negation[k]=""
+ elif self.has_key('~'+k):
+ self.local[k]=self['~'+k]
+ del self['~'+k]
+ self.negation[k]="NOT "
+ # run the generic filtering code
+ (where_part,clip_part) = Filter.sql(self,api,join_with)
+ for (k,v) in self.local.items():
+ try:
+ # locate hook function associated with key
+ method=LeaseFilter.__dict__['sql_'+k]
+ where_part += " %s %s(%s)" %(self.join_with,self.negation[k],method(self,self.local[k]))
+ except Exception,e:
+ raise PLCInvalidArgument,"LeaseFilter: something wrong with filter key %s, val was %r -- %r"%(k,v,e)
+ if Filter.debug: print 'LeaseFilter.sql: where_part=',where_part,'clip_part',clip_part
+ return (where_part,clip_part)
+
+######## xxx not sure where this belongs yet
+# given a set of nodes, and a timeslot,
+# returns the available leases that have at least a given duration
+def free_leases (api, node_ids, t_from, t_until, min_duration):
+
+ # get the leases for these nodes and timeslot
+ filter = {'node_id':node_ids,
+ 'clip': (t_from, t_until),
+ # sort by node, and inside one node, chronologically
+ '-SORT' : ('node_id','t_from'),
+ }
+ leases = Leases (api, filter)
+
+ result=[]
+
+ # sort node_ids
+ node_ids.sort()
+
+ # scan nodes from the input
+ input_node_id=0
+ # scan nodes from the leases
+ lease_node_id=0
+
+ return '?? what now ??'
+
+def node_free_leases (node_id, node_leases, t_from, t_until):
+
+ # no lease yet : return one solid lease
+ if not node_leases:
+ return [ {'node_id':node_id,
+ 't_from':t_from,
+ 't_until':t_until} ]
+
+ result=[]
+ current_time=t_from
+ is_on=LeaseFilter.time_in_range(node_leases[0]['t_from'],t_from,t_until)
+
+ while True:
+# print 'DBG','current_time',current_time,'is_on',is_on,'result',result
+ # lease is active
+ if is_on:
+ current_time=node_leases[0]['t_until']
+ is_on=False
+ del node_leases[0]
+ if not node_leases: return result
+ # free, has no remaining lease
+ elif not node_leases:
+ result.append( {'node_id':node_id, 't_from':current_time, 't_until': t_until} )
+ return result
+ # free and has remaining leases
+ else:
+ next_time = node_leases[0]['t_from']
+ result.append( {'node_id':node_id,'t_from':current_time,'t_until':next_time})
+ current_time = next_time
+ is_on=True
+
+