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 | 59 | def __setitem__(self, key, value): | 59 | def __setitem__(self, key, value): |
7 | 60 | self._conf[key] = value | 60 | self._conf[key] = value |
8 | 61 | 61 | ||
9 | 62 | def __getitem__(self, key): | ||
10 | 63 | return self._conf[key] | ||
11 | 64 | |||
12 | 62 | def drop(self, key): | 65 | def drop(self, key): |
13 | 63 | self._conf.pop(key, None) | 66 | self._conf.pop(key, None) |
14 | 64 | 67 | ||
15 | @@ -83,7 +86,8 @@ class ConfigMap(object): | |||
16 | 83 | class Route(ConfigMap): | 86 | class Route(ConfigMap): |
17 | 84 | """Represents a route configuration.""" | 87 | """Represents a route configuration.""" |
18 | 85 | 88 | ||
20 | 86 | route_fn_tpl = '%(base)s/network-scripts/route-%(name)s' | 89 | route_fn_tpl_ipv4 = '%(base)s/network-scripts/route-%(name)s' |
21 | 90 | route_fn_tpl_ipv6 = '%(base)s/network-scripts/route6-%(name)s' | ||
22 | 87 | 91 | ||
23 | 88 | def __init__(self, route_name, base_sysconf_dir): | 92 | def __init__(self, route_name, base_sysconf_dir): |
24 | 89 | super(Route, self).__init__() | 93 | super(Route, self).__init__() |
25 | @@ -102,9 +106,58 @@ class Route(ConfigMap): | |||
26 | 102 | return r | 106 | return r |
27 | 103 | 107 | ||
28 | 104 | @property | 108 | @property |
32 | 105 | def path(self): | 109 | def path_ipv4(self): |
33 | 106 | return self.route_fn_tpl % ({'base': self._base_sysconf_dir, | 110 | return self.route_fn_tpl_ipv4 % ({'base': self._base_sysconf_dir, |
34 | 107 | 'name': self._route_name}) | 111 | 'name': self._route_name}) |
35 | 112 | |||
36 | 113 | @property | ||
37 | 114 | def path_ipv6(self): | ||
38 | 115 | return self.route_fn_tpl_ipv6 % ({'base': self._base_sysconf_dir, | ||
39 | 116 | 'name': self._route_name}) | ||
40 | 117 | |||
41 | 118 | def is_ipv6_route(self, address): | ||
42 | 119 | return ':' in address | ||
43 | 120 | |||
44 | 121 | def to_string(self, proto="ipv4"): | ||
45 | 122 | # only accept ipv4 and ipv6 | ||
46 | 123 | if proto not in ['ipv4', 'ipv6']: | ||
47 | 124 | raise ValueError("Unknown protocol '%s'" % (str(proto))) | ||
48 | 125 | buf = six.StringIO() | ||
49 | 126 | buf.write(_make_header()) | ||
50 | 127 | if self._conf: | ||
51 | 128 | buf.write("\n") | ||
52 | 129 | # need to reindex IPv4 addresses | ||
53 | 130 | # (because Route can contain a mix of IPv4 and IPv6) | ||
54 | 131 | reindex = -1 | ||
55 | 132 | for key in sorted(self._conf.keys()): | ||
56 | 133 | if 'ADDRESS' in key: | ||
57 | 134 | index = key.replace('ADDRESS', '') | ||
58 | 135 | address_value = str(self._conf[key]) | ||
59 | 136 | # only accept combinations: | ||
60 | 137 | # if proto ipv6 only display ipv6 routes | ||
61 | 138 | # if proto ipv4 only display ipv4 routes | ||
62 | 139 | # do not add ipv6 routes if proto is ipv4 | ||
63 | 140 | # do not add ipv4 routes if proto is ipv6 | ||
64 | 141 | # (this array will contain a mix of ipv4 and ipv6) | ||
65 | 142 | if proto == "ipv4" and not self.is_ipv6_route(address_value): | ||
66 | 143 | netmask_value = str(self._conf['NETMASK' + index]) | ||
67 | 144 | gateway_value = str(self._conf['GATEWAY' + index]) | ||
68 | 145 | # increase IPv4 index | ||
69 | 146 | reindex = reindex + 1 | ||
70 | 147 | buf.write("%s=%s\n" % ('ADDRESS' + str(reindex), | ||
71 | 148 | _quote_value(address_value))) | ||
72 | 149 | buf.write("%s=%s\n" % ('GATEWAY' + str(reindex), | ||
73 | 150 | _quote_value(gateway_value))) | ||
74 | 151 | buf.write("%s=%s\n" % ('NETMASK' + str(reindex), | ||
75 | 152 | _quote_value(netmask_value))) | ||
76 | 153 | elif proto == "ipv6" and self.is_ipv6_route(address_value): | ||
77 | 154 | netmask_value = str(self._conf['NETMASK' + index]) | ||
78 | 155 | gateway_value = str(self._conf['GATEWAY' + index]) | ||
79 | 156 | buf.write("%s/%s via %s\n" % (address_value, | ||
80 | 157 | netmask_value, | ||
81 | 158 | gateway_value)) | ||
82 | 159 | |||
83 | 160 | return buf.getvalue() | ||
84 | 108 | 161 | ||
85 | 109 | 162 | ||
86 | 110 | class NetInterface(ConfigMap): | 163 | class NetInterface(ConfigMap): |
87 | @@ -211,65 +264,119 @@ class Renderer(renderer.Renderer): | |||
88 | 211 | iface_cfg[new_key] = old_value | 264 | iface_cfg[new_key] = old_value |
89 | 212 | 265 | ||
90 | 213 | @classmethod | 266 | @classmethod |
103 | 214 | def _render_subnet(cls, iface_cfg, route_cfg, subnet): | 267 | def _render_subnets(cls, iface_cfg, subnets): |
104 | 215 | subnet_type = subnet.get('type') | 268 | # setting base values |
105 | 216 | if subnet_type == 'dhcp6': | 269 | iface_cfg['BOOTPROTO'] = 'none' |
106 | 217 | iface_cfg['DHCPV6C'] = True | 270 | |
107 | 218 | iface_cfg['IPV6INIT'] = True | 271 | # modifying base values according to subnets |
108 | 219 | iface_cfg['BOOTPROTO'] = 'dhcp' | 272 | for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
109 | 220 | elif subnet_type in ['dhcp4', 'dhcp']: | 273 | subnet_type = subnet.get('type') |
110 | 221 | iface_cfg['BOOTPROTO'] = 'dhcp' | 274 | if subnet_type == 'dhcp6': |
99 | 222 | elif subnet_type == 'static': | ||
100 | 223 | iface_cfg['BOOTPROTO'] = 'static' | ||
101 | 224 | if subnet_is_ipv6(subnet): | ||
102 | 225 | iface_cfg['IPV6ADDR'] = subnet['address'] | ||
111 | 226 | iface_cfg['IPV6INIT'] = True | 275 | iface_cfg['IPV6INIT'] = True |
112 | 276 | iface_cfg['DHCPV6C'] = True | ||
113 | 277 | iface_cfg['BOOTPROTO'] = 'dhcp' | ||
114 | 278 | elif subnet_type in ['dhcp4', 'dhcp']: | ||
115 | 279 | iface_cfg['BOOTPROTO'] = 'dhcp' | ||
116 | 280 | elif subnet_type == 'static': | ||
117 | 281 | # grep BOOTPROTO sysconfig.txt -A2 | head -3 | ||
118 | 282 | # BOOTPROTO=none|bootp|dhcp | ||
119 | 283 | # 'bootp' or 'dhcp' cause a DHCP client | ||
120 | 284 | # to run on the device. Any other | ||
121 | 285 | # value causes any static configuration | ||
122 | 286 | # in the file to be applied. | ||
123 | 287 | # ==> the following should not be set to 'static' | ||
124 | 288 | # but should remain 'none' | ||
125 | 289 | # if iface_cfg['BOOTPROTO'] == 'none': | ||
126 | 290 | # iface_cfg['BOOTPROTO'] = 'static' | ||
127 | 291 | if subnet_is_ipv6(subnet): | ||
128 | 292 | iface_cfg['IPV6INIT'] = True | ||
129 | 227 | else: | 293 | else: |
162 | 228 | iface_cfg['IPADDR'] = subnet['address'] | 294 | raise ValueError("Unknown subnet type '%s' found" |
163 | 229 | else: | 295 | " for interface '%s'" % (subnet_type, |
164 | 230 | raise ValueError("Unknown subnet type '%s' found" | 296 | iface_cfg.name)) |
165 | 231 | " for interface '%s'" % (subnet_type, | 297 | |
166 | 232 | iface_cfg.name)) | 298 | # set IPv4 and IPv6 static addresses |
167 | 233 | if 'netmask' in subnet: | 299 | ipv4_index = -1 |
168 | 234 | iface_cfg['NETMASK'] = subnet['netmask'] | 300 | ipv6_index = -1 |
169 | 235 | is_ipv6 = subnet.get('ipv6') | 301 | for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): |
170 | 236 | for route in subnet.get('routes', []): | 302 | subnet_type = subnet.get('type') |
171 | 237 | if _is_default_route(route): | 303 | if subnet_type == 'dhcp6': |
172 | 238 | if ( | 304 | continue |
173 | 239 | (subnet.get('ipv4') and | 305 | elif subnet_type in ['dhcp4', 'dhcp']: |
174 | 240 | route_cfg.has_set_default_ipv4) or | 306 | continue |
175 | 241 | (subnet.get('ipv6') and | 307 | elif subnet_type == 'static': |
176 | 242 | route_cfg.has_set_default_ipv6) | 308 | if subnet_is_ipv6(subnet): |
177 | 243 | ): | 309 | ipv6_index = ipv6_index + 1 |
178 | 244 | raise ValueError("Duplicate declaration of default " | 310 | if 'netmask' in subnet and str(subnet['netmask']) != "": |
179 | 245 | "route found for interface '%s'" | 311 | ipv6_cidr = (subnet['address'] + |
180 | 246 | % (iface_cfg.name)) | 312 | '/' + |
181 | 247 | # NOTE(harlowja): ipv6 and ipv4 default gateways | 313 | str(subnet['netmask'])) |
150 | 248 | gw_key = 'GATEWAY0' | ||
151 | 249 | nm_key = 'NETMASK0' | ||
152 | 250 | addr_key = 'ADDRESS0' | ||
153 | 251 | # The owning interface provides the default route. | ||
154 | 252 | # | ||
155 | 253 | # TODO(harlowja): add validation that no other iface has | ||
156 | 254 | # also provided the default route? | ||
157 | 255 | iface_cfg['DEFROUTE'] = True | ||
158 | 256 | if 'gateway' in route: | ||
159 | 257 | if is_ipv6: | ||
160 | 258 | iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] | ||
161 | 259 | route_cfg.has_set_default_ipv6 = True | ||
182 | 260 | else: | 314 | else: |
195 | 261 | iface_cfg['GATEWAY'] = route['gateway'] | 315 | ipv6_cidr = subnet['address'] |
196 | 262 | route_cfg.has_set_default_ipv4 = True | 316 | if ipv6_index == 0: |
197 | 263 | else: | 317 | iface_cfg['IPV6ADDR'] = ipv6_cidr |
198 | 264 | gw_key = 'GATEWAY%s' % route_cfg.last_idx | 318 | elif ipv6_index == 1: |
199 | 265 | nm_key = 'NETMASK%s' % route_cfg.last_idx | 319 | iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr |
200 | 266 | addr_key = 'ADDRESS%s' % route_cfg.last_idx | 320 | else: |
201 | 267 | route_cfg.last_idx += 1 | 321 | iface_cfg['IPV6ADDR_SECONDARIES'] = ( |
202 | 268 | for (old_key, new_key) in [('gateway', gw_key), | 322 | iface_cfg['IPV6ADDR_SECONDARIES'] + |
203 | 269 | ('netmask', nm_key), | 323 | " " + ipv6_cidr) |
204 | 270 | ('network', addr_key)]: | 324 | else: |
205 | 271 | if old_key in route: | 325 | ipv4_index = ipv4_index + 1 |
206 | 272 | route_cfg[new_key] = route[old_key] | 326 | if ipv4_index == 0: |
207 | 327 | iface_cfg['IPADDR'] = subnet['address'] | ||
208 | 328 | if 'netmask' in subnet: | ||
209 | 329 | iface_cfg['NETMASK'] = subnet['netmask'] | ||
210 | 330 | else: | ||
211 | 331 | iface_cfg['IPADDR' + str(ipv4_index)] = \ | ||
212 | 332 | subnet['address'] | ||
213 | 333 | if 'netmask' in subnet: | ||
214 | 334 | iface_cfg['NETMASK' + str(ipv4_index)] = \ | ||
215 | 335 | subnet['netmask'] | ||
216 | 336 | |||
217 | 337 | @classmethod | ||
218 | 338 | def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets): | ||
219 | 339 | for i, subnet in enumerate(subnets, start=len(iface_cfg.children)): | ||
220 | 340 | for route in subnet.get('routes', []): | ||
221 | 341 | is_ipv6 = subnet.get('ipv6') | ||
222 | 342 | |||
223 | 343 | if _is_default_route(route): | ||
224 | 344 | if ( | ||
225 | 345 | (subnet.get('ipv4') and | ||
226 | 346 | route_cfg.has_set_default_ipv4) or | ||
227 | 347 | (subnet.get('ipv6') and | ||
228 | 348 | route_cfg.has_set_default_ipv6) | ||
229 | 349 | ): | ||
230 | 350 | raise ValueError("Duplicate declaration of default " | ||
231 | 351 | "route found for interface '%s'" | ||
232 | 352 | % (iface_cfg.name)) | ||
233 | 353 | # NOTE(harlowja): ipv6 and ipv4 default gateways | ||
234 | 354 | gw_key = 'GATEWAY0' | ||
235 | 355 | nm_key = 'NETMASK0' | ||
236 | 356 | addr_key = 'ADDRESS0' | ||
237 | 357 | # The owning interface provides the default route. | ||
238 | 358 | # | ||
239 | 359 | # TODO(harlowja): add validation that no other iface has | ||
240 | 360 | # also provided the default route? | ||
241 | 361 | iface_cfg['DEFROUTE'] = True | ||
242 | 362 | if 'gateway' in route: | ||
243 | 363 | if is_ipv6: | ||
244 | 364 | iface_cfg['IPV6_DEFAULTGW'] = route['gateway'] | ||
245 | 365 | route_cfg.has_set_default_ipv6 = True | ||
246 | 366 | else: | ||
247 | 367 | iface_cfg['GATEWAY'] = route['gateway'] | ||
248 | 368 | route_cfg.has_set_default_ipv4 = True | ||
249 | 369 | |||
250 | 370 | else: | ||
251 | 371 | gw_key = 'GATEWAY%s' % route_cfg.last_idx | ||
252 | 372 | nm_key = 'NETMASK%s' % route_cfg.last_idx | ||
253 | 373 | addr_key = 'ADDRESS%s' % route_cfg.last_idx | ||
254 | 374 | route_cfg.last_idx += 1 | ||
255 | 375 | for (old_key, new_key) in [('gateway', gw_key), | ||
256 | 376 | ('netmask', nm_key), | ||
257 | 377 | ('network', addr_key)]: | ||
258 | 378 | if old_key in route: | ||
259 | 379 | route_cfg[new_key] = route[old_key] | ||
260 | 273 | 380 | ||
261 | 274 | @classmethod | 381 | @classmethod |
262 | 275 | def _render_bonding_opts(cls, iface_cfg, iface): | 382 | def _render_bonding_opts(cls, iface_cfg, iface): |
263 | @@ -295,15 +402,9 @@ class Renderer(renderer.Renderer): | |||
264 | 295 | iface_subnets = iface.get("subnets", []) | 402 | iface_subnets = iface.get("subnets", []) |
265 | 296 | iface_cfg = iface_contents[iface_name] | 403 | iface_cfg = iface_contents[iface_name] |
266 | 297 | route_cfg = iface_cfg.routes | 404 | route_cfg = iface_cfg.routes |
276 | 298 | if len(iface_subnets) == 1: | 405 | |
277 | 299 | cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0]) | 406 | cls._render_subnets(iface_cfg, iface_subnets) |
278 | 300 | elif len(iface_subnets) > 1: | 407 | cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets) |
270 | 301 | for i, isubnet in enumerate(iface_subnets, | ||
271 | 302 | start=len(iface_cfg.children)): | ||
272 | 303 | iface_sub_cfg = iface_cfg.copy() | ||
273 | 304 | iface_sub_cfg.name = "%s:%s" % (iface_name, i) | ||
274 | 305 | iface_cfg.children.append(iface_sub_cfg) | ||
275 | 306 | cls._render_subnet(iface_sub_cfg, route_cfg, isubnet) | ||
279 | 307 | 408 | ||
280 | 308 | @classmethod | 409 | @classmethod |
281 | 309 | def _render_bond_interfaces(cls, network_state, iface_contents): | 410 | def _render_bond_interfaces(cls, network_state, iface_contents): |
282 | @@ -387,7 +488,10 @@ class Renderer(renderer.Renderer): | |||
283 | 387 | if iface_cfg: | 488 | if iface_cfg: |
284 | 388 | contents[iface_cfg.path] = iface_cfg.to_string() | 489 | contents[iface_cfg.path] = iface_cfg.to_string() |
285 | 389 | if iface_cfg.routes: | 490 | if iface_cfg.routes: |
287 | 390 | contents[iface_cfg.routes.path] = iface_cfg.routes.to_string() | 491 | contents[iface_cfg.routes.path_ipv4] = \ |
288 | 492 | iface_cfg.routes.to_string("ipv4") | ||
289 | 493 | contents[iface_cfg.routes.path_ipv6] = \ | ||
290 | 494 | iface_cfg.routes.to_string("ipv6") | ||
291 | 391 | return contents | 495 | return contents |
292 | 392 | 496 | ||
293 | 393 | def render_network_state(self, network_state, target=None): | 497 | 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 | 476 | expected_buf = ''' | 476 | expected_buf = ''' |
300 | 477 | # Created by cloud-init on instance boot automatically, do not edit. | 477 | # Created by cloud-init on instance boot automatically, do not edit. |
301 | 478 | # | 478 | # |
303 | 479 | BOOTPROTO=static | 479 | BOOTPROTO=none |
304 | 480 | DEVICE=eth0 | 480 | DEVICE=eth0 |
305 | 481 | IPADDR=192.168.1.5 | 481 | IPADDR=192.168.1.5 |
306 | 482 | NETMASK=255.255.255.0 | 482 | NETMASK=255.255.255.0 |
307 | @@ -533,7 +533,6 @@ NETWORKING=yes | |||
308 | 533 | mock.patch.object(util, 'load_file', return_value='')) | 533 | mock.patch.object(util, 'load_file', return_value='')) |
309 | 534 | mocks.enter_context( | 534 | mocks.enter_context( |
310 | 535 | mock.patch.object(os.path, 'isfile', return_value=False)) | 535 | mock.patch.object(os.path, 'isfile', return_value=False)) |
311 | 536 | |||
312 | 537 | rh_distro.apply_network(BASE_NET_CFG_IPV6, False) | 536 | rh_distro.apply_network(BASE_NET_CFG_IPV6, False) |
313 | 538 | 537 | ||
314 | 539 | self.assertEqual(len(write_bufs), 4) | 538 | self.assertEqual(len(write_bufs), 4) |
315 | @@ -626,11 +625,10 @@ IPV6_AUTOCONF=no | |||
316 | 626 | expected_buf = ''' | 625 | expected_buf = ''' |
317 | 627 | # Created by cloud-init on instance boot automatically, do not edit. | 626 | # Created by cloud-init on instance boot automatically, do not edit. |
318 | 628 | # | 627 | # |
320 | 629 | BOOTPROTO=static | 628 | BOOTPROTO=none |
321 | 630 | DEVICE=eth0 | 629 | DEVICE=eth0 |
323 | 631 | IPV6ADDR=2607:f0d0:1002:0011::2 | 630 | IPV6ADDR=2607:f0d0:1002:0011::2/64 |
324 | 632 | IPV6INIT=yes | 631 | IPV6INIT=yes |
325 | 633 | NETMASK=64 | ||
326 | 634 | NM_CONTROLLED=no | 632 | NM_CONTROLLED=no |
327 | 635 | ONBOOT=yes | 633 | ONBOOT=yes |
328 | 636 | TYPE=Ethernet | 634 | 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 | 137 | """ | 137 | """ |
335 | 138 | # Created by cloud-init on instance boot automatically, do not edit. | 138 | # Created by cloud-init on instance boot automatically, do not edit. |
336 | 139 | # | 139 | # |
338 | 140 | BOOTPROTO=static | 140 | BOOTPROTO=none |
339 | 141 | DEFROUTE=yes | 141 | DEFROUTE=yes |
340 | 142 | DEVICE=eth0 | 142 | DEVICE=eth0 |
341 | 143 | GATEWAY=172.19.3.254 | 143 | GATEWAY=172.19.3.254 |
342 | @@ -205,38 +205,14 @@ nameserver 172.19.0.12 | |||
343 | 205 | # Created by cloud-init on instance boot automatically, do not edit. | 205 | # Created by cloud-init on instance boot automatically, do not edit. |
344 | 206 | # | 206 | # |
345 | 207 | BOOTPROTO=none | 207 | BOOTPROTO=none |
346 | 208 | DEVICE=eth0 | ||
347 | 209 | HWADDR=fa:16:3e:ed:9a:59 | ||
348 | 210 | NM_CONTROLLED=no | ||
349 | 211 | ONBOOT=yes | ||
350 | 212 | TYPE=Ethernet | ||
351 | 213 | USERCTL=no | ||
352 | 214 | """.lstrip()), | ||
353 | 215 | ('etc/sysconfig/network-scripts/ifcfg-eth0:0', | ||
354 | 216 | """ | ||
355 | 217 | # Created by cloud-init on instance boot automatically, do not edit. | ||
356 | 218 | # | ||
357 | 219 | BOOTPROTO=static | ||
358 | 220 | DEFROUTE=yes | 208 | DEFROUTE=yes |
360 | 221 | DEVICE=eth0:0 | 209 | DEVICE=eth0 |
361 | 222 | GATEWAY=172.19.3.254 | 210 | GATEWAY=172.19.3.254 |
362 | 223 | HWADDR=fa:16:3e:ed:9a:59 | 211 | HWADDR=fa:16:3e:ed:9a:59 |
363 | 224 | IPADDR=172.19.1.34 | 212 | IPADDR=172.19.1.34 |
364 | 213 | IPADDR1=10.0.0.10 | ||
365 | 225 | NETMASK=255.255.252.0 | 214 | NETMASK=255.255.252.0 |
380 | 226 | NM_CONTROLLED=no | 215 | NETMASK1=255.255.255.0 |
367 | 227 | ONBOOT=yes | ||
368 | 228 | TYPE=Ethernet | ||
369 | 229 | USERCTL=no | ||
370 | 230 | """.lstrip()), | ||
371 | 231 | ('etc/sysconfig/network-scripts/ifcfg-eth0:1', | ||
372 | 232 | """ | ||
373 | 233 | # Created by cloud-init on instance boot automatically, do not edit. | ||
374 | 234 | # | ||
375 | 235 | BOOTPROTO=static | ||
376 | 236 | DEVICE=eth0:1 | ||
377 | 237 | HWADDR=fa:16:3e:ed:9a:59 | ||
378 | 238 | IPADDR=10.0.0.10 | ||
379 | 239 | NETMASK=255.255.255.0 | ||
381 | 240 | NM_CONTROLLED=no | 216 | NM_CONTROLLED=no |
382 | 241 | ONBOOT=yes | 217 | ONBOOT=yes |
383 | 242 | TYPE=Ethernet | 218 | TYPE=Ethernet |
384 | @@ -266,7 +242,7 @@ nameserver 172.19.0.12 | |||
385 | 266 | }], | 242 | }], |
386 | 267 | "ip_address": "172.19.1.34", "id": "network0" | 243 | "ip_address": "172.19.1.34", "id": "network0" |
387 | 268 | }, { | 244 | }, { |
389 | 269 | "network_id": "public-ipv6", | 245 | "network_id": "public-ipv6-a", |
390 | 270 | "type": "ipv6", "netmask": "", | 246 | "type": "ipv6", "netmask": "", |
391 | 271 | "link": "tap1a81968a-79", | 247 | "link": "tap1a81968a-79", |
392 | 272 | "routes": [ | 248 | "routes": [ |
393 | @@ -277,6 +253,20 @@ nameserver 172.19.0.12 | |||
394 | 277 | } | 253 | } |
395 | 278 | ], | 254 | ], |
396 | 279 | "ip_address": "2001:DB8::10", "id": "network1" | 255 | "ip_address": "2001:DB8::10", "id": "network1" |
397 | 256 | }, { | ||
398 | 257 | "network_id": "public-ipv6-b", | ||
399 | 258 | "type": "ipv6", "netmask": "64", | ||
400 | 259 | "link": "tap1a81968a-79", | ||
401 | 260 | "routes": [ | ||
402 | 261 | ], | ||
403 | 262 | "ip_address": "2001:DB9::10", "id": "network2" | ||
404 | 263 | }, { | ||
405 | 264 | "network_id": "public-ipv6-c", | ||
406 | 265 | "type": "ipv6", "netmask": "64", | ||
407 | 266 | "link": "tap1a81968a-79", | ||
408 | 267 | "routes": [ | ||
409 | 268 | ], | ||
410 | 269 | "ip_address": "2001:DB10::10", "id": "network3" | ||
411 | 280 | }], | 270 | }], |
412 | 281 | "links": [ | 271 | "links": [ |
413 | 282 | { | 272 | { |
414 | @@ -296,41 +286,16 @@ nameserver 172.19.0.12 | |||
415 | 296 | # Created by cloud-init on instance boot automatically, do not edit. | 286 | # Created by cloud-init on instance boot automatically, do not edit. |
416 | 297 | # | 287 | # |
417 | 298 | BOOTPROTO=none | 288 | BOOTPROTO=none |
418 | 299 | DEVICE=eth0 | ||
419 | 300 | HWADDR=fa:16:3e:ed:9a:59 | ||
420 | 301 | NM_CONTROLLED=no | ||
421 | 302 | ONBOOT=yes | ||
422 | 303 | TYPE=Ethernet | ||
423 | 304 | USERCTL=no | ||
424 | 305 | """.lstrip()), | ||
425 | 306 | ('etc/sysconfig/network-scripts/ifcfg-eth0:0', | ||
426 | 307 | """ | ||
427 | 308 | # Created by cloud-init on instance boot automatically, do not edit. | ||
428 | 309 | # | ||
429 | 310 | BOOTPROTO=static | ||
430 | 311 | DEFROUTE=yes | 289 | DEFROUTE=yes |
432 | 312 | DEVICE=eth0:0 | 290 | DEVICE=eth0 |
433 | 313 | GATEWAY=172.19.3.254 | 291 | GATEWAY=172.19.3.254 |
434 | 314 | HWADDR=fa:16:3e:ed:9a:59 | 292 | HWADDR=fa:16:3e:ed:9a:59 |
435 | 315 | IPADDR=172.19.1.34 | 293 | IPADDR=172.19.1.34 |
436 | 316 | NETMASK=255.255.252.0 | ||
437 | 317 | NM_CONTROLLED=no | ||
438 | 318 | ONBOOT=yes | ||
439 | 319 | TYPE=Ethernet | ||
440 | 320 | USERCTL=no | ||
441 | 321 | """.lstrip()), | ||
442 | 322 | ('etc/sysconfig/network-scripts/ifcfg-eth0:1', | ||
443 | 323 | """ | ||
444 | 324 | # Created by cloud-init on instance boot automatically, do not edit. | ||
445 | 325 | # | ||
446 | 326 | BOOTPROTO=static | ||
447 | 327 | DEFROUTE=yes | ||
448 | 328 | DEVICE=eth0:1 | ||
449 | 329 | HWADDR=fa:16:3e:ed:9a:59 | ||
450 | 330 | IPV6ADDR=2001:DB8::10 | 294 | IPV6ADDR=2001:DB8::10 |
451 | 295 | IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64" | ||
452 | 331 | IPV6INIT=yes | 296 | IPV6INIT=yes |
453 | 332 | IPV6_DEFAULTGW=2001:DB8::1 | 297 | IPV6_DEFAULTGW=2001:DB8::1 |
455 | 333 | NETMASK= | 298 | NETMASK=255.255.252.0 |
456 | 334 | NM_CONTROLLED=no | 299 | NM_CONTROLLED=no |
457 | 335 | ONBOOT=yes | 300 | ONBOOT=yes |
458 | 336 | TYPE=Ethernet | 301 | 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