Merge ~harald-jensas/cloud-init:bug/1847517 into cloud-init:master

Proposed by Harald Jensås
Status: Merged
Approved by: Ryan Harper
Approved revision: 4cd556d88a2660dd383bbff2664f08a77f915016
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~harald-jensas/cloud-init:bug/1847517
Merge into: cloud-init:master
Diff against target: 210 lines (+141/-3)
5 files modified
cloudinit/net/eni.py (+6/-1)
cloudinit/net/sysconfig.py (+6/-1)
cloudinit/sources/helpers/openstack.py (+2/-1)
tests/unittests/test_datasource/test_configdrive.py (+39/-0)
tests/unittests/test_net.py (+88/-0)
Reviewer Review Type Date Requested Status
Ryan Harper Approve
Server Team CI bot continuous-integration Approve
Review via email: mp+373932@code.launchpad.net

Commit message

net: handle openstack dhcpv6-stateless configuration

Openstack subnets can be configured to use SLAAC by setting
ipv6_address_mode=dhcpv6-stateless. When this is the case
the sysconfig interface configuration should use
IPV6_AUTOCONF=yes and not set DHCPV6C=yes.

This change sets the subnets type property to the full
network['type'] from openstack metadata.

cloudinit/net/sysconfig.py and cloudinit/net/eni.py
are updated to support new subnet types:
  - 'ipv6_dhcpv6-stateless' => IPV6_AUTOCONF=yes
  - 'ipv6_dhcpv6-stateful' => DHCPV6C=yes

Type 'dhcp6' in sysconfig is kept for backward compatibility
with any implementations that set subnet_type == 'dhcp6'.

LP: #1847517

To post a comment you must log in.
Revision history for this message
Scott Moser (smoser) wrote :

thanks for the well done bug report and merge proposal!

Please add a test to the unfortunately long and difficult to follow tests/unittests/test_net.py .

Also, as this is your first submission:

To contribute, you must sign the Canonical Contributor License Agreement
(CLA) [1].

If you have already signed it as an individual, your Launchpad user will
be listed in the contributor-agreement-canonical launchpad group [2].
Unfortunately there is no easy way to check if an organization or company
you are doing work for has signed. If you are unsure or have questions,
email <email address hidden> or ping powersj in #cloud-init channel
via freenode.

For information on how to sign, please see the HACKING document [3].

Thanks again, and please feel free to reach out with any questions.


[1] http://www.canonical.com/contributors
[2] https://launchpad.net/~contributor-agreement-canonical/+members
[3] http://cloudinit.readthedocs.io/en/latest/topics/hacking.html

Revision history for this message
Harald Jensås (harald-jensas) wrote :

> thanks for the well done bug report and merge proposal!
>
> Please add a test to the unfortunately long and difficult to follow
> tests/unittests/test_net.py .
>

Thanks, I did'nt find these tests in my first iteration.
I added tests in tests/unittests/test_datasource/test_configdrive.py and tests/unittests/test_net.py.

> Also, as this is your first submission:
>
> To contribute, you must sign the Canonical Contributor License Agreement
> (CLA) [1].
>

I've submitted the form.

Revision history for this message
Harald Jensås (harald-jensas) wrote :

Oh, I noticed now I missed the comment in code.

I will update this to check specifically for the values we expect and raise an error for other cases.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for working on this! The eni path needs fixing for stateless; see in-line comment.

review: Needs Fixing
Revision history for this message
Harald Jensås (harald-jensas) wrote :

> Thanks for working on this! The eni path needs fixing for stateless; see in-
> line comment.

Good catch Ryan! I've updated the patch to fix the eni path as well.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks! I've reworded the commit message, see above.
If you agree, feel free to add in your SoB to the commit
message box above if you like.

Revision history for this message
Harald Jensås (harald-jensas) wrote :

Thanks! The reworded commit message works for me.

Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:4cd556d88a2660dd383bbff2664f08a77f915016
https://jenkins.ubuntu.com/server/job/cloud-init-ci/1208/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Revision history for this message
Ryan Harper (raharper) wrote :

Thanks for this! It's ready to merge; just waiting for launchpad to add you to the CLA group and then we'll land.

Revision history for this message
Harald Jensås (harald-jensas) wrote :

fyi, I'm a member of the CLA group now.

Revision history for this message
Ryan Harper (raharper) wrote :

Thanks!

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
2index b129bb6..530922b 100644
3--- a/cloudinit/net/eni.py
4+++ b/cloudinit/net/eni.py
5@@ -411,8 +411,13 @@ class Renderer(renderer.Renderer):
6 else:
7 ipv4_subnet_mtu = subnet.get('mtu')
8 iface['inet'] = subnet_inet
9- if subnet['type'].startswith('dhcp'):
10+ if (subnet['type'] == 'dhcp4' or subnet['type'] == 'dhcp6' or
11+ subnet['type'] == 'ipv6_dhcpv6-stateful'):
12+ # Configure network settings using DHCP or DHCPv6
13 iface['mode'] = 'dhcp'
14+ elif subnet['type'] == 'ipv6_dhcpv6-stateless':
15+ # Configure network settings using SLAAC from RAs
16+ iface['mode'] = 'auto'
17
18 # do not emit multiple 'auto $IFACE' lines as older (precise)
19 # ifupdown complains
20diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
21index 87b548e..4e65676 100644
22--- a/cloudinit/net/sysconfig.py
23+++ b/cloudinit/net/sysconfig.py
24@@ -343,10 +343,15 @@ class Renderer(renderer.Renderer):
25 for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
26 mtu_key = 'MTU'
27 subnet_type = subnet.get('type')
28- if subnet_type == 'dhcp6':
29+ if subnet_type == 'dhcp6' or subnet_type == 'ipv6_dhcpv6-stateful':
30 # TODO need to set BOOTPROTO to dhcp6 on SUSE
31 iface_cfg['IPV6INIT'] = True
32+ # Configure network settings using DHCPv6
33 iface_cfg['DHCPV6C'] = True
34+ elif subnet_type == 'ipv6_dhcpv6-stateless':
35+ iface_cfg['IPV6INIT'] = True
36+ # Configure network settings using SLAAC from RAs
37+ iface_cfg['IPV6_AUTOCONF'] = True
38 elif subnet_type in ['dhcp4', 'dhcp']:
39 iface_cfg['BOOTPROTO'] = 'dhcp'
40 elif subnet_type == 'static':
41diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
42index 8f06911..d1c4601 100644
43--- a/cloudinit/sources/helpers/openstack.py
44+++ b/cloudinit/sources/helpers/openstack.py
45@@ -585,7 +585,8 @@ def convert_net_json(network_json=None, known_macs=None):
46 subnet = dict((k, v) for k, v in network.items()
47 if k in valid_keys['subnet'])
48 if 'dhcp' in network['type']:
49- t = 'dhcp6' if network['type'].startswith('ipv6') else 'dhcp4'
50+ t = (network['type'] if network['type'].startswith('ipv6')
51+ else 'dhcp4')
52 subnet.update({
53 'type': t,
54 })
55diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
56index 520c50f..8c788c1 100644
57--- a/tests/unittests/test_datasource/test_configdrive.py
58+++ b/tests/unittests/test_datasource/test_configdrive.py
59@@ -499,6 +499,45 @@ class TestNetJson(CiTestCase):
60 known_macs=KNOWN_MACS)
61 self.assertEqual(myds.network_config, network_config)
62
63+ def test_network_config_conversion_dhcp6(self):
64+ """Test some ipv6 input network json and check the expected
65+ conversions."""
66+ in_data = {
67+ 'links': [
68+ {'vif_id': '2ecc7709-b3f7-4448-9580-e1ec32d75bbd',
69+ 'ethernet_mac_address': 'fa:16:3e:69:b0:58',
70+ 'type': 'ovs', 'mtu': None, 'id': 'tap2ecc7709-b3'},
71+ {'vif_id': '2f88d109-5b57-40e6-af32-2472df09dc33',
72+ 'ethernet_mac_address': 'fa:16:3e:d4:57:ad',
73+ 'type': 'ovs', 'mtu': None, 'id': 'tap2f88d109-5b'},
74+ ],
75+ 'networks': [
76+ {'link': 'tap2ecc7709-b3', 'type': 'ipv6_dhcpv6-stateless',
77+ 'network_id': '6d6357ac-0f70-4afa-8bd7-c274cc4ea235',
78+ 'id': 'network0'},
79+ {'link': 'tap2f88d109-5b', 'type': 'ipv6_dhcpv6-stateful',
80+ 'network_id': 'd227a9b3-6960-4d94-8976-ee5788b44f54',
81+ 'id': 'network1'},
82+ ]
83+ }
84+ out_data = {
85+ 'version': 1,
86+ 'config': [
87+ {'mac_address': 'fa:16:3e:69:b0:58',
88+ 'mtu': None,
89+ 'name': 'enp0s1',
90+ 'subnets': [{'type': 'ipv6_dhcpv6-stateless'}],
91+ 'type': 'physical'},
92+ {'mac_address': 'fa:16:3e:d4:57:ad',
93+ 'mtu': None,
94+ 'name': 'enp0s2',
95+ 'subnets': [{'type': 'ipv6_dhcpv6-stateful'}],
96+ 'type': 'physical'}
97+ ],
98+ }
99+ conv_data = openstack.convert_net_json(in_data, known_macs=KNOWN_MACS)
100+ self.assertEqual(out_data, conv_data)
101+
102 def test_network_config_conversions(self):
103 """Tests a bunch of input network json and checks the
104 expected conversions."""
105diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
106index b659741..f5a9cae 100644
107--- a/tests/unittests/test_net.py
108+++ b/tests/unittests/test_net.py
109@@ -1070,6 +1070,82 @@ NETWORK_CONFIGS = {
110 """),
111 },
112 },
113+ 'dhcpv6_stateless': {
114+ 'expected_eni': textwrap.dedent("""\
115+ auto lo
116+ iface lo inet loopback
117+
118+ auto iface0
119+ iface iface0 inet6 auto
120+ """).rstrip(' '),
121+ 'expected_netplan': textwrap.dedent("""
122+ network:
123+ version: 2
124+ ethernets:
125+ iface0:
126+ dhcp6: true
127+ """).rstrip(' '),
128+ 'yaml': textwrap.dedent("""\
129+ version: 1
130+ config:
131+ - type: 'physical'
132+ name: 'iface0'
133+ subnets:
134+ - {'type': 'ipv6_dhcpv6-stateless'}
135+ """).rstrip(' '),
136+ 'expected_sysconfig': {
137+ 'ifcfg-iface0': textwrap.dedent("""\
138+ BOOTPROTO=none
139+ DEVICE=iface0
140+ IPV6_AUTOCONF=yes
141+ IPV6INIT=yes
142+ DEVICE=iface0
143+ NM_CONTROLLED=no
144+ ONBOOT=yes
145+ STARTMODE=auto
146+ TYPE=Ethernet
147+ USERCTL=no
148+ """),
149+ },
150+ },
151+ 'dhcpv6_stateful': {
152+ 'expected_eni': textwrap.dedent("""\
153+ auto lo
154+ iface lo inet loopback
155+
156+ auto iface0
157+ iface iface0 inet6 dhcp
158+ """).rstrip(' '),
159+ 'expected_netplan': textwrap.dedent("""
160+ network:
161+ version: 2
162+ ethernets:
163+ iface0:
164+ dhcp6: true
165+ """).rstrip(' '),
166+ 'yaml': textwrap.dedent("""\
167+ version: 1
168+ config:
169+ - type: 'physical'
170+ name: 'iface0'
171+ subnets:
172+ - {'type': 'ipv6_dhcpv6-stateful'}
173+ """).rstrip(' '),
174+ 'expected_sysconfig': {
175+ 'ifcfg-iface0': textwrap.dedent("""\
176+ BOOTPROTO=none
177+ DEVICE=iface0
178+ DHCPV6C=yes
179+ IPV6INIT=yes
180+ DEVICE=iface0
181+ NM_CONTROLLED=no
182+ ONBOOT=yes
183+ STARTMODE=auto
184+ TYPE=Ethernet
185+ USERCTL=no
186+ """),
187+ },
188+ },
189 'all': {
190 'expected_eni': ("""\
191 auto lo
192@@ -2781,6 +2857,18 @@ USERCTL=no
193 self._compare_files_to_expected(entry[self.expected_name], found)
194 self._assert_headers(found)
195
196+ def test_dhcpv6_stateless_config(self):
197+ entry = NETWORK_CONFIGS['dhcpv6_stateless']
198+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
199+ self._compare_files_to_expected(entry[self.expected_name], found)
200+ self._assert_headers(found)
201+
202+ def test_dhcpv6_stateful_config(self):
203+ entry = NETWORK_CONFIGS['dhcpv6_stateful']
204+ found = self._render_and_read(network_config=yaml.load(entry['yaml']))
205+ self._compare_files_to_expected(entry[self.expected_name], found)
206+ self._assert_headers(found)
207+
208 def test_check_ifcfg_rh(self):
209 """ifcfg-rh plugin is added NetworkManager.conf if conf present."""
210 render_dir = self.tmp_dir()

Subscribers

People subscribed via source and target branches