2 # Thierry Parmentelat -- INRIA
4 # Utilities for filtering on leases
7 from types import StringTypes
8 from PLC.Faults import *
9 from PLC.Filter import Filter
10 from PLC.Parameter import Parameter, Mixed
11 from PLC.Timestamp import Timestamp
13 # supersede the generic Filter class to support time intersection
14 class LeaseFilter (Filter):
16 # general notes on input parameters
17 # int_timestamp: number of seconds since the epoch
18 # str_timestamp: see Timestamp.sql_validate
19 # timeslot: a tuple (from,until), each being either int_timestamp or str_timestamp
21 local_fields = { 'alive': Mixed ( Parameter (int, "int_timestamp: leases alive at that time"),
22 Parameter (str, "str_timestamp: leases alive at that time"),
23 Parameter (tuple,"timeslot: the leases alive during this timeslot")),
24 'clip': Mixed ( Parameter (int, "int_timestamp: leases alive after that time"),
25 Parameter (str, "str_timestamp: leases alive after at that time"),
26 Parameter (tuple,"timeslot: the leases alive during this timeslot")),
29 def __init__(self, fields = {}, filter = {},
30 doc = "Lease filter -- adds the 'alive' and 'clip' capabilities for filtering on leases"):
31 Filter.__init__(self,fields,filter,doc)
32 self.fields.update (LeaseFilter.local_fields)
37 def quote (timestamp): return Timestamp.cast_long(timestamp)
39 ## basic SQL utilities
41 def sql_time_intersect (f1,u1,f2,u2):
42 # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
43 return ("((%(f1)s <= %(f2)s) AND (%(f2)s <= %(u1)s)) " + \
44 "OR ((%(f1)s <= %(u2)s) AND (%(u2)s <= %(u1)s)) " + \
45 "OR ((%(f2)s<=%(f1)s) AND (%(u1)s<=%(u2)s))")%locals()
48 def time_in_range (timestamp,f1,u1):
49 return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
50 and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
53 def sql_time_in_range (timestamp,f1,u1):
54 # is timestamp in [f1,u1]
55 return "((%(f1)s <= %(timestamp)s) AND (%(timestamp)s <= %(u1)s))"%locals()
58 def sql_timeslot_after (f1,u1,mark):
59 # is the lease alive after mark, i.e. u1 >= mark
60 return "(%(u1)s >= %(mark)s)"%locals()
63 ## hooks for the local fields
64 def sql_alive (self, alive):
65 if isinstance (alive,int) or isinstance (alive, StringTypes):
66 # the lease is alive at that time if from <= alive <= until
67 alive=LeaseFilter.quote(alive)
68 return LeaseFilter.sql_time_in_range(alive,'t_from','t_until')
69 elif isinstance (alive,tuple):
71 f=LeaseFilter.quote(f)
72 u=LeaseFilter.quote(u)
73 return LeaseFilter.sql_time_intersect (f,u,'t_from','t_until')
74 else: raise PLCInvalidArgument ("LeaseFilter: alive field %r"%alive)
76 def sql_clip (self, clip):
77 if isinstance (clip,int) or isinstance (clip, StringTypes):
78 start=LeaseFilter.quote(clip)
79 return LeaseFilter.sql_timeslot_after('t_from','t_until',start)
80 elif isinstance (clip,tuple):
82 f=LeaseFilter.quote(f)
83 u=LeaseFilter.quote(u)
84 return LeaseFilter.sql_time_intersect(f,u,'t_from','t_until')
85 else: raise PLCInvalidArgument ("LeaseFilter: clip field %r"%clip)
88 ## supersede the generic Filter 'sql' method
89 def sql(self, api, join_with = "AND"):
90 # preserve locally what belongs to us, hide it from the superclass
91 # self.local is a dict local_key : user_value
92 # self.negation is a dict local_key : string
95 for (k,v) in LeaseFilter.local_fields.items():
100 elif self.has_key('~'+k):
101 self.local[k]=self['~'+k]
103 self.negation[k]="NOT "
104 # run the generic filtering code
105 (where_part,clip_part) = Filter.sql(self,api,join_with)
106 for (k,v) in self.local.items():
108 # locate hook function associated with key
109 method=LeaseFilter.__dict__['sql_'+k]
110 where_part += " %s %s(%s)" %(self.join_with,self.negation[k],method(self,self.local[k]))
112 raise PLCInvalidArgument,"LeaseFilter: something wrong with filter key %s, val was %r -- %r"%(k,v,e)
113 if Filter.debug: print 'LeaseFilter.sql: where_part=',where_part,'clip_part',clip_part
114 return (where_part,clip_part)
116 ######## xxx not sure where this belongs yet
117 # given a set of nodes, and a timeslot,
118 # returns the available leases that have at least a given duration
119 def free_leases (api, node_ids, t_from, t_until, min_duration):
121 # get the leases for these nodes and timeslot
122 filter = {'node_id':node_ids,
123 'clip': (t_from, t_until),
124 # sort by node, and inside one node, chronologically
125 '-SORT' : ('node_id','t_from'),
127 leases = Leases (api, filter)
134 # scan nodes from the input
136 # scan nodes from the leases
139 return '?? what now ??'
141 def node_free_leases (node_id, node_leases, t_from, t_until):
143 # no lease yet : return one solid lease
145 return [ {'node_id':node_id,
151 is_on=LeaseFilter.time_in_range(node_leases[0]['t_from'],t_from,t_until)
154 # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
157 current_time=node_leases[0]['t_until']
160 if not node_leases: return result
161 # free, has no remaining lease
162 elif not node_leases:
163 result.append( {'node_id':node_id, 't_from':current_time, 't_until': t_until} )
165 # free and has remaining leases
167 next_time = node_leases[0]['t_from']
168 result.append( {'node_id':node_id,'t_from':current_time,'t_until':next_time})
169 current_time = next_time