Statistics
| Branch: | Tag: | Revision:

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