Statistics
| Branch: | Tag: | Revision:

root / snf-cyclades-app / synnefo / db / pools / __init__.py @ 435bb7fb

History | View | Annotate | Download (10.3 kB)

1 03992c72 Christos Stavrakakis
from bitarray import bitarray
2 03992c72 Christos Stavrakakis
from base64 import b64encode, b64decode
3 03992c72 Christos Stavrakakis
import ipaddr
4 03992c72 Christos Stavrakakis
5 03992c72 Christos Stavrakakis
AVAILABLE = True
6 03992c72 Christos Stavrakakis
UNAVAILABLE = False
7 03992c72 Christos Stavrakakis
8 03992c72 Christos Stavrakakis
9 03992c72 Christos Stavrakakis
class PoolManager(object):
10 03992c72 Christos Stavrakakis
    """PoolManager for DB PoolTable models.
11 03992c72 Christos Stavrakakis

12 03992c72 Christos Stavrakakis
    This class implements a persistent Pool mechanism based on rows of
13 03992c72 Christos Stavrakakis
    PoolTable objects. Values that are pooled by this class, are mapped to an
14 03992c72 Christos Stavrakakis
    index on a bitarray, which is the one that is stored on the DB.
15 03992c72 Christos Stavrakakis

16 03992c72 Christos Stavrakakis
    The object that will be used in order to initialize this pool, must have
17 03992c72 Christos Stavrakakis
    two string attributes (available_map and reserved_map) and the size of the
18 03992c72 Christos Stavrakakis
    pool.
19 03992c72 Christos Stavrakakis

20 03992c72 Christos Stavrakakis
    Subclasses of PoolManager must implement value_to_index and index_to_value
21 03992c72 Christos Stavrakakis
    method's in order to denote how the value will be mapped to the index in
22 03992c72 Christos Stavrakakis
    the bitarray.
23 03992c72 Christos Stavrakakis

24 03992c72 Christos Stavrakakis
    Important!!: Updates on a PoolManager object are not reflected to the DB,
25 03992c72 Christos Stavrakakis
    until save() method is called.
26 03992c72 Christos Stavrakakis

27 03992c72 Christos Stavrakakis
    """
28 03992c72 Christos Stavrakakis
    def __init__(self, pool_table):
29 03992c72 Christos Stavrakakis
        self.pool_table = pool_table
30 3e7c63f8 Christos Stavrakakis
        self.pool_size = pool_table.size
31 03992c72 Christos Stavrakakis
        if pool_table.available_map:
32 03992c72 Christos Stavrakakis
            self.available = _bitarray_from_string(pool_table.available_map)
33 03992c72 Christos Stavrakakis
            self.reserved = _bitarray_from_string(pool_table.reserved_map)
34 03992c72 Christos Stavrakakis
        else:
35 3e7c63f8 Christos Stavrakakis
            self.available = self._create_empty_pool(self.pool_size)
36 3e7c63f8 Christos Stavrakakis
            self.reserved = self._create_empty_pool(self.pool_size)
37 3e7c63f8 Christos Stavrakakis
            self.add_padding(self.pool_size)
38 3e7c63f8 Christos Stavrakakis
39 3e7c63f8 Christos Stavrakakis
    def _create_empty_pool(self, size):
40 3e7c63f8 Christos Stavrakakis
        ba = bitarray(size)
41 03992c72 Christos Stavrakakis
        ba.setall(AVAILABLE)
42 03992c72 Christos Stavrakakis
        return ba
43 03992c72 Christos Stavrakakis
44 3e7c63f8 Christos Stavrakakis
    def add_padding(self, pool_size):
45 3e7c63f8 Christos Stavrakakis
        bits = find_padding(pool_size)
46 35e2f2d4 Christos Stavrakakis
        self.available.extend([UNAVAILABLE] * bits)
47 3e7c63f8 Christos Stavrakakis
        self.reserved.extend([UNAVAILABLE] * bits)
48 3e7c63f8 Christos Stavrakakis
49 3e7c63f8 Christos Stavrakakis
    def cut_padding(self, pool_size):
50 3e7c63f8 Christos Stavrakakis
        bits = find_padding(pool_size)
