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