2 # Thierry Parmentelat -- INRIA
4 # Utilities for filtering on leases
6 # w0622: we keep on using 'filter' as a variable name
7 # pylint: disable=c0111, c0103, w0622
11 from PLC.Faults import * # pylint: disable=w0401, w0614
12 from PLC.Filter import Filter
13 from PLC.Parameter import Parameter, Mixed
14 from PLC.Timestamp import Timestamp
16 # supersede the generic Filter class to support time intersection
19 class LeaseFilter(Filter):
21 # general notes on input parameters
22 # int_timestamp: number of seconds since the epoch
23 # str_timestamp: see Timestamp.sql_validate
24 # timeslot: a tuple (from, until), each being either int_timestamp or
29 Parameter(int, "int_timestamp: leases alive at that time"),
30 Parameter(str, "str_timestamp: leases alive at that time"),
31 Parameter(list, "timeslot: the leases alive during this timeslot"),
34 Parameter(int, "int_timestamp: leases alive after that time"),
35 Parameter(str, "str_timestamp: leases alive after that time"),
36 Parameter(list, "timeslot: the leases alive during this timeslot"),
39 # {'day' : 0} : all leases from today and on
40 # {'day' : 1} : all leases today (localtime at the myplc)
41 # {'day' : 2} : all leases today and tomorrow (localtime at the myplc)
45 "clip on a number of days from today and on;"
46 " 0 means no limit in the future"),
49 def __init__(self, fields=None, filter=None,
50 doc="Lease filter -- adds the 'alive' and 'clip'"
51 "capabilities for filtering on leases"):
56 Filter.__init__(self, fields, filter, doc)
57 self.fields.update(LeaseFilter.local_fields)
62 return Timestamp.cast_long(timestamp)
66 def sql_time_intersect(f1, u1, f2, u2):
67 # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
69 "(({f1} <= {f2}) AND ({f2} <= {u1})) "
70 "OR (({f1} <= {u2}) AND ({u2} <= {u1})) "
71 "OR (({f2}<={f1}) AND ({u1}<={u2}))"
72 .format(f1=f1, u1=u1, f2=f2, u2=u2))
75 def time_in_range(timestamp, f1, u1):
76 return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
77 and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
80 def sql_time_in_range(timestamp, f1, u1):
81 # is timestamp in [f1, u1]
83 "(({f1} <= {timestamp}) AND ({timestamp} <= {u1}))"
84 .format(timestamp=timestamp, f1=f1, u1=u1))
87 def sql_timeslot_after(f1, u1, mark):
88 # is the lease alive after mark, i.e. u1 >= mark
89 return "({u1} >= {mark})".format(u1=u1, mark=mark)
91 # hooks for the local fields
92 def sql_alive(self, alive):
93 if isinstance(alive, (int, str)):
94 # the lease is alive at that time if from <= alive <= until
95 alive = LeaseFilter.quote(alive)
96 return LeaseFilter.sql_time_in_range(alive, 't_from', 't_until')
97 elif isinstance(alive, (tuple, list)):
99 f = LeaseFilter.quote(f)
100 u = LeaseFilter.quote(u)
101 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
103 raise PLCInvalidArgument("LeaseFilter: alive field {}"
106 def sql_clip(self, clip):
107 if isinstance(clip, int) or isinstance(clip, str):
108 start = LeaseFilter.quote(clip)
109 return LeaseFilter.sql_timeslot_after('t_from', 't_until', start)
110 elif isinstance(clip, (tuple, list)):
112 f = LeaseFilter.quote(f)
113 u = LeaseFilter.quote(u)
114 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
116 raise PLCInvalidArgument("LeaseFilter: clip field {}"
119 # the whole key to implementing day is to compute today's beginning
120 def today_start(self):
122 st = time.localtime()
123 seconds_today = st.tm_hour * 3600 + st.tm_min * 60 + st.tm_sec
124 return int(time.time()) - seconds_today
126 # supersede the generic Filter 'sql' method
127 def sql(self, api, join_with="AND"):
128 # implement 'day' as a clip
131 raise PLCInvalidArgument("LeaseFilter cannot have both 'clip' and 'day'")
132 today = self.today_start()
133 nb_days = self['day']
137 self['clip'] = (today, today + nb_days * 24 * 3600)
140 # preserve locally what belongs to us, hide it from the superclass
141 # self.local is a dict local_key : user_value
142 # self.negation is a dict local_key : string
145 for (k, v) in list(LeaseFilter.local_fields.items()):
147 self.local[k] = self[k]
149 self.negation[k] = ""
150 elif ('~' + k) in self:
151 self.local[k] = self['~' + k]
153 self.negation[k] = "NOT "
154 # run the generic filtering code
155 (where_part, clip_part) = Filter.sql(self, api, join_with)
156 for (k, v) in list(self.local.items()):
158 # locate hook function associated with key
159 method = LeaseFilter.__dict__['sql_' + k]
160 where_part += " {} {}({})"\
161 .format(self.join_with,
163 method(self, self.local[k]))
164 except Exception as e:
165 raise PLCInvalidArgument(
166 "LeaseFilter: something wrong with filter"
167 "key {}, val was {} -- {}".format(k, v, e))
168 return (where_part, clip_part)
170 # xxx not sure where this belongs yet
171 # given a set of nodes, and a timeslot,
172 # returns the available leases that have at least a given duration
175 def free_leases(api, node_ids, t_from, t_until, min_duration):
177 # get the leases for these nodes and timeslot
178 filter = {'node_id': node_ids,
179 'clip': (t_from, t_until),
180 # sort by node, and inside one node, chronologically
181 '-SORT': ('node_id', 't_from'),
183 leases = Leases(api, filter)
190 # scan nodes from the input
192 # scan nodes from the leases
195 return '?? what now ??'
198 def node_free_leases(node_id, node_leases, t_from, t_until):
200 # no lease yet : return one solid lease
202 return [{'node_id': node_id,
207 current_time = t_from
208 is_on = LeaseFilter.time_in_range(
209 node_leases[0]['t_from'], t_from, t_until)
212 # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
215 current_time = node_leases[0]['t_until']
220 # free, has no remaining lease
221 elif not node_leases:
224 't_from': current_time, 't_until': t_until})
226 # free and has remaining leases
228 next_time = node_leases[0]['t_from']
231 't_from': current_time, 't_until': next_time})
232 current_time = next_time