51 3e7c63f8 Christos Stavrakakis
        self.available = self.available[:-bits]
52 3e7c63f8 Christos Stavrakakis
        self.reserved = self.reserved[:-bits]
53 3e7c63f8 Christos Stavrakakis
54 03992c72 Christos Stavrakakis
    @property
55 03992c72 Christos Stavrakakis
    def pool(self):
56 03992c72 Christos Stavrakakis
        return (self.available & self.reserved)
57 03992c72 Christos Stavrakakis
58 413fb1dd Christos Stavrakakis
    def get(self, value=None):
59 03992c72 Christos Stavrakakis
        """Get a value from the pool."""
60 413fb1dd Christos Stavrakakis
        if value is None:
61 691c98cf Christos Stavrakakis
            if self.empty():
62 691c98cf Christos Stavrakakis
                raise EmptyPool
63 413fb1dd Christos Stavrakakis
            # Get the first available index
64 413fb1dd Christos Stavrakakis
            index = int(self.pool.index(AVAILABLE))
65 413fb1dd Christos Stavrakakis
            assert(index < self.pool_size)
66 413fb1dd Christos Stavrakakis
            self._reserve(index)
67 413fb1dd Christos Stavrakakis
            return self.index_to_value(index)
68 413fb1dd Christos Stavrakakis
        else:
69 ae994d2e Christos Stavrakakis
            if not self.contains(value):
70 ae994d2e Christos Stavrakakis
                raise InvalidValue("Value %s does not belong to pool." % value)
71 413fb1dd Christos Stavrakakis
            if self.is_available(value):
72 413fb1dd Christos Stavrakakis
                self.reserve(value)
73 413fb1dd Christos Stavrakakis
                return value
74 413fb1dd Christos Stavrakakis
            else:
75 413fb1dd Christos Stavrakakis
                raise ValueNotAvailable("Value %s is not available" % value)
76 03992c72 Christos Stavrakakis
77 03992c72 Christos Stavrakakis
    def put(self, value, external=False):
78 03992c72 Christos Stavrakakis
        """Return a value to the pool."""
79 03992c72 Christos Stavrakakis
        if value is None:
80 03992c72 Christos Stavrakakis
            raise ValueError
81 3b2984dc Christos Stavrakakis
        if not self.contains(value):
82 3b2984dc Christos Stavrakakis
            raise InvalidValue("%s does not belong to pool." % value)
83 03992c72 Christos Stavrakakis
        index = self.value_to_index(value)
84 03992c72 Christos Stavrakakis
        self._release(index, external)
85 03992c72 Christos Stavrakakis
86 fdc94944 Christos Stavrakakis
    def reserve(self, value, external=False):
87 03992c72 Christos Stavrakakis
        """Reserve a value."""
88 3b2984dc Christos Stavrakakis
        if not self.contains(value):
89 3b2984dc Christos Stavrakakis
            raise InvalidValue("%s does not belong to pool." % value)
90 03992c72 Christos Stavrakakis
        index = self.value_to_index(value)
91 03992c72 Christos Stavrakakis
        self._reserve(index, external)
92 03992c72 Christos Stavrakakis
        return True
93 03992c72 Christos Stavrakakis
94 03992c72 Christos Stavrakakis
    def save(self, db=True):
95 03992c72 Christos Stavrakakis
        """Save changes to the DB."""
96 03992c72 Christos Stavrakakis
        self.pool_table.available_map = _bitarray_to_string(self.available)
97 03992c72 Christos Stavrakakis
        self.pool_table.reserved_map = _bitarray_to_string(self.reserved)
98 03992c72 Christos Stavrakakis
        if db:
99 03992c72 Christos Stavrakakis
            self.pool_table.save()
100 03992c72 Christos Stavrakakis
101 03992c72 Christos Stavrakakis
    def empty(self):
102 03992c72 Christos Stavrakakis
        """Return True when pool is empty."""
103 03992c72 Christos Stavrakakis
        return not self.pool.any()
104 03992c72 Christos Stavrakakis
105 03992c72 Christos Stavrakakis
    def size(self):
106 3e7c63f8 Christos Stavrakakis
        """Return the size of the bitarray(original size + padding)."""
107 03992c72 Christos Stavrakakis
        return self.pool.length()
