root / snf-cyclades-app / synnefo / db / pools / __init__.py @ 03992c72
History | View | Annotate | Download (9.2 kB)
1 |
from bitarray import bitarray |
---|---|
2 |
from base64 import b64encode, b64decode |
3 |
import ipaddr |
4 |
|
5 |
AVAILABLE = True
|
6 |
UNAVAILABLE = False
|
7 |
|
8 |
|
9 |
class PoolManager(object): |
10 |
"""PoolManager for DB PoolTable models.
|
11 |
|
12 |
This class implements a persistent Pool mechanism based on rows of
|
13 |
PoolTable objects. Values that are pooled by this class, are mapped to an
|
14 |
index on a bitarray, which is the one that is stored on the DB.
|
15 |
|
16 |
The object that will be used in order to initialize this pool, must have
|
17 |
two string attributes (available_map and reserved_map) and the size of the
|
18 |
pool.
|
19 |
|
20 |
Subclasses of PoolManager must implement value_to_index and index_to_value
|
21 |
method's in order to denote how the value will be mapped to the index in
|
22 |
the bitarray.
|
23 |
|
24 |
Important!!: Updates on a PoolManager object are not reflected to the DB,
|
25 |
until save() method is called.
|
26 |
|
27 |
"""
|
28 |
def __init__(self, pool_table): |
29 |
self.pool_table = pool_table
|
30 |
|
31 |
if pool_table.available_map:
|
32 |
self.available = _bitarray_from_string(pool_table.available_map)
|
33 |
self.reserved = _bitarray_from_string(pool_table.reserved_map)
|
34 |
else:
|
35 |
size = self.pool_table.size
|
36 |
padding = find_padding(size) |
37 |
size = size + padding |
38 |
self.pool_size = size
|
39 |
self.available = self._create_empty_pool() |
40 |
self.reserved = self._create_empty_pool() |
41 |
for i in xrange(0, padding): |
42 |
self._reserve(size - i - 1, external=True) |
43 |
|
44 |
def _create_empty_pool(self): |
45 |
assert(self.pool_size % 8 == 0) |
46 |
ba = bitarray(self.pool_size)
|
47 |
ba.setall(AVAILABLE) |
48 |
return ba
|
49 |
|
50 |
@property
|
51 |
def pool(self): |
52 |
return (self.available & self.reserved) |
53 |
|
54 |
def get(self): |
55 |
"""Get a value from the pool."""
|
56 |
if self.empty(): |
57 |
raise EmptyPool
|
58 |
# Get the first available index
|
59 |
index = int(self.pool.index(AVAILABLE)) |
60 |
self._reserve(index)
|
61 |
return self.index_to_value(index) |
62 |
|
63 |
def put(self, value, external=False): |
64 |
"""Return a value to the pool."""
|
65 |
if value is None: |
66 |
raise ValueError |
67 |
index = self.value_to_index(value)
|
68 |
self._release(index, external)
|
69 |
|
70 |
def reserve(self, value, external=True): |
71 |
"""Reserve a value."""
|
72 |
index = self.value_to_index(value)
|
73 |
self._reserve(index, external)
|
74 |
return True |
75 |
|
76 |
def save(self, db=True): |
77 |
"""Save changes to the DB."""
|
78 |
self.pool_table.available_map = _bitarray_to_string(self.available) |
79 |
self.pool_table.reserved_map = _bitarray_to_string(self.reserved) |
80 |
if db:
|
81 |
self.pool_table.save()
|
82 |
|
83 |
def empty(self): |
84 |
"""Return True when pool is empty."""
|
85 |
return not self.pool.any() |
86 |
|
87 |
def size(self): |
88 |
return self.pool.length() |
89 |
|
90 |
def _reserve(self, index, external=False): |
91 |
if external:
|
92 |
self.reserved[index] = UNAVAILABLE
|
93 |
else:
|
94 |
self.available[index] = UNAVAILABLE
|
95 |
|
96 |
def _release(self, index, external=False): |
97 |
if external:
|
98 |
self.reserved[index] = AVAILABLE
|
99 |
else:
|
100 |
self.available[index] = AVAILABLE
|
101 |
|
102 |
def count_available(self): |
103 |
return self.pool.count(AVAILABLE) |
104 |
|
105 |
def count_unavailable(self): |
106 |
return self.pool.count(UNAVAILABLE) |
107 |
|
108 |
def count_reserved(self): |
109 |
return self.reserved.count(UNAVAILABLE) |
110 |
|
111 |
def count_unreserved(self): |
112 |
return self.size() - self.count_reserved() |
113 |
|
114 |
def is_available(self, value, index=False): |
115 |
if not index: |
116 |
idx = self.value_to_index(value)
|
117 |
else:
|
118 |
idx = value |
119 |
return self.pool[idx] == AVAILABLE |
120 |
|
121 |
def is_reserved(self, value, index=False): |
122 |
if not index: |
123 |
idx = self.value_to_index(value)
|
124 |
else:
|
125 |
idx = value |
126 |
return self.reserved[idx] == UNAVAILABLE |
127 |
|
128 |
def to_01(self): |
129 |
return self.pool.to01() |
130 |
|
131 |
def to_map(self): |
132 |
return self.to_01().replace("0", "X").replace("1", ".") |
133 |
|
134 |
def index_to_value(self, index): |
135 |
raise NotImplementedError |
136 |
|
137 |
def value_to_index(self, value): |
138 |
raise NotImplementedError |
139 |
|
140 |
|
141 |
class EmptyPool(Exception): |
142 |
pass
|
143 |
|
144 |
|
145 |
def find_padding(size): |
146 |
extra = size % 8
|
147 |
return extra and (8 - extra) or 0 |
148 |
|
149 |
|
150 |
def bitarray_to_01(bitarray_): |
151 |
return bitarray_.to01()
|
152 |
|
153 |
|
154 |
def bitarray_to_map(bitarray_): |
155 |
return bitarray_to_01(bitarray_).replace("0", "X").replace("1", ".") |
156 |
|
157 |
|
158 |
def _bitarray_from_string(bitarray_): |
159 |
ba = bitarray() |
160 |
ba.fromstring(b64decode(bitarray_)) |
161 |
return ba
|
162 |
|
163 |
|
164 |
def _bitarray_to_string(bitarray_): |
165 |
return b64encode(bitarray_.tostring())
|
166 |
|
167 |
##
|
168 |
## Custom pools
|
169 |
##
|
170 |
|
171 |
|
172 |
class BridgePool(PoolManager): |
173 |
def index_to_value(self, index): |
174 |
return self.pool_table.base + str(index) |
175 |
|
176 |
def value_to_index(self, value): |
177 |
return int(value.replace(self.pool_table.base, "")) |
178 |
|
179 |
|
180 |
class MacPrefixPool(PoolManager): |
181 |
def __init__(self, pool_table): |
182 |
do_init = False if pool_table.available_map else True |
183 |
super(MacPrefixPool, self).__init__(pool_table) |
184 |
if do_init:
|
185 |
for i in xrange(1, self.size()): |
186 |
if not self.validate_mac(self.index_to_value(i)): |
187 |
self._reserve(i, external=True) |
188 |
# Reserve the first mac-prefix for public-networks
|
189 |
self._reserve(0, external=True) |
190 |
|
191 |
def index_to_value(self, index): |
192 |
"""Convert index to mac_prefix"""
|
193 |
base = self.pool_table.base
|
194 |
a = hex(int(base.replace(":", ""), 16) + index).replace("0x", '') |
195 |
mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)]) |
196 |
return mac_prefix
|
197 |
|
198 |
def value_to_index(self, value): |
199 |
base = self.pool_table.base
|
200 |
return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16) |
201 |
|
202 |
@staticmethod
|
203 |
def validate_mac(value): |
204 |
hex_ = value.replace(":", "") |
205 |
bin_ = bin(int(hex_, 16))[2:].zfill(8) |
206 |
return bin_[6] == '1' and bin_[7] == '0' |
207 |
|
208 |
|
209 |
# class IPPool(PoolManager):
|
210 |
# def __init__(self, network):
|
211 |
# do_init = False if network.pool else True
|
212 |
# self.net = ipaddr.IPNetwork(network.subnet)
|
213 |
# network.size = self.net.numhosts
|
214 |
# super(IPPool, self).__init__(network)
|
215 |
# gateway = network.gateway
|
216 |
# self.gateway = gateway and ipaddr.IPAddress(gateway) or None
|
217 |
# if do_init:
|
218 |
# self._reserve(0)
|
219 |
# self.put(gateway)
|
220 |
# self._reserve(self.size() - 1)
|
221 |
#
|
222 |
# def value_to_index(self, value):
|
223 |
# addr = ipaddr.IPAddress(value)
|
224 |
# return int(addr) - int(self.net.network)
|
225 |
#
|
226 |
# def index_to_value(self, index):
|
227 |
# return str(self.net[index])
|
228 |
|
229 |
class IPPool(object): |
230 |
"""IP pool class, based on a models.Network object
|
231 |
|
232 |
Implementation of an IP address pool based on a models.Network
|
233 |
object.
|
234 |
|
235 |
"""
|
236 |
def __init__(self, network): |
237 |
self.net = network
|
238 |
self.network = ipaddr.IPNetwork(self.net.subnet) |
239 |
|
240 |
gateway = self.net.gateway
|
241 |
self.gateway = gateway and ipaddr.IPAddress(gateway) or None |
242 |
|
243 |
if self.net.reservations: |
244 |
self.reservations = bitarray()
|
245 |
self.reservations.fromstring(b64decode(self.net.reservations)) |
246 |
else:
|
247 |
numhosts = self.network.numhosts
|
248 |
self.reservations = bitarray(numhosts)
|
249 |
self.reservations.setall(False) |
250 |
self.reservations[0] = True |
251 |
self.reservations[numhosts - 1] = True |
252 |
if self.gateway: |
253 |
self.reserve(self.gateway) |
254 |
|
255 |
def _contains(self, address): |
256 |
if address is None: |
257 |
return False |
258 |
addr = ipaddr.IPAddress(address) |
259 |
|
260 |
return (addr in self.network) |
261 |
|
262 |
def _address_index(self, address): |
263 |
"""Convert IP address to bitarray index
|
264 |
|
265 |
"""
|
266 |
if not self._contains(address): |
267 |
raise Exception("%s does not contain %s" % |
268 |
(str(self.network), address)) |
269 |
addr = ipaddr.IPAddress(address) |
270 |
|
271 |
return int(addr) - int(self.network.network) |
272 |
|
273 |
def _mark(self, address, value): |
274 |
index = self._address_index(address)
|
275 |
self.reservations[index] = value
|
276 |
|
277 |
def reserve(self, address): |
278 |
self._mark(address, True) |
279 |
|
280 |
def release(self, address): |
281 |
self._mark(address, False) |
282 |
|
283 |
def is_reserved(self, address): |
284 |
index = self._address_index(address)
|
285 |
return self.reservations[index] |
286 |
|
287 |
def is_full(self): |
288 |
return self.reservations.all() |
289 |
|
290 |
def count_reserved(self): |
291 |
return self.reservations.count(True) |
292 |
|
293 |
def count_free(self): |
294 |
return self.reservations.count(False) |
295 |
|
296 |
def get_map(self): |
297 |
return self.reservations.to01().replace("1", "X").replace("0", ".") |
298 |
|
299 |
def get_free_address(self): |
300 |
"""Get the first available address."""
|
301 |
if self.is_full(): |
302 |
raise IPPool.IPPoolExhausted("%s if full" % str(self.network)) |
303 |
|
304 |
index = self.reservations.index(False) |
305 |
address = str(self.network[index]) |
306 |
self.reserve(address)
|
307 |
return address
|
308 |
|
309 |
def save(self): |
310 |
"""Update the Network model and save it to DB."""
|
311 |
self._update_network()
|
312 |
self.net.save()
|
313 |
|
314 |
def _update_network(self): |
315 |
self.net.reservations = b64encode(self.reservations.tostring()) |
316 |
|
317 |
class IPPoolExhausted(Exception): |
318 |
pass
|