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
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
index 504e4d0..ee7783d 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -59,6 +59,9 @@ class ConfigMap(object):
59 def __setitem__(self, key, value):59 def __setitem__(self, key, value):
60 self._conf[key] = value60 self._conf[key] = value
6161
62 def __getitem__(self, key):
63 return self._conf[key]
64
62 def drop(self, key):65 def drop(self, key):
63 self._conf.pop(key, None)66 self._conf.pop(key, None)
6467
@@ -83,7 +86,8 @@ class ConfigMap(object):
83class Route(ConfigMap):86class Route(ConfigMap):
84 """Represents a route configuration."""87 """Represents a route configuration."""
8588
86 route_fn_tpl = '%(base)s/network-scripts/route-%(name)s'89 route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s'
90 route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s'
8791
88 def __init__(self, route_name, base_sysconf_dir):92 def __init__(self, route_name, base_sysconf_dir):
89 super(Route, self).__init__()93 super(Route, self).__init__()
@@ -102,10 +106,59 @@ class Route(ConfigMap):
102 return r106 return r
103107
104 @property108 @property
105 def path(self):109 def path_ipv4(self):
106 return self.route_fn_tpl % ({'base': self._base_sysconf_dir,110 return self.route_fn_tpl_ipv4 % ({'base': self._base_sysconf_dir,
111 'name': self._route_name})
112
113 @property
114 def path_ipv6(self):
115 return self.route_fn_tpl_ipv6 % ({'base': self._base_sysconf_dir,
107 'name': self._route_name})116 'name': self._route_name})
108117
118 def is_ipv6_route(self, address):
119 return ':' in address
120
121 def to_string_ipv4(self):
122 buf = six.StringIO()
123 buf.write(_make_header())
124 if self._conf:
125 buf.write("\n")
126 # need to reindex IPv4 addresses (because Route can contain a mix of IPv4 and IPv6)
127 reindex = -1
128 for key in sorted(self._conf.keys()):
129 if 'ADDRESS' in key:
130 index = key.replace('ADDRESS','')
131 address_value = str(self._conf[key])
132 # if this is an IPv6 route, then ignore it
133 if self.is_ipv6_route(address_value):
134 continue
135 netmask_value = str(self._conf['NETMASK' + index])
136 gateway_value = str(self._conf['GATEWAY' + index])
137
138 reindex = reindex + 1
139 buf.write("%s=%s\n" % ('ADDRESS' + str(reindex), _quote_value(address_value)))
140 buf.write("%s=%s\n" % ('GATEWAY' + str(reindex), _quote_value(gateway_value)))
141 buf.write("%s=%s\n" % ('NETMASK' + str(reindex), _quote_value(netmask_value)))
142 return buf.getvalue()
143
144 def to_string_ipv6(self):
145 buf = six.StringIO()
146 buf.write(_make_header())
147 if self._conf:
148 buf.write("\n")
149 for key in sorted(self._conf.keys()):
150 if 'ADDRESS' in key:
151 index = key.replace('ADDRESS','')
152 address_key = key
153 address_value = str(self._conf[address_key])
154 if not self.is_ipv6_route(address_value):
155 continue
156 netmask_key = 'NETMASK' + index
157 netmask_value = str(self._conf[netmask_key])
158 gateway_key = 'GATEWAY' + index
159 gateway_value = str(self._conf[gateway_key])
160 buf.write("%s/%s via %s\n" % (address_value, netmask_value, gateway_value))
161 return buf.getvalue()
109162
110class NetInterface(ConfigMap):163class NetInterface(ConfigMap):
111 """Represents a sysconfig/networking-script (and its config + children)."""164 """Represents a sysconfig/networking-script (and its config + children)."""
@@ -211,27 +264,70 @@ class Renderer(renderer.Renderer):
211 iface_cfg[new_key] = old_value264 iface_cfg[new_key] = old_value
212265
213 @classmethod266 @classmethod
214 def _render_subnet(cls, iface_cfg, route_cfg, subnet):267 def _render_subnets(cls, iface_cfg, subnets):
215 subnet_type = subnet.get('type')268 # setting base values
216 if subnet_type == 'dhcp6':269 iface_cfg['BOOTPROTO'] = 'none'
217 iface_cfg['DHCPV6C'] = True270
218 iface_cfg['IPV6INIT'] = True271 # modifying base values according to subnets
219 iface_cfg['BOOTPROTO'] = 'dhcp'272 for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
220 elif subnet_type in ['dhcp4', 'dhcp']:273 subnet_type = subnet.get('type')
221 iface_cfg['BOOTPROTO'] = 'dhcp'274 if subnet_type == 'dhcp6':
222 elif subnet_type == 'static':
223 iface_cfg['BOOTPROTO'] = 'static'
224 if subnet_is_ipv6(subnet):
225 iface_cfg['IPV6ADDR'] = subnet['address']
226 iface_cfg['IPV6INIT'] = True275 iface_cfg['IPV6INIT'] = True
276 iface_cfg['DHCPV6C'] = True
277 iface_cfg['BOOTPROTO'] = 'dhcp'
278 elif subnet_type in ['dhcp4', 'dhcp']:
279 iface_cfg['BOOTPROTO'] = 'dhcp'
280 elif subnet_type == 'static':
281 # grep BOOTPROTO /usr/share/doc/initscripts-9.49.37/sysconfig.txt -A2 | head -3
282 # BOOTPROTO=none|bootp|dhcp
283 # 'bootp' or 'dhcp' cause a DHCP client to run on the device. Any other
284 # value causes any static configuration in the file to be applied.
285 # ==> the following should not be set to 'static', but should remain 'none'
286 #if iface_cfg['BOOTPROTO'] == 'none':
287 # iface_cfg['BOOTPROTO'] = 'static'
288 if subnet_is_ipv6(subnet):
289 iface_cfg['IPV6INIT'] = True
227 else:290 else:
228 iface_cfg['IPADDR'] = subnet['address']291 raise ValueError("Unknown subnet type '%s' found"
229 else:292 " for interface '%s'" % (subnet_type,
230 raise ValueError("Unknown subnet type '%s' found"
231 " for interface '%s'" % (subnet_type,
232 iface_cfg.name))293 iface_cfg.name))
233 if 'netmask' in subnet:294
234 iface_cfg['NETMASK'] = subnet['netmask']295 # set IPv4 and IPv6 static addresses
296 ipv4_index = -1
297 ipv6_index = -1
298 for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
299 subnet_type = subnet.get('type')
300 if subnet_type == 'dhcp6':
301 continue
302 elif subnet_type in ['dhcp4', 'dhcp']:
303 continue
304 elif subnet_type == 'static':
305 if subnet_is_ipv6(subnet):
306 ipv6_index = ipv6_index + 1
307 if 'netmask' in subnet and str(subnet['netmask']) != "":
308 ipv6_cidr = subnet['address'] + '/' + str(subnet['netmask'])
309 else:
310 ipv6_cidr = subnet['address']
311 if ipv6_index == 0:
312 iface_cfg['IPV6ADDR'] = ipv6_cidr
313 elif ipv6_index == 1:
314 iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr
315 else:
316 iface_cfg['IPV6ADDR_SECONDARIES'] = iface_cfg['IPV6ADDR_SECONDARIES'] + " " + ipv6_cidr
317 else:
318 ipv4_index = ipv4_index + 1
319 if ipv4_index == 0:
320 iface_cfg['IPADDR'] = subnet['address']
321 if 'netmask' in subnet:
322 iface_cfg['NETMASK'] = subnet['netmask']
323 else:
324 iface_cfg['IPADDR' + str(ipv4_index)] = subnet['address']
325 if 'netmask' in subnet:
326 iface_cfg['NETMASK' + str(ipv4_index)] = subnet['netmask']
327
328 @classmethod
329 def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
330 for i, subnet in enumerate(subnets,start=len(iface_cfg.children)):
235 for route in subnet.get('routes', []):331 for route in subnet.get('routes', []):
236 if subnet.get('ipv6'):332 if subnet.get('ipv6'):
237 gw_cfg = 'IPV6_DEFAULTGW'333 gw_cfg = 'IPV6_DEFAULTGW'
@@ -295,15 +391,9 @@ class Renderer(renderer.Renderer):
295 iface_subnets = iface.get("subnets", [])391 iface_subnets = iface.get("subnets", [])
296 iface_cfg = iface_contents[iface_name]392 iface_cfg = iface_contents[iface_name]
297 route_cfg = iface_cfg.routes393 route_cfg = iface_cfg.routes
298 if len(iface_subnets) == 1:394
299 cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0])395 cls._render_subnets(iface_cfg, iface_subnets)
300 elif len(iface_subnets) > 1:396 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
301 for i, isubnet in enumerate(iface_subnets,
302 start=len(iface_cfg.children)):
303 iface_sub_cfg = iface_cfg.copy()
304 iface_sub_cfg.name = "%s:%s" % (iface_name, i)
305 iface_cfg.children.append(iface_sub_cfg)
306 cls._render_subnet(iface_sub_cfg, route_cfg, isubnet)
307397
308 @classmethod398 @classmethod
309 def _render_bond_interfaces(cls, network_state, iface_contents):399 def _render_bond_interfaces(cls, network_state, iface_contents):
@@ -387,7 +477,8 @@ class Renderer(renderer.Renderer):
387 if iface_cfg:477 if iface_cfg:
388 contents[iface_cfg.path] = iface_cfg.to_string()478 contents[iface_cfg.path] = iface_cfg.to_string()
389 if iface_cfg.routes:479 if iface_cfg.routes:
390 contents[iface_cfg.routes.path] = iface_cfg.routes.to_string()480 contents[iface_cfg.routes.path_ipv4] = iface_cfg.routes.to_string_ipv4()
481 contents[iface_cfg.routes.path_ipv6] = iface_cfg.routes.to_string_ipv6()
391 return contents482 return contents
392483
393 def render_network_state(self, network_state, target=None):484 def render_network_state(self, network_state, target=None):
diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py
index 8837066..6d6b985 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -431,7 +431,7 @@ NETWORKING=yes
431 expected_buf = '''431 expected_buf = '''
432# Created by cloud-init on instance boot automatically, do not edit.432# Created by cloud-init on instance boot automatically, do not edit.
433#433#
434BOOTPROTO=static434BOOTPROTO=none
435DEVICE=eth0435DEVICE=eth0
436IPADDR=192.168.1.5436IPADDR=192.168.1.5
437NETMASK=255.255.255.0437NETMASK=255.255.255.0
@@ -488,7 +488,6 @@ NETWORKING=yes
488 mock.patch.object(util, 'load_file', return_value=''))488 mock.patch.object(util, 'load_file', return_value=''))
489 mocks.enter_context(489 mocks.enter_context(
490 mock.patch.object(os.path, 'isfile', return_value=False))490 mock.patch.object(os.path, 'isfile', return_value=False))
491
492 rh_distro.apply_network(BASE_NET_CFG_IPV6, False)491 rh_distro.apply_network(BASE_NET_CFG_IPV6, False)
493492
494 self.assertEqual(len(write_bufs), 4)493 self.assertEqual(len(write_bufs), 4)
@@ -581,11 +580,10 @@ IPV6_AUTOCONF=no
581 expected_buf = '''580 expected_buf = '''
582# Created by cloud-init on instance boot automatically, do not edit.581# Created by cloud-init on instance boot automatically, do not edit.
583#582#
584BOOTPROTO=static583BOOTPROTO=none
585DEVICE=eth0584DEVICE=eth0
586IPV6ADDR=2607:f0d0:1002:0011::2585IPV6ADDR=2607:f0d0:1002:0011::2/64
587IPV6INIT=yes586IPV6INIT=yes
588NETMASK=64
589NM_CONTROLLED=no587NM_CONTROLLED=no
590ONBOOT=yes588ONBOOT=yes
591TYPE=Ethernet589TYPE=Ethernet
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index 89e7536..05fa8f9 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -136,7 +136,7 @@ OS_SAMPLES = [
136 """136 """
137# Created by cloud-init on instance boot automatically, do not edit.137# Created by cloud-init on instance boot automatically, do not edit.
138#138#
139BOOTPROTO=static139BOOTPROTO=none
140DEFROUTE=yes140DEFROUTE=yes
141DEVICE=eth0141DEVICE=eth0
142GATEWAY=172.19.3.254142GATEWAY=172.19.3.254
@@ -204,38 +204,14 @@ nameserver 172.19.0.12
204# Created by cloud-init on instance boot automatically, do not edit.204# Created by cloud-init on instance boot automatically, do not edit.
205#205#
206BOOTPROTO=none206BOOTPROTO=none
207DEVICE=eth0
208HWADDR=fa:16:3e:ed:9a:59
209NM_CONTROLLED=no
210ONBOOT=yes
211TYPE=Ethernet
212USERCTL=no
213""".lstrip()),
214 ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
215 """
216# Created by cloud-init on instance boot automatically, do not edit.
217#
218BOOTPROTO=static
219DEFROUTE=yes207DEFROUTE=yes
220DEVICE=eth0:0208DEVICE=eth0
221GATEWAY=172.19.3.254209GATEWAY=172.19.3.254
222HWADDR=fa:16:3e:ed:9a:59210HWADDR=fa:16:3e:ed:9a:59
223IPADDR=172.19.1.34211IPADDR=172.19.1.34
212IPADDR1=10.0.0.10
224NETMASK=255.255.252.0213NETMASK=255.255.252.0
225NM_CONTROLLED=no214NETMASK1=255.255.255.0
226ONBOOT=yes
227TYPE=Ethernet
228USERCTL=no
229""".lstrip()),
230 ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
231 """
232# Created by cloud-init on instance boot automatically, do not edit.
233#
234BOOTPROTO=static
235DEVICE=eth0:1
236HWADDR=fa:16:3e:ed:9a:59
237IPADDR=10.0.0.10
238NETMASK=255.255.255.0
239NM_CONTROLLED=no215NM_CONTROLLED=no
240ONBOOT=yes216ONBOOT=yes
241TYPE=Ethernet217TYPE=Ethernet
@@ -264,8 +240,9 @@ nameserver 172.19.0.12
264 "gateway": "172.19.3.254",240 "gateway": "172.19.3.254",
265 }],241 }],
266 "ip_address": "172.19.1.34", "id": "network0"242 "ip_address": "172.19.1.34", "id": "network0"
267 }, {243 },
268 "network_id": "public-ipv6",244 {
245 "network_id": "public-ipv6-a",
269 "type": "ipv6", "netmask": "",246 "type": "ipv6", "netmask": "",
270 "link": "tap1a81968a-79",247 "link": "tap1a81968a-79",
271 "routes": [248 "routes": [
@@ -276,7 +253,24 @@ nameserver 172.19.0.12
276 }253 }
277 ],254 ],
278 "ip_address": "2001:DB8::10", "id": "network1"255 "ip_address": "2001:DB8::10", "id": "network1"
279 }],256 },
257 {
258 "network_id": "public-ipv6-b",
259 "type": "ipv6", "netmask": "64",
260 "link": "tap1a81968a-79",
261 "routes": [
262 ],
263 "ip_address": "2001:DB9::10", "id": "network2"
264 },
265 {
266 "network_id": "public-ipv6-c",
267 "type": "ipv6", "netmask": "64",
268 "link": "tap1a81968a-79",
269 "routes": [
270 ],
271 "ip_address": "2001:DB10::10", "id": "network3"
272 }
273 ],
280 "links": [274 "links": [
281 {275 {
282 "ethernet_mac_address": "fa:16:3e:ed:9a:59",276 "ethernet_mac_address": "fa:16:3e:ed:9a:59",
@@ -295,41 +289,16 @@ nameserver 172.19.0.12
295# Created by cloud-init on instance boot automatically, do not edit.289# Created by cloud-init on instance boot automatically, do not edit.
296#290#
297BOOTPROTO=none291BOOTPROTO=none
298DEVICE=eth0
299HWADDR=fa:16:3e:ed:9a:59
300NM_CONTROLLED=no
301ONBOOT=yes
302TYPE=Ethernet
303USERCTL=no
304""".lstrip()),
305 ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
306 """
307# Created by cloud-init on instance boot automatically, do not edit.
308#
309BOOTPROTO=static
310DEFROUTE=yes292DEFROUTE=yes
311DEVICE=eth0:0293DEVICE=eth0
312GATEWAY=172.19.3.254294GATEWAY=172.19.3.254
313HWADDR=fa:16:3e:ed:9a:59295HWADDR=fa:16:3e:ed:9a:59
314IPADDR=172.19.1.34296IPADDR=172.19.1.34
315NETMASK=255.255.252.0
316NM_CONTROLLED=no
317ONBOOT=yes
318TYPE=Ethernet
319USERCTL=no
320""".lstrip()),
321 ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
322 """
323# Created by cloud-init on instance boot automatically, do not edit.
324#
325BOOTPROTO=static
326DEFROUTE=yes
327DEVICE=eth0:1
328HWADDR=fa:16:3e:ed:9a:59
329IPV6ADDR=2001:DB8::10297IPV6ADDR=2001:DB8::10
298IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64"
330IPV6INIT=yes299IPV6INIT=yes
331IPV6_DEFAULTGW=2001:DB8::1300IPV6_DEFAULTGW=2001:DB8::1
332NETMASK=301NETMASK=255.255.252.0
333NM_CONTROLLED=no302NM_CONTROLLED=no
334ONBOOT=yes303ONBOOT=yes
335TYPE=Ethernet304TYPE=Ethernet

Subscribers

People subscribed via source and target branches