108 03992c72 Christos Stavrakakis
109 03992c72 Christos Stavrakakis
    def _reserve(self, index, external=False):
110 03992c72 Christos Stavrakakis
        if external:
111 03992c72 Christos Stavrakakis
            self.reserved[index] = UNAVAILABLE
112 03992c72 Christos Stavrakakis
        else:
113 03992c72 Christos Stavrakakis
            self.available[index] = UNAVAILABLE
114 03992c72 Christos Stavrakakis
115 03992c72 Christos Stavrakakis
    def _release(self, index, external=False):
116 03992c72 Christos Stavrakakis
        if external:
117 03992c72 Christos Stavrakakis
            self.reserved[index] = AVAILABLE
118 03992c72 Christos Stavrakakis
        else:
119 03992c72 Christos Stavrakakis
            self.available[index] = AVAILABLE
120 03992c72 Christos Stavrakakis
121 3b2984dc Christos Stavrakakis
    def contains(self, value, index=False):
122 3b2984dc Christos Stavrakakis
        if index is False:
123 3b2984dc Christos Stavrakakis
            index = self.value_to_index(value)
124 ae994d2e Christos Stavrakakis
        return index >= 0 and index < self.pool_size
125 ae994d2e Christos Stavrakakis
126 03992c72 Christos Stavrakakis
    def count_available(self):
127 03992c72 Christos Stavrakakis
        return self.pool.count(AVAILABLE)
128 03992c72 Christos Stavrakakis
129 03992c72 Christos Stavrakakis
    def count_unavailable(self):
130 fcf2479a Christos Stavrakakis
        return self.pool_size - self.count_available()
131 03992c72 Christos Stavrakakis
132 03992c72 Christos Stavrakakis
    def count_reserved(self):
133 3e7c63f8 Christos Stavrakakis
        return self.reserved[:self.pool_size].count(UNAVAILABLE)
134 03992c72 Christos Stavrakakis
135 03992c72 Christos Stavrakakis
    def count_unreserved(self):
136 3e7c63f8 Christos Stavrakakis
        return self.pool_size - self.count_reserved()
137 03992c72 Christos Stavrakakis
138 03992c72 Christos Stavrakakis
    def is_available(self, value, index=False):
139 3b2984dc Christos Stavrakakis
        if not self.contains(value, index=index):
140 3b2984dc Christos Stavrakakis
            raise InvalidValue("%s does not belong to pool." % value)
141 03992c72 Christos Stavrakakis
        if not index:
142 03992c72 Christos Stavrakakis
            idx = self.value_to_index(value)
143 03992c72 Christos Stavrakakis
        else:
144 03992c72 Christos Stavrakakis
            idx = value
145 03992c72 Christos Stavrakakis
        return self.pool[idx] == AVAILABLE
146 03992c72 Christos Stavrakakis
147 03992c72 Christos Stavrakakis
    def is_reserved(self, value, index=False):
148 3b2984dc Christos Stavrakakis
        if not self.contains(value, index=index):
149 3b2984dc Christos Stavrakakis
            raise InvalidValue("%s does not belong to pool." % value)
150 03992c72 Christos Stavrakakis
        if not index:
151 03992c72 Christos Stavrakakis
            idx = self.value_to_index(value)
152 03992c72 Christos Stavrakakis
        else:
153 03992c72 Christos Stavrakakis
            idx = value
154 03992c72 Christos Stavrakakis
        return self.reserved[idx] == UNAVAILABLE
155 03992c72 Christos Stavrakakis
156 03992c72 Christos Stavrakakis
    def to_01(self):
157 3e7c63f8 Christos Stavrakakis
        return self.pool[:self.pool_size].to01()
158 03992c72 Christos Stavrakakis
159 03992c72 Christos Stavrakakis
    def to_map(self):
160 03992c72 Christos Stavrakakis
        return self.to_01().replace("0", "X").replace("1", ".")
161 03992c72 Christos Stavrakakis
162 3e7c63f8 Christos Stavrakakis
    def extend(self, bits_num):
163 3e7c63f8 Christos Stavrakakis
        assert(bits_num >= 0)
164 3e7c63f8 Christos Stavrakakis
        self.resize(bits_num)
