Statistics
| Branch: | Tag: | Revision:

root / lib / network.py @ a5c198eb

History | View | Annotate | Download (8 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
from base64 import b64encode
30
from base64 import b64decode
31

    
32
from ganeti import errors
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 AddressPool(object):
51
  """Address pool class, wrapping an C{objects.Network} object.
52

53
  This class provides methods to manipulate address pools, backed by
54
  L{objects.Network} objects.
55

56
  """
57
  FREE = bitarray("0")
58
  RESERVED = bitarray("1")
59

    
60
  def __init__(self, network):
61
    """Initialize a new IPv4 address pool from an L{objects.Network} object.
62

63
    @type network: L{objects.Network}
64
    @param network: the network object from which the pool will be generated
65

66
    """
67
    self.network = None
68
    self.gateway = None
69
    self.network6 = None
70
    self.gateway6 = None
71

    
72
    self.net = network
73

    
74
    self.network = ipaddr.IPNetwork(self.net.network)
75
    if self.network.numhosts > IPV4_NETWORK_MAX_NUM_HOSTS:
76
      raise errors.AddressPoolError("A big network with %s host(s) is currently"
77
                                    " not supported. please specify at most a"
78
                                    " /%s network" %
79
                                    (str(self.network.numhosts),
80
                                     IPV4_NETWORK_MAX_SIZE))
81

    
82
    if self.network.numhosts < IPV4_NETWORK_MIN_NUM_HOSTS:
83
      raise errors.AddressPoolError("A network with only %s host(s) is too"
84
                                    " small, please specify at least a /%s"
85
                                    " network" %
86
                                    (str(self.network.numhosts),
87
                                     IPV4_NETWORK_MIN_SIZE))
88
    if self.net.gateway:
89
      self.gateway = ipaddr.IPAddress(self.net.gateway)
90

    
91
    if self.net.network6:
92
      self.network6 = ipaddr.IPv6Network(self.net.network6)
93
    if self.net.gateway6:
94
      self.gateway6 = ipaddr.IPv6Address(self.net.gateway6)
95

    
96
    if self.net.reservations:
97
      self.reservations = bitarray()
98
      # pylint: disable=E1103
99
      self.reservations.frombytes(b64decode(self.net.reservations))
100
    else:
101
      self.reservations = bitarray(self.network.numhosts)
102
      # pylint: disable=E1103
103
      self.reservations.setall(False)
104

    
105
    if self.net.ext_reservations:
106
      self.ext_reservations = bitarray()
107
      # pylint: disable=E1103
108
      self.ext_reservations.frombytes(b64decode(self.net.ext_reservations))
109
    else:
110
      self.ext_reservations = bitarray(self.network.numhosts)
111
      # pylint: disable=E1103
112
      self.ext_reservations.setall(False)
113

    
114
    assert len(self.reservations) == self.network.numhosts
115
    assert len(self.ext_reservations) == self.network.numhosts
116

    
117
  def Contains(self, address):
118
    if address is None:
119
      return False
120
    addr = ipaddr.IPAddress(address)
121

    
122
    return addr in self.network
123

    
124
  def _GetAddrIndex(self, address):
125
    addr = ipaddr.IPAddress(address)
126

    
127
    if not addr in self.network:
128
      raise errors.AddressPoolError("%s does not contain %s" %
129
                                    (self.network, addr))
130

    
131
    return int(addr) - int(self.network.network)
132

    
133
  def Update(self):
134
    """Write address pools back to the network object.
135

136
    """
137
    # pylint: disable=E1103
138
    self.net.ext_reservations = b64encode(self.ext_reservations.tobytes())
139
    self.net.reservations = b64encode(self.reservations.tobytes())
140

    
141
  def _Mark(self, address, value=True, external=False):
142
    idx = self._GetAddrIndex(address)
143
    if external:
144
      self.ext_reservations[idx] = value
145
    else:
146
      self.reservations[idx] = value
147
    self.Update()
148

    
149
  def _GetSize(self):
150
    return 2 ** (32 - self.network.prefixlen)
151

    
152
  @property
153
  def all_reservations(self):
154
    """Return a combined map of internal and external reservations.
155

156
    """
157
    return (self.reservations | self.ext_reservations)
158

    
159
  def Validate(self):
160
    assert len(self.reservations) == self._GetSize()
161
    assert len(self.ext_reservations) == self._GetSize()
162

    
163
    if self.gateway is not None:
164
      assert self.gateway in self.network
165

    
166
    if self.network6 and self.gateway6:
167
      assert self.gateway6 in self.network6 or self.gateway6.is_link_local
168

    
169
    return True
170

    
171
  def IsFull(self):
172
    """Check whether the network is full.
173

174
    """
175
    return self.all_reservations.all()
176

    
177
  def GetReservedCount(self):
178
    """Get the count of reserved addresses.
179

180
    """
181
    return self.all_reservations.count(True)
182

    
183
  def GetFreeCount(self):
184
    """Get the count of unused addresses.
185

186
    """
187
    return self.all_reservations.count(False)
188

    
189
  def GetMap(self):
190
    """Return a textual representation of the network's occupation status.
191

192
    """
193
    return self.all_reservations.to01().replace("1", "X").replace("0", ".")
194

    
195
  def IsReserved(self, address, external=False):
196
    """Checks if the given IP is reserved.
197

198
    """
199
    idx = self._GetAddrIndex(address)
200
    if external:
201
      return self.ext_reservations[idx]
202
    else:
203
      return self.reservations[idx]
204

    
205
  def Reserve(self, address, external=False):
206
    """Mark an address as used.
207

208
    """
209
    if self.IsReserved(address, external):
210
      if external:
211
        msg = "IP %s is already externally reserved" % address
212
      else:
213
        msg = "IP %s is already used by an instance" % address
214
      raise errors.AddressPoolError(msg)
215

    
216
    self._Mark(address, external=external)
217

    
218
  def Release(self, address, external=False):
219
    """Release a given address reservation.
220

221
    """
222
    if not self.IsReserved(address, external):
223
      if external:
224
        msg = "IP %s is not externally reserved" % address
225
      else:
226
        msg = "IP %s is not used by an instance" % address
227
      raise errors.AddressPoolError(msg)
228

    
229
    self._Mark(address, value=False, external=external)
230

    
231
  def GetFreeAddress(self):
232
    """Returns the first available address.
233

234
    """
235
    if self.IsFull():
236
      raise errors.AddressPoolError("%s is full" % self.network)
237

    
238
    idx = self.all_reservations.index(False)
239
    address = str(self.network[idx])
240
    self.Reserve(address)
241
    return address
242

    
243
  def GenerateFree(self):
244
    """Returns the first free address of the network.
245

246
    @raise errors.AddressPoolError: Pool is full
247

248
    """
249
    idx = self.all_reservations.search(self.FREE, 1)
250
    if idx:
251
      return str(self.network[idx[0]])
252
    else:
253
      raise errors.AddressPoolError("%s is full" % self.network)
254

    
255
  def GetExternalReservations(self):
256
    """Returns a list of all externally reserved addresses.
257

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

    
263
  @classmethod
264
  def InitializeNetwork(cls, net):
265
    """Initialize an L{objects.Network} object.
266

267
    Reserve the network, broadcast and gateway IP addresses.
268

269
    """
270
    obj = cls(net)
271
    obj.Update()
272
    for ip in [obj.network[0], obj.network[-1]]:
273
      obj.Reserve(ip, external=True)
274
    if obj.net.gateway is not None:
275
      obj.Reserve(obj.net.gateway, external=True)
276
    obj.Validate()
277
    return obj