Statistics
| Branch: | Tag: | Revision:

root / lib / network.py @ a3f00eae

History | View | Annotate | Download (7.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
      self.reservations.frombytes(b64decode(self.net.reservations))
99
    else:
100
      self.reservations = bitarray(self.network.numhosts)
101
      # pylint: disable=E1103
102
      self.reservations.setall(False)
103

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

    
112
    assert len(self.reservations) == self.network.numhosts
113
    assert len(self.ext_reservations) == self.network.numhosts
114

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

    
120
    return addr in self.network
121

    
122
  def _GetAddrIndex(self, address):
123
    addr = ipaddr.IPAddress(address)
124

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

    
129
    return int(addr) - int(self.network.network)
130

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

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

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

    
147
  def _GetSize(self):
148
    return 2 ** (32 - self.network.prefixlen)
149

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

154
    """
155
    return (self.reservations | self.ext_reservations)
156

    
157
  def Validate(self):
158
    assert len(self.reservations) == self._GetSize()
159
    assert len(self.ext_reservations) == self._GetSize()
160
    all_res = self.reservations & self.ext_reservations
161

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

    
165
    if self.network6 and self.gateway6:
166
      assert self.gateway6 in self.network6
167

    
168
    return True
169

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

245
    @raise errors.AddressPoolError: Pool is full
246

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

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

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

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

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

268
    """
269
    obj = cls(net)
270
    obj.Update()
271
    for ip in [obj.network[0], obj.network[-1]]:
272
      obj.Reserve(ip, external=True)
273
    obj.Validate()
274
    return obj