165 3e7c63f8 Christos Stavrakakis
166 3e7c63f8 Christos Stavrakakis
    def shrink(self, bits_num):
167 3e7c63f8 Christos Stavrakakis
        assert(bits_num >= 0)
168 3e7c63f8 Christos Stavrakakis
        size = self.pool_size
169 3e7c63f8 Christos Stavrakakis
        tmp = self.available[(size - bits_num): size]
170 3e7c63f8 Christos Stavrakakis
        if tmp.count(UNAVAILABLE):
171 8d5795b4 Christos Stavrakakis
            raise Exception("Cannot shrink. In use")
172 3e7c63f8 Christos Stavrakakis
        self.resize(-bits_num)
173 3e7c63f8 Christos Stavrakakis
174 3e7c63f8 Christos Stavrakakis
    def resize(self, bits_num):
175 3e7c63f8 Christos Stavrakakis
        if bits_num == 0:
176 3e7c63f8 Christos Stavrakakis
            return
177 3e7c63f8 Christos Stavrakakis
        # Cut old padding
178 3e7c63f8 Christos Stavrakakis
        self.cut_padding(self.pool_size)
179 3e7c63f8 Christos Stavrakakis
        # Do the resize
180 3e7c63f8 Christos Stavrakakis
        if bits_num > 0:
181 3e7c63f8 Christos Stavrakakis
            self.available.extend([AVAILABLE] * bits_num)
182 3e7c63f8 Christos Stavrakakis
            self.reserved.extend([AVAILABLE] * bits_num)
183 3e7c63f8 Christos Stavrakakis
        else:
184 3e7c63f8 Christos Stavrakakis
            self.available = self.available[:bits_num]
185 3e7c63f8 Christos Stavrakakis
            self.reserved = self.reserved[:bits_num]
186 3e7c63f8 Christos Stavrakakis
        # Add new padding
187 3e7c63f8 Christos Stavrakakis
        self.pool_size = self.pool_size + bits_num
188 3e7c63f8 Christos Stavrakakis
        self.add_padding(self.pool_size)
189 3e7c63f8 Christos Stavrakakis
        self.pool_table.size = self.pool_size
190 3e7c63f8 Christos Stavrakakis
191 03992c72 Christos Stavrakakis
    def index_to_value(self, index):
192 03992c72 Christos Stavrakakis
        raise NotImplementedError
193 03992c72 Christos Stavrakakis
194 03992c72 Christos Stavrakakis
    def value_to_index(self, value):
195 03992c72 Christos Stavrakakis
        raise NotImplementedError
196 03992c72 Christos Stavrakakis
197 d793786b Christos Stavrakakis
    def __repr__(self):
198 d793786b Christos Stavrakakis
        return repr(self.pool_table)
199 d793786b Christos Stavrakakis
200 03992c72 Christos Stavrakakis
201 03992c72 Christos Stavrakakis
class EmptyPool(Exception):
202 03992c72 Christos Stavrakakis
    pass
203 03992c72 Christos Stavrakakis
204 03992c72 Christos Stavrakakis
205 413fb1dd Christos Stavrakakis
class ValueNotAvailable(Exception):
206 413fb1dd Christos Stavrakakis
    pass
207 413fb1dd Christos Stavrakakis
208 413fb1dd Christos Stavrakakis
209 ae994d2e Christos Stavrakakis
class InvalidValue(Exception):
210 ae994d2e Christos Stavrakakis
    pass
211 ae994d2e Christos Stavrakakis
212 ae994d2e Christos Stavrakakis
213 03992c72 Christos Stavrakakis
def find_padding(size):
214 03992c72 Christos Stavrakakis
    extra = size % 8
215 03992c72 Christos Stavrakakis
    return extra and (8 - extra) or 0
216 03992c72 Christos Stavrakakis
217 03992c72 Christos Stavrakakis
218 03992c72 Christos Stavrakakis
def bitarray_to_01(bitarray_):
219 03992c72 Christos Stavrakakis
    return bitarray_.to01()
220 03992c72 Christos Stavrakakis
221 03992c72 Christos Stavrakakis
222 03992c72 Christos Stavrakakis
def bitarray_to_map(bitarray_):
223 03992c72 Christos Stavrakakis
    return bitarray_to_01(bitarray_).replace("0", "X").replace("1", ".")
