Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (8.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
        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
    def __repr__(self):
176
        return repr(self.pool_table)
177

    
178

    
179
class EmptyPool(Exception):
180
    pass
181

    
182

    
183
def find_padding(size):
184
    extra = size % 8
185
    return extra and (8 - extra) or 0
186

    
187

    
188
def bitarray_to_01(bitarray_):
189
    return bitarray_.to01()
190

    
191

    
192
def bitarray_to_map(bitarray_):
193
    return bitarray_to_01(bitarray_).replace("0", "X").replace("1", ".")
194

    
195

    
196
def _bitarray_from_string(bitarray_):
197
    ba = bitarray()
198
    ba.frombytes(b64decode(bitarray_))
199
    return ba
200

    
201

    
202
def _bitarray_to_string(bitarray_):
203
    return b64encode(bitarray_.tobytes())
204

    
205
##
206
## Custom pools
207
##
208

    
209

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

    
215
    def value_to_index(self, value):
216
        return int(value.replace(self.pool_table.base, "")) - 1
217

    
218

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

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

    
237
    def value_to_index(self, value):
238
        base = self.pool_table.base
239
        return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16)
240

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

    
247

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

    
264
    def value_to_index(self, value):
265
        addr = ipaddr.IPAddress(value)
266
        return int(addr) - int(self.net.network)
267

    
268
    def index_to_value(self, index):
269
        return str(self.net[index])
270

    
271
    def contains(self, address):
272
        addr = ipaddr.IPAddress(address)
273
        return addr in self.net