Merge branch 'stable-2.16' into stable-2.17
[ganeti-github.git] / lib / network.py
1 #
2 #
3
4 # Copyright (C) 2011, 2012 Google Inc.
5 # All rights reserved.
6 #
7 # Redistribution and use in source and binary forms, with or without
8 # modification, are permitted provided that the following conditions are
9 # met:
10 #
11 # 1. Redistributions of source code must retain the above copyright notice,
12 # this list of conditions and the following disclaimer.
13 #
14 # 2. Redistributions in binary form must reproduce the above copyright
15 # notice, this list of conditions and the following disclaimer in the
16 # documentation and/or other materials provided with the distribution.
17 #
18 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
19 # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
20 # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
21 # PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
22 # CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
23 # EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
24 # PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
25 # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
26 # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
27 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
28 # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29
30
31 """IP address pool management functions.
32
33 """
34
35 import ipaddr
36
37 from bitarray import bitarray
38
39 from ganeti import errors
40
41
42 def _ComputeIpv4NumHosts(network_size):
43 """Derives the number of hosts in an IPv4 network from the size.
44
45 """
46 return 2 ** (32 - network_size)
47
48
49 IPV4_NETWORK_MIN_SIZE = 30
50 # FIXME: This limit is for performance reasons. Remove when refactoring
51 # for performance tuning was successful.
52 IPV4_NETWORK_MAX_SIZE = 16
53 IPV4_NETWORK_MIN_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MIN_SIZE)
54 IPV4_NETWORK_MAX_NUM_HOSTS = _ComputeIpv4NumHosts(IPV4_NETWORK_MAX_SIZE)
55
56
57 class AddressPool(object):
58 """Address pool class, wrapping an C{objects.Network} object.
59
60 This class provides methods to manipulate address pools, backed by
61 L{objects.Network} objects.
62
63 """
64 FREE = bitarray("0")
65 RESERVED = bitarray("1")
66
67 def __init__(self, network):
68 """Initialize a new IPv4 address pool from an L{objects.Network} object.
69
70 @type network: L{objects.Network}
71 @param network: the network object from which the pool will be generated
72
73 """
74 self.network = None
75 self.gateway = None
76 self.network6 = None
77 self.gateway6 = None
78
79 self.net = network
80
81 self.network = ipaddr.IPNetwork(self.net.network)
82 if self.network.numhosts > IPV4_NETWORK_MAX_NUM_HOSTS:
83 raise errors.AddressPoolError("A big network with %s host(s) is currently"
84 " not supported. please specify at most a"
85 " /%s network" %
86 (str(self.network.numhosts),
87 IPV4_NETWORK_MAX_SIZE))
88
89 if self.network.numhosts < IPV4_NETWORK_MIN_NUM_HOSTS:
90 raise errors.AddressPoolError("A network with only %s host(s) is too"
91 " small, please specify at least a /%s"
92 " network" %
93 (str(self.network.numhosts),
94 IPV4_NETWORK_MIN_SIZE))
95 if self.net.gateway:
96 self.gateway = ipaddr.IPAddress(self.net.gateway)
97
98 if self.net.network6:
99 self.network6 = ipaddr.IPv6Network(self.net.network6)
100 if self.net.gateway6:
101 self.gateway6 = ipaddr.IPv6Address(self.net.gateway6)
102
103 if self.net.reservations:
104 self.reservations = bitarray(self.net.reservations)
105 else:
106 self.reservations = bitarray(self.network.numhosts)
107 # pylint: disable=E1103
108 self.reservations.setall(False)
109
110 if self.net.ext_reservations:
111 self.ext_reservations = bitarray(self.net.ext_reservations)
112 else:
113 self.ext_reservations = bitarray(self.network.numhosts)
114 # pylint: disable=E1103
115 self.ext_reservations.setall(False)
116
117 assert len(self.reservations) == self.network.numhosts
118 assert len(self.ext_reservations) == self.network.numhosts
119
120 def Contains(self, address):
121 if address is None:
122 return False
123 addr = ipaddr.IPAddress(address)
124
125 return addr in self.network
126
127 def _GetAddrIndex(self, address):
128 addr = ipaddr.IPAddress(address)
129
130 if not addr in self.network:
131 raise errors.AddressPoolError("%s does not contain %s" %
132 (self.network, addr))
133
134 return int(addr) - int(self.network.network)
135
136 def Update(self):
137 """Write address pools back to the network object.
138
139 """
140 # pylint: disable=E1103
141 self.net.ext_reservations = self.ext_reservations.to01()
142 self.net.reservations = self.reservations.to01()
143
144 def _Mark(self, address, value=True, external=False):
145 idx = self._GetAddrIndex(address)
146 if external:
147 self.ext_reservations[idx] = value
148 else:
149 self.reservations[idx] = value
150 self.Update()
151
152 def _GetSize(self):
153 return 2 ** (32 - self.network.prefixlen)
154
155 @property
156 def all_reservations(self):
157 """Return a combined map of internal and external reservations.
158
159 """
160 return (self.reservations | self.ext_reservations)
161
162 def Validate(self):
163 assert len(self.reservations) == self._GetSize()
164 assert len(self.ext_reservations) == self._GetSize()
165
166 if self.gateway is not None:
167 assert self.gateway in self.network
168
169 if self.network6 and self.gateway6:
170 assert self.gateway6 in self.network6 or self.gateway6.is_link_local
171
172 def IsFull(self):
173 """Check whether the network is full.
174
175 """
176 return self.all_reservations.all()
177
178 def GetReservedCount(self):
179 """Get the count of reserved addresses.
180
181 """
182 return self.all_reservations.count(True)
183
184 def GetFreeCount(self):
185 """Get the count of unused addresses.
186
187 """
188 return self.all_reservations.count(False)
189
190 def GetMap(self):
191 """Return a textual representation of the network's occupation status.
192
193 """
194 return self.all_reservations.to01().replace("1", "X").replace("0", ".")
195
196 def IsReserved(self, address, external=False):
197 """Checks if the given IP is reserved.
198
199 """
200 idx = self._GetAddrIndex(address)
201 if external:
202 return self.ext_reservations[idx]
203 else:
204 return self.reservations[idx]
205
206 def Reserve(self, address, external=False):
207 """Mark an address as used.
208
209 """
210 if self.IsReserved(address, external):
211 if external:
212 msg = "IP %s is already externally reserved" % address
213 else:
214 msg = "IP %s is already used by an instance" % address
215 raise errors.AddressPoolError(msg)
216
217 self._Mark(address, external=external)
218
219 def Release(self, address, external=False):
220 """Release a given address reservation.
221
222 """
223 if not self.IsReserved(address, external):
224 if external:
225 msg = "IP %s is not externally reserved" % address
226 else:
227 msg = "IP %s is not used by an instance" % address
228 raise errors.AddressPoolError(msg)
229
230 self._Mark(address, value=False, external=external)
231
232 def GetFreeAddress(self):
233 """Returns the first available address.
234
235 """
236 if self.IsFull():
237 raise errors.AddressPoolError("%s is full" % self.network)
238
239 idx = self.all_reservations.index(False)
240 address = str(self.network[idx])
241 self.Reserve(address)
242 return address
243
244 def GenerateFree(self):
245 """Returns the first free address of the network.
246
247 @raise errors.AddressPoolError: Pool is full
248
249 """
250 idx = self.all_reservations.search(self.FREE, 1)
251 if idx:
252 return str(self.network[idx[0]])
253 else:
254 raise errors.AddressPoolError("%s is full" % self.network)
255
256 def GetExternalReservations(self):
257 """Returns a list of all externally reserved addresses.
258
259 """
260 # pylint: disable=E1103
261 idxs = self.ext_reservations.search(self.RESERVED)
262 return [str(self.network[idx]) for idx in idxs]
263
264 @classmethod
265 def InitializeNetwork(cls, net):
266 """Initialize an L{objects.Network} object.
267
268 Reserve the network, broadcast and gateway IP addresses.
269
270 """
271 obj = cls(net)
272 obj.Update()
273 for ip in [obj.network[0], obj.network[-1]]:
274 obj.Reserve(ip, external=True)
275 if obj.net.gateway is not None:
276 obj.Reserve(obj.net.gateway, external=True)
277 obj.Validate()
278 return obj