224 03992c72 Christos Stavrakakis
225 03992c72 Christos Stavrakakis
226 03992c72 Christos Stavrakakis
def _bitarray_from_string(bitarray_):
227 03992c72 Christos Stavrakakis
    ba = bitarray()
228 698306b8 Christos Stavrakakis
    ba.frombytes(b64decode(bitarray_))
229 03992c72 Christos Stavrakakis
    return ba
230 03992c72 Christos Stavrakakis
231 03992c72 Christos Stavrakakis
232 03992c72 Christos Stavrakakis
def _bitarray_to_string(bitarray_):
233 698306b8 Christos Stavrakakis
    return b64encode(bitarray_.tobytes())
234 03992c72 Christos Stavrakakis
235 03992c72 Christos Stavrakakis
##
236 03992c72 Christos Stavrakakis
## Custom pools
237 03992c72 Christos Stavrakakis
##
238 03992c72 Christos Stavrakakis
239 03992c72 Christos Stavrakakis
240 03992c72 Christos Stavrakakis
class BridgePool(PoolManager):
241 03992c72 Christos Stavrakakis
    def index_to_value(self, index):
242 dd2689f9 Christos Stavrakakis
        # Bridge indexes should start from 1
243 dd2689f9 Christos Stavrakakis
        return self.pool_table.base + str(index + 1)
244 03992c72 Christos Stavrakakis
245 03992c72 Christos Stavrakakis
    def value_to_index(self, value):
246 dd2689f9 Christos Stavrakakis
        return int(value.replace(self.pool_table.base, "")) - 1
247 03992c72 Christos Stavrakakis
248 03992c72 Christos Stavrakakis
249 03992c72 Christos Stavrakakis
class MacPrefixPool(PoolManager):
250 03992c72 Christos Stavrakakis
    def __init__(self, pool_table):
251 03992c72 Christos Stavrakakis
        do_init = False if pool_table.available_map else True
252 03992c72 Christos Stavrakakis
        super(MacPrefixPool, self).__init__(pool_table)
253 03992c72 Christos Stavrakakis
        if do_init:
254 3e7c63f8 Christos Stavrakakis
            for i in xrange(1, self.pool_size):
255 03992c72 Christos Stavrakakis
                if not self.validate_mac(self.index_to_value(i)):
256 03992c72 Christos Stavrakakis
                    self._reserve(i, external=True)
257 03992c72 Christos Stavrakakis
            # Reserve the first mac-prefix for public-networks
258 03992c72 Christos Stavrakakis
            self._reserve(0, external=True)
259 03992c72 Christos Stavrakakis
260 03992c72 Christos Stavrakakis
    def index_to_value(self, index):
261 03992c72 Christos Stavrakakis
        """Convert index to mac_prefix"""
262 03992c72 Christos Stavrakakis
        base = self.pool_table.base
263 03992c72 Christos Stavrakakis
        a = hex(int(base.replace(":", ""), 16) + index).replace("0x", '')
264 03992c72 Christos Stavrakakis
        mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)])
265 03992c72 Christos Stavrakakis
        return mac_prefix
266 03992c72 Christos Stavrakakis
267 03992c72 Christos Stavrakakis
    def value_to_index(self, value):
268 03992c72 Christos Stavrakakis
        base = self.pool_table.base
269 03992c72 Christos Stavrakakis
        return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16)
270 03992c72 Christos Stavrakakis
271 03992c72 Christos Stavrakakis
    @staticmethod
272 03992c72 Christos Stavrakakis
    def validate_mac(value):
273 03992c72 Christos Stavrakakis
        hex_ = value.replace(":", "")
274 03992c72 Christos Stavrakakis
        bin_ = bin(int(hex_, 16))[2:].zfill(8)
275 03992c72 Christos Stavrakakis
        return bin_[6] == '1' and bin_[7] == '0'
276 03992c72 Christos Stavrakakis
277 03992c72 Christos Stavrakakis
278 fdc94944 Christos Stavrakakis
class IPPool(PoolManager):
279 fdc94944 Christos Stavrakakis
    def __init__(self, pool_table):
280 b4695420 Christos Stavrakakis
        subnet = pool_table.subnet
281 b4695420 Christos Stavrakakis
        self.net = ipaddr.IPNetwork(subnet.cidr)
282 f8714db8 Christos Stavrakakis
        self.offset = pool_table.offset
283 f8714db8 Christos Stavrakakis
        self.base = pool_table.base
284 833f2ad5 Christos Stavrakakis
        if pool_table.available_map:
285 833f2ad5 Christos Stavrakakis
            initialized_pool = True
286 a1d3bc8a Christos Stavrakakis
        else:
287 833f2ad5 Christos Stavrakakis
            initialized_pool = False
288 fdc94944 Christos Stavrakakis
        super(IPPool, self).__init__(pool_table)
289 833f2ad5 Christos Stavrakakis
        if not initialized_pool:
290 833f2ad5 Christos Stavrakakis
            self.check_pool_integrity()
291 833f2ad5 Christos Stavrakakis
292 f8714db8 Christos Stavrakakis
    def check_pool_integrity(self):
293 833f2ad5 Christos Stavrakakis
        """Check the integrity of the IP pool
294 833f2ad5 Christos Stavrakakis

295 833f2ad5 Christos Stavrakakis
        This check is required only for old IP pools(one IP pool per network
296 833f2ad5 Christos Stavrakakis
        that contained the whole subnet cidr) that had not been initialized
297 833f2ad5 Christos Stavrakakis
        before the migration. This checks that the network, gateway and
298 833f2ad5 Christos Stavrakakis
        broadcast IP addresses are externally reserved, and if not reserves
299 833f2ad5 Christos Stavrakakis
        them.
300 833f2ad5 Christos Stavrakakis

301 833f2ad5 Christos Stavrakakis
        """
302 833f2ad5 Christos Stavrakakis
        subnet = self.pool_table.subnet
303 833f2ad5 Christos Stavrakakis
        check_ips = [str(self.net.network), str(self.net.broadcast)]
304 833f2ad5 Christos Stavrakakis
        if subnet.gateway is not None:
305 833f2ad5 Christos Stavrakakis
            check_ips.append(subnet.gateway)
306 833f2ad5 Christos Stavrakakis
        for ip in check_ips:
307 833f2ad5 Christos Stavrakakis
            if self.contains(ip):
308 833f2ad5 Christos Stavrakakis
                self.reserve(ip, external=True)
309 03992c72 Christos Stavrakakis
310 fdc94944 Christos Stavrakakis
    def value_to_index(self, value):
311 fdc94944 Christos Stavrakakis
        addr = ipaddr.IPAddress(value)
312 f82dfec6 Christos Stavrakakis
        return int(addr) - int(self.net.network) - int(self.offset)
313 03992c72 Christos Stavrakakis
314 fdc94944 Christos Stavrakakis
    def index_to_value(self, index):
315 f82dfec6 Christos Stavrakakis
        return str(self.net[index + int(self.offset)])
316 a3d99af0 Christos Stavrakakis
317 e59cda53 Christos Stavrakakis
    def contains(self, address, index=False):
318 e59cda53 Christos Stavrakakis
        if index is False:
319 e59cda53 Christos Stavrakakis
            addr = ipaddr.IPAddress(address)
320 e59cda53 Christos Stavrakakis
            if addr not in self.net:
321 e59cda53 Christos Stavrakakis
                return False
322 e59cda53 Christos Stavrakakis
        return super(IPPool, self).contains(address, index=False)
323 4445f97a Dionysis Grigoropoulos
324 4445f97a Dionysis Grigoropoulos
    def return_start(self):
325 e4758367 Dionysis Grigoropoulos
        return str(ipaddr.IPAddress(ipaddr.IPNetwork(self.base).network) +
326 e4758367 Dionysis Grigoropoulos
                   self.offset)
327 4445f97a Dionysis Grigoropoulos
328 4445f97a Dionysis Grigoropoulos
    def return_end(self):
329 e4758367 Dionysis Grigoropoulos
        return str(ipaddr.IPAddress(ipaddr.IPNetwork(self.base).network) +
330 e4758367 Dionysis Grigoropoulos
                   self.offset + self.pool_size - 1)