2 # Thierry Parmentelat -- INRIA
4 # 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
18 class LeaseFilter (Filter):
20 # general notes on input parameters
21 # int_timestamp: number of seconds since the epoch
22 # str_timestamp: see Timestamp.sql_validate
23 # timeslot: a tuple (from, until), each being either int_timestamp or
29 int, "int_timestamp: leases alive at that time"),
31 str, "str_timestamp: leases alive at that time"),
33 tuple, "timeslot: the leases alive during this timeslot")),
36 int, "int_timestamp: leases alive after that time"),
38 str, "str_timestamp: leases alive after at that time"),
40 tuple, "timeslot: the leases alive during this timeslot")),
42 # {'day' : 0} : all leases from today and on
43 # {'day' : 1} : all leases today (localtime at the myplc)
44 # {'day' : 2} : all leases today and tomorrow (localtime at the myplc)
46 'day': Parameter(int, "clip on a number of days from today and on;"
47 " 0 means no limit in the future"),
50 def __init__(self, fields={}, filter={},
51 doc="Lease filter -- adds the 'alive' and 'clip'"
52 "capabilities for filtering on leases"):
53 Filter.__init__(self, fields, filter, doc)
54 self.fields.update(LeaseFilter.local_fields)
58 def quote(timestamp): return Timestamp.cast_long(timestamp)
62 def sql_time_intersect(f1, u1, f2, u2):
63 # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
64 return ("(({f1} <= {f2}) AND ({f2} <= {u1})) " +
65 "OR (({f1} <= {u2}) AND ({u2} <= {u1})) " +
66 "OR (({f2}<={f1}) AND ({u1}<={u2}))").format(**locals())
69 def time_in_range(timestamp, f1, u1):
70 return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
71 and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
74 def sql_time_in_range(timestamp, f1, u1):
75 # is timestamp in [f1, u1]
76 return "(({f1} <= {timestamp}) AND ({timestamp} <= {u1}))"\
80 def sql_timeslot_after(f1, u1, mark):
81 # is the lease alive after mark, i.e. u1 >= mark
82 return "({u1} >= {mark})".format(**locals())
84 # hooks for the local fields
85 def sql_alive(self, alive):
86 if isinstance(alive, int) or isinstance(alive, StringTypes):
87 # the lease is alive at that time if from <= alive <= until
88 alive = LeaseFilter.quote(alive)
89 return LeaseFilter.sql_time_in_range(alive, 't_from', 't_until')
90 elif isinstance(alive, tuple):
92 f = LeaseFilter.quote(f)
93 u = LeaseFilter.quote(u)
94 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
96 raise PLCInvalidArgument("LeaseFilter: alive field {}"
99 def sql_clip(self, clip):
100 if isinstance(clip, int) or isinstance(clip, StringTypes):
101 start = LeaseFilter.quote(clip)
102 return LeaseFilter.sql_timeslot_after('t_from', 't_until', start)
103 elif isinstance(clip, tuple):
105 f = LeaseFilter.quote(f)
106 u = LeaseFilter.quote(u)
107 return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
109 raise PLCInvalidArgument("LeaseFilter: clip field {}"
112 # the whole key to implementing day is to compute today's beginning
113 def today_start(self):
115 st = time.localtime()
116 seconds_today = st.tm_hour * 3600 + st.tm_min * 60 + st.tm_sec
117 return int(time.time()) - seconds_today
119 # supersede the generic Filter 'sql' method
120 def sql(self, api, join_with="AND"):
121 # implement 'day' as a clip
124 raise PLCInvalidArgument("LeaseFilter cannot have both 'clip' and 'day'")
125 today = self.today_start()
126 nb_days = self['day']
130 self['clip'] = (today, today + nb_days * 24 * 3600)
133 # preserve locally what belongs to us, hide it from the superclass
134 # self.local is a dict local_key : user_value
135 # self.negation is a dict local_key : string
138 for (k, v) in LeaseFilter.local_fields.items():
140 self.local[k] = self[k]
142 self.negation[k] = ""
143 elif ('~' + k) in self:
144 self.local[k] = self['~' + k]
146 self.negation[k] = "NOT "
147 # run the generic filtering code
148 (where_part, clip_part) = Filter.sql(self, api, join_with)
149 for (k, v) in self.local.items():
151 # locate hook function associated with key
152 method = LeaseFilter.__dict__['sql_' + k]
153 where_part += " {} {}({})"\
154 .format(self.join_with,
156 method(self, self.local[k]))
158 raise PLCInvalidArgument(
159 "LeaseFilter: something wrong with filter"
160 "key {}, val was {} -- {}".format(k, v, e))
161 return (where_part, clip_part)
163 # xxx not sure where this belongs yet
164 # given a set of nodes, and a timeslot,
165 # returns the available leases that have at least a given duration
168 def free_leases(api, node_ids, t_from, t_until, min_duration):
170 # get the leases for these nodes and timeslot
171 filter = {'node_id': node_ids,
172 'clip': (t_from, t_until),
173 # sort by node, and inside one node, chronologically
174 '-SORT': ('node_id', 't_from'),
176 leases = Leases(api, filter)
183 # scan nodes from the input
185 # scan nodes from the leases
188 return '?? what now ??'
191 def node_free_leases(node_id, node_leases, t_from, t_until):
193 # no lease yet : return one solid lease
195 return [{'node_id': node_id,
200 current_time = t_from
201 is_on = LeaseFilter.time_in_range(
202 node_leases[0]['t_from'], t_from, t_until)
205 # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
208 current_time = node_leases[0]['t_until']
213 # free, has no remaining lease
214 elif not node_leases:
217 't_from': current_time, 't_until': t_until})
219 # free and has remaining leases
221 next_time = node_leases[0]['t_from']
224 't_from': current_time, 't_until': next_time})
225 current_time = next_time