2 # Thierry Parmentelat -- INRIA
4 # Utilities for filtering on leases
9 from PLC.Faults import *
10 from PLC.Filter import Filter
11 from PLC.Parameter import Parameter, Mixed
12 from PLC.Timestamp import Timestamp
14 # supersede the generic Filter class to support time intersection
17 class LeaseFilter (Filter):
19 # general notes on input parameters
20 # int_timestamp: number of seconds since the epoch
21 # str_timestamp: see Timestamp.sql_validate
22 # timeslot: a tuple (from, until), each being either int_timestamp or
28 int, "int_timestamp: leases alive at that time"),
30 str, "str_timestamp: leases alive at that time"),
32 tuple, "timeslot: the leases alive during this timeslot")),
35 int, "int_timestamp: leases alive after that time"),
37 str, "str_timestamp: leases alive after at that time"),
39 tuple, "timeslot: the leases alive during this timeslot")),
41 # {'day' : 0} : all leases from today and on
42 # {'day' : 1} : all leases today (localtime at the myplc)
43 # {'day' : 2} : all leases today and tomorrow (localtime at the myplc)
45 'day': Parameter(int, "clip on a number of days from today and on;"
46 " 0 means no limit in the future"),
49 def __init__(self, fields={}, filter={},
50 doc="Lease filter -- adds the 'alive' and 'clip'"
51 "capabilities for filtering on leases"):
52 Filter.__init__(self, fields, filter, doc)
53 self.fields.update(LeaseFilter.local_fields)
57 def quote(timestamp): return Timestamp.cast_long(timestamp)
61 def sql_time_intersect(f1, u1, f2, u2):
62 # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
63 return ("(({f1} <= {f2}) AND ({f2} <= {u1})) " +
64 "OR (({f1} <= {u2}) AND ({u2} <= {u1})) " +
65 "OR (({f2}<={f1}) AND ({u1}<={u2}))").format(**locals())
68 def time_in_range(timestamp, f1, u1):
69 return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
70 and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
73 def sql_time_in_range(timestamp, f1, u1):
74 # is timestamp in [f1, u1]
75 return "(({f1} <= {timestamp}) AND ({timestamp} <= {u1}))"\
79 def sql_timeslot_after(f1, u1, mark):
80 # is the lease alive after mark, i.e. u1 >= mark
81 return "({u1} >= {mark})".format(**locals())
83 # hooks for the local fields
84 def sql_alive(self, alive):
85 if isinstance(alive, int) or isinstance(alive, str):
86 # the lease is alive at that time if from <= alive <= until
87 alive = LeaseFilter.quote(alive)
88 return LeaseFilter.sql_time_in_range(alive, 't_from', 't_until')
89 elif isinstance(alive, tuple):
91 f = LeaseFilter.quote(f)
92 u = LeaseFilter.quote(u)
93 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
95 raise PLCInvalidArgument("LeaseFilter: alive field {}"
98 def sql_clip(self, clip):
99 if isinstance(clip, int) or isinstance(clip, str):
100 start = LeaseFilter.quote(clip)
101 return LeaseFilter.sql_timeslot_after('t_from', 't_until', start)
102 elif isinstance(clip, tuple):
104 f = LeaseFilter.quote(f)
105 u = LeaseFilter.quote(u)
106 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
108 raise PLCInvalidArgument("LeaseFilter: clip field {}"
111 # the whole key to implementing day is to compute today's beginning
112 def today_start(self):
114 st = time.localtime()
115 seconds_today = st.tm_hour * 3600 + st.tm_min * 60 + st.tm_sec
116 return int(time.time()) - seconds_today
118 # supersede the generic Filter 'sql' method
119 def sql(self, api, join_with="AND"):
120 # implement 'day' as a clip
123 raise PLCInvalidArgument("LeaseFilter cannot have both 'clip' and 'day'")
124 today = self.today_start()
125 nb_days = self['day']
129 self['clip'] = (today, today + nb_days * 24 * 3600)
132 # preserve locally what belongs to us, hide it from the superclass
133 # self.local is a dict local_key : user_value
134 # self.negation is a dict local_key : string
137 for (k, v) in list(LeaseFilter.local_fields.items()):
139 self.local[k] = self[k]
141 self.negation[k] = ""
142 elif ('~' + k) in self:
143 self.local[k] = self['~' + k]
145 self.negation[k] = "NOT "
146 # run the generic filtering code
147 (where_part, clip_part) = Filter.sql(self, api, join_with)
148 for (k, v) in list(self.local.items()):
150 # locate hook function associated with key
151 method = LeaseFilter.__dict__['sql_' + k]
152 where_part += " {} {}({})"\
153 .format(self.join_with,
155 method(self, self.local[k]))
156 except Exception as e:
157 raise PLCInvalidArgument(
158 "LeaseFilter: something wrong with filter"
159 "key {}, val was {} -- {}".format(k, v, e))
160 return (where_part, clip_part)
162 # xxx not sure where this belongs yet
163 # given a set of nodes, and a timeslot,
164 # returns the available leases that have at least a given duration
167 def free_leases(api, node_ids, t_from, t_until, min_duration):
169 # get the leases for these nodes and timeslot
170 filter = {'node_id': node_ids,
171 'clip': (t_from, t_until),
172 # sort by node, and inside one node, chronologically
173 '-SORT': ('node_id', 't_from'),
175 leases = Leases(api, filter)
182 # scan nodes from the input
184 # scan nodes from the leases
187 return '?? what now ??'
190 def node_free_leases(node_id, node_leases, t_from, t_until):
192 # no lease yet : return one solid lease
194 return [{'node_id': node_id,
199 current_time = t_from
200 is_on = LeaseFilter.time_in_range(
201 node_leases[0]['t_from'], t_from, t_until)
204 # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
207 current_time = node_leases[0]['t_until']
212 # free, has no remaining lease
213 elif not node_leases:
216 't_from': current_time, 't_until': t_until})
218 # free and has remaining leases
220 next_time = node_leases[0]['t_from']
223 't_from': current_time, 't_until': next_time})
224 current_time = next_time