86948ffcd11ae96909b5634fb6477711d43002eb
[plcapi.git] / PLC / LeaseFilter.py
1 #
2 # Thierry Parmentelat -- INRIA
3 #
4 # Utilities for filtering on leases
5 #
6
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
12
13 # supersede the generic Filter class to support time intersection
14 class LeaseFilter (Filter):
15
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
20
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")),
27                      }
28
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)
33
34
35     ## canonical type
36     @staticmethod
37     def quote (timestamp): return Timestamp.cast_long(timestamp)
38
39     ## basic SQL utilities
40     @staticmethod
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()
46
47     @staticmethod
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)
51
52     @staticmethod
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()
56
57     @staticmethod
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()
61
62
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):
70             (f,u)=alive
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)
75
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):
81             (f,u)=clip
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)
86
87
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
93         self.local={}
94         self.negation={}
95         for (k,v) in LeaseFilter.local_fields.items():
96             if self.has_key(k):
97                 self.local[k]=self[k]
98                 del self[k]
99                 self.negation[k]=""
100             elif self.has_key('~'+k):
101                 self.local[k]=self['~'+k]
102                 del 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():
107             try:
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]))
111             except Exception,e:
112                 raise PLCInvalidArgument,"LeaseFilter: something wrong with filter key %s, val was %r -- %r"%(k,v,e)
113         if Filter.debug:
114             print >> log, 'LeaseFilter.sql: where_part=',where_part,'clip_part',clip_part
115         return (where_part,clip_part)
116
117 ######## xxx not sure where this belongs yet
118 # given a set of nodes, and a timeslot,
119 # returns the available leases that have at least a given duration
120 def free_leases (api, node_ids, t_from, t_until, min_duration):
121
122     # get the leases for these nodes and timeslot
123     filter = {'node_id':node_ids,
124               'clip': (t_from, t_until),
125               # sort by node, and inside one node, chronologically
126               '-SORT' : ('node_id','t_from'),
127               }
128     leases = Leases (api, filter)
129
130     result=[]
131
132     # sort node_ids
133     node_ids.sort()
134
135     # scan nodes from the input
136     input_node_id=0
137     # scan nodes from the leases
138     lease_node_id=0
139
140     return '?? what now ??'
141
142 def node_free_leases (node_id, node_leases, t_from, t_until):
143
144     # no lease yet : return one solid lease
145     if not node_leases:
146         return [ {'node_id':node_id,
147                   't_from':t_from,
148                   't_until':t_until} ]
149
150     result=[]
151     current_time=t_from
152     is_on=LeaseFilter.time_in_range(node_leases[0]['t_from'],t_from,t_until)
153
154     while True:
155 #        print 'DBG','current_time',current_time,'is_on',is_on,'result',result
156         # lease is active
157         if is_on:
158             current_time=node_leases[0]['t_until']
159             is_on=False
160             del node_leases[0]
161             if not node_leases: return result
162         # free, has no remaining lease
163         elif not node_leases:
164             result.append( {'node_id':node_id, 't_from':current_time, 't_until': t_until} )
165             return result
166         # free and has remaining leases
167         else:
168             next_time = node_leases[0]['t_from']
169             result.append( {'node_id':node_id,'t_from':current_time,'t_until':next_time})
170             current_time = next_time
171             is_on=True