Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8 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
        self.pool_size = pool_table.size
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
            self.available = self._create_empty_pool(self.pool_size)
36
            self.reserved = self._create_empty_pool(self.pool_size)
37
            self.add_padding(self.pool_size)
38

    
39
    def _create_empty_pool(self, size):
40
        ba = bitarray(size)
41
        ba.setall(AVAILABLE)
42
        return ba
43

    
44
    def add_padding(self, pool_size):
45
        bits = find_padding(pool_size)
46
        self.available.extend([UNAVAILABLE] * bits)
47
        self.reserved.extend([UNAVAILABLE] * bits)
48

    
49
    def cut_padding(self, pool_size):
50
        bits = find_padding(pool_size)
51
        self.available = self.available[:-bits]
52
        self.reserved = self.reserved[:-bits]
53

    
54
    @property
55
    def pool(self):
56
        return (self.available & self.reserved)
57

    
58
    def get(self):
59
        """Get a value from the pool."""
60
        if self.empty():
61
            raise EmptyPool
62
        # Get the first available index
63
        index = int(self.pool.index(AVAILABLE))
64
        assert(index < self.pool_size)
65
        self._reserve(index)
66
        return self.index_to_value(index)
67

    
68
    def put(self, value, external=False):
69
        """Return a value to the pool."""
70
        if value is None:
71
            raise ValueError
72
        index = self.value_to_index(value)
73
        self._release(index, external)
74

    
75
    def reserve(self, value, external=False):
76
        """Reserve a value."""
77
        index = self.value_to_index(value)
78
        self._reserve(index, external)
79
        return True
80

    
81
    def save(self, db=True):
82
        """Save changes to the DB."""
83
        self.pool_table.available_map = _bitarray_to_string(self.available)
84
        self.pool_table.reserved_map = _bitarray_to_string(self.reserved)
85
        if db:
86
            self.pool_table.save()
87

    
88
    def empty(self):
89
        """Return True when pool is empty."""
90
        return not self.pool.any()
91

    
92
    def size(self):
93
        """Return the size of the bitarray(original size + padding)."""
94
        return self.pool.length()
95

    
96
    def _reserve(self, index, external=False):
97
        if external:
98
            self.reserved[index] = UNAVAILABLE
99
        else:
100
            self.available[index] = UNAVAILABLE
101

    
102
    def _release(self, index, external=False):
103
        if external:
104
            self.reserved[index] = AVAILABLE
105
        else:
106
            self.available[index] = AVAILABLE
107

    
108
    def count_available(self):
109
        return self.pool.count(AVAILABLE)
110

    
111
    def count_unavailable(self):
112
        return self.pool_size - self.count_available()
113

    
114
    def count_reserved(self):
115
        return self.reserved[:self.pool_size].count(UNAVAILABLE)
116

    
117
    def count_unreserved(self):
118
        return self.pool_size - self.count_reserved()
119

    
120
    def is_available(self, value, index=False):
121
        if not index:
122
            idx = self.value_to_index(value)
123
        else:
124
            idx = value
125
        return self.pool[idx] == AVAILABLE
126

    
127
    def is_reserved(self, value, index=False):
128
        if not index:
129
            idx = self.value_to_index(value)
130
        else:
131
            idx = value
132
        return self.reserved[idx] == UNAVAILABLE
133

    
134
    def to_01(self):
135
        return self.pool[:self.pool_size].to01()
136

    
137
    def to_map(self):
138
        return self.to_01().replace("0", "X").replace("1", ".")
139

    
140
    def extend(self, bits_num):
141
        assert(bits_num >= 0)
142
        self.resize(bits_num)
143

    
144
    def shrink(self, bits_num):
145
        assert(bits_num >= 0)
146
        size = self.pool_size
147
        tmp = self.available[(size - bits_num): size]
148
        if tmp.count(UNAVAILABLE):
149
            raise Exception("Can not shrink. In use")
150
        self.resize(-bits_num)
151

    
152
    def resize(self, bits_num):
153
        if bits_num == 0:
154
            return
155
        # Cut old padding
156
        self.cut_padding(self.pool_size)
157
        # Do the resize
158
        if bits_num > 0:
159
            self.available.extend([AVAILABLE] * bits_num)
160
            self.reserved.extend([AVAILABLE] * bits_num)
161
        else:
162
            self.available = self.available[:bits_num]
163
            self.reserved = self.reserved[:bits_num]
164
        # Add new padding
165
        self.pool_size = self.pool_size + bits_num
166
        self.add_padding(self.pool_size)
167
        self.pool_table.size = self.pool_size
168

    
169
    def index_to_value(self, index):
170
        raise NotImplementedError
171

    
172
    def value_to_index(self, value):
173
        raise NotImplementedError
174

    
175

    
176
class EmptyPool(Exception):
177
    pass
178

    
179

    
180
def find_padding(size):
181
    extra = size % 8
182
    return extra and (8 - extra) or 0
183

    
184

    
185
def bitarray_to_01(bitarray_):
186
    return bitarray_.to01()
187

    
188

    
189
def bitarray_to_map(bitarray_):
190
    return bitarray_to_01(bitarray_).replace("0", "X").replace("1", ".")
191

    
192

    
193
def _bitarray_from_string(bitarray_):
194
    ba = bitarray()
195
    ba.frombytes(b64decode(bitarray_))
196
    return ba
197

    
198

    
199
def _bitarray_to_string(bitarray_):
200
    return b64encode(bitarray_.tobytes())
201

    
202
##
203
## Custom pools
204
##
205

    
206

    
207
class BridgePool(PoolManager):
208
    def index_to_value(self, index):
209
        # Bridge indexes should start from 1
210
        return self.pool_table.base + str(index + 1)
211

    
212
    def value_to_index(self, value):
213
        return int(value.replace(self.pool_table.base, "")) - 1
214

    
215

    
216
class MacPrefixPool(PoolManager):
217
    def __init__(self, pool_table):
218
        do_init = False if pool_table.available_map else True
219
        super(MacPrefixPool, self).__init__(pool_table)
220
        if do_init:
221
            for i in xrange(1, self.pool_size):
222
                if not self.validate_mac(self.index_to_value(i)):
223
                    self._reserve(i, external=True)
224
            # Reserve the first mac-prefix for public-networks
225
            self._reserve(0, external=True)
226

    
227
    def index_to_value(self, index):
228
        """Convert index to mac_prefix"""
229
        base = self.pool_table.base
230
        a = hex(int(base.replace(":", ""), 16) + index).replace("0x", '')
231
        mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)])
232
        return mac_prefix
233

    
234
    def value_to_index(self, value):
235
        base = self.pool_table.base
236
        return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16)
237

    
238
    @staticmethod
239
    def validate_mac(value):
240
        hex_ = value.replace(":", "")
241
        bin_ = bin(int(hex_, 16))[2:].zfill(8)
242
        return bin_[6] == '1' and bin_[7] == '0'
243

    
244

    
245
class IPPool(PoolManager):
246
    def __init__(self, pool_table):
247
        do_init = False if pool_table.available_map else True
248
        network = pool_table.network
249
        self.net = ipaddr.IPNetwork(network.subnet)
250
        if not pool_table.size:
251
            pool_table.size = self.net.numhosts
252
        super(IPPool, self).__init__(pool_table)
253
        gateway = network.gateway
254
        self.gateway = gateway and ipaddr.IPAddress(gateway) or None
255
        if do_init:
256
            self._reserve(0, external=True)
257
            if gateway:
258
                self.reserve(gateway, external=True)
259
            self._reserve(self.pool_size - 1, external=True)
260

    
261
    def value_to_index(self, value):
262
        addr = ipaddr.IPAddress(value)
263
        return int(addr) - int(self.net.network)
264

    
265
    def index_to_value(self, index):
266
        return str(self.net[index])