Statistics
| Branch: | Tag: | Revision:

root / lib / network.py @ 8fdedd45

History | View | Annotate | Download (9.2 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
    if address:
139
      addr = ipaddr.IPAddress(address)
140
      if addr.version == constants.IP4_VERSION and self.network:
141
        return addr in self.network
142
      elif addr.version == constants.IP6_VERSION and self.network6:
143
        return addr in self.network6
144

    
145
    return False
146

    
147
  def IsReserved(self, address):
148
    return False
149

    
150
  def Reserve(self, address, external):
151
    pass
152

    
153
  def Release(self, address, external):
154
    pass
155

    
156
  def GenerateFree(self):
157
    raise errors.OpPrereqError("Cannot generate IP in a Generic Network",
158
                               errors.ECODE_INVAL)
159

    
160
  def GetStats(self):
161
    return {}
162

    
163

    
164
class AddressPool(GenericNetwork):
165
  """Address pool class, wrapping an C{objects.Network} object.
166

167
  This class provides methods to manipulate address pools, backed by
168
  L{objects.Network} objects.
169

170
  """
171
  FREE = bitarray("0")
172
  RESERVED = bitarray("1")
173

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

177
    @type network: L{objects.Network}
178
    @param network: the network object from which the pool will be generated
179

180
    """
181
    super(AddressPool, self).__init__(nobj)
182
    if self.nobj.reservations and self.nobj.ext_reservations:
183
      self.reservations = bitarray(self.nobj.reservations)
184
      self.ext_reservations = bitarray(self.nobj.ext_reservations)
185
    else:
186
      self._InitializeReservations()
187

    
188
    self._Validate()
189

    
190
  def _InitializeReservations(self):
191
    self.reservations = bitarray(self.network.numhosts)
192
    self.reservations.setall(False) # pylint: disable=E1103
193

    
194
    self.ext_reservations = bitarray(self.network.numhosts)
195
    self.ext_reservations.setall(False) # pylint: disable=E1103
196

    
197
    for ip in [self.network[0], self.network[-1]]:
198
      self.Reserve(ip, external=True)
199

    
200
    if self.nobj.gateway:
201
      self.Reserve(self.nobj.gateway, external=True)
202

    
203
    self._Update()
204

    
205
  def _GetAddrIndex(self, address):
206
    addr = ipaddr.IPAddress(address)
207
    assert addr in self.network
208
    return int(addr) - int(self.network.network)
209

    
210
  def _Update(self):
211
    """Write address pools back to the network object.
212

213
    """
214
    # pylint: disable=E1103
215
    self.nobj.ext_reservations = self.ext_reservations.to01()
216
    self.nobj.reservations = self.reservations.to01()
217

    
218
  def _Mark(self, address, value=True, external=False):
219
    idx = self._GetAddrIndex(address)
220
    if external:
221
      self.ext_reservations[idx] = value
222
    else:
223
      self.reservations[idx] = value
224
    self._Update()
225

    
226
  def _GetSize(self):
227
    return 2 ** (32 - self.network.prefixlen)
228

    
229
  @property
230
  def _all_reservations(self):
231
    """Return a combined map of internal and external reservations.
232

233
    """
234
    return (self.reservations | self.ext_reservations)
235

    
236
  def _Validate(self):
237
    super(AddressPool, self)._Validate()
238
    assert len(self.reservations) == self.network.numhosts
239
    assert len(self.ext_reservations) == self.network.numhosts
240
    all_res = self.reservations & self.ext_reservations
241
    assert not all_res.any()
242

    
243
  def _GetReservedCount(self):
244
    """Get the count of reserved addresses.
245

246
    """
247
    return self._all_reservations.count(True)
248

    
249
  def _GetFreeCount(self):
250
    """Get the count of unused addresses.
251

252
    """
253
    return self._all_reservations.count(False)
254

    
255
  def _GetMap(self):
256
    """Return a textual representation of the network's occupation status.
257

258
    """
259
    return self._all_reservations.to01().replace("1", "X").replace("0", ".")
260

    
261
  def _GetExternalReservations(self):
262
    """Returns a list of all externally reserved addresses.
263

264
    """
265
    # pylint: disable=E1103
266
    idxs = self.ext_reservations.search(self.RESERVED)
267
    return [str(self.network[idx]) for idx in idxs]
268

    
269
  def IsReserved(self, address):
270
    """Checks if the given IP is reserved.
271

272
    """
273
    idx = self._GetAddrIndex(address)
274
    return self._all_reservations[idx]
275

    
276
  def Reserve(self, address, external=False):
277
    """Mark an address as used.
278

279
    """
280
    if self.IsReserved(address):
281
      raise errors.AddressPoolError("%s is already reserved" % address)
282
    self._Mark(address, external=external)
283

    
284
  def Release(self, address, external=False):
285
    """Release a given address reservation.
286

287
    """
288
    self._Mark(address, value=False, external=external)
289

    
290
  def GetFreeAddress(self):
291
    """Returns the first available address.
292

293
    """
294
    if self.IsFull():
295
      raise errors.AddressPoolError("%s is full" % self.network)
296

    
297
    idx = self.all_reservations.index(False)
298
    address = str(self.network[idx])
299
    self.Reserve(address)
300
    return address
301

    
302
  def GenerateFree(self):
303
    """Returns the first free address of the network.
304

305
    @raise errors.AddressPoolError: Pool is full
306

307
    """
308
    idx = self._all_reservations.search(self.FREE, 1)
309
    if not idx:
310
      raise errors.NetworkError("%s is full" % self.network)
311
    return str(self.network[idx[0]])
312

    
313
  def GetStats(self):
314
    """Returns statistics for a network address pool.
315

316
    """
317
    return {
318
      "free_count": self._GetFreeCount(),
319
      "reserved_count": self._GetReservedCount(),
320
      "map": self._GetMap(),
321
      "external_reservations":
322
        utils.CommaJoin(self._GetExternalReservations()),
323
      }