Statistics
| Branch: | Tag: | Revision:

root / lib / network.py @ 7aea0198

History | View | Annotate | Download (9.6 kB)

1
#
2
#
3

    
4
# Copyright (C) 2011, 2012 Google Inc.
5
#
6
# This program is free software; you can redistribute it and/or modify
7
# it under the terms of the GNU General Public License as published by
8
# the Free Software Foundation; either version 2 of the License, or
9
# (at your option) any later version.
10
#
11
# This program is distributed in the hope that it will be useful, but
12
# WITHOUT ANY WARRANTY; without even the implied warranty of
13
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14
# General Public License for more details.
15
#
16
# You should have received a copy of the GNU General Public License
17
# along with this program; if not, write to the Free Software
18
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19
# 02110-1301, USA.
20

    
21

    
22
"""IP address pool management functions.
23

24
"""
25

    
26
import ipaddr
27

    
28
from bitarray import bitarray
29

    
30
from ganeti import errors
31
from ganeti import utils
32
from ganeti import constants
33
import logging
34

    
35

    
36
def _ComputeIpv4NumHosts(network_size):
37
  """Derives the number of hosts in an IPv4 network from the size.
38

39
  """
40
  return 2 ** (32 - network_size)
41

    
42

    
43
IPV4_NETWORK_MIN_SIZE = 30
44
# FIXME: This limit is for performance reasons. Remove when refactoring
45
# for performance tuning was successful.
46
IPV4_NETWORK_MAX_SIZE = 16
47
IPV4_NETWORK_MIN_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MIN_SIZE)
48
IPV4_NETWORK_MAX_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MAX_SIZE)
49

    
50

    
51
class Network(object):
52
  """ Wrapper Class for networks.
53

54
  Used to get a network out of a L{objects.Network}. In case nobj
55
  has an IPv4 subnet it returns an AddressPool object. Otherwise
56
  a GenericNetwork object is created. To get a network use:
57
  network.Network(nobj)
58

59
  """
60
  def __new__(cls, nobj):
61
    if nobj.network:
62
      return AddressPool(nobj)
63
    else:
64
      return GenericNetwork(nobj)
65

    
66
  @classmethod
67
  def Check(cls, address, network):
68
    try:
69
      if network:
70
        network = ipaddr.IPNetwork(network)
71
      if address:
72
        address = ipaddr.IPAddress(address)
73
    except ValueError, e:
74
      raise errors.OpPrereqError(e, errors.ECODE_INVAL)
75

    
76
    if address and not network:
77
      raise errors.OpPrereqError("Address '%s' but no network." % address,
78
                                 errors.ECODE_INVAL)
79
    if address and address not in network:
80
      raise errors.OpPrereqError("Address '%s' not in network '%s'." %
81
                                 (address, network),
82
                                 errors.ECODE_INVAL)
83

    
84
  def Contains(self, address):
85
    pass
86

    
87
  def IsReserved(self, address):
88
    pass
89

    
90
  def Reserve(self, address, external):
91
    pass
92

    
93
  def Release(self, address, external):
94
    pass
95

    
96
  def GenerateFree(self):
97
    pass
98

    
99
  def GetStats(self):
100
    pass
101

    
102

    
103
class GenericNetwork(object):
104
  """ Base class for networks.
105

106
  This includes all info and methods deriving from subnets and gateways
107
  both IPv4 and IPv6. Implements basic checks and abstracts the methods
108
  that are invoked by external methods.
109

110
  """
111
  def __init__(self, nobj):
112
    """Initializes a Generic Network from an L{objects.Network} object.
113

114
    @type nobj: L{objects.Network}
115
    @param nobj: the network object
116

117
    """
118
    self.network = None
119
    self.gateway = None
120
    self.network6 = None
121
    self.gateway6 = None
122

    
123
    self.nobj = nobj
124

    
125
    if self.nobj.gateway and not self.nobj.network:
126
      raise errors.OpPrereqError("Gateway without network. Cannot proceed")
127

    
128
    if self.nobj.network:
129
      self.network = ipaddr.IPNetwork(self.nobj.network)
130
      if self.nobj.gateway:
131
        self.gateway = ipaddr.IPAddress(self.nobj.gateway)
132
        if self.gateway not in self.network:
133
          raise errors.OpPrereqError("Gateway not in network.",
134
                                     errors.ECODE_INVAL)
135

    
136
    if self.nobj.gateway6 and not self.nobj.network6:
137
      raise errors.OpPrereqError("IPv6 Gateway without IPv6 network."
138
                                 " Cannot proceed.",
139
                                 errors.ECODE_INVAL)
140
    if self.nobj.network6:
141
      self.network6 = ipaddr.IPv6Network(self.nobj.network6)
142
      if self.nobj.gateway6:
143
        self.gateway6 = ipaddr.IPv6Address(self.nobj.gateway6)
144
        if self.gateway6 not in self.network6:
145
          raise errors.OpPrereqError("IPv6 Gateway not in IPv6 network.",
146
                                     errors.ECODE_INVAL)
147

    
148
  def _Validate(self):
149
    if self.gateway:
150
      assert self.network
151
      assert self.gateway in self.network
152
    if self.gateway6:
153
      assert self.network6
154
      assert self.gateway6 in self.network6
155

    
156
  def Contains(self, address):
157
    if address:
158
      addr = ipaddr.IPAddress(address)
159
      if addr.version == constants.IP4_VERSION and self.network:
160
        return addr in self.network
161
      elif addr.version == constants.IP6_VERSION and self.network6:
162
        return addr in self.network6
163

    
164
    return False
165

    
166
  def IsReserved(self, address):
167
    logging.info("Check if %s is reserved in network %s",
168
                 address, self.nobj.name)
169
    return False
170

    
171
  def Reserve(self, address, external):
172
    pass
173

    
174
  def Release(self, address, external):
175
    pass
176

    
177
  def GenerateFree(self):
178
    raise errors.OpPrereqError("Cannot generate IP in a Generic Network",
179
                               errors.ECODE_INVAL)
180

    
181
  def GetStats(self):
182
    return {}
183

    
184

    
185
class AddressPool(GenericNetwork):
186
  """Address pool class, wrapping an C{objects.Network} object.
187

188
  This class provides methods to manipulate address pools, backed by
189
  L{objects.Network} objects.
190

191
  """
192
  FREE = bitarray("0")
193
  RESERVED = bitarray("1")
194

    
195
  def __init__(self, nobj):
196
    """Initialize a new IPv4 address pool from an L{objects.Network} object.
197

198
    @type network: L{objects.Network}
199
    @param network: the network object from which the pool will be generated
200

201
    """
202
    super(AddressPool, self).__init__(nobj)
203
    if self.nobj.reservations and self.nobj.ext_reservations:
204
      self.reservations = bitarray(self.nobj.reservations)
205
      self.ext_reservations = bitarray(self.nobj.ext_reservations)
206
    else:
207
      self._InitializeReservations()
208

    
209
    self._Validate()
210

    
211
  def _InitializeReservations(self):
212
    self.reservations = bitarray(self.network.numhosts)
213
    self.reservations.setall(False) # pylint: disable=E1103
214

    
215
    self.ext_reservations = bitarray(self.network.numhosts)
216
    self.ext_reservations.setall(False) # pylint: disable=E1103
217

    
218
    for ip in [self.network[0], self.network[-1]]:
219
      self.Reserve(ip, external=True)
220

    
221
    if self.nobj.gateway:
222
      self.Reserve(self.nobj.gateway, external=True)
223

    
224
    self._Update()
225

    
226
  def _GetAddrIndex(self, address):
227
    addr = ipaddr.IPAddress(address)
228
    assert addr in self.network
229
    return int(addr) - int(self.network.network)
230

    
231
  def _Update(self):
232
    """Write address pools back to the network object.
233

234
    """
235
    # pylint: disable=E1103
236
    self.nobj.ext_reservations = self.ext_reservations.to01()
237
    self.nobj.reservations = self.reservations.to01()
238

    
239
  def _Mark(self, address, value=True, external=False):
240
    idx = self._GetAddrIndex(address)
241
    if external:
242
      self.ext_reservations[idx] = value
243
    else:
244
      self.reservations[idx] = value
245
    self._Update()
246

    
247
  def _GetSize(self):
248
    return 2 ** (32 - self.network.prefixlen)
249

    
250
  @property
251
  def _all_reservations(self):
252
    """Return a combined map of internal and external reservations.
253

254
    """
255
    return (self.reservations | self.ext_reservations)
256

    
257
  def _Validate(self):
258
    super(AddressPool, self)._Validate()
259
    assert len(self.reservations) == self.network.numhosts
260
    assert len(self.ext_reservations) == self.network.numhosts
261
    all_res = self.reservations & self.ext_reservations
262
    assert not all_res.any()
263

    
264
  def _GetReservedCount(self):
265
    """Get the count of reserved addresses.
266

267
    """
268
    return self._all_reservations.count(True)
269

    
270
  def _GetFreeCount(self):
271
    """Get the count of unused addresses.
272

273
    """
274
    return self._all_reservations.count(False)
275

    
276
  def _GetMap(self):
277
    """Return a textual representation of the network's occupation status.
278

279
    """
280
    return self._all_reservations.to01().replace("1", "X").replace("0", ".")
281

    
282
  def _GetExternalReservations(self):
283
    """Returns a list of all externally reserved addresses.
284

285
    """
286
    # pylint: disable=E1103
287
    idxs = self.ext_reservations.search(self.RESERVED)
288
    return [str(self.network[idx]) for idx in idxs]
289

    
290
  def IsReserved(self, address):
291
    """Checks if the given IP is reserved.
292

293
    """
294
    idx = self._GetAddrIndex(address)
295
    return self._all_reservations[idx]
296

    
297
  def Reserve(self, address, external=False):
298
    """Mark an address as used.
299

300
    """
301
    if self.IsReserved(address):
302
      raise errors.NetworkError("%s is already reserved" % address)
303
    self._Mark(address, external=external)
304

    
305
  def Release(self, address, external=False):
306
    """Release a given address reservation.
307

308
    """
309
    self._Mark(address, value=False, external=external)
310

    
311
  def GetFreeAddress(self):
312
    """Returns the first available address.
313

314
    """
315
    if self._all_reservations.all():
316
      raise errors.NetworkError("%s is full" % self.network)
317

    
318
    idx = self._all_reservations.index(False)
319
    address = str(self.network[idx])
320
    self.Reserve(address)
321
    return address
322

    
323
  def GenerateFree(self):
324
    """Returns the first free address of the network.
325

326
    @raise errors.NetworkError: Pool is full
327

328
    """
329
    idx = self._all_reservations.search(self.FREE, 1)
330
    if not idx:
331
      raise errors.NetworkError("%s is full" % self.network)
332
    return str(self.network[idx[0]])
333

    
334
  def GetStats(self):
335
    """Returns statistics for a network address pool.
336

337
    """
338
    return {
339
      "free_count": self._GetFreeCount(),
340
      "reserved_count": self._GetReservedCount(),
341
      "map": self._GetMap(),
342
      "external_reservations":
343
        utils.CommaJoin(self._GetExternalReservations()),
344
      }