Statistics
| Branch: | Tag: | Revision:

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)