StringTypes has gone
[plcapi.git] / PLC / LeaseFilter.py
1 #
2 # Thierry Parmentelat -- INRIA
3 #
4 # Utilities for filtering on leases
5
6 import time
7 import calendar
8
9 from PLC.Faults import *
10 from PLC.Filter import Filter
11 from PLC.Parameter import Parameter, Mixed
12 from PLC.Timestamp import Timestamp
13
14 # supersede the generic Filter class to support time intersection
15
16
17 class LeaseFilter (Filter):
18
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
23     # str_timestamp
24
25     local_fields = {
26         'alive': Mixed(
27             Parameter(
28                 int,  "int_timestamp: leases alive at that time"),
29             Parameter(
30                 str,  "str_timestamp: leases alive at that time"),
31             Parameter(
32                 tuple, "timeslot: the leases alive during this timeslot")),
33         'clip':  Mixed(
34             Parameter(
35                 int,  "int_timestamp: leases alive after that time"),
36             Parameter(
37                 str,  "str_timestamp: leases alive after at that time"),
38             Parameter(
39                 tuple, "timeslot: the leases alive during this timeslot")),
40         ########## macros
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)
44         # etc..
45         'day': Parameter(int, "clip on a number of days from today and on;"
46                          " 0 means no limit in the future"),
47     }
48
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)
54
55     # canonical type
56     @staticmethod
57     def quote(timestamp): return Timestamp.cast_long(timestamp)
58
59     # basic SQL utilities
60     @staticmethod
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())
66
67     @staticmethod
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)
71
72     @staticmethod
73     def sql_time_in_range(timestamp, f1, u1):
74         # is timestamp in [f1, u1]
75         return "(({f1} <= {timestamp}) AND ({timestamp} <= {u1}))"\
76             .format(**locals())
77
78     @staticmethod
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())
82
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):
90             (f, u) = alive
91             f = LeaseFilter.quote(f)
92             u = LeaseFilter.quote(u)
93             return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
94         else:
95             raise PLCInvalidArgument("LeaseFilter: alive field {}"
96                                      .format(alive))
97
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):
103             (f, u) = clip
104             f = LeaseFilter.quote(f)
105             u = LeaseFilter.quote(u)
106             return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
107         else:
108             raise PLCInvalidArgument("LeaseFilter: clip field {}"
109                                      .format(clip))
110
111     # the whole key to implementing day is to compute today's beginning
112     def today_start(self):
113         # a struct_time
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
117
118     # supersede the generic Filter 'sql' method
119     def sql(self, api, join_with="AND"):
120         # implement 'day' as a clip
121         if 'day' in self:
122             if 'clip' in self:
123                 raise PLCInvalidArgument("LeaseFilter cannot have both 'clip' and 'day'")
124             today = self.today_start()
125             nb_days = self['day']
126             if nb_days == 0:
127                 self['clip'] = today
128             else:
129                 self['clip'] = (today, today + nb_days * 24 * 3600)
130             del self['day']
131
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
135         self.local = {}
136         self.negation = {}
137         for (k, v) in list(LeaseFilter.local_fields.items()):
138             if k in self:
139                 self.local[k] = self[k]
140                 del self[k]
141                 self.negation[k] = ""
142             elif ('~' + k) in self:
143                 self.local[k] = self['~' + k]
144                 del 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()):
149             try:
150                 # locate hook function associated with key
151                 method = LeaseFilter.__dict__['sql_' + k]
152                 where_part += " {} {}({})"\
153                               .format(self.join_with,
154                                       self.negation[k],
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)
161
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
165
166
167 def free_leases(api, node_ids, t_from, t_until, min_duration):
168
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'),
174               }
175     leases = Leases(api, filter)
176
177     result = []
178
179     # sort node_ids
180     node_ids.sort()
181
182     # scan nodes from the input
183     input_node_id = 0
184     # scan nodes from the leases
185     lease_node_id = 0
186
187     return '?? what now ??'
188
189
190 def node_free_leases(node_id, node_leases, t_from, t_until):
191
192     # no lease yet : return one solid lease
193     if not node_leases:
194         return [{'node_id': node_id,
195                  't_from': t_from,
196                  't_until': t_until}]
197
198     result = []
199     current_time = t_from
200     is_on = LeaseFilter.time_in_range(
201         node_leases[0]['t_from'], t_from, t_until)
202
203     while True:
204         # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
205         # lease is active
206         if is_on:
207             current_time = node_leases[0]['t_until']
208             is_on = False
209             del node_leases[0]
210             if not node_leases:
211                 return result
212         # free, has no remaining lease
213         elif not node_leases:
214             result.append(
215                 {'node_id': node_id,
216                  't_from': current_time, 't_until': t_until})
217             return result
218         # free and has remaining leases
219         else:
220             next_time = node_leases[0]['t_from']
221             result.append(
222                 {'node_id': node_id,
223                  't_from': current_time, 't_until': next_time})
224             current_time = next_time
225             is_on = True