Merge ~akaris/cloud-init:bug1679817-c into cloud-init:master
- Git
- lp:~akaris/cloud-init
- bug1679817-c
- Merge into master
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Merge reported by: | Scott Moser | ||||||||||||
Merged at revision: | 1a401c57978855b5d56ac23976252e2506d206fa | ||||||||||||
Proposed branch: | ~akaris/cloud-init:bug1679817-c | ||||||||||||
Merge into: | cloud-init:master | ||||||||||||
Diff against target: |
453 lines (+194/-132) 3 files modified
cloudinit/net/sysconfig.py (+169/-70) tests/unittests/test_distros/test_netconfig.py (+3/-5) tests/unittests/test_net.py (+22/-57) |
||||||||||||
Related bugs: |
|
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Server Team CI bot | continuous-integration | Approve | |
cloud-init Commiters | Pending | ||
Review via email: mp+324195@code.launchpad.net |
This proposal has been superseded by a proposal from 2017-05-17.
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
Description of the change
Andreas Karis (akaris) wrote : | # |
Server Team CI bot (server-team-bot) wrote : | # |
FAILED: Continuous integration, rev:1a401c57978
No commit message was specified in the merge proposal. Click on the following link and set the commit message (if you want a jenkins rebuild you need to trigger it yourself):
https:/
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Server Team CI bot (server-team-bot) wrote : | # |
PASSED: Continuous integration, rev:48bf21bcb60
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
Hi,
I'm marking this as 'merged' based on the fact that the new merge proposal *is* merged.
(https:/
Please move back to 'Needs Review' (and explain) if you think otherwise.
Preview Diff
1 | diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py |
2 | index d981277..d04ae8f 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,9 +106,53 @@ 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 | - 'name': self._route_name}) |
32 | + def path_ipv4(self): |
33 | + return self.route_fn_tpl_ipv4 % ({'base': self._base_sysconf_dir, |
34 | + 'name': self._route_name}) |
35 | + |
36 | + @property |
37 | + def path_ipv6(self): |
38 | + return self.route_fn_tpl_ipv6 % ({'base': self._base_sysconf_dir, |
39 | + 'name': self._route_name}) |
40 | + |
41 | + def is_ipv6_route(self, address): |
42 | + return ':' in address |
43 | + |
44 | + def to_string(self, proto="ipv4"): |
45 | + buf = six.StringIO() |
46 | + buf.write(_make_header()) |
47 | + if self._conf: |
48 | + buf.write("\n") |
49 | + # need to reindex IPv4 addresses |
50 | + # (because Route can contain a mix of IPv4 and IPv6) |
51 | + reindex = -1 |
52 | + for key in sorted(self._conf.keys()): |
53 | + if 'ADDRESS' in key: |
54 | + index = key.replace('ADDRESS', '') |
55 | + address_value = str(self._conf[key]) |
56 | + # only accept combinations: |
57 | + # ipv6 route and proto ipv6 |
58 | + # ipv4 route and proto ipv4 |
59 | + # do not add any other routes |
60 | + if proto == "ipv4" and not self.is_ipv6_route(address_value): |
61 | + netmask_value = str(self._conf['NETMASK' + index]) |
62 | + gateway_value = str(self._conf['GATEWAY' + index]) |
63 | + # increase IPv4 index |
64 | + reindex = reindex + 1 |
65 | + buf.write("%s=%s\n" % ('ADDRESS' + str(reindex), |
66 | + _quote_value(address_value))) |
67 | + buf.write("%s=%s\n" % ('GATEWAY' + str(reindex), |
68 | + _quote_value(gateway_value))) |
69 | + buf.write("%s=%s\n" % ('NETMASK' + str(reindex), |
70 | + _quote_value(netmask_value))) |
71 | + elif proto == "ipv6" and self.is_ipv6_route(address_value): |
72 | + netmask_value = str(self._conf['NETMASK' + index]) |
73 | + gateway_value = str(self._conf['GATEWAY' + index]) |
74 | + buf.write("%s/%s via %s\n" % (address_value, |
75 | + netmask_value, |
76 | + gateway_value)) |
77 | + |
78 | + return buf.getvalue() |
79 | |
80 | |
81 | class NetInterface(ConfigMap): |
82 | @@ -211,65 +259,119 @@ class Renderer(renderer.Renderer): |
83 | iface_cfg[new_key] = old_value |
84 | |
85 | @classmethod |
86 | - def _render_subnet(cls, iface_cfg, route_cfg, subnet): |
87 | - subnet_type = subnet.get('type') |
88 | - if subnet_type == 'dhcp6': |
89 | - iface_cfg['DHCPV6C'] = True |
90 | - iface_cfg['IPV6INIT'] = True |
91 | - iface_cfg['BOOTPROTO'] = 'dhcp' |
92 | - elif subnet_type in ['dhcp4', 'dhcp']: |
93 | - iface_cfg['BOOTPROTO'] = 'dhcp' |
94 | - elif subnet_type == 'static': |
95 | - iface_cfg['BOOTPROTO'] = 'static' |
96 | - if subnet_is_ipv6(subnet): |
97 | - iface_cfg['IPV6ADDR'] = subnet['address'] |
98 | + def _render_subnets(cls, iface_cfg, subnets): |
99 | + # setting base values |
100 | + iface_cfg['BOOTPROTO'] = 'none' |
101 | + |
102 | + # modifying base values according to subnets |
103 | + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
104 | + subnet_type = subnet.get('type') |
105 | + if subnet_type == 'dhcp6': |
106 | iface_cfg['IPV6INIT'] = True |
107 | + iface_cfg['DHCPV6C'] = True |
108 | + iface_cfg['BOOTPROTO'] = 'dhcp' |
109 | + elif subnet_type in ['dhcp4', 'dhcp']: |
110 | + iface_cfg['BOOTPROTO'] = 'dhcp' |
111 | + elif subnet_type == 'static': |
112 | + # grep BOOTPROTO sysconfig.txt -A2 | head -3 |
113 | + # BOOTPROTO=none|bootp|dhcp |
114 | + # 'bootp' or 'dhcp' cause a DHCP client |
115 | + # to run on the device. Any other |
116 | + # value causes any static configuration |
117 | + # in the file to be applied. |
118 | + # ==> the following should not be set to 'static' |
119 | + # but should remain 'none' |
120 | + # if iface_cfg['BOOTPROTO'] == 'none': |
121 | + # iface_cfg['BOOTPROTO'] = 'static' |
122 | + if subnet_is_ipv6(subnet): |
123 | + iface_cfg['IPV6INIT'] = True |
124 | else: |
125 | - iface_cfg['IPADDR'] = subnet['address'] |
126 | - else: |
127 | - raise ValueError("Unknown subnet type '%s' found" |
128 | - " for interface '%s'" % (subnet_type, |
129 | - iface_cfg.name)) |
130 | - if 'netmask' in subnet: |
131 | - iface_cfg['NETMASK'] = subnet['netmask'] |
132 | - is_ipv6 = subnet.get('ipv6') |
133 | - for route in subnet.get('routes', []): |
134 | - if _is_default_route(route): |
135 | - if ( |
136 | - (subnet.get('ipv4') and |
137 | - route_cfg.has_set_default_ipv4) or |
138 | - (subnet.get('ipv6') and |
139 | - route_cfg.has_set_default_ipv6) |
140 | - ): |
141 | - raise ValueError("Duplicate declaration of default " |
142 | - "route found for interface '%s'" |
143 | - % (iface_cfg.name)) |
144 | - # NOTE(harlowja): ipv6 and ipv4 default gateways |
145 | - gw_key = 'GATEWAY0' |
146 | - nm_key = 'NETMASK0' |
147 | - addr_key = 'ADDRESS0' |
148 | - # The owning interface provides the default route. |
149 | - # |
150 | - # TODO(harlowja): add validation that no other iface has |
151 | - # also provided the default route? |
152 | - iface_cfg['DEFROUTE'] = True |
153 | - if 'gateway' in route: |
154 | - if is_ipv6: |
155 | - iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] |
156 | - route_cfg.has_set_default_ipv6 = True |
157 | + raise ValueError("Unknown subnet type '%s' found" |
158 | + " for interface '%s'" % (subnet_type, |
159 | + iface_cfg.name)) |
160 | + |
161 | + # set IPv4 and IPv6 static addresses |
162 | + ipv4_index = -1 |
163 | + ipv6_index = -1 |
164 | + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
165 | + subnet_type = subnet.get('type') |
166 | + if subnet_type == 'dhcp6': |
167 | + continue |
168 | + elif subnet_type in ['dhcp4', 'dhcp']: |
169 | + continue |
170 | + elif subnet_type == 'static': |
171 | + if subnet_is_ipv6(subnet): |
172 | + ipv6_index = ipv6_index + 1 |
173 | + if 'netmask' in subnet and str(subnet['netmask']) != "": |
174 | + ipv6_cidr = (subnet['address'] + |
175 | + '/' + |
176 | + str(subnet['netmask'])) |
177 | else: |
178 | - iface_cfg['GATEWAY'] = route['gateway'] |
179 | - route_cfg.has_set_default_ipv4 = True |
180 | - else: |
181 | - gw_key = 'GATEWAY%s' % route_cfg.last_idx |
182 | - nm_key = 'NETMASK%s' % route_cfg.last_idx |
183 | - addr_key = 'ADDRESS%s' % route_cfg.last_idx |
184 | - route_cfg.last_idx += 1 |
185 | - for (old_key, new_key) in [('gateway', gw_key), |
186 | - ('netmask', nm_key), |
187 | - ('network', addr_key)]: |
188 | - if old_key in route: |
189 | - route_cfg[new_key] = route[old_key] |
190 | + ipv6_cidr = subnet['address'] |
191 | + if ipv6_index == 0: |
192 | + iface_cfg['IPV6ADDR'] = ipv6_cidr |
193 | + elif ipv6_index == 1: |
194 | + iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr |
195 | + else: |
196 | + iface_cfg['IPV6ADDR_SECONDARIES'] = ( |
197 | + iface_cfg['IPV6ADDR_SECONDARIES'] + |
198 | + " " + ipv6_cidr) |
199 | + else: |
200 | + ipv4_index = ipv4_index + 1 |
201 | + if ipv4_index == 0: |
202 | + iface_cfg['IPADDR'] = subnet['address'] |
203 | + if 'netmask' in subnet: |
204 | + iface_cfg['NETMASK'] = subnet['netmask'] |
205 | + else: |
206 | + iface_cfg['IPADDR' + str(ipv4_index)] = \ |
207 | + subnet['address'] |
208 | + if 'netmask' in subnet: |
209 | + iface_cfg['NETMASK' + str(ipv4_index)] = \ |
210 | + subnet['netmask'] |
211 | + |
212 | + @classmethod |
213 | + def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): |
214 | + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
215 | + for route in subnet.get('routes', []): |
216 | + is_ipv6 = subnet.get('ipv6') |
217 | + |
218 | + if _is_default_route(route): |
219 | + if ( |
220 | + (subnet.get('ipv4') and |
221 | + route_cfg.has_set_default_ipv4) or |
222 | + (subnet.get('ipv6') and |
223 | + route_cfg.has_set_default_ipv6) |
224 | + ): |
225 | + raise ValueError("Duplicate declaration of default " |
226 | + "route found for interface '%s'" |
227 | + % (iface_cfg.name)) |
228 | + # NOTE(harlowja): ipv6 and ipv4 default gateways |
229 | + gw_key = 'GATEWAY0' |
230 | + nm_key = 'NETMASK0' |
231 | + addr_key = 'ADDRESS0' |
232 | + # The owning interface provides the default route. |
233 | + # |
234 | + # TODO(harlowja): add validation that no other iface has |
235 | + # also provided the default route? |
236 | + iface_cfg['DEFROUTE'] = True |
237 | + if 'gateway' in route: |
238 | + if is_ipv6: |
239 | + iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] |
240 | + route_cfg.has_set_default_ipv6 = True |
241 | + else: |
242 | + iface_cfg['GATEWAY'] = route['gateway'] |
243 | + route_cfg.has_set_default_ipv4 = True |
244 | + |
245 | + else: |
246 | + gw_key = 'GATEWAY%s' % route_cfg.last_idx |
247 | + nm_key = 'NETMASK%s' % route_cfg.last_idx |
248 | + addr_key = 'ADDRESS%s' % route_cfg.last_idx |
249 | + route_cfg.last_idx += 1 |
250 | + for (old_key, new_key) in [('gateway', gw_key), |
251 | + ('netmask', nm_key), |
252 | + ('network', addr_key)]: |
253 | + if old_key in route: |
254 | + route_cfg[new_key] = route[old_key] |
255 | |
256 | @classmethod |
257 | def _render_bonding_opts(cls, iface_cfg, iface): |
258 | @@ -295,15 +397,9 @@ class Renderer(renderer.Renderer): |
259 | iface_subnets = iface.get("subnets", []) |
260 | iface_cfg = iface_contents[iface_name] |
261 | route_cfg = iface_cfg.routes |
262 | - if len(iface_subnets) == 1: |
263 | - cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) |
264 | - elif len(iface_subnets) > 1: |
265 | - for i, isubnet in enumerate(iface_subnets, |
266 | - start=len(iface_cfg.children)): |
267 | - iface_sub_cfg = iface_cfg.copy() |
268 | - iface_sub_cfg.name = "%s:%s" % (iface_name, i) |
269 | - iface_cfg.children.append(iface_sub_cfg) |
270 | - cls._render_subnet(iface_sub_cfg, route_cfg, isubnet) |
271 | + |
272 | + cls._render_subnets(iface_cfg, iface_subnets) |
273 | + cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) |
274 | |
275 | @classmethod |
276 | def _render_bond_interfaces(cls, network_state, iface_contents): |
277 | @@ -387,7 +483,10 @@ class Renderer(renderer.Renderer): |
278 | if iface_cfg: |
279 | contents[iface_cfg.path] = iface_cfg.to_string() |
280 | if iface_cfg.routes: |
281 | - contents[iface_cfg.routes.path] = iface_cfg.routes.to_string() |
282 | + contents[iface_cfg.routes.path_ipv4] = \ |
283 | + iface_cfg.routes.to_string("ipv4") |
284 | + contents[iface_cfg.routes.path_ipv6] = \ |
285 | + iface_cfg.routes.to_string("ipv6") |
286 | return contents |
287 | |
288 | def render_network_state(self, network_state, target=None): |
289 | diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py |
290 | index 1e10a33..fd7c051 100644 |
291 | --- a/tests/unittests/test_distros/test_netconfig.py |
292 | +++ b/tests/unittests/test_distros/test_netconfig.py |
293 | @@ -476,7 +476,7 @@ NETWORKING=yes |
294 | expected_buf = ''' |
295 | # Created by cloud-init on instance boot automatically, do not edit. |
296 | # |
297 | -BOOTPROTO=static |
298 | +BOOTPROTO=none |
299 | DEVICE=eth0 |
300 | IPADDR=192.168.1.5 |
301 | NETMASK=255.255.255.0 |
302 | @@ -533,7 +533,6 @@ NETWORKING=yes |
303 | mock.patch.object(util, 'load_file', return_value='')) |
304 | mocks.enter_context( |
305 | mock.patch.object(os.path, 'isfile', return_value=False)) |
306 | - |
307 | rh_distro.apply_network(BASE_NET_CFG_IPV6, False) |
308 | |
309 | self.assertEqual(len(write_bufs), 4) |
310 | @@ -626,11 +625,10 @@ IPV6_AUTOCONF=no |
311 | expected_buf = ''' |
312 | # Created by cloud-init on instance boot automatically, do not edit. |
313 | # |
314 | -BOOTPROTO=static |
315 | +BOOTPROTO=none |
316 | DEVICE=eth0 |
317 | -IPV6ADDR=2607:f0d0:1002:0011::2 |
318 | +IPV6ADDR=2607:f0d0:1002:0011::2/64 |
319 | IPV6INIT=yes |
320 | -NETMASK=64 |
321 | NM_CONTROLLED=no |
322 | ONBOOT=yes |
323 | TYPE=Ethernet |
324 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
325 | index d36d0e7..cf4cedc 100644 |
326 | --- a/tests/unittests/test_net.py |
327 | +++ b/tests/unittests/test_net.py |
328 | @@ -137,7 +137,7 @@ OS_SAMPLES = [ |
329 | """ |
330 | # Created by cloud-init on instance boot automatically, do not edit. |
331 | # |
332 | -BOOTPROTO=static |
333 | +BOOTPROTO=none |
334 | DEFROUTE=yes |
335 | DEVICE=eth0 |
336 | GATEWAY=172.19.3.254 |
337 | @@ -205,38 +205,14 @@ nameserver 172.19.0.12 |
338 | # Created by cloud-init on instance boot automatically, do not edit. |
339 | # |
340 | BOOTPROTO=none |
341 | -DEVICE=eth0 |
342 | -HWADDR=fa:16:3e:ed:9a:59 |
343 | -NM_CONTROLLED=no |
344 | -ONBOOT=yes |
345 | -TYPE=Ethernet |
346 | -USERCTL=no |
347 | -""".lstrip()), |
348 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:0', |
349 | - """ |
350 | -# Created by cloud-init on instance boot automatically, do not edit. |
351 | -# |
352 | -BOOTPROTO=static |
353 | DEFROUTE=yes |
354 | -DEVICE=eth0:0 |
355 | +DEVICE=eth0 |
356 | GATEWAY=172.19.3.254 |
357 | HWADDR=fa:16:3e:ed:9a:59 |
358 | IPADDR=172.19.1.34 |
359 | +IPADDR1=10.0.0.10 |
360 | NETMASK=255.255.252.0 |
361 | -NM_CONTROLLED=no |
362 | -ONBOOT=yes |
363 | -TYPE=Ethernet |
364 | -USERCTL=no |
365 | -""".lstrip()), |
366 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:1', |
367 | - """ |
368 | -# Created by cloud-init on instance boot automatically, do not edit. |
369 | -# |
370 | -BOOTPROTO=static |
371 | -DEVICE=eth0:1 |
372 | -HWADDR=fa:16:3e:ed:9a:59 |
373 | -IPADDR=10.0.0.10 |
374 | -NETMASK=255.255.255.0 |
375 | +NETMASK1=255.255.255.0 |
376 | NM_CONTROLLED=no |
377 | ONBOOT=yes |
378 | TYPE=Ethernet |
379 | @@ -266,7 +242,7 @@ nameserver 172.19.0.12 |
380 | }], |
381 | "ip_address": "172.19.1.34", "id": "network0" |
382 | }, { |
383 | - "network_id": "public-ipv6", |
384 | + "network_id": "public-ipv6-a", |
385 | "type": "ipv6", "netmask": "", |
386 | "link": "tap1a81968a-79", |
387 | "routes": [ |
388 | @@ -277,6 +253,20 @@ nameserver 172.19.0.12 |
389 | } |
390 | ], |
391 | "ip_address": "2001:DB8::10", "id": "network1" |
392 | + }, { |
393 | + "network_id": "public-ipv6-b", |
394 | + "type": "ipv6", "netmask": "64", |
395 | + "link": "tap1a81968a-79", |
396 | + "routes": [ |
397 | + ], |
398 | + "ip_address": "2001:DB9::10", "id": "network2" |
399 | + }, { |
400 | + "network_id": "public-ipv6-c", |
401 | + "type": "ipv6", "netmask": "64", |
402 | + "link": "tap1a81968a-79", |
403 | + "routes": [ |
404 | + ], |
405 | + "ip_address": "2001:DB10::10", "id": "network3" |
406 | }], |
407 | "links": [ |
408 | { |
409 | @@ -296,41 +286,16 @@ nameserver 172.19.0.12 |
410 | # Created by cloud-init on instance boot automatically, do not edit. |
411 | # |
412 | BOOTPROTO=none |
413 | -DEVICE=eth0 |
414 | -HWADDR=fa:16:3e:ed:9a:59 |
415 | -NM_CONTROLLED=no |
416 | -ONBOOT=yes |
417 | -TYPE=Ethernet |
418 | -USERCTL=no |
419 | -""".lstrip()), |
420 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:0', |
421 | - """ |
422 | -# Created by cloud-init on instance boot automatically, do not edit. |
423 | -# |
424 | -BOOTPROTO=static |
425 | DEFROUTE=yes |
426 | -DEVICE=eth0:0 |
427 | +DEVICE=eth0 |
428 | GATEWAY=172.19.3.254 |
429 | HWADDR=fa:16:3e:ed:9a:59 |
430 | IPADDR=172.19.1.34 |
431 | -NETMASK=255.255.252.0 |
432 | -NM_CONTROLLED=no |
433 | -ONBOOT=yes |
434 | -TYPE=Ethernet |
435 | -USERCTL=no |
436 | -""".lstrip()), |
437 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:1', |
438 | - """ |
439 | -# Created by cloud-init on instance boot automatically, do not edit. |
440 | -# |
441 | -BOOTPROTO=static |
442 | -DEFROUTE=yes |
443 | -DEVICE=eth0:1 |
444 | -HWADDR=fa:16:3e:ed:9a:59 |
445 | IPV6ADDR=2001:DB8::10 |
446 | +IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" |
447 | IPV6INIT=yes |
448 | IPV6_DEFAULTGW=2001:DB8::1 |
449 | -NETMASK= |
450 | +NETMASK=255.255.252.0 |
451 | NM_CONTROLLED=no |
452 | ONBOOT=yes |
453 | TYPE=Ethernet |
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