Revision 031d2db1

b/doc/design-network.rst
88 88
bitfields, the length of the network size each:
89 89

  
90 90
``reservations``
91
  This field holds all IP addresses reserved by Ganeti instances, as
92
  well as cluster IP addresses (node addresses + cluster master)
91
  This field holds all IP addresses reserved by Ganeti instances.
93 92

  
94 93
``external reservations``
95 94
  This field holds all IP addresses that are manually reserved by the
96
  administrator, because some other equipment is using them outside the
97
  scope of Ganeti.
95
  administrator (external gateway, IPs of external servers, etc) or
96
  automatically by ganeti (the network/broadcast addresses,
97
  Cluster IPs (node addresses + cluster master)). These IPs are excluded
98
  from the IP pool and cannot be assigned automatically by ganeti to
99
  instances (via ip=pool).
98 100

  
99 101
The bitfields are implemented using the python-bitarray package for
100 102
space efficiency and their binary value stored base64-encoded for JSON
101 103
compatibility. This approach gives relatively compact representations
102 104
even for large IPv4 networks (e.g. /20).
103 105

  
104
Ganeti-owned IP addresses (node + master IPs) are reserved automatically
105
if the cluster's data network itself is placed under pool management.
106
Cluster IP addresses (node + master IPs) are reserved automatically
107
as external if the cluster's data network itself is placed under
108
pool management.
106 109

  
107 110
Helper ConfigWriter methods provide free IP address generation and
108 111
reservation, using a TemporaryReservationManager.
......
129 132

  
130 133
We also introduce a new ``ip`` address value, ``constants.NIC_IP_POOL``,
131 134
that specifies that a given NIC's IP address should be obtained using
132
the IP address pool of the specified network. This value is only valid
135
the first available IP address inside the pool of the specified network.
136
(reservations OR external_reservations). This value is only valid
133 137
for NICs belonging to a network. A NIC's IP address can also be
134 138
specified manually, as long as it is contained in the network the NIC
135
is connected to.
139
is connected to. In case this IP is externally reserved, Ganeti will produce
140
an error which the user can override if explicitly requested. Of course
141
this IP will be reserved and will not be able to be assigned to another
142
instance.
136 143

  
137 144

  
138 145
Hooks
b/lib/cmdlib/instance.py
1035 1035
            self.LogInfo("Chose IP %s from network %s", nic.ip, nobj.name)
1036 1036
          else:
1037 1037
            try:
1038
              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId())
1038
              self.cfg.ReserveIp(net_uuid, nic.ip, self.proc.GetECId(),
1039
                                 check=self.op.conflicts_check)
1039 1040
            except errors.ReservationError:
1040 1041
              raise errors.OpPrereqError("IP address %s already in use"
1041 1042
                                         " or does not belong to network %s" %
......
2620 2621
        # Reserve new IP if in the new network if any
2621 2622
        elif new_net_uuid:
2622 2623
          try:
2623
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId())
2624
            self.cfg.ReserveIp(new_net_uuid, new_ip, self.proc.GetECId(),
2625
                               check=self.op.conflicts_check)
2624 2626
            self.LogInfo("Reserving IP %s in network %s",
2625 2627
                         new_ip, new_net_obj.name)
2626 2628
          except errors.ReservationError:
b/lib/cmdlib/network.py
365 365
    if self.op.add_reserved_ips:
366 366
      for ip in self.op.add_reserved_ips:
367 367
        try:
368
          if self.pool.IsReserved(ip):
369
            self.LogWarning("IP address %s is already reserved", ip)
370
          else:
371
            self.pool.Reserve(ip, external=True)
368
          self.pool.Reserve(ip, external=True)
372 369
        except errors.AddressPoolError, err:
373 370
          self.LogWarning("Cannot reserve IP address %s: %s", ip, err)
374 371

  
......
378 375
          self.LogWarning("Cannot unreserve Gateway's IP")
379 376
          continue
380 377
        try:
381
          if not self.pool.IsReserved(ip):
382
            self.LogWarning("IP address %s is already unreserved", ip)
383
          else:
384
            self.pool.Release(ip, external=True)
378
          self.pool.Release(ip, external=True)
385 379
        except errors.AddressPoolError, err:
386 380
          self.LogWarning("Cannot release IP address %s: %s", ip, err)
387 381

  
b/lib/config.py
384 384
    _, address, _ = self._temporary_ips.Generate([], gen_one, ec_id)
385 385
    return address
386 386

  
387
  def _UnlockedReserveIp(self, net_uuid, address, ec_id):
387
  def _UnlockedReserveIp(self, net_uuid, address, ec_id, check=True):
388 388
    """Reserve a given IPv4 address for use by an instance.
389 389

  
390 390
    """
......
392 392
    pool = network.AddressPool(nobj)
393 393
    try:
394 394
      isreserved = pool.IsReserved(address)
395
      isextreserved = pool.IsReserved(address, external=True)
395 396
    except errors.AddressPoolError:
396 397
      raise errors.ReservationError("IP address not in network")
397 398
    if isreserved:
398 399
      raise errors.ReservationError("IP address already in use")
400
    if check and isextreserved:
401
      raise errors.ReservationError("IP is externally reserved")
399 402

  
400 403
    return self._temporary_ips.Reserve(ec_id,
401 404
                                       (constants.RESERVE_ACTION,
402 405
                                        address, net_uuid))
403 406

  
404 407
  @locking.ssynchronized(_config_lock, shared=1)
405
  def ReserveIp(self, net_uuid, address, ec_id):
408
  def ReserveIp(self, net_uuid, address, ec_id, check=True):
406 409
    """Reserve a given IPv4 address for use by an instance.
407 410

  
408 411
    """
409 412
    if net_uuid:
410
      return self._UnlockedReserveIp(net_uuid, address, ec_id)
413
      return self._UnlockedReserveIp(net_uuid, address, ec_id, check)
411 414

  
412 415
  @locking.ssynchronized(_config_lock, shared=1)
413 416
  def ReserveLV(self, lv_name, ec_id):
b/lib/network.py
153 153
  def Validate(self):
154 154
    assert len(self.reservations) == self._GetSize()
155 155
    assert len(self.ext_reservations) == self._GetSize()
156
    all_res = self.reservations & self.ext_reservations
157
    assert not all_res.any()
158 156

  
159 157
    if self.gateway is not None:
160 158
      assert self.gateway in self.network
......
188 186
    """
189 187
    return self.all_reservations.to01().replace("1", "X").replace("0", ".")
190 188

  
191
  def IsReserved(self, address):
189
  def IsReserved(self, address, external=False):
192 190
    """Checks if the given IP is reserved.
193 191

  
194 192
    """
195 193
    idx = self._GetAddrIndex(address)
196
    return self.all_reservations[idx]
194
    if external:
195
      return self.ext_reservations[idx]
196
    else:
197
      return self.reservations[idx]
197 198

  
198 199
  def Reserve(self, address, external=False):
199 200
    """Mark an address as used.
200 201

  
201 202
    """
202
    if self.IsReserved(address):
203
      raise errors.AddressPoolError("%s is already reserved" % address)
203
    if self.IsReserved(address, external):
204
      if external:
205
        msg = "IP %s is already externally reserved" % address
206
      else:
207
        msg = "IP %s is already used by an instance" % address
208
      raise errors.AddressPoolError(msg)
209

  
204 210
    self._Mark(address, external=external)
205 211

  
206 212
  def Release(self, address, external=False):
207 213
    """Release a given address reservation.
208 214

  
209 215
    """
216
    if not self.IsReserved(address, external):
217
      if external:
218
        msg = "IP %s is not externally reserved" % address
219
      else:
220
        msg = "IP %s is not used by an instance" % address
221
      raise errors.AddressPoolError(msg)
222

  
210 223
    self._Mark(address, value=False, external=external)
211 224

  
212 225
  def GetFreeAddress(self):
b/man/gnt-instance.rst
135 135
    connected to the said network. ``--no-conflicts-check`` can be used
136 136
    to override this check. The special value **pool** causes Ganeti to
137 137
    select an IP from the the network the NIC is or will be connected to.
138
    One can pick an externally reserved IP of a network along with
139
    ``--no-conflict-check``. Note that this IP cannot be assigned to
140
    any other instance until it gets released.
138 141

  
139 142
mode
140 143
    specifies the connection mode for this NIC: routed, bridged or
b/test/py/cmdlib/testsupport/config_mock.py
28 28
from ganeti import config
29 29
from ganeti import constants
30 30
from ganeti import objects
31
from ganeti.network import AddressPool
31 32

  
32 33
import mocks
33 34

  
......
507 508
    if mac is None:
508 509
      mac = "aa:00:00:aa:%02x:%02x" % (nic_id / 0xff, nic_id % 0xff)
509 510
    if isinstance(network, objects.Network):
511
      if ip:
512
        pool = AddressPool(network)
513
        pool.Reserve(ip)
510 514
      network = network.uuid
511 515
    if nicparams is None:
512 516
      nicparams = {}

Also available in: Unified diff