Revision 22dc14c4 lib/network.py

b/lib/network.py
28 28
from bitarray import bitarray
29 29

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

  
32 34

  
33 35
def _ComputeIpv4NumHosts(network_size):
......
45 47
IPV4_NETWORK_MAX_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MAX_SIZE)
46 48

  
47 49

  
48
class AddressPool(object):
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):
49 161
  """Address pool class, wrapping an C{objects.Network} object.
50 162

  
51 163
  This class provides methods to manipulate address pools, backed by
......
55 167
  FREE = bitarray("0")
56 168
  RESERVED = bitarray("1")
57 169

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

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

  
64 176
    """
65
    self.network = None
66
    self.gateway = None
67
    self.network6 = None
68
    self.gateway6 = None
69

  
70
    self.net = network
71

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

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

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

  
94
    if self.net.reservations:
95
      self.reservations = bitarray(self.net.reservations)
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)
96 181
    else:
97
      self.reservations = bitarray(self.network.numhosts)
98
      # pylint: disable=E1103
99
      self.reservations.setall(False)
182
      self._InitializeReservations()
100 183

  
101
    if self.net.ext_reservations:
102
      self.ext_reservations = bitarray(self.net.ext_reservations)
103
    else:
104
      self.ext_reservations = bitarray(self.network.numhosts)
105
      # pylint: disable=E1103
106
      self.ext_reservations.setall(False)
184
    self._Validate()
107 185

  
108
    assert len(self.reservations) == self.network.numhosts
109
    assert len(self.ext_reservations) == self.network.numhosts
186
  def _InitializeReservations(self):
187
    self.reservations = bitarray(self.network.numhosts)
188
    self.reservations.setall(False) # pylint: disable=E1103
110 189

  
111
  def Contains(self, address):
112
    if address is None:
113
      return False
114
    addr = ipaddr.IPAddress(address)
190
    self.ext_reservations = bitarray(self.network.numhosts)
191
    self.ext_reservations.setall(False) # pylint: disable=E1103
115 192

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

  
118
  def _GetAddrIndex(self, address):
119
    addr = ipaddr.IPAddress(address)
196
    if self.nobj.gateway:
197
      self.Reserve(self.nobj.gateway, external=True)
120 198

  
121
    if not addr in self.network:
122
      raise errors.AddressPoolError("%s does not contain %s" %
123
                                    (self.network, addr))
199
    self._Update()
124 200

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

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

  
130 209
    """
......
138 217
      self.ext_reservations[idx] = value
139 218
    else:
140 219
      self.reservations[idx] = value
141
    self.Update()
220
    self._Update()
142 221

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

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

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

  
153
  def Validate(self):
154
    assert len(self.reservations) == self._GetSize()
155
    assert len(self.ext_reservations) == self._GetSize()
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
156 236
    all_res = self.reservations & self.ext_reservations
157 237
    assert not all_res.any()
158 238

  
159
    if self.gateway is not None:
160
      assert self.gateway in self.network
161

  
162
    if self.network6 and self.gateway6:
163
      assert self.gateway6 in self.network6
164

  
165
    return True
166

  
167
  def IsFull(self):
168
    """Check whether the network is full.
169

  
170
    """
171
    return self.all_reservations.all()
172

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

  
176 242
    """
177
    return self.all_reservations.count(True)
243
    return self._all_reservations.count(True)
178 244

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

  
182 248
    """
183
    return self.all_reservations.count(False)
249
    return self._all_reservations.count(False)
184 250

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

  
188 254
    """
189
    return self.all_reservations.to01().replace("1", "X").replace("0", ".")
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]
190 264

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

  
194 268
    """
195 269
    idx = self._GetAddrIndex(address)
196
    return self.all_reservations[idx]
270
    return self._all_reservations[idx]
197 271

  
198 272
  def Reserve(self, address, external=False):
199 273
    """Mark an address as used.
......
227 301
    @raise errors.AddressPoolError: Pool is full
228 302

  
229 303
    """
230
    idx = self.all_reservations.search(self.FREE, 1)
231
    if idx:
232
      return str(self.network[idx[0]])
233
    else:
234
      raise errors.AddressPoolError("%s is full" % self.network)
235

  
236
  def GetExternalReservations(self):
237
    """Returns a list of all externally reserved addresses.
238

  
239
    """
240
    # pylint: disable=E1103
241
    idxs = self.ext_reservations.search(self.RESERVED)
242
    return [str(self.network[idx]) for idx in idxs]
243

  
244
  @classmethod
245
  def InitializeNetwork(cls, net):
246
    """Initialize an L{objects.Network} object.
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]])
247 308

  
248
    Reserve the network, broadcast and gateway IP addresses.
309
  def GetStats(self):
310
    """Returns statistics for a network address pool.
249 311

  
250 312
    """
251
    obj = cls(net)
252
    obj.Update()
253
    for ip in [obj.network[0], obj.network[-1]]:
254
      obj.Reserve(ip, external=True)
255
    if obj.net.gateway is not None:
256
      obj.Reserve(obj.net.gateway, external=True)
257
    obj.Validate()
258
    return obj
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
      }

Also available in: Unified diff