Statistics
| Branch: | Tag: | Revision:

root / lib / network.py @ 22dc14c4

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

    
34

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

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

    
41

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

    
49

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

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

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

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

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

    
83

    
84
class GenericNetwork(object):
85
  """ Base class for networks.
86

87
  This includes all info and methods deriving from subnets and gateways
88
  both IPv4 and IPv6. Implements basic checks and abstracts the methods
89
  that are invoked by external methods.
90

91
  """
92
  def __init__(self, nobj):
93
    """Initializes a Generic Network from an L{objects.Network} object.
94

95
    @type nobj: L{objects.Network}
96
    @param nobj: the network object
97

98
    """
99
    self.network = None
100
    self.gateway = None
101
    self.network6 = None
102
    self.gateway6 = None
103

    
104
    self.nobj = nobj
105

    
106
    if self.nobj.gateway and not self.nobj.network:
107
      raise errors.OpPrereqError("Gateway without network. Cannot proceed")
108

    
109
    if self.nobj.network:
110
      self.network = ipaddr.IPNetwork(self.nobj.network)
111
      if self.nobj.gateway:
112
        self.gateway = ipaddr.IPAddress(self.nobj.gateway)
113
        if self.gateway not in self.network:
114
          raise errors.OpPrereqError("Gateway not in network.",
115
                                     errors.ECODE_INVAL)
116

    
117
    if self.nobj.gateway6 and not self.nobj.network6:
118
      raise errors.OpPrereqError("IPv6 Gateway without IPv6 network."
119
                                 " Cannot proceed.",
120
                                 errors.ECODE_INVAL)
121
    if self.nobj.network6:
122
      self.network6 = ipaddr.IPv6Network(self.nobj.network6)
123
      if self.nobj.gateway6:
124
        self.gateway6 = ipaddr.IPv6Address(self.nobj.gateway6)
125
        if self.gateway6 not in self.network6:
126
          raise errors.OpPrereqError("IPv6 Gateway not in IPv6 network.",
127
                                     errors.ECODE_INVAL)
128

    
129
  def _Validate(self):
130
    if self.gateway:
131
      assert self.network
132
      assert self.gateway in self.network
133
    if self.gateway6:
134
      assert self.network6
135
      assert self.gateway6 in self.network6
136

    
137
  def Contains(self, address):
138
    addr = ipaddr.IPAddress(address)
139
    if addr.version == constants.IP4_VERSION and self.network:
140
      return addr in self.network
141
    elif addr.version == constants.IP6_VERSION and self.network6:
142
      return addr in self.network6
143

    
144
  def IsReserved(self, address):
145
    raise NotImplementedError
146

    
147
  def Reserve(self, address, external):
148
    raise NotImplementedError
149

    
150
  def Release(self, address, external):
151
    raise NotImplementedError
152

    
153
  def GenerateFree(self):
154
    raise NotImplementedError
155

    
156
  def GetStats(self):
157
    return {}
158

    
159

    
160
class AddressPool(GenericNetwork):
161
  """Address pool class, wrapping an C{objects.Network} object.
162

163
  This class provides methods to manipulate address pools, backed by
164
  L{objects.Network} objects.
165

166
  """
167
  FREE = bitarray("0")
168
  RESERVED = bitarray("1")
169

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

173
    @type network: L{objects.Network}
174
    @param network: the network object from which the pool will be generated
175

176
    """
177
    super(AddressPool, self).__init__(nobj)
178
    if self.nobj.reservations and self.nobj.ext_reservations:
179
      self.reservations = bitarray(self.nobj.reservations)
180
      self.ext_reservations = bitarray(self.nobj.ext_reservations)
181
    else:
182
      self._InitializeReservations()
183

    
184
    self._Validate()
185

    
186
  def _InitializeReservations(self):
187
    self.reservations = bitarray(self.network.numhosts)
188
    self.reservations.setall(False) # pylint: disable=E1103
189

    
190
    self.ext_reservations = bitarray(self.network.numhosts)
191
    self.ext_reservations.setall(False) # pylint: disable=E1103
192

    
193
    for ip in [self.network[0], self.network[-1]]:
194
      self.Reserve(ip, external=True)
195

    
196
    if self.nobj.gateway:
197
      self.Reserve(self.nobj.gateway, external=True)
198

    
199
    self._Update()
200

    
201
  def _GetAddrIndex(self, address):
202
    addr = ipaddr.IPAddress(address)
203
    assert addr in self.network
204
    return int(addr) - int(self.network.network)
205

    
206
  def _Update(self):
207
    """Write address pools back to the network object.
208

209
    """
210
    # pylint: disable=E1103
211
    self.net.ext_reservations = self.ext_reservations.to01()
212
    self.net.reservations = self.reservations.to01()
213

    
214
  def _Mark(self, address, value=True, external=False):
215
    idx = self._GetAddrIndex(address)
216
    if external:
217
      self.ext_reservations[idx] = value
218
    else:
219
      self.reservations[idx] = value
220
    self._Update()
221

    
222
  def _GetSize(self):
223
    return 2 ** (32 - self.network.prefixlen)
224

    
225
  @property
226
  def _all_reservations(self):
227
    """Return a combined map of internal and external reservations.
228

229
    """
230
    return (self.reservations | self.ext_reservations)
231

    
232
  def _Validate(self):
233
    super(AddressPool, self)._Validate()
234
    assert len(self.reservations) == self.network.numhosts
235
    assert len(self.ext_reservations) == self.network.numhosts
236
    all_res = self.reservations & self.ext_reservations
237
    assert not all_res.any()
238

    
239
  def _GetReservedCount(self):
240
    """Get the count of reserved addresses.
241

242
    """
243
    return self._all_reservations.count(True)
244

    
245
  def _GetFreeCount(self):
246
    """Get the count of unused addresses.
247

248
    """
249
    return self._all_reservations.count(False)
250

    
251
  def _GetMap(self):
252
    """Return a textual representation of the network's occupation status.
253

254
    """
255
    return self._all_reservations.to01().replace("1", "X").replace("0", ".")
256

    
257
  def _GetExternalReservations(self):
258
    """Returns a list of all externally reserved addresses.
259

260
    """
261
    # pylint: disable=E1103
262
    idxs = self.ext_reservations.search(self.RESERVED)
263
    return [str(self.network[idx]) for idx in idxs]
264

    
265
  def IsReserved(self, address):
266
    """Checks if the given IP is reserved.
267

268
    """
269
    idx = self._GetAddrIndex(address)
270
    return self._all_reservations[idx]
271

    
272
  def Reserve(self, address, external=False):
273
    """Mark an address as used.
274

275
    """
276
    if self.IsReserved(address):
277
      raise errors.AddressPoolError("%s is already reserved" % address)
278
    self._Mark(address, external=external)
279

    
280
  def Release(self, address, external=False):
281
    """Release a given address reservation.
282

283
    """
284
    self._Mark(address, value=False, external=external)
285

    
286
  def GetFreeAddress(self):
287
    """Returns the first available address.
288

289
    """
290
    if self.IsFull():
291
      raise errors.AddressPoolError("%s is full" % self.network)
292

    
293
    idx = self.all_reservations.index(False)
294
    address = str(self.network[idx])
295
    self.Reserve(address)
296
    return address
297

    
298
  def GenerateFree(self):
299
    """Returns the first free address of the network.
300

301
    @raise errors.AddressPoolError: Pool is full
302

303
    """
304
    idx = self._all_reservations.search(self.FREE, 1)
305
    if not idx:
306
      raise errors.NetworkError("%s is full" % self.network)
307
    return str(self.network[idx[0]])
308

    
309
  def GetStats(self):
310
    """Returns statistics for a network address pool.
311

312
    """
313
    return {
314
      "free_count": self._GetFreeCount(),
315
      "reserved_count": self._GetReservedCount(),
316
      "map": self._GetMap(),
317
      "external_reservations":
318
        utils.CommaJoin(self._GetExternalReservations()),
319
      }