4 # Thierry Parmentelat -- INRIA
6 # Utilities for filtering on leases
9 from types import StringTypes
10 from PLC.Faults import *
11 from PLC.Filter import Filter
12 from PLC.Parameter import Parameter, Mixed
13 from PLC.Timestamp import Timestamp
15 # supersede the generic Filter class to support time intersection
16 class LeaseFilter (Filter):
18 # general notes on input parameters
19 # int_timestamp: number of seconds since the epoch
20 # str_timestamp: see Timestamp.sql_validate
21 # timeslot: a tuple (from,until), each being either int_timestamp or str_timestamp
23 local_fields = { 'alive': Mixed ( Parameter (int, "int_timestamp: leases alive at that time"),
24 Parameter (str, "str_timestamp: leases alive at that time"),
25 Parameter (tuple,"timeslot: the leases alive during this timeslot")),
26 'clip': Mixed ( Parameter (int, "int_timestamp: leases alive after that time"),
27 Parameter (str, "str_timestamp: leases alive after at that time"),
28 Parameter (tuple,"timeslot: the leases alive during this timeslot")),
31 def __init__(self, fields = {}, filter = {},
32 doc = "Lease filter -- adds the 'alive' and 'clip' capabilities for filtering on leases"):
33 Filter.__init__(self,fields,filter,doc)
34 self.fields.update (LeaseFilter.local_fields)
39 def quote (timestamp): return Timestamp.cast_long(timestamp)
41 ## basic SQL utilities
43 def sql_time_intersect (f1,u1,f2,u2):
44 # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
45 return ("((%(f1)s <= %(f2)s) AND (%(f2)s <= %(u1)s)) " + \
46 "OR ((%(f1)s <= %(u2)s) AND (%(u2)s <= %(u1)s)) " + \
47 "OR ((%(f2)s<=%(f1)s) AND (%(u1)s<=%(u2)s))")%locals()
50 def time_in_range (timestamp,f1,u1):
51 return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
52 and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
55 def sql_time_in_range (timestamp,f1,u1):
56 # is timestamp in [f1,u1]
57 return "((%(f1)s <= %(timestamp)s) AND (%(timestamp)s <= %(u1)s))"%locals()
60 def sql_timeslot_after (f1,u1,mark):
61 # is the lease alive after mark, i.e. u1 >= mark
62 return "(%(u1)s >= %(mark)s)"%locals()
65 ## hooks for the local fields
66 def sql_alive (self, alive):
67 if isinstance (alive,int) or isinstance (alive, StringTypes):
68 # the lease is alive at that time if from <= alive <= until
69 alive=LeaseFilter.quote(alive)
70 return LeaseFilter.sql_time_in_range(alive,'t_from','t_until')
71 elif isinstance (alive,tuple):
73 f=LeaseFilter.quote(f)
74 u=LeaseFilter.quote(u)
75 return LeaseFilter.sql_time_intersect (f,u,'t_from','t_until')
76 else: raise PLCInvalidArgument ("LeaseFilter: alive field %r"%alive)
78 def sql_clip (self, clip):
79 if isinstance (clip,int) or isinstance (clip, StringTypes):
80 start=LeaseFilter.quote(clip)
81 return LeaseFilter.sql_timeslot_after('t_from','t_until',start)
82 elif isinstance (clip,tuple):
84 f=LeaseFilter.quote(f)
85 u=LeaseFilter.quote(u)
86 return LeaseFilter.sql_time_intersect(f,u,'t_from','t_until')
87 else: raise PLCInvalidArgument ("LeaseFilter: clip field %r"%clip)
90 ## supersede the generic Filter 'sql' method
91 def sql(self, api, join_with = "AND"):
92 # preserve locally what belongs to us, hide it from the superclass
93 # self.local is a dict local_key : user_value
94 # self.negation is a dict local_key : string
97 for (k,v) in LeaseFilter.local_fields.items():
102 elif self.has_key('~'+k):
103 self.local[k]=self['~'+k]
105 self.negation[k]="NOT "
106 # run the generic filtering code
107 (where_part,clip_part) = Filter.sql(self,api,join_with)
108 for (k,v) in self.local.items():
110 # locate hook function associated with key
111 method=LeaseFilter.__dict__['sql_'+k]
112 where_part += " %s %s(%s)" %(self.join_with,self.negation[k],method(self,self.local[k]))
114 raise PLCInvalidArgument,"LeaseFilter: something wrong with filter key %s, val was %r -- %r"%(k,v,e)
115 if Filter.debug: print 'LeaseFilter.sql: where_part=',where_part,'clip_part',clip_part
116 return (where_part,clip_part)
118 ######## xxx not sure where this belongs yet
119 # given a set of nodes, and a timeslot,
120 # returns the available leases that have at least a given duration
121 def free_leases (api, node_ids, t_from, t_until, min_duration):
123 # get the leases for these nodes and timeslot
124 filter = {'node_id':node_ids,
125 'clip': (t_from, t_until),
126 # sort by node, and inside one node, chronologically
127 '-SORT' : ('node_id','t_from'),
129 leases = Leases (api, filter)
136 # scan nodes from the input
138 # scan nodes from the leases
141 return '?? what now ??'
143 def node_free_leases (node_id, node_leases, t_from, t_until):
145 # no lease yet : return one solid lease
147 return [ {'node_id':node_id,
153 is_on=LeaseFilter.time_in_range(node_leases[0]['t_from'],t_from,t_until)
156 # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
159 current_time=node_leases[0]['t_until']
162 if not node_leases: return result
163 # free, has no remaining lease
164 elif not node_leases:
165 result.append( {'node_id':node_id, 't_from':current_time, 't_until': t_until} )
167 # free and has remaining leases
169 next_time = node_leases[0]['t_from']
170 result.append( {'node_id':node_id,'t_from':current_time,'t_until':next_time})
171 current_time = next_time