leasefilter pep8’ed
[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
15
16 class LeaseFilter (Filter):
17
18     # general notes on input parameters
19     # int_timestamp: number of seconds since the epoch
20     # str_timestamp: see Timestamp.sql_validate
21     # timeslot: a tuple (from, until), each being either int_timestamp or
22     # str_timestamp
23
24     local_fields = {
25         'alive': Mixed(
26             Parameter(
27                 int,  "int_timestamp: leases alive at that time"),
28             Parameter(
29                 str,  "str_timestamp: leases alive at that time"),
30             Parameter(
31                 tuple, "timeslot: the leases alive during this timeslot")),
32         'clip':  Mixed(
33             Parameter(
34                 int,  "int_timestamp: leases alive after that time"),
35             Parameter(
36                 str,  "str_timestamp: leases alive after at that time"),
37             Parameter(
38                 tuple, "timeslot: the leases alive during this timeslot")),
39     }
40
41     def __init__(self, fields={}, filter={},
42                  doc="Lease filter -- adds the 'alive' and 'clip'"
43                  "capabilities for filtering on leases"):
44         Filter.__init__(self, fields, filter, doc)
45         self.fields.update(LeaseFilter.local_fields)
46
47     # canonical type
48     @staticmethod
49     def quote(timestamp): return Timestamp.cast_long(timestamp)
50
51     # basic SQL utilities
52     @staticmethod
53     def sql_time_intersect(f1, u1, f2, u2):
54         # either f2 is in [f1,u1], or u2 is in [f1,u1], or f2<=f1<=u1<=u2
55         return ("(({f1} <= {f2}) AND ({f2} <= {u1})) " +
56                 "OR (({f1} <= {u2}) AND ({u2} <= {u1})) " +
57                 "OR (({f2}<={f1}) AND ({u1}<={u2}))").format(**locals())
58
59     @staticmethod
60     def time_in_range(timestamp, f1, u1):
61         return Timestamp.cast_long(f1) <= Timestamp.cast_long(timestamp) \
62             and Timestamp.cast_long(timestamp) <= Timestamp.cast_long(u1)
63
64     @staticmethod
65     def sql_time_in_range(timestamp, f1, u1):
66         # is timestamp in [f1, u1]
67         return "(({f1} <= {timestamp}) AND ({timestamp} <= {u1}))"\
68             .format(**locals())
69
70     @staticmethod
71     def sql_timeslot_after(f1, u1, mark):
72         # is the lease alive after mark, i.e. u1 >= mark
73         return "({u1} >= {mark})".format(**locals())
74
75     # hooks for the local fields
76     def sql_alive(self, alive):
77         if isinstance(alive, int) or isinstance(alive, StringTypes):
78             # the lease is alive at that time if from <= alive <= until
79             alive = LeaseFilter.quote(alive)
80             return LeaseFilter.sql_time_in_range(alive, 't_from', 't_until')
81         elif isinstance(alive, tuple):
82             (f, u) = alive
83             f = LeaseFilter.quote(f)
84             u = LeaseFilter.quote(u)
85             return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
86         else:
87             raise PLCInvalidArgument("LeaseFilter: alive field {}"
88                                      .format(alive))
89
90     def sql_clip(self, clip):
91         if isinstance(clip, int) or isinstance(clip, StringTypes):
92             start = LeaseFilter.quote(clip)
93             return LeaseFilter.sql_timeslot_after('t_from', 't_until', start)
94         elif isinstance(clip, tuple):
95             (f, u) = clip
96             f = LeaseFilter.quote(f)
97             u = LeaseFilter.quote(u)
98             return LeaseFilter.sql_time_intersect(f, u, 't_from', 't_until')
99         else:
100             raise PLCInvalidArgument("LeaseFilter: clip field {}"
101                                      .format(clip))
102
103     # supersede the generic Filter 'sql' method
104     def sql(self, api, join_with="AND"):
105         # preserve locally what belongs to us, hide it from the superclass
106         # self.local is a dict    local_key : user_value
107         # self.negation is a dict  local_key : string
108         self.local = {}
109         self.negation = {}
110         for (k, v) in LeaseFilter.local_fields.items():
111             if k in self:
112                 self.local[k] = self[k]
113                 del self[k]
114                 self.negation[k] = ""
115             elif ('~' + k) in self:
116                 self.local[k] = self['~' + k]
117                 del self['~' + k]
118                 self.negation[k] = "NOT "
119         # run the generic filtering code
120         (where_part, clip_part) = Filter.sql(self, api, join_with)
121         for (k, v) in self.local.items():
122             try:
123                 # locate hook function associated with key
124                 method = LeaseFilter.__dict__['sql_' + k]
125                 where_part += " {} {}({})"\
126                               .format(self.join_with,
127                                       self.negation[k],
128                                       method(self, self.local[k]))
129             except Exception, e:
130                 raise PLCInvalidArgument(
131                     "LeaseFilter: something wrong with filter"
132                     "key {}, val was {} -- {}".format(k, v, e))
133         return (where_part, clip_part)
134
135 # xxx not sure where this belongs yet
136 # given a set of nodes, and a timeslot,
137 # returns the available leases that have at least a given duration
138
139
140 def free_leases(api, node_ids, t_from, t_until, min_duration):
141
142     # get the leases for these nodes and timeslot
143     filter = {'node_id': node_ids,
144               'clip': (t_from, t_until),
145               # sort by node, and inside one node, chronologically
146               '-SORT': ('node_id', 't_from'),
147               }
148     leases = Leases(api, filter)
149
150     result = []
151
152     # sort node_ids
153     node_ids.sort()
154
155     # scan nodes from the input
156     input_node_id = 0
157     # scan nodes from the leases
158     lease_node_id = 0
159
160     return '?? what now ??'
161
162
163 def node_free_leases(node_id, node_leases, t_from, t_until):
164
165     # no lease yet : return one solid lease
166     if not node_leases:
167         return [{'node_id': node_id,
168                  't_from': t_from,
169                  't_until': t_until}]
170
171     result = []
172     current_time = t_from
173     is_on = LeaseFilter.time_in_range(
174         node_leases[0]['t_from'], t_from, t_until)
175
176     while True:
177         # print 'DBG','current_time',current_time,'is_on',is_on,'result',result
178         # lease is active
179         if is_on:
180             current_time = node_leases[0]['t_until']
181             is_on = False
182             del node_leases[0]
183             if not node_leases:
184                 return result
185         # free, has no remaining lease
186         elif not node_leases:
187             result.append(
188                 {'node_id': node_id,
189                  't_from': current_time, 't_until': t_until})
190             return result
191         # free and has remaining leases
192         else:
193             next_time = node_leases[0]['t_from']
194             result.append(
195                 {'node_id': node_id,
196                  't_from': current_time, 't_until': next_time})
197             current_time = next_time
198             is_on = True