Merge ~akaris/cloud-init:bug1679817-c into cloud-init:master
- Git
- lp:~akaris/cloud-init
- bug1679817-c
- Merge into master
Status: | Merged | ||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Approved by: | Scott Moser | ||||||||||||
Approved revision: | 48bf21bcb6046f1a2d1d029d286dcbe9eaf4f62e | ||||||||||||
Merged at revision: | f38fa41317602908139aa96e930b634f65e39555 | ||||||||||||
Proposed branch: | ~akaris/cloud-init:bug1679817-c | ||||||||||||
Merge into: | cloud-init:master | ||||||||||||
Diff against target: |
458 lines (+199/-132) 3 files modified
cloudinit/net/sysconfig.py (+174/-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 |
---|---|---|---|
Scott Moser | Approve | ||
Server Team CI bot | continuous-integration | Approve | |
Review via email: mp+324196@code.launchpad.net |
This proposal supersedes 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 : Posted in a previous version of this proposal | # |
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
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:1a401c57978
https:/
Executed test runs:
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
SUCCESS: https:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
Hi,
I assume this will still work for Centos 6 (5?)
It looks very good, thanks for your work.
Will this work correctly for centos 5 and 6 ?
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:/
Click here to trigger a rebuild:
https:/
Scott Moser (smoser) wrote : | # |
Assuming this works with centos 5, 6, 7, I'm happy to pull this.
Andreas Karis (akaris) wrote : | # |
Hi,
Let me test all of this out once more and give a final ack.
- Andreas
Andreas Karis (akaris) wrote : | # |
Test procedure for RHEL 7:
### Enable config drive IPv4/IPv6 address injection ###
On the compute nodes and controllers, configure
~~~
crudini --set /etc/nova/nova.conf DEFAULT injected_
crudini --set /etc/nova/nova.conf DEFAULT flat_injected true
crudini --set /etc/nova/nova.conf DEFAULT force_config_drive true
crudini --set /etc/nova/nova.conf DEFAULT config_drive_cdrom True
crudini --set /etc/nova/nova.conf DEFAULT debug true
crudini --set /etc/nova/nova.conf DEFAULT use_ipv6 true
crudini --set /etc/nova/nova.conf os_vif_linux_bridge use_ipv6 true
crudini --set /etc/nova/nova.conf libvirt inject_partition -1
~~~
Restart all OpenStack services on computes and controllers:
~~~
systemctl list-units | grep nova | awk '{print $1}' | xargs -I {} systemctl restart {}
~~~
### Use a recent version of cloud-init within the instances ###
~~~
sudo yumdownloader cloud-init pyserial python-jinja2 python-babel python-markupsafe pytz
for i in *.rpm;do virt-customize -a rhel.qcow2 --upload $i:/root/$i ; done
virt-customize -a rhel.qcow2 -v --run-command 'yum -y localinstall /root/*.rpm'
source overcloudrc
~~~
Set password for console login
~~~
virt-customize -a rhel.qcow2 --root-password password:Redhat01
~~~
Create glance image
~~~
glance image-create --name rhel-cloud-init --file rhel.qcow2 --container-format bare --disk-format qcow2 --progress
~~~
### Open all security groups and add keypair ###
~~~
nova secgroup-add-rule default icmp -1 -1 0.0.0.0/0
nova secgroup-add-rule default tcp 1 65535 0.0.0.0/0
nova secgroup-add-rule default udp 1 65535 0.0.0.0/0
nova secgroup-add-rule default icmp -1 -1 ::/0
nova secgroup-add-rule default tcp 1 65535 ::/0
nova secgroup-add-rule default udp 1 65535 ::/0
~~~
~~~
nova keypair-add --pub-key ~/.ssh/id_rsa.pub id_rsa
~~~
### Configure networks without DHCP ###
Make sure that none of the subnets has DHCP enabled!
~~~
# access network
neutron net-create provider1 --provider:
neutron subnet-create --gateway 10.0.0.1 --allocation-pool start=10.
neutron subnet-update provider1-subnet --disable-dhcp
# test networks
neutron net-create private-no-dhcp-1
neutron net-delete private-no-dhcp-1
neutron net-create private-no-dhcp-1
neutron net-create private-no-dhcp-2
neutron net-create private-no-dhcp-3
neutron subnet-create --disable-dhcp private-no-dhcp-1 192.168.100.0/24
neutron subnet-create --disable-dhcp private-no-dhcp-1 192.168.101.0/24
neutron subnet-create --disable-dhcp private-no-dhcp-1 192.168.102.0/24
neutron subnet-create --disable-dhcp private-no-dhcp-2 192.168.200.0/24
neutron subnet-create --disable-dhcp --ip-version 6 private-no-dhcp-2 2000:192:
neutron subnet-create --disable-dhcp --ip-version 6 private-no-dhcp-2 2000:192:
neutron subnet-create --disable-dhcp --ip-version 6 --gateway 2000:192:168:202::1 private-no-dhcp-3 2000:192:
neutr...
Andreas Karis (akaris) wrote : | # |
One issue that I have with this still is the dup default route generation for IPv6:
[root@rhel-
default via 192.168.200.1 dev eth2
10.0.0.0/24 dev eth0 proto kernel scope link src 10.0.0.106
169.254.0.0/16 dev eth0 scope link metric 1002
169.254.0.0/16 dev eth1 scope link metric 1003
169.254.0.0/16 dev eth2 scope link metric 1004
169.254.0.0/16 dev eth3 scope link metric 1005
192.168.101.0/24 dev eth1 proto kernel scope link src 192.168.101.7
192.168.200.0/24 dev eth2 proto kernel scope link src 192.168.200.2
[root@rhel-
unreachable ::/96 dev lo metric 1024 error -113
unreachable ::ffff:0.0.0.0/96 dev lo metric 1024 error -113
2000:192:
2000:192:
unreachable 2002:a00::/24 dev lo metric 1024 error -113
unreachable 2002:7f00::/24 dev lo metric 1024 error -113
unreachable 2002:a9fe::/32 dev lo metric 1024 error -113
unreachable 2002:ac10::/28 dev lo metric 1024 error -113
unreachable 2002:c0a8::/32 dev lo metric 1024 error -113
unreachable 2002:e000::/19 dev lo metric 1024 error -113
unreachable 3ffe:ffff::/32 dev lo metric 1024 error -113
fe80::/64 dev eth0 proto kernel metric 256
fe80::/64 dev eth1 proto kernel metric 256
fe80::/64 dev eth2 proto kernel metric 256
fe80::/64 dev eth3 proto kernel metric 256
default via 2000:192:168:201::1 dev eth2 metric 1
default via 2000:192:168:201::1 dev eth2 metric 1024
This could be easy to work around. However, it was never taken into account before that default routes should not be added in the route- and route6- files before, so I'm wondering if this should go into a different fix instead of overloading this one.
Scott Moser (smoser) wrote : | # |
Akaris, I'm fine to have another subsequent fix to pick up your issue in the last comment.
I'm interested in knowing though if this works for rhel 5 and rhel 6.
I dont want to regress those platforms.
Were you able to verify that ?
Scott
Andreas Karis (akaris) wrote : | # |
RHEL 6:
~~~
# make sure to have rhel-guest-
# make sure to have python-
# make sure to have python-
virt-customize -a rhel-guest-
for i in *el6ost*.rpm;do virt-customize -a rhel6-cloud-
virt-customize -a rhel6-cloud-
for i in cloudinit.
virt-customize -a rhel6-cloud-
glance image-create --name rhel6-cloud-init --file rhel6-cloud-
nova boot --nic net-id=$NETID1 --nic net-id=$NETID2 --nic net-id=$NETID3 --nic net-id=$NETID4 --image rhel6-cloud-init --flavor m1.small --key-name id_rsa rhel6-cloud-init
~~~
Unfortunately, for RHEL 6, this is not working.
Andreas Karis (akaris) wrote : | # |
* not working in the sense that: cloud-init is not running for me, my test is flawed, and so far I cannot test this. I'll check with lars about how we can test this.
Lars Kellogg-Stedman (larsks) wrote : | # |
Just chiming in to say that this seems to work great under RHEL 7 for both plain ipv4 and mixed ipv4/ipv6 environments, with config drive or without. I haven't tested under EL6.
Scott Moser (smoser) wrote : | # |
Thanks for the input.
We can't break rhel/centos 5 and 6 though.
On May 22, 2017 5:12:13 PM EDT, Lars Kellogg-Stedman <email address hidden> wrote:
>Just chiming in to say that this seems to work great under RHEL 7 for
>both plain ipv4 and mixed ipv4/ipv6 environments, with config drive or
>without. I haven't tested under EL6.
>--
>https:/
>You are reviewing the proposed merge of ~akaris/cloud-init:bug1679817-c
>into cloud-init:master.
Andreas Karis (akaris) wrote : | # |
Hi,
I think we can at least forget about CentOS/RHEL 5:
https:/
https:/
They went both end of production phase 3 at March 31, 2017
Remains testing for 6 to do
- Andreas
Scott Moser (smoser) wrote : | # |
Andreas,
thanks for the RHEL 5 info, II agree.
Lars Kellogg-Stedman (larsks) wrote : | # |
I have tested this under RHEL 6 and it seems to work just fine. It produces a more correct network configuration than does the current master, and it runs without producing any errors.
Setup for testing:
- booted with a recent rhel-guest-image
- removed cloud-init via 'yum -y remove cloud-init'
- cloned cloud-init repository from https:/
- installed via "python setup.py install --init-system sysvinit"
Testing:
- rm -rf /etc/sysconfig/
- cloud-init init --local
- cloud-init init
At this point, /etc/resolv.conf and /etc/sysconfig/
As akaris indicated, I don't think it makes sense to worry about RHEL5 given its current lifecycle stage.
Scott Moser (smoser) wrote : | # |
Thanks. I'll pull this.
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal | # |
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 : Posted in a previous version of this proposal | # |
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..58c5713 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,58 @@ 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 | + # only accept ipv4 and ipv6 |
46 | + if proto not in ['ipv4', 'ipv6']: |
47 | + raise ValueError("Unknown protocol '%s'" % (str(proto))) |
48 | + buf = six.StringIO() |
49 | + buf.write(_make_header()) |
50 | + if self._conf: |
51 | + buf.write("\n") |
52 | + # need to reindex IPv4 addresses |
53 | + # (because Route can contain a mix of IPv4 and IPv6) |
54 | + reindex = -1 |
55 | + for key in sorted(self._conf.keys()): |
56 | + if 'ADDRESS' in key: |
57 | + index = key.replace('ADDRESS', '') |
58 | + address_value = str(self._conf[key]) |
59 | + # only accept combinations: |
60 | + # if proto ipv6 only display ipv6 routes |
61 | + # if proto ipv4 only display ipv4 routes |
62 | + # do not add ipv6 routes if proto is ipv4 |
63 | + # do not add ipv4 routes if proto is ipv6 |
64 | + # (this array will contain a mix of ipv4 and ipv6) |
65 | + if proto == "ipv4" and not self.is_ipv6_route(address_value): |
66 | + netmask_value = str(self._conf['NETMASK' + index]) |
67 | + gateway_value = str(self._conf['GATEWAY' + index]) |
68 | + # increase IPv4 index |
69 | + reindex = reindex + 1 |
70 | + buf.write("%s=%s\n" % ('ADDRESS' + str(reindex), |
71 | + _quote_value(address_value))) |
72 | + buf.write("%s=%s\n" % ('GATEWAY' + str(reindex), |
73 | + _quote_value(gateway_value))) |
74 | + buf.write("%s=%s\n" % ('NETMASK' + str(reindex), |
75 | + _quote_value(netmask_value))) |
76 | + elif proto == "ipv6" and self.is_ipv6_route(address_value): |
77 | + netmask_value = str(self._conf['NETMASK' + index]) |
78 | + gateway_value = str(self._conf['GATEWAY' + index]) |
79 | + buf.write("%s/%s via %s\n" % (address_value, |
80 | + netmask_value, |
81 | + gateway_value)) |
82 | + |
83 | + return buf.getvalue() |
84 | |
85 | |
86 | class NetInterface(ConfigMap): |
87 | @@ -211,65 +264,119 @@ 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 sysconfig.txt -A2 | head -3 |
118 | + # BOOTPROTO=none|bootp|dhcp |
119 | + # 'bootp' or 'dhcp' cause a DHCP client |
120 | + # to run on the device. Any other |
121 | + # value causes any static configuration |
122 | + # in the file to be applied. |
123 | + # ==> the following should not be set to 'static' |
124 | + # but should remain 'none' |
125 | + # if iface_cfg['BOOTPROTO'] == 'none': |
126 | + # iface_cfg['BOOTPROTO'] = 'static' |
127 | + if subnet_is_ipv6(subnet): |
128 | + iface_cfg['IPV6INIT'] = True |
129 | else: |
130 | - iface_cfg['IPADDR'] = subnet['address'] |
131 | - else: |
132 | - raise ValueError("Unknown subnet type '%s' found" |
133 | - " for interface '%s'" % (subnet_type, |
134 | - iface_cfg.name)) |
135 | - if 'netmask' in subnet: |
136 | - iface_cfg['NETMASK'] = subnet['netmask'] |
137 | - is_ipv6 = subnet.get('ipv6') |
138 | - for route in subnet.get('routes', []): |
139 | - if _is_default_route(route): |
140 | - if ( |
141 | - (subnet.get('ipv4') and |
142 | - route_cfg.has_set_default_ipv4) or |
143 | - (subnet.get('ipv6') and |
144 | - route_cfg.has_set_default_ipv6) |
145 | - ): |
146 | - raise ValueError("Duplicate declaration of default " |
147 | - "route found for interface '%s'" |
148 | - % (iface_cfg.name)) |
149 | - # NOTE(harlowja): ipv6 and ipv4 default gateways |
150 | - gw_key = 'GATEWAY0' |
151 | - nm_key = 'NETMASK0' |
152 | - addr_key = 'ADDRESS0' |
153 | - # The owning interface provides the default route. |
154 | - # |
155 | - # TODO(harlowja): add validation that no other iface has |
156 | - # also provided the default route? |
157 | - iface_cfg['DEFROUTE'] = True |
158 | - if 'gateway' in route: |
159 | - if is_ipv6: |
160 | - iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] |
161 | - route_cfg.has_set_default_ipv6 = True |
162 | + raise ValueError("Unknown subnet type '%s' found" |
163 | + " for interface '%s'" % (subnet_type, |
164 | + iface_cfg.name)) |
165 | + |
166 | + # set IPv4 and IPv6 static addresses |
167 | + ipv4_index = -1 |
168 | + ipv6_index = -1 |
169 | + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
170 | + subnet_type = subnet.get('type') |
171 | + if subnet_type == 'dhcp6': |
172 | + continue |
173 | + elif subnet_type in ['dhcp4', 'dhcp']: |
174 | + continue |
175 | + elif subnet_type == 'static': |
176 | + if subnet_is_ipv6(subnet): |
177 | + ipv6_index = ipv6_index + 1 |
178 | + if 'netmask' in subnet and str(subnet['netmask']) != "": |
179 | + ipv6_cidr = (subnet['address'] + |
180 | + '/' + |
181 | + str(subnet['netmask'])) |
182 | else: |
183 | - iface_cfg['GATEWAY'] = route['gateway'] |
184 | - route_cfg.has_set_default_ipv4 = True |
185 | - else: |
186 | - gw_key = 'GATEWAY%s' % route_cfg.last_idx |
187 | - nm_key = 'NETMASK%s' % route_cfg.last_idx |
188 | - addr_key = 'ADDRESS%s' % route_cfg.last_idx |
189 | - route_cfg.last_idx += 1 |
190 | - for (old_key, new_key) in [('gateway', gw_key), |
191 | - ('netmask', nm_key), |
192 | - ('network', addr_key)]: |
193 | - if old_key in route: |
194 | - route_cfg[new_key] = route[old_key] |
195 | + ipv6_cidr = subnet['address'] |
196 | + if ipv6_index == 0: |
197 | + iface_cfg['IPV6ADDR'] = ipv6_cidr |
198 | + elif ipv6_index == 1: |
199 | + iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr |
200 | + else: |
201 | + iface_cfg['IPV6ADDR_SECONDARIES'] = ( |
202 | + iface_cfg['IPV6ADDR_SECONDARIES'] + |
203 | + " " + ipv6_cidr) |
204 | + else: |
205 | + ipv4_index = ipv4_index + 1 |
206 | + if ipv4_index == 0: |
207 | + iface_cfg['IPADDR'] = subnet['address'] |
208 | + if 'netmask' in subnet: |
209 | + iface_cfg['NETMASK'] = subnet['netmask'] |
210 | + else: |
211 | + iface_cfg['IPADDR' + str(ipv4_index)] = \ |
212 | + subnet['address'] |
213 | + if 'netmask' in subnet: |
214 | + iface_cfg['NETMASK' + str(ipv4_index)] = \ |
215 | + subnet['netmask'] |
216 | + |
217 | + @classmethod |
218 | + def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): |
219 | + for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
220 | + for route in subnet.get('routes', []): |
221 | + is_ipv6 = subnet.get('ipv6') |
222 | + |
223 | + if _is_default_route(route): |
224 | + if ( |
225 | + (subnet.get('ipv4') and |
226 | + route_cfg.has_set_default_ipv4) or |
227 | + (subnet.get('ipv6') and |
228 | + route_cfg.has_set_default_ipv6) |
229 | + ): |
230 | + raise ValueError("Duplicate declaration of default " |
231 | + "route found for interface '%s'" |
232 | + % (iface_cfg.name)) |
233 | + # NOTE(harlowja): ipv6 and ipv4 default gateways |
234 | + gw_key = 'GATEWAY0' |
235 | + nm_key = 'NETMASK0' |
236 | + addr_key = 'ADDRESS0' |
237 | + # The owning interface provides the default route. |
238 | + # |
239 | + # TODO(harlowja): add validation that no other iface has |
240 | + # also provided the default route? |
241 | + iface_cfg['DEFROUTE'] = True |
242 | + if 'gateway' in route: |
243 | + if is_ipv6: |
244 | + iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] |
245 | + route_cfg.has_set_default_ipv6 = True |
246 | + else: |
247 | + iface_cfg['GATEWAY'] = route['gateway'] |
248 | + route_cfg.has_set_default_ipv4 = True |
249 | + |
250 | + else: |
251 | + gw_key = 'GATEWAY%s' % route_cfg.last_idx |
252 | + nm_key = 'NETMASK%s' % route_cfg.last_idx |
253 | + addr_key = 'ADDRESS%s' % route_cfg.last_idx |
254 | + route_cfg.last_idx += 1 |
255 | + for (old_key, new_key) in [('gateway', gw_key), |
256 | + ('netmask', nm_key), |
257 | + ('network', addr_key)]: |
258 | + if old_key in route: |
259 | + route_cfg[new_key] = route[old_key] |
260 | |
261 | @classmethod |
262 | def _render_bonding_opts(cls, iface_cfg, iface): |
263 | @@ -295,15 +402,9 @@ class Renderer(renderer.Renderer): |
264 | iface_subnets = iface.get("subnets", []) |
265 | iface_cfg = iface_contents[iface_name] |
266 | route_cfg = iface_cfg.routes |
267 | - if len(iface_subnets) == 1: |
268 | - cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) |
269 | - elif len(iface_subnets) > 1: |
270 | - for i, isubnet in enumerate(iface_subnets, |
271 | - start=len(iface_cfg.children)): |
272 | - iface_sub_cfg = iface_cfg.copy() |
273 | - iface_sub_cfg.name = "%s:%s" % (iface_name, i) |
274 | - iface_cfg.children.append(iface_sub_cfg) |
275 | - cls._render_subnet(iface_sub_cfg, route_cfg, isubnet) |
276 | + |
277 | + cls._render_subnets(iface_cfg, iface_subnets) |
278 | + cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) |
279 | |
280 | @classmethod |
281 | def _render_bond_interfaces(cls, network_state, iface_contents): |
282 | @@ -387,7 +488,10 @@ class Renderer(renderer.Renderer): |
283 | if iface_cfg: |
284 | contents[iface_cfg.path] = iface_cfg.to_string() |
285 | if iface_cfg.routes: |
286 | - contents[iface_cfg.routes.path] = iface_cfg.routes.to_string() |
287 | + contents[iface_cfg.routes.path_ipv4] = \ |
288 | + iface_cfg.routes.to_string("ipv4") |
289 | + contents[iface_cfg.routes.path_ipv6] = \ |
290 | + iface_cfg.routes.to_string("ipv6") |
291 | return contents |
292 | |
293 | def render_network_state(self, network_state, target=None): |
294 | diff --git a/tests/unittests/test_distros/test_netconfig.py b/tests/unittests/test_distros/test_netconfig.py |
295 | index 1e10a33..fd7c051 100644 |
296 | --- a/tests/unittests/test_distros/test_netconfig.py |
297 | +++ b/tests/unittests/test_distros/test_netconfig.py |
298 | @@ -476,7 +476,7 @@ NETWORKING=yes |
299 | expected_buf = ''' |
300 | # Created by cloud-init on instance boot automatically, do not edit. |
301 | # |
302 | -BOOTPROTO=static |
303 | +BOOTPROTO=none |
304 | DEVICE=eth0 |
305 | IPADDR=192.168.1.5 |
306 | NETMASK=255.255.255.0 |
307 | @@ -533,7 +533,6 @@ NETWORKING=yes |
308 | mock.patch.object(util, 'load_file', return_value='')) |
309 | mocks.enter_context( |
310 | mock.patch.object(os.path, 'isfile', return_value=False)) |
311 | - |
312 | rh_distro.apply_network(BASE_NET_CFG_IPV6, False) |
313 | |
314 | self.assertEqual(len(write_bufs), 4) |
315 | @@ -626,11 +625,10 @@ IPV6_AUTOCONF=no |
316 | expected_buf = ''' |
317 | # Created by cloud-init on instance boot automatically, do not edit. |
318 | # |
319 | -BOOTPROTO=static |
320 | +BOOTPROTO=none |
321 | DEVICE=eth0 |
322 | -IPV6ADDR=2607:f0d0:1002:0011::2 |
323 | +IPV6ADDR=2607:f0d0:1002:0011::2/64 |
324 | IPV6INIT=yes |
325 | -NETMASK=64 |
326 | NM_CONTROLLED=no |
327 | ONBOOT=yes |
328 | TYPE=Ethernet |
329 | diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py |
330 | index d36d0e7..cf4cedc 100644 |
331 | --- a/tests/unittests/test_net.py |
332 | +++ b/tests/unittests/test_net.py |
333 | @@ -137,7 +137,7 @@ OS_SAMPLES = [ |
334 | """ |
335 | # Created by cloud-init on instance boot automatically, do not edit. |
336 | # |
337 | -BOOTPROTO=static |
338 | +BOOTPROTO=none |
339 | DEFROUTE=yes |
340 | DEVICE=eth0 |
341 | GATEWAY=172.19.3.254 |
342 | @@ -205,38 +205,14 @@ nameserver 172.19.0.12 |
343 | # Created by cloud-init on instance boot automatically, do not edit. |
344 | # |
345 | BOOTPROTO=none |
346 | -DEVICE=eth0 |
347 | -HWADDR=fa:16:3e:ed:9a:59 |
348 | -NM_CONTROLLED=no |
349 | -ONBOOT=yes |
350 | -TYPE=Ethernet |
351 | -USERCTL=no |
352 | -""".lstrip()), |
353 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:0', |
354 | - """ |
355 | -# Created by cloud-init on instance boot automatically, do not edit. |
356 | -# |
357 | -BOOTPROTO=static |
358 | DEFROUTE=yes |
359 | -DEVICE=eth0:0 |
360 | +DEVICE=eth0 |
361 | GATEWAY=172.19.3.254 |
362 | HWADDR=fa:16:3e:ed:9a:59 |
363 | IPADDR=172.19.1.34 |
364 | +IPADDR1=10.0.0.10 |
365 | NETMASK=255.255.252.0 |
366 | -NM_CONTROLLED=no |
367 | -ONBOOT=yes |
368 | -TYPE=Ethernet |
369 | -USERCTL=no |
370 | -""".lstrip()), |
371 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:1', |
372 | - """ |
373 | -# Created by cloud-init on instance boot automatically, do not edit. |
374 | -# |
375 | -BOOTPROTO=static |
376 | -DEVICE=eth0:1 |
377 | -HWADDR=fa:16:3e:ed:9a:59 |
378 | -IPADDR=10.0.0.10 |
379 | -NETMASK=255.255.255.0 |
380 | +NETMASK1=255.255.255.0 |
381 | NM_CONTROLLED=no |
382 | ONBOOT=yes |
383 | TYPE=Ethernet |
384 | @@ -266,7 +242,7 @@ nameserver 172.19.0.12 |
385 | }], |
386 | "ip_address": "172.19.1.34", "id": "network0" |
387 | }, { |
388 | - "network_id": "public-ipv6", |
389 | + "network_id": "public-ipv6-a", |
390 | "type": "ipv6", "netmask": "", |
391 | "link": "tap1a81968a-79", |
392 | "routes": [ |
393 | @@ -277,6 +253,20 @@ nameserver 172.19.0.12 |
394 | } |
395 | ], |
396 | "ip_address": "2001:DB8::10", "id": "network1" |
397 | + }, { |
398 | + "network_id": "public-ipv6-b", |
399 | + "type": "ipv6", "netmask": "64", |
400 | + "link": "tap1a81968a-79", |
401 | + "routes": [ |
402 | + ], |
403 | + "ip_address": "2001:DB9::10", "id": "network2" |
404 | + }, { |
405 | + "network_id": "public-ipv6-c", |
406 | + "type": "ipv6", "netmask": "64", |
407 | + "link": "tap1a81968a-79", |
408 | + "routes": [ |
409 | + ], |
410 | + "ip_address": "2001:DB10::10", "id": "network3" |
411 | }], |
412 | "links": [ |
413 | { |
414 | @@ -296,41 +286,16 @@ nameserver 172.19.0.12 |
415 | # Created by cloud-init on instance boot automatically, do not edit. |
416 | # |
417 | BOOTPROTO=none |
418 | -DEVICE=eth0 |
419 | -HWADDR=fa:16:3e:ed:9a:59 |
420 | -NM_CONTROLLED=no |
421 | -ONBOOT=yes |
422 | -TYPE=Ethernet |
423 | -USERCTL=no |
424 | -""".lstrip()), |
425 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:0', |
426 | - """ |
427 | -# Created by cloud-init on instance boot automatically, do not edit. |
428 | -# |
429 | -BOOTPROTO=static |
430 | DEFROUTE=yes |
431 | -DEVICE=eth0:0 |
432 | +DEVICE=eth0 |
433 | GATEWAY=172.19.3.254 |
434 | HWADDR=fa:16:3e:ed:9a:59 |
435 | IPADDR=172.19.1.34 |
436 | -NETMASK=255.255.252.0 |
437 | -NM_CONTROLLED=no |
438 | -ONBOOT=yes |
439 | -TYPE=Ethernet |
440 | -USERCTL=no |
441 | -""".lstrip()), |
442 | - ('etc/sysconfig/network-scripts/ifcfg-eth0:1', |
443 | - """ |
444 | -# Created by cloud-init on instance boot automatically, do not edit. |
445 | -# |
446 | -BOOTPROTO=static |
447 | -DEFROUTE=yes |
448 | -DEVICE=eth0:1 |
449 | -HWADDR=fa:16:3e:ed:9a:59 |
450 | IPV6ADDR=2001:DB8::10 |
451 | +IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" |
452 | IPV6INIT=yes |
453 | IPV6_DEFAULTGW=2001:DB8::1 |
454 | -NETMASK= |
455 | +NETMASK=255.255.252.0 |
456 | NM_CONTROLLED=no |
457 | ONBOOT=yes |
458 | 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