First draft for leases
[plcapi.git] / PLC / LeaseFilter.py
1 #
2 # $Id$
3 # $URL$
4 # Thierry Parmentelat -- INRIA
5 #
6 # Utilities for filtering on leases
7
8
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
14
15 # supersede the generic Filter class to support time intersection
16 class LeaseFilter (Filter):
17     
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
22
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")),
29                      }
30
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) 
35
36     
37     ## canonical type
38     @staticmethod
39     def quote (timestamp): return Timestamp.cast_long(timestamp)
40
41     ## basic SQL utilities
42     @staticmethod
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()
48
49     @staticmethod
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)
53
54     @staticmethod
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()
58
59     @staticmethod
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()
63
64
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):
72             (f,u)=alive
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)
77
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):
83             (f,u)=clip
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)
88
89
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
95         self.local={}
96         self.negation={}
97         for (k,v) in LeaseFilter.local_fields.items():
98             if self.has_key(k):
99                 self.local[k]=self[k]
100                 del self[k]
101                 self.negation[k]=""
102             elif self.has_key('~'+k):
103                 self.local[k]=self['~'+k]
104                 del 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():
109             try:
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]))
113             except Exception,e:
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)
117
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):
122     
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'),
128               }
129     leases = Leases (api, filter)
130
131     result=[]
132
133     # sort node_ids
134     node_ids.sort()
135
136     # scan nodes from the input
137     input_node_id=0
138     # scan nodes from the leases
139     lease_node_id=0
140
141     return '?? what now ??'
142
143 def node_free_leases (node_id, node_leases, t_from, t_until):
144
145     # no lease yet : return one solid lease
146     if not node_leases:
147         return [ {'node_id':node_id,
148                   't_from':t_from,
149                   't_until':t_until} ]
150
151     result=[]
152     current_time=t_from
153     is_on=LeaseFilter.time_in_range(node_leases[0]['t_from'],t_from,t_until)
154
155     while True:
156 #        print 'DBG','current_time',current_time,'is_on',is_on,'result',result
157         # lease is active
158         if is_on:
159             current_time=node_leases[0]['t_until']
160             is_on=False
161             del node_leases[0]
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} )
166             return result
167         # free and has remaining leases
168         else:
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
172             is_on=True
173
174