Merge ~akaris/cloud-init:bug1679817 into cloud-init:master

Proposed by Andreas Karis
Status: Superseded
Proposed branch: ~akaris/cloud-init:bug1679817
Merge into: cloud-init:master
Diff against target: 377 lines (+153/-95)
3 files modified
cloudinit/net/sysconfig.py (+122/-31)
tests/unittests/test_distros/test_netconfig.py (+3/-5)
tests/unittests/test_net.py (+28/-59)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Needs Fixing
cloud-init Commiters Pending
Review via email: mp+322998@code.launchpad.net

This proposal has been superseded by a proposal from 2017-04-24.

Commit message

Fix dual stack IPv4/IPv6 configuration for RHEL

Dual stack IPv4/IPv6 configuration via config drive is broken for RHEL7.
This patch fixes several scenarios for IPv4/IPv6/dual stack with multiple IP assignment
Removes unpopular IPv4 alias files and invalid IPv6 alias files

Also fixes associated unit tests

LP: #1679817
LP: #1685534
LP: #1685532

Description of the change

Fix dual stack IPv4/IPv6 configuration for RHEL

Dual stack IPv4/IPv6 configuration via config drive is broken for RHEL7.
This patch fixes several scenarios for IPv4/IPv6/dual stack with multiple IP assignment
Removes unpopular IPv4 alias files and invalid IPv6 alias files

Also fixes associated unit tests

LP: #1679817
LP: #1685534
LP: #1685532

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
~akaris/cloud-init:bug1679817 updated
9ad5280... by Andreas Karis

Remove BOOTPROTO=static and make it BOOTPROTO=none as it should be

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)
~akaris/cloud-init:bug1679817 updated
2d818c2... by Andreas Karis

wrapped str around netmask in cases it is integer

7569e43... by Andreas Karis

added route handling for ipv4 and ipv6 routes

a94ded7... by Andreas Karis

Moved IPv6 routes to route6-

Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Needs Fixing (continuous-integration)

Unmerged commits

a94ded7... by Andreas Karis

Moved IPv6 routes to route6-

7569e43... by Andreas Karis

added route handling for ipv4 and ipv6 routes

2d818c2... by Andreas Karis

wrapped str around netmask in cases it is integer

9ad5280... by Andreas Karis

Remove BOOTPROTO=static and make it BOOTPROTO=none as it should be

e43facb... by Andreas Karis

bug1679817, bug1685534, bug1685532

ca539b9... by Andreas Karis

fix1

6186d58... by Andreas Karis

bug1679817

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
2index 504e4d0..ee7783d 100644
3--- a/cloudinit/net/sysconfig.py
4+++ b/cloudinit/net/sysconfig.py
5@@ -59,6 +59,9 @@ class ConfigMap(object):
6 def __setitem__(self, key, value):
7 self._conf[key] = value
8
9+ def __getitem__(self, key):
10+ return self._conf[key]
11+
12 def drop(self, key):
13 self._conf.pop(key, None)
14
15@@ -83,7 +86,8 @@ class ConfigMap(object):
16 class Route(ConfigMap):
17 """Represents a route configuration."""
18
19- route_fn_tpl = '%(base)s/network-scripts/route-%(name)s'
20+ route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s'
21+ route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s'
22
23 def __init__(self, route_name, base_sysconf_dir):
24 super(Route, self).__init__()
25@@ -102,10 +106,59 @@ class Route(ConfigMap):
26 return r
27
28 @property
29- def path(self):
30- return self.route_fn_tpl % ({'base': self._base_sysconf_dir,
31+ def path_ipv4(self):
32+ return self.route_fn_tpl_ipv4 % ({'base': self._base_sysconf_dir,
33+ 'name': self._route_name})
34+
35+ @property
36+ def path_ipv6(self):
37+ return self.route_fn_tpl_ipv6 % ({'base': self._base_sysconf_dir,
38 'name': self._route_name})
39
40+ def is_ipv6_route(self, address):
41+ return ':' in address
42+
43+ def to_string_ipv4(self):
44+ buf = six.StringIO()
45+ buf.write(_make_header())
46+ if self._conf:
47+ buf.write("\n")
48+ # need to reindex IPv4 addresses (because Route can contain a mix of IPv4 and IPv6)
49+ reindex = -1
50+ for key in sorted(self._conf.keys()):
51+ if 'ADDRESS' in key:
52+ index = key.replace('ADDRESS','')
53+ address_value = str(self._conf[key])
54+ # if this is an IPv6 route, then ignore it
55+ if self.is_ipv6_route(address_value):
56+ continue
57+ netmask_value = str(self._conf['NETMASK' + index])
58+ gateway_value = str(self._conf['GATEWAY' + index])
59+
60+ reindex = reindex + 1
61+ buf.write("%s=%s\n" % ('ADDRESS' + str(reindex), _quote_value(address_value)))
62+ buf.write("%s=%s\n" % ('GATEWAY' + str(reindex), _quote_value(gateway_value)))
63+ buf.write("%s=%s\n" % ('NETMASK' + str(reindex), _quote_value(netmask_value)))
64+ return buf.getvalue()
65+
66+ def to_string_ipv6(self):
67+ buf = six.StringIO()
68+ buf.write(_make_header())
69+ if self._conf:
70+ buf.write("\n")
71+ for key in sorted(self._conf.keys()):
72+ if 'ADDRESS' in key:
73+ index = key.replace('ADDRESS','')
74+ address_key = key
75+ address_value = str(self._conf[address_key])
76+ if not self.is_ipv6_route(address_value):
77+ continue
78+ netmask_key = 'NETMASK' + index
79+ netmask_value = str(self._conf[netmask_key])
80+ gateway_key = 'GATEWAY' + index
81+ gateway_value = str(self._conf[gateway_key])
82+ buf.write("%s/%s via %s\n" % (address_value, netmask_value, gateway_value))
83+ return buf.getvalue()
84
85 class NetInterface(ConfigMap):
86 """Represents a sysconfig/networking-script (and its config + children)."""
87@@ -211,27 +264,70 @@ class Renderer(renderer.Renderer):
88 iface_cfg[new_key] = old_value
89
90 @classmethod
91- def _render_subnet(cls, iface_cfg, route_cfg, subnet):
92- subnet_type = subnet.get('type')
93- if subnet_type == 'dhcp6':
94- iface_cfg['DHCPV6C'] = True
95- iface_cfg['IPV6INIT'] = True
96- iface_cfg['BOOTPROTO'] = 'dhcp'
97- elif subnet_type in ['dhcp4', 'dhcp']:
98- iface_cfg['BOOTPROTO'] = 'dhcp'
99- elif subnet_type == 'static':
100- iface_cfg['BOOTPROTO'] = 'static'
101- if subnet_is_ipv6(subnet):
102- iface_cfg['IPV6ADDR'] = subnet['address']
103+ def _render_subnets(cls, iface_cfg, subnets):
104+ # setting base values
105+ iface_cfg['BOOTPROTO'] = 'none'
106+
107+ # modifying base values according to subnets
108+ for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
109+ subnet_type = subnet.get('type')
110+ if subnet_type == 'dhcp6':
111 iface_cfg['IPV6INIT'] = True
112+ iface_cfg['DHCPV6C'] = True
113+ iface_cfg['BOOTPROTO'] = 'dhcp'
114+ elif subnet_type in ['dhcp4', 'dhcp']:
115+ iface_cfg['BOOTPROTO'] = 'dhcp'
116+ elif subnet_type == 'static':
117+ # grep BOOTPROTO /usr/share/doc/initscripts-9.49.37/sysconfig.txt -A2 | head -3
118+ # BOOTPROTO=none|bootp|dhcp
119+ # 'bootp' or 'dhcp' cause a DHCP client to run on the device. Any other
120+ # value causes any static configuration in the file to be applied.
121+ # ==> the following should not be set to 'static', but should remain 'none'
122+ #if iface_cfg['BOOTPROTO'] == 'none':
123+ # iface_cfg['BOOTPROTO'] = 'static'
124+ if subnet_is_ipv6(subnet):
125+ iface_cfg['IPV6INIT'] = True
126 else:
127- iface_cfg['IPADDR'] = subnet['address']
128- else:
129- raise ValueError("Unknown subnet type '%s' found"
130- " for interface '%s'" % (subnet_type,
131+ raise ValueError("Unknown subnet type '%s' found"
132+ " for interface '%s'" % (subnet_type,
133 iface_cfg.name))
134- if 'netmask' in subnet:
135- iface_cfg['NETMASK'] = subnet['netmask']
136+
137+ # set IPv4 and IPv6 static addresses
138+ ipv4_index = -1
139+ ipv6_index = -1
140+ for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
141+ subnet_type = subnet.get('type')
142+ if subnet_type == 'dhcp6':
143+ continue
144+ elif subnet_type in ['dhcp4', 'dhcp']:
145+ continue
146+ elif subnet_type == 'static':
147+ if subnet_is_ipv6(subnet):
148+ ipv6_index = ipv6_index + 1
149+ if 'netmask' in subnet and str(subnet['netmask']) != "":
150+ ipv6_cidr = subnet['address'] + '/' + str(subnet['netmask'])
151+ else:
152+ ipv6_cidr = subnet['address']
153+ if ipv6_index == 0:
154+ iface_cfg['IPV6ADDR'] = ipv6_cidr
155+ elif ipv6_index == 1:
156+ iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr
157+ else:
158+ iface_cfg['IPV6ADDR_SECONDARIES'] = iface_cfg['IPV6ADDR_SECONDARIES'] + " " + ipv6_cidr
159+ else:
160+ ipv4_index = ipv4_index + 1
161+ if ipv4_index == 0:
162+ iface_cfg['IPADDR'] = subnet['address']
163+ if 'netmask' in subnet:
164+ iface_cfg['NETMASK'] = subnet['netmask']
165+ else:
166+ iface_cfg['IPADDR' + str(ipv4_index)] = subnet['address']
167+ if 'netmask' in subnet:
168+ iface_cfg['NETMASK' + str(ipv4_index)] = subnet['netmask']
169+
170+ @classmethod
171+ def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
172+ for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
173 for route in subnet.get('routes', []):
174 if subnet.get('ipv6'):
175 gw_cfg = 'IPV6_DEFAULTGW'
176@@ -295,15 +391,9 @@ class Renderer(renderer.Renderer):
177 iface_subnets = iface.get("subnets", [])
178 iface_cfg = iface_contents[iface_name]
179 route_cfg = iface_cfg.routes
180- if len(iface_subnets) == 1:
181- cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0])
182- elif len(iface_subnets) > 1:
183- for i, isubnet in enumerate(iface_subnets,
184- start=len(iface_cfg.children)):
185- iface_sub_cfg = iface_cfg.copy()
186- iface_sub_cfg.name = "%s:%s" % (iface_name, i)
187- iface_cfg.children.append(iface_sub_cfg)
188- cls._render_subnet(iface_sub_cfg, route_cfg, isubnet)
189+
190+ cls._render_subnets(iface_cfg, iface_subnets)
191+ cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
192
193 @classmethod
194 def _render_bond_interfaces(cls, network_state, iface_contents):
195@@ -387,7 +477,8 @@ class Renderer(renderer.Renderer):
196 if iface_cfg:
197 contents[iface_cfg.path] = iface_cfg.to_string()
198 if iface_cfg.routes:
199- contents[iface_cfg.routes.path] = iface_cfg.routes.to_string()
200+ contents[iface_cfg.routes.path_ipv4] = iface_cfg.routes.to_string_ipv4()
201+ contents[iface_cfg.routes.path_ipv6] = iface_cfg.routes.to_string_ipv6()
202 return contents
203
204 def render_network_state(self, network_state, target=None):
205diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
206index 8837066..6d6b985 100644
207--- a/tests/unittests/test_distros/test_netconfig.py
208+++ b/tests/unittests/test_distros/test_netconfig.py
209@@ -431,7 +431,7 @@ NETWORKING=yes
210 expected_buf = '''
211 # Created by cloud-init on instance boot automatically, do not edit.
212 #
213-BOOTPROTO=static
214+BOOTPROTO=none
215 DEVICE=eth0
216 IPADDR=192.168.1.5
217 NETMASK=255.255.255.0
218@@ -488,7 +488,6 @@ NETWORKING=yes
219 mock.patch.object(util, 'load_file', return_value=''))
220 mocks.enter_context(
221 mock.patch.object(os.path, 'isfile', return_value=False))
222-
223 rh_distro.apply_network(BASE_NET_CFG_IPV6, False)
224
225 self.assertEqual(len(write_bufs), 4)
226@@ -581,11 +580,10 @@ IPV6_AUTOCONF=no
227 expected_buf = '''
228 # Created by cloud-init on instance boot automatically, do not edit.
229 #
230-BOOTPROTO=static
231+BOOTPROTO=none
232 DEVICE=eth0
233-IPV6ADDR=2607:f0d0:1002:0011::2
234+IPV6ADDR=2607:f0d0:1002:0011::2/64
235 IPV6INIT=yes
236-NETMASK=64
237 NM_CONTROLLED=no
238 ONBOOT=yes
239 TYPE=Ethernet
240diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
241index 89e7536..05fa8f9 100644
242--- a/tests/unittests/test_net.py
243+++ b/tests/unittests/test_net.py
244@@ -136,7 +136,7 @@ OS_SAMPLES = [
245 """
246 # Created by cloud-init on instance boot automatically, do not edit.
247 #
248-BOOTPROTO=static
249+BOOTPROTO=none
250 DEFROUTE=yes
251 DEVICE=eth0
252 GATEWAY=172.19.3.254
253@@ -204,38 +204,14 @@ nameserver 172.19.0.12
254 # Created by cloud-init on instance boot automatically, do not edit.
255 #
256 BOOTPROTO=none
257-DEVICE=eth0
258-HWADDR=fa:16:3e:ed:9a:59
259-NM_CONTROLLED=no
260-ONBOOT=yes
261-TYPE=Ethernet
262-USERCTL=no
263-""".lstrip()),
264- ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
265- """
266-# Created by cloud-init on instance boot automatically, do not edit.
267-#
268-BOOTPROTO=static
269 DEFROUTE=yes
270-DEVICE=eth0:0
271+DEVICE=eth0
272 GATEWAY=172.19.3.254
273 HWADDR=fa:16:3e:ed:9a:59
274 IPADDR=172.19.1.34
275+IPADDR1=10.0.0.10
276 NETMASK=255.255.252.0
277-NM_CONTROLLED=no
278-ONBOOT=yes
279-TYPE=Ethernet
280-USERCTL=no
281-""".lstrip()),
282- ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
283- """
284-# Created by cloud-init on instance boot automatically, do not edit.
285-#
286-BOOTPROTO=static
287-DEVICE=eth0:1
288-HWADDR=fa:16:3e:ed:9a:59
289-IPADDR=10.0.0.10
290-NETMASK=255.255.255.0
291+NETMASK1=255.255.255.0
292 NM_CONTROLLED=no
293 ONBOOT=yes
294 TYPE=Ethernet
295@@ -264,8 +240,9 @@ nameserver 172.19.0.12
296 "gateway": "172.19.3.254",
297 }],
298 "ip_address": "172.19.1.34", "id": "network0"
299- }, {
300- "network_id": "public-ipv6",
301+ },
302+ {
303+ "network_id": "public-ipv6-a",
304 "type": "ipv6", "netmask": "",
305 "link": "tap1a81968a-79",
306 "routes": [
307@@ -276,7 +253,24 @@ nameserver 172.19.0.12
308 }
309 ],
310 "ip_address": "2001:DB8::10", "id": "network1"
311- }],
312+ },
313+ {
314+ "network_id": "public-ipv6-b",
315+ "type": "ipv6", "netmask": "64",
316+ "link": "tap1a81968a-79",
317+ "routes": [
318+ ],
319+ "ip_address": "2001:DB9::10", "id": "network2"
320+ },
321+ {
322+ "network_id": "public-ipv6-c",
323+ "type": "ipv6", "netmask": "64",
324+ "link": "tap1a81968a-79",
325+ "routes": [
326+ ],
327+ "ip_address": "2001:DB10::10", "id": "network3"
328+ }
329+ ],
330 "links": [
331 {
332 "ethernet_mac_address": "fa:16:3e:ed:9a:59",
333@@ -295,41 +289,16 @@ nameserver 172.19.0.12
334 # Created by cloud-init on instance boot automatically, do not edit.
335 #
336 BOOTPROTO=none
337-DEVICE=eth0
338-HWADDR=fa:16:3e:ed:9a:59
339-NM_CONTROLLED=no
340-ONBOOT=yes
341-TYPE=Ethernet
342-USERCTL=no
343-""".lstrip()),
344- ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
345- """
346-# Created by cloud-init on instance boot automatically, do not edit.
347-#
348-BOOTPROTO=static
349 DEFROUTE=yes
350-DEVICE=eth0:0
351+DEVICE=eth0
352 GATEWAY=172.19.3.254
353 HWADDR=fa:16:3e:ed:9a:59
354 IPADDR=172.19.1.34
355-NETMASK=255.255.252.0
356-NM_CONTROLLED=no
357-ONBOOT=yes
358-TYPE=Ethernet
359-USERCTL=no
360-""".lstrip()),
361- ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
362- """
363-# Created by cloud-init on instance boot automatically, do not edit.
364-#
365-BOOTPROTO=static
366-DEFROUTE=yes
367-DEVICE=eth0:1
368-HWADDR=fa:16:3e:ed:9a:59
369 IPV6ADDR=2001:DB8::10
370+IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64"
371 IPV6INIT=yes
372 IPV6_DEFAULTGW=2001:DB8::1
373-NETMASK=
374+NETMASK=255.255.252.0
375 NM_CONTROLLED=no
376 ONBOOT=yes
377 TYPE=Ethernet

Subscribers

People subscribed via source and target branches