Statistics
| Branch: | Tag: | Revision:

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

History | View | Annotate | Download (6.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

    
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=False):
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.frombytes(b64decode(bitarray_))
161
    return ba
162

    
163

    
164
def _bitarray_to_string(bitarray_):
165
    return b64encode(bitarray_.tobytes())
166

    
167
##
168
## Custom pools
169
##
170

    
171

    
172
class BridgePool(PoolManager):
173
    def index_to_value(self, index):
174
        # Bridge indexes should start from 1
175
        return self.pool_table.base + str(index + 1)
176

    
177
    def value_to_index(self, value):
178
        return int(value.replace(self.pool_table.base, "")) - 1
179

    
180

    
181
class MacPrefixPool(PoolManager):
182
    def __init__(self, pool_table):
183
        do_init = False if pool_table.available_map else True
184
        super(MacPrefixPool, self).__init__(pool_table)
185
        if do_init:
186
            for i in xrange(1, self.size()):
187
                if not self.validate_mac(self.index_to_value(i)):
188
                    self._reserve(i, external=True)
189
            # Reserve the first mac-prefix for public-networks
190
            self._reserve(0, external=True)
191

    
192
    def index_to_value(self, index):
193
        """Convert index to mac_prefix"""
194
        base = self.pool_table.base
195
        a = hex(int(base.replace(":", ""), 16) + index).replace("0x", '')
196
        mac_prefix = ":".join([a[x:x + 2] for x in xrange(0, len(a), 2)])
197
        return mac_prefix
198

    
199
    def value_to_index(self, value):
200
        base = self.pool_table.base
201
        return int(value.replace(":", ""), 16) - int(base.replace(":", ""), 16)
202

    
203
    @staticmethod
204
    def validate_mac(value):
205
        hex_ = value.replace(":", "")
206
        bin_ = bin(int(hex_, 16))[2:].zfill(8)
207
        return bin_[6] == '1' and bin_[7] == '0'
208

    
209

    
210
class IPPool(PoolManager):
211
    def __init__(self, pool_table):
212
        do_init = False if pool_table.available_map else True
213
        network = pool_table.network
214
        self.net = ipaddr.IPNetwork(network.subnet)
215
        if not pool_table.size:
216
            pool_table.size = self.net.numhosts
217
        super(IPPool, self).__init__(pool_table)
218
        gateway = network.gateway
219
        self.gateway = gateway and ipaddr.IPAddress(gateway) or None
220
        if do_init:
221
            self._reserve(0, external=True)
222
            if gateway:
223
                self.reserve(gateway, external=True)
224
            self._reserve(self.size() - 1, external=True)
225

    
226
    def value_to_index(self, value):
227
        addr = ipaddr.IPAddress(value)
228
        return int(addr) - int(self.net.network)
229

    
230
    def index_to_value(self, index):
231
        return str(self.net[index])