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

Proposed by Andreas Karis
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)
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

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

To post a comment you must log in.
Revision history for this message
Andreas Karis (akaris) wrote : Posted in a previous version of this proposal

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

Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal

FAILED: Continuous integration, rev:1a401c57978855b5d56ac23976252e2506d206fa
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://code.launchpad.net/~akaris/cloud-init/+git/cloud-init/+merge/324195/+edit-commit-message

https://jenkins.ubuntu.com/server/job/cloud-init-ci/345/
Executed test runs:
    SUCCESS: https://jenkins.ubuntu.com/server/job/cloud-init-ci/nodes=metal-amd64/345
    SUCCESS: https://jenkins.ubuntu.com/server/job/cloud-init-ci/nodes=metal-arm64/345
    SUCCESS: https://jenkins.ubuntu.com/server/job/cloud-init-ci/nodes=metal-ppc64el/345
    SUCCESS: https://jenkins.ubuntu.com/server/job/cloud-init-ci/nodes=vm-i386/345

Click here to trigger a rebuild:
https://jenkins.ubuntu.com/server/job/cloud-init-ci/345/rebuild

review: Needs Fixing (continuous-integration)
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
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 ?

review: Needs Information
Revision history for this message
Server Team CI bot (server-team-bot) wrote :
review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

Assuming this works with centos 5, 6, 7, I'm happy to pull this.

Revision history for this message
Andreas Karis (akaris) wrote :

Hi,

Let me test all of this out once more and give a final ack.

- Andreas

Revision history for this message
Andreas Karis (akaris) wrote :
Download full text (12.3 KiB)

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_network_template /usr/lib/python2.7/site-packages/nova/virt/interfaces.template
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:network_type vlan --provider:physical_network $PROVIDER_PHYSICAL_NETWORK --provider:segmentation_id $PROVIDER_SEGMENTATION_ID --shared --router:external
neutron subnet-create --gateway 10.0.0.1 --allocation-pool start=10.0.0.100,end=10.0.0.150 --dns-nameserver 8.8.8.8 --name provider1-subnet provider1 10.0.0.0/24
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:168:200::/64
neutron subnet-create --disable-dhcp --ip-version 6 private-no-dhcp-2 2000:192:168:201::/64
neutron subnet-create --disable-dhcp --ip-version 6 --gateway 2000:192:168:202::1 private-no-dhcp-3 2000:192:168:202::/64
neutr...

Revision history for this message
Andreas Karis (akaris) wrote :

One issue that I have with this still is the dup default route generation for IPv6:

[root@rhel-cloud-init-patch-test network-scripts]# ip r
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-cloud-init-patch-test network-scripts]# ip -6 r
unreachable ::/96 dev lo metric 1024 error -113
unreachable ::ffff:0.0.0.0/96 dev lo metric 1024 error -113
2000:192:168:201::/64 dev eth2 proto kernel metric 256
2000:192:168:204::/64 dev eth3 proto kernel metric 256
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.

Revision history for this message
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

Revision history for this message
Andreas Karis (akaris) wrote :

RHEL 6:
~~~
# make sure to have rhel-guest-image-6.10-23.x86_64.qcow2
# make sure to have python-crypto-2.6.1-1.el6ost.x86_64.rpm
# make sure to have python-oauthlib-0.6.0-3.el6ost.noarch.rpm
virt-customize -a rhel-guest-image-6.10-23.x86_64.qcow2 --root-password password:Redhat01
for i in *el6ost*.rpm;do virt-customize -a rhel6-cloud-init.patch.qcow2 --upload $i:/root/$i ; done
virt-customize -a rhel6-cloud-init.patch.qcow2 -v --run-command 'yum -y localinstall /root/*.rpm'
for i in cloudinit.0.7.9.patch.tar.gz ; do virt-customize -a rhel6-cloud-init.patch.qcow2 --upload $i:/root/$i; done
virt-customize -a rhel6-cloud-init.patch.qcow2 -v --run-command 'rm -Rf /usr/lib/python2.6/site-packages/cloudinit ; tar -xzf /root/cloudinit.0.7.9.patch.tar.gz -C /usr/lib/python2.6/site-packages/'
glance image-create --name rhel6-cloud-init --file rhel6-cloud-init.patch.qcow2 --container-format bare --disk-format qcow2 --progress
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.

Revision history for this message
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.

Revision history for this message
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.

Revision history for this message
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://code.launchpad.net/~akaris/cloud-init/+git/cloud-init/+merge/324196
>You are reviewing the proposed merge of ~akaris/cloud-init:bug1679817-c
>into cloud-init:master.

Revision history for this message
Andreas Karis (akaris) wrote :

Hi,

I think we can at least forget about CentOS/RHEL 5:
https://wiki.centos.org/FAQ/General#head-fe8a0be91ee3e7dea812e8694491e1dde5b75e6d
https://access.redhat.com/support/policy/updates/errata

They went both end of production phase 3 at March 31, 2017

Remains testing for 6 to do

- Andreas

Revision history for this message
Scott Moser (smoser) wrote :

Andreas,
thanks for the RHEL 5 info, II agree.

Revision history for this message
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://git.launchpad.net/~akaris/cloud-init and checked out the "bug1679817-c" branch
- installed via "python setup.py install --init-system sysvinit"

Testing:

- rm -rf /etc/sysconfig/network-scripts/ifcfg-eth* /etc/resolv.conf /var/lib/cloud/* /var/log/cloud*
- cloud-init init --local
- cloud-init init

At this point, /etc/resolv.conf and /etc/sysconfig/network-scripts/ifcfg-eth0 are generated and correct, and interfaces are up and configured.

As akaris indicated, I don't think it makes sense to worry about RHEL5 given its current lifecycle stage.

Revision history for this message
Scott Moser (smoser) wrote :

Thanks. I'll pull this.

review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote : Posted in a previous version of this proposal
review: Approve (continuous-integration)
Revision history for this message
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://code.launchpad.net/~akaris/cloud-init/+git/cloud-init/+merge/324196)
Please move back to 'Needs Review' (and explain) if you think otherwise.

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 d981277..58c5713 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,9 +106,58 @@ 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,
107 'name': self._route_name})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,
116 'name': self._route_name})
117
118 def is_ipv6_route(self, address):
119 return ':' in address
120
121 def to_string(self, proto="ipv4"):
122 # only accept ipv4 and ipv6
123 if proto not in ['ipv4', 'ipv6']:
124 raise ValueError("Unknown protocol '%s'" % (str(proto)))
125 buf = six.StringIO()
126 buf.write(_make_header())
127 if self._conf:
128 buf.write("\n")
129 # need to reindex IPv4 addresses
130 # (because Route can contain a mix of IPv4 and IPv6)
131 reindex = -1
132 for key in sorted(self._conf.keys()):
133 if 'ADDRESS' in key:
134 index = key.replace('ADDRESS', '')
135 address_value = str(self._conf[key])
136 # only accept combinations:
137 # if proto ipv6 only display ipv6 routes
138 # if proto ipv4 only display ipv4 routes
139 # do not add ipv6 routes if proto is ipv4
140 # do not add ipv4 routes if proto is ipv6
141 # (this array will contain a mix of ipv4 and ipv6)
142 if proto == "ipv4" and not self.is_ipv6_route(address_value):
143 netmask_value = str(self._conf['NETMASK' + index])
144 gateway_value = str(self._conf['GATEWAY' + index])
145 # increase IPv4 index
146 reindex = reindex + 1
147 buf.write("%s=%s\n" % ('ADDRESS' + str(reindex),
148 _quote_value(address_value)))
149 buf.write("%s=%s\n" % ('GATEWAY' + str(reindex),
150 _quote_value(gateway_value)))
151 buf.write("%s=%s\n" % ('NETMASK' + str(reindex),
152 _quote_value(netmask_value)))
153 elif proto == "ipv6" and self.is_ipv6_route(address_value):
154 netmask_value = str(self._conf['NETMASK' + index])
155 gateway_value = str(self._conf['GATEWAY' + index])
156 buf.write("%s/%s via %s\n" % (address_value,
157 netmask_value,
158 gateway_value))
159
160 return buf.getvalue()
108161
109162
110class NetInterface(ConfigMap):163class NetInterface(ConfigMap):
@@ -211,65 +264,119 @@ 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 sysconfig.txt -A2 | head -3
282 # BOOTPROTO=none|bootp|dhcp
283 # 'bootp' or 'dhcp' cause a DHCP client
284 # to run on the device. Any other
285 # value causes any static configuration
286 # in the file to be applied.
287 # ==> the following should not be set to 'static'
288 # but should remain 'none'
289 # if iface_cfg['BOOTPROTO'] == 'none':
290 # iface_cfg['BOOTPROTO'] = 'static'
291 if subnet_is_ipv6(subnet):
292 iface_cfg['IPV6INIT'] = True
227 else:293 else:
228 iface_cfg['IPADDR'] = subnet['address']294 raise ValueError("Unknown subnet type '%s' found"
229 else:295 " for interface '%s'" % (subnet_type,
230 raise ValueError("Unknown subnet type '%s' found"296 iface_cfg.name))
231 " for interface '%s'" % (subnet_type,297
232 iface_cfg.name))298 # set IPv4 and IPv6 static addresses
233 if 'netmask' in subnet:299 ipv4_index = -1
234 iface_cfg['NETMASK'] = subnet['netmask']300 ipv6_index = -1
235 is_ipv6 = subnet.get('ipv6')301 for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
236 for route in subnet.get('routes', []):302 subnet_type = subnet.get('type')
237 if _is_default_route(route):303 if subnet_type == 'dhcp6':
238 if (304 continue
239 (subnet.get('ipv4') and305 elif subnet_type in ['dhcp4', 'dhcp']:
240 route_cfg.has_set_default_ipv4) or306 continue
241 (subnet.get('ipv6') and307 elif subnet_type == 'static':
242 route_cfg.has_set_default_ipv6)308 if subnet_is_ipv6(subnet):
243 ):309 ipv6_index = ipv6_index + 1
244 raise ValueError("Duplicate declaration of default "310 if 'netmask' in subnet and str(subnet['netmask']) != "":
245 "route found for interface '%s'"311 ipv6_cidr = (subnet['address'] +
246 % (iface_cfg.name))312 '/' +
247 # NOTE(harlowja): ipv6 and ipv4 default gateways313 str(subnet['netmask']))
248 gw_key = 'GATEWAY0'
249 nm_key = 'NETMASK0'
250 addr_key = 'ADDRESS0'
251 # The owning interface provides the default route.
252 #
253 # TODO(harlowja): add validation that no other iface has
254 # also provided the default route?
255 iface_cfg['DEFROUTE'] = True
256 if 'gateway' in route:
257 if is_ipv6:
258 iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
259 route_cfg.has_set_default_ipv6 = True
260 else:314 else:
261 iface_cfg['GATEWAY'] = route['gateway']315 ipv6_cidr = subnet['address']
262 route_cfg.has_set_default_ipv4 = True316 if ipv6_index == 0:
263 else:317 iface_cfg['IPV6ADDR'] = ipv6_cidr
264 gw_key = 'GATEWAY%s' % route_cfg.last_idx318 elif ipv6_index == 1:
265 nm_key = 'NETMASK%s' % route_cfg.last_idx319 iface_cfg['IPV6ADDR_SECONDARIES'] = ipv6_cidr
266 addr_key = 'ADDRESS%s' % route_cfg.last_idx320 else:
267 route_cfg.last_idx += 1321 iface_cfg['IPV6ADDR_SECONDARIES'] = (
268 for (old_key, new_key) in [('gateway', gw_key),322 iface_cfg['IPV6ADDR_SECONDARIES'] +
269 ('netmask', nm_key),323 " " + ipv6_cidr)
270 ('network', addr_key)]:324 else:
271 if old_key in route:325 ipv4_index = ipv4_index + 1
272 route_cfg[new_key] = route[old_key]326 if ipv4_index == 0:
327 iface_cfg['IPADDR'] = subnet['address']
328 if 'netmask' in subnet:
329 iface_cfg['NETMASK'] = subnet['netmask']
330 else:
331 iface_cfg['IPADDR' + str(ipv4_index)] = \
332 subnet['address']
333 if 'netmask' in subnet:
334 iface_cfg['NETMASK' + str(ipv4_index)] = \
335 subnet['netmask']
336
337 @classmethod
338 def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
339 for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
340 for route in subnet.get('routes', []):
341 is_ipv6 = subnet.get('ipv6')
342
343 if _is_default_route(route):
344 if (
345 (subnet.get('ipv4') and
346 route_cfg.has_set_default_ipv4) or
347 (subnet.get('ipv6') and
348 route_cfg.has_set_default_ipv6)
349 ):
350 raise ValueError("Duplicate declaration of default "
351 "route found for interface '%s'"
352 % (iface_cfg.name))
353 # NOTE(harlowja): ipv6 and ipv4 default gateways
354 gw_key = 'GATEWAY0'
355 nm_key = 'NETMASK0'
356 addr_key = 'ADDRESS0'
357 # The owning interface provides the default route.
358 #
359 # TODO(harlowja): add validation that no other iface has
360 # also provided the default route?
361 iface_cfg['DEFROUTE'] = True
362 if 'gateway' in route:
363 if is_ipv6:
364 iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
365 route_cfg.has_set_default_ipv6 = True
366 else:
367 iface_cfg['GATEWAY'] = route['gateway']
368 route_cfg.has_set_default_ipv4 = True
369
370 else:
371 gw_key = 'GATEWAY%s' % route_cfg.last_idx
372 nm_key = 'NETMASK%s' % route_cfg.last_idx
373 addr_key = 'ADDRESS%s' % route_cfg.last_idx
374 route_cfg.last_idx += 1
375 for (old_key, new_key) in [('gateway', gw_key),
376 ('netmask', nm_key),
377 ('network', addr_key)]:
378 if old_key in route:
379 route_cfg[new_key] = route[old_key]
273380
274 @classmethod381 @classmethod
275 def _render_bonding_opts(cls, iface_cfg, iface):382 def _render_bonding_opts(cls, iface_cfg, iface):
@@ -295,15 +402,9 @@ class Renderer(renderer.Renderer):
295 iface_subnets = iface.get("subnets", [])402 iface_subnets = iface.get("subnets", [])
296 iface_cfg = iface_contents[iface_name]403 iface_cfg = iface_contents[iface_name]
297 route_cfg = iface_cfg.routes404 route_cfg = iface_cfg.routes
298 if len(iface_subnets) == 1:405
299 cls._render_subnet(iface_cfg, route_cfg, iface_subnets[0])406 cls._render_subnets(iface_cfg, iface_subnets)
300 elif len(iface_subnets) > 1:407 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)
307408
308 @classmethod409 @classmethod
309 def _render_bond_interfaces(cls, network_state, iface_contents):410 def _render_bond_interfaces(cls, network_state, iface_contents):
@@ -387,7 +488,10 @@ class Renderer(renderer.Renderer):
387 if iface_cfg:488 if iface_cfg:
388 contents[iface_cfg.path] = iface_cfg.to_string()489 contents[iface_cfg.path] = iface_cfg.to_string()
389 if iface_cfg.routes:490 if iface_cfg.routes:
390 contents[iface_cfg.routes.path] = iface_cfg.routes.to_string()491 contents[iface_cfg.routes.path_ipv4] = \
492 iface_cfg.routes.to_string("ipv4")
493 contents[iface_cfg.routes.path_ipv6] = \
494 iface_cfg.routes.to_string("ipv6")
391 return contents495 return contents
392496
393 def render_network_state(self, network_state, target=None):497 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 1e10a33..fd7c051 100644
--- a/tests/unittests/test_distros/test_netconfig.py
+++ b/tests/unittests/test_distros/test_netconfig.py
@@ -476,7 +476,7 @@ NETWORKING=yes
476 expected_buf = '''476 expected_buf = '''
477# Created by cloud-init on instance boot automatically, do not edit.477# Created by cloud-init on instance boot automatically, do not edit.
478#478#
479BOOTPROTO=static479BOOTPROTO=none
480DEVICE=eth0480DEVICE=eth0
481IPADDR=192.168.1.5481IPADDR=192.168.1.5
482NETMASK=255.255.255.0482NETMASK=255.255.255.0
@@ -533,7 +533,6 @@ NETWORKING=yes
533 mock.patch.object(util, 'load_file', return_value=''))533 mock.patch.object(util, 'load_file', return_value=''))
534 mocks.enter_context(534 mocks.enter_context(
535 mock.patch.object(os.path, 'isfile', return_value=False))535 mock.patch.object(os.path, 'isfile', return_value=False))
536
537 rh_distro.apply_network(BASE_NET_CFG_IPV6, False)536 rh_distro.apply_network(BASE_NET_CFG_IPV6, False)
538537
539 self.assertEqual(len(write_bufs), 4)538 self.assertEqual(len(write_bufs), 4)
@@ -626,11 +625,10 @@ IPV6_AUTOCONF=no
626 expected_buf = '''625 expected_buf = '''
627# Created by cloud-init on instance boot automatically, do not edit.626# Created by cloud-init on instance boot automatically, do not edit.
628#627#
629BOOTPROTO=static628BOOTPROTO=none
630DEVICE=eth0629DEVICE=eth0
631IPV6ADDR=2607:f0d0:1002:0011::2630IPV6ADDR=2607:f0d0:1002:0011::2/64
632IPV6INIT=yes631IPV6INIT=yes
633NETMASK=64
634NM_CONTROLLED=no632NM_CONTROLLED=no
635ONBOOT=yes633ONBOOT=yes
636TYPE=Ethernet634TYPE=Ethernet
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index d36d0e7..cf4cedc 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -137,7 +137,7 @@ OS_SAMPLES = [
137 """137 """
138# Created by cloud-init on instance boot automatically, do not edit.138# Created by cloud-init on instance boot automatically, do not edit.
139#139#
140BOOTPROTO=static140BOOTPROTO=none
141DEFROUTE=yes141DEFROUTE=yes
142DEVICE=eth0142DEVICE=eth0
143GATEWAY=172.19.3.254143GATEWAY=172.19.3.254
@@ -205,38 +205,14 @@ nameserver 172.19.0.12
205# Created by cloud-init on instance boot automatically, do not edit.205# Created by cloud-init on instance boot automatically, do not edit.
206#206#
207BOOTPROTO=none207BOOTPROTO=none
208DEVICE=eth0
209HWADDR=fa:16:3e:ed:9a:59
210NM_CONTROLLED=no
211ONBOOT=yes
212TYPE=Ethernet
213USERCTL=no
214""".lstrip()),
215 ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
216 """
217# Created by cloud-init on instance boot automatically, do not edit.
218#
219BOOTPROTO=static
220DEFROUTE=yes208DEFROUTE=yes
221DEVICE=eth0:0209DEVICE=eth0
222GATEWAY=172.19.3.254210GATEWAY=172.19.3.254
223HWADDR=fa:16:3e:ed:9a:59211HWADDR=fa:16:3e:ed:9a:59
224IPADDR=172.19.1.34212IPADDR=172.19.1.34
213IPADDR1=10.0.0.10
225NETMASK=255.255.252.0214NETMASK=255.255.252.0
226NM_CONTROLLED=no215NETMASK1=255.255.255.0
227ONBOOT=yes
228TYPE=Ethernet
229USERCTL=no
230""".lstrip()),
231 ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
232 """
233# Created by cloud-init on instance boot automatically, do not edit.
234#
235BOOTPROTO=static
236DEVICE=eth0:1
237HWADDR=fa:16:3e:ed:9a:59
238IPADDR=10.0.0.10
239NETMASK=255.255.255.0
240NM_CONTROLLED=no216NM_CONTROLLED=no
241ONBOOT=yes217ONBOOT=yes
242TYPE=Ethernet218TYPE=Ethernet
@@ -266,7 +242,7 @@ nameserver 172.19.0.12
266 }],242 }],
267 "ip_address": "172.19.1.34", "id": "network0"243 "ip_address": "172.19.1.34", "id": "network0"
268 }, {244 }, {
269 "network_id": "public-ipv6",245 "network_id": "public-ipv6-a",
270 "type": "ipv6", "netmask": "",246 "type": "ipv6", "netmask": "",
271 "link": "tap1a81968a-79",247 "link": "tap1a81968a-79",
272 "routes": [248 "routes": [
@@ -277,6 +253,20 @@ nameserver 172.19.0.12
277 }253 }
278 ],254 ],
279 "ip_address": "2001:DB8::10", "id": "network1"255 "ip_address": "2001:DB8::10", "id": "network1"
256 }, {
257 "network_id": "public-ipv6-b",
258 "type": "ipv6", "netmask": "64",
259 "link": "tap1a81968a-79",
260 "routes": [
261 ],
262 "ip_address": "2001:DB9::10", "id": "network2"
263 }, {
264 "network_id": "public-ipv6-c",
265 "type": "ipv6", "netmask": "64",
266 "link": "tap1a81968a-79",
267 "routes": [
268 ],
269 "ip_address": "2001:DB10::10", "id": "network3"
280 }],270 }],
281 "links": [271 "links": [
282 {272 {
@@ -296,41 +286,16 @@ nameserver 172.19.0.12
296# Created by cloud-init on instance boot automatically, do not edit.286# Created by cloud-init on instance boot automatically, do not edit.
297#287#
298BOOTPROTO=none288BOOTPROTO=none
299DEVICE=eth0
300HWADDR=fa:16:3e:ed:9a:59
301NM_CONTROLLED=no
302ONBOOT=yes
303TYPE=Ethernet
304USERCTL=no
305""".lstrip()),
306 ('etc/sysconfig/network-scripts/ifcfg-eth0:0',
307 """
308# Created by cloud-init on instance boot automatically, do not edit.
309#
310BOOTPROTO=static
311DEFROUTE=yes289DEFROUTE=yes
312DEVICE=eth0:0290DEVICE=eth0
313GATEWAY=172.19.3.254291GATEWAY=172.19.3.254
314HWADDR=fa:16:3e:ed:9a:59292HWADDR=fa:16:3e:ed:9a:59
315IPADDR=172.19.1.34293IPADDR=172.19.1.34
316NETMASK=255.255.252.0
317NM_CONTROLLED=no
318ONBOOT=yes
319TYPE=Ethernet
320USERCTL=no
321""".lstrip()),
322 ('etc/sysconfig/network-scripts/ifcfg-eth0:1',
323 """
324# Created by cloud-init on instance boot automatically, do not edit.
325#
326BOOTPROTO=static
327DEFROUTE=yes
328DEVICE=eth0:1
329HWADDR=fa:16:3e:ed:9a:59
330IPV6ADDR=2001:DB8::10294IPV6ADDR=2001:DB8::10
295IPV6ADDR_SECONDARIES="2001:DB9::10/64 2001:DB10::10/64"
331IPV6INIT=yes296IPV6INIT=yes
332IPV6_DEFAULTGW=2001:DB8::1297IPV6_DEFAULTGW=2001:DB8::1
333NETMASK=298NETMASK=255.255.252.0
334NM_CONTROLLED=no299NM_CONTROLLED=no
335ONBOOT=yes300ONBOOT=yes
336TYPE=Ethernet301TYPE=Ethernet

Subscribers

People subscribed via source and target branches