root / snf-cyclades-app / synnefo / db / pools / __init__.py @ 4e3789fd
History | View | Annotate | Download (11.9 kB)
1 |
# Copyright 2012, 2013 GRNET S.A. All rights reserved.
|
---|---|
2 |
#
|
3 |
# Redistribution and use in source and binary forms, with or without
|
4 |
# modification, are permitted provided that the following conditions
|
5 |
# are met:
|
6 |
#
|
7 |
# 1. Redistributions of source code must retain the above copyright
|
8 |
# notice, this list of conditions and the following disclaimer.
|
9 |
#
|
10 |
# 2. Redistributions in binary form must reproduce the above copyright
|
11 |
# notice, this list of conditions and the following disclaimer in the
|
12 |
# documentation and/or other materials provided with the distribution.
|
13 |
#
|
14 |
# THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
|
15 |
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
16 |
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
17 |
# ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
|
18 |
# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
19 |
# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
|
20 |
# OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
|
21 |
# HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
22 |
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
|
23 |
# OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
|
24 |
# SUCH DAMAGE.
|
25 |
#
|
26 |
# The views and conclusions contained in the software and documentation are
|
27 |
# those of the authors and should not be interpreted as representing official
|
28 |
# policies, either expressed or implied, of GRNET S.A.
|
29 |
|
30 |
from bitarray import bitarray |
31 |
from base64 import b64encode, b64decode |
32 |
import ipaddr |
33 |
|
34 |
AVAILABLE = True
|
35 |
UNAVAILABLE = False
|
36 |
|
37 |
|
38 |
class PoolManager(object): |
39 |
"""PoolManager for DB PoolTable models.
|
40 |
|
41 |
This class implements a persistent Pool mechanism based on rows of
|
42 |
PoolTable objects. Values that are pooled by this class, are mapped to an
|
43 |
index on a bitarray, which is the one that is stored on the DB.
|
44 |
|
45 |
The object that will be used in order to initialize this pool, must have
|
46 |
two string attributes (available_map and reserved_map) and the size of the
|
47 |
pool.
|
48 |
|
49 |
Subclasses of PoolManager must implement value_to_index and index_to_value
|
50 |
method's in order to denote how the value will be mapped to the index in
|
51 |
the bitarray.
|
52 |
|
53 |
Important!!: Updates on a PoolManager object are not reflected to the DB,
|
54 |
until save() method is called.
|
55 |
|
56 |
"""
|
57 |
def __init__(self, pool_table): |
58 |
self.pool_table = pool_table
|
59 |
self.pool_size = pool_table.size
|
60 |
if pool_table.available_map:
|
61 |
self.available = _bitarray_from_string(pool_table.available_map)
|
62 |
self.reserved = _bitarray_from_string(pool_table.reserved_map)
|
63 |
else:
|
64 |
self.available = self._create_empty_pool(self.pool_size) |
65 |
self.reserved = self._create_empty_pool(self.pool_size) |
66 |
self.add_padding(self.pool_size) |
67 |
|
68 |
def _create_empty_pool(self, size): |
69 |
ba = bitarray(size) |
70 |
ba.setall(AVAILABLE) |
71 |
return ba
|
72 |
|
73 |
def add_padding(self, pool_size): |
74 |
bits = find_padding(pool_size) |
75 |
self.available.extend([UNAVAILABLE] * bits)
|
76 |
self.reserved.extend([UNAVAILABLE] * bits)
|
77 |
|
78 |
def cut_padding(self, pool_size): |
79 |
bits = find_padding(pool_size) |
80 |
self.available = self.available[:-bits] |
81 |
self.reserved = self.reserved[:-bits] |
82 |
|
83 |
@property
|
84 |
def pool(self): |
85 |
return (self.available & self.reserved) |
86 |
|
87 |
def get(self, value=None): |
88 |
"""Get a value from the pool."""
|
89 |
if value is None: |
90 |
if self.empty(): |
91 |
raise EmptyPool
|
92 |
# Get the first available index
|
93 |
index = int(self.pool.index(AVAILABLE)) |
94 |
assert(index < self.pool_size) |
95 |
self._reserve(index)
|
96 |
return self.index_to_value(index) |
97 |
else:
|
98 |
if not self.contains(value): |
99 |
raise InvalidValue("Value %s does not belong to pool." % value) |
100 |
if self.is_available(value): |
101 |
self.reserve(value)
|
102 |
return value
|
103 |
else:
|
104 |
raise ValueNotAvailable("Value %s is not available" % value) |
105 |
|
106 |
def put(self, value, external=False): |
107 |
"""Return a value to the pool."""
|
108 |
if value is None: |
109 |
raise ValueError |
110 |
if not self.contains(value): |
111 |
raise InvalidValue("%s does not belong to pool." % value) |
112 |
index = self.value_to_index(value)
|
113 |
self._release(index, external)
|
114 |
|
115 |
def reserve(self, value, external=False): |
116 |
"""Reserve a value."""
|
117 |
if not self.contains(value): |
118 |
raise InvalidValue("%s does not belong to pool." % value) |
119 |
index = self.value_to_index(value)
|
120 |
self._reserve(index, external)
|
121 |
return True |
122 |
|
123 |
def save(self, db=True): |
124 |
"""Save changes to the DB."""
|
125 |
self.pool_table.available_map = _bitarray_to_string(self.available) |
126 |
self.pool_table.reserved_map = _bitarray_to_string(self.reserved) |
127 |
if db:
|
128 |
self.pool_table.save()
|
129 |
|
130 |
def empty(self): |
131 |
"""Return True when pool is empty."""
|
132 |
return not self.pool.any() |
133 |
|
134 |
def size(self): |
135 |
"""Return the size of the bitarray(original size + padding)."""
|
136 |
return self.pool.length() |
137 |
|
138 |
def _reserve(self, index, external=False): |
139 |
if external:
|
140 |
self.reserved[index] = UNAVAILABLE
|
141 |
else:
|
142 |
self.available[index] = UNAVAILABLE
|
143 |
|
144 |
def _release(self, index, external=False): |
145 |
if external:
|
146 |
self.reserved[index] = AVAILABLE
|
147 |
else:
|
148 |
self.available[index] = AVAILABLE
|
149 |
|
150 |
def contains(self, value, index=False): |
151 |
if index is False: |
152 |
index = self.value_to_index(value)
|
153 |
return index >= 0 and index < self.pool_size |
154 |
|
155 |
def count_available(self): |
156 |
return self.pool.count(AVAILABLE) |
157 |
|
158 |
def count_unavailable(self): |
159 |
return self.pool_size - self.count_available() |
160 |
|
161 |
def count_reserved(self): |
162 |
return self.reserved[:self.pool_size].count(UNAVAILABLE) |
163 |
|
164 |
def count_unreserved(self): |
165 |
return self.pool_size - self.count_reserved() |
166 |
|
167 |
def is_available(self, value, index=False): |
168 |
if not self.contains(value, index=index): |
169 |
raise InvalidValue("%s does not belong to pool." % value) |
170 |
if not index: |
171 |
idx = self.value_to_index(value)
|
172 |
else:
|
173 |
idx = value |
174 |
return self.pool[idx] == AVAILABLE |
175 |
|
176 |
def is_reserved(self, value, index=False): |
177 |
if not self.contains(value, index=index): |
178 |
raise InvalidValue("%s does not belong to pool." % value) |
179 |
if not index: |
180 |
idx = self.value_to_index(value)
|
181 |
else:
|
182 |
idx = value |
183 |
return self.reserved[idx] == UNAVAILABLE |
184 |
|
185 |
def to_01(self): |
186 |
return self.pool[:self.pool_size].to01() |
187 |
|
188 |
def to_map(self): |
189 |
return self.to_01().replace("0", "X").replace("1", ".") |
190 |
|
191 |
def extend(self, bits_num): |
192 |
assert(bits_num >= 0) |
193 |
self.resize(bits_num)
|
194 |
|
195 |
def shrink(self, bits_num): |
196 |
assert(bits_num >= 0) |
197 |
size = self.pool_size
|
198 |
tmp = self.available[(size - bits_num): size]
|
199 |
if tmp.count(UNAVAILABLE):
|
200 |
raise Exception("Cannot shrink. In use") |
201 |
self.resize(-bits_num)
|
202 |
|
203 |
def resize(self, bits_num): |
204 |
if bits_num == 0: |
205 |
return
|
206 |
# Cut old padding
|
207 |
self.cut_padding(self.pool_size) |
208 |
# Do the resize
|
209 |
if bits_num > 0: |
210 |
self.available.extend([AVAILABLE] * bits_num)
|
211 |
self.reserved.extend([AVAILABLE] * bits_num)
|
212 |
else:
|
213 |
self.available = self.available[:bits_num] |
214 |
self.reserved = self.reserved[:bits_num] |
215 |
# Add new padding
|
216 |
self.pool_size = self.pool_size + bits_num |
217 |
self.add_padding(self.pool_size) |
218 |
self.pool_table.size = self.pool_size |
219 |
|
220 |
def index_to_value(self, index): |
221 |
raise NotImplementedError |
222 |
|
223 |
def value_to_index(self, value): |
224 |
raise NotImplementedError |
225 |
|
226 |
def __repr__(self): |
227 |
return repr(self.pool_table) |
228 |
|
229 |
|
230 |
class EmptyPool(Exception): |
231 |
pass
|
232 |
|
233 |
|
234 |
class ValueNotAvailable(Exception): |
235 |
pass
|
236 |
|
237 |
|
238 |
class InvalidValue(Exception): |
239 |
pass
|
240 |
|
241 |
|
242 |
def find_padding(size): |
243 |
extra = size % 8
|
244 |
return extra and (8 - extra) or 0 |
245 |
|
246 |
|
247 |
def bitarray_to_01(bitarray_): |
248 |
return bitarray_.to01()
|
249 |
|
250 |
|
251 |
def bitarray_to_map(bitarray_): |
252 |
return bitarray_to_01(bitarray_).replace("0", "X").replace("1", ".") |
253 |
|
254 |
|
255 |
def _bitarray_from_string(bitarray_): |
256 |
ba = bitarray() |
257 |
ba.frombytes(b64decode(bitarray_)) |
258 |
return ba
|
259 |
|
260 |
|
261 |
def _bitarray_to_string(bitarray_): |
262 |
return b64encode(bitarray_.tobytes())
|
263 |
|
264 |
##
|
265 |
## Custom pools
|
266 |
##
|
267 |
|
268 |
|
269 |
class BridgePool(PoolManager): |
270 |
def index_to_value(self, index): |
271 |
# Bridge indexes should start from 1
|
272 |
return self.pool_table.base + str(index + 1) |
273 |
|
274 |
def value_to_index(self, value): |
275 |
return int(value.replace(self.pool_table.base, "")) - 1 |
276 |
|
277 |
|
278 |
class MacPrefixPool(PoolManager): |
279 |
def __init__(self, pool_table): |
280 |
do_init = False if pool_table.available_map else True |
281 |
super(MacPrefixPool, self).__init__(pool_table) |
282 |
if do_init:
|
283 |
for i in xrange(1, self.pool_size): |
284 |
if not self.validate_mac(self.index_to_value(i)): |
285 |
self._reserve(i, external=True) |
286 |
# Reserve the first mac-prefix for public-networks
|
287 |
self._reserve(0, external=True) |
288 |
|
289 |
def index_to_value(self, index): |
290 |
"""Convert index to mac_prefix"""
|
291 |
base = self.pool_table.base
|
292 |
a = hex(int(base.replace(":", ""), 16) + index).replace("0x", '') |
293 |
mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)]) |
294 |
return mac_prefix
|
295 |
|
296 |
def value_to_index(self, value): |
297 |
base = self.pool_table.base
|
298 |
return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16) |
299 |
|
300 |
@staticmethod
|
301 |
def validate_mac(value): |
302 |
hex_ = value.replace(":", "") |
303 |
bin_ = bin(int(hex_, 16))[2:].zfill(8) |
304 |
return bin_[6] == '1' and bin_[7] == '0' |
305 |
|
306 |
|
307 |
class IPPool(PoolManager): |
308 |
def __init__(self, pool_table): |
309 |
subnet = pool_table.subnet |
310 |
self.net = ipaddr.IPNetwork(subnet.cidr)
|
311 |
self.offset = pool_table.offset
|
312 |
self.base = pool_table.base
|
313 |
if pool_table.available_map:
|
314 |
initialized_pool = True
|
315 |
else:
|
316 |
initialized_pool = False
|
317 |
super(IPPool, self).__init__(pool_table) |
318 |
if not initialized_pool: |
319 |
self.check_pool_integrity()
|
320 |
|
321 |
def check_pool_integrity(self): |
322 |
"""Check the integrity of the IP pool
|
323 |
|
324 |
This check is required only for old IP pools(one IP pool per network
|
325 |
that contained the whole subnet cidr) that had not been initialized
|
326 |
before the migration. This checks that the network, gateway and
|
327 |
broadcast IP addresses are externally reserved, and if not reserves
|
328 |
them.
|
329 |
|
330 |
"""
|
331 |
subnet = self.pool_table.subnet
|
332 |
check_ips = [str(self.net.network), str(self.net.broadcast)] |
333 |
if subnet.gateway is not None: |
334 |
check_ips.append(subnet.gateway) |
335 |
for ip in check_ips: |
336 |
if self.contains(ip): |
337 |
self.reserve(ip, external=True) |
338 |
|
339 |
def value_to_index(self, value): |
340 |
addr = ipaddr.IPAddress(value) |
341 |
return int(addr) - int(self.net.network) - int(self.offset) |
342 |
|
343 |
def index_to_value(self, index): |
344 |
return str(self.net[index + int(self.offset)]) |
345 |
|
346 |
def contains(self, address, index=False): |
347 |
if index is False: |
348 |
try:
|
349 |
addr = ipaddr.IPAddress(address) |
350 |
except ValueError: |
351 |
raise InvalidValue("Invalid IP address") |
352 |
|
353 |
if addr not in self.net: |
354 |
return False |
355 |
return super(IPPool, self).contains(address, index=False) |
356 |
|
357 |
def return_start(self): |
358 |
return str(ipaddr.IPAddress(ipaddr.IPNetwork(self.base).network) + |
359 |
self.offset)
|
360 |
|
361 |
def return_end(self): |
362 |
return str(ipaddr.IPAddress(ipaddr.IPNetwork(self.base).network) + |
363 |
self.offset + self.pool_size - 1) |