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