Merge ~rjschwei/cloud-init:setDefRoute into cloud-init:master

Proposed by Robert Schweikert
Status: Merged
Approved by: Dan Watkins
Approved revision: c7eece435f5a32b6ece9d7c01bcf0f1d0892d4af
Merge reported by: Server Team CI bot
Merged at revision: not available
Proposed branch: ~rjschwei/cloud-init:setDefRoute
Merge into: cloud-init:master
Diff against target: 273 lines (+118/-17)
3 files modified
cloudinit/net/network_state.py (+33/-8)
cloudinit/net/sysconfig.py (+22/-9)
tests/unittests/test_net.py (+63/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
Ryan Harper Approve
Dan Watkins Approve
Ryan Harper Pending
Review via email: mp+362020@code.launchpad.net

Description of the change

net/sysconfig: Handle default route setup for dhcp configured NICs

When the network configuration has a default route configured and
another network device that is configured with dhcp, SUSE sysconfig
output should not accept the default route provided by the dhcp
server.

LP: #1812117

To post a comment you must log in.
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:af3d1694fa54eb6d3ba66ae0c8e2ee5044284844
https://jenkins.ubuntu.com/server/job/cloud-init-ci/527/
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/527/rebuild

review: Approve (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

Apologies for the delay in getting to this! There are a couple of things inline that I think need to be reworked somewhat, as well as a few more minor improvements suggested.

review: Needs Fixing
Revision history for this message
Robert Schweikert (rjschwei) wrote :

modified the @property implementation as suggested to reduce overhead, "better" naming for method that determines if there is a default route.

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

PASSED: Continuous integration, rev:02b1d8585c803690e5a8571dd3e4b2422d2c4ab5
https://jenkins.ubuntu.com/server/job/cloud-init-ci/603/
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/603/rebuild

review: Approve (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

Hey Robert,

Thanks for the changes and your continued work on this. :)

On Wed, Feb 27, 2019 at 01:12:42AM -0000, Robert Schweikert wrote:
> > - def iter_routes(self, filter_func=None):
>
> Given that we have all the properties collected together, well the
> iterator interrupts the collection , while I was here I figured I'd do
> a little bit of
> "maintenance/cleanliness/code-smell/whatever-name-you-prefer" work.

Cool, thanks, just checking I hadn't missed anything. :)

> > + def _find_default_route(self):
>
> Well there is a naming pattern in use in the code base using "maybe"
> so this could be renamed to "_maybe_has_default_route" fine by me.

Yep, that works!

> > def render_network_state(self, network_state, templates=None, target=None):
> > + # Force the knowledge of a default route for the network state
> > + # into the renderer, this is needed to write the proper ifcfg-
> > + # on SUSE distros
> > + self.__class__._network_default_route = network_state.has_default_route
>
> That is not something I can answer. The decision to implement most of
> the internals of the renderer as "classmethod" was not mine.

Ah, I hadn't realised it was all classmethods, that's less than ideal.
That said, something like https://paste.ubuntu.com/p/SgnhHZkyKB/ is what
I had in mind here, and that works even given the fact we don't have an
instance to store data on.

Thanks!

Revision history for this message
Robert Schweikert (rjschwei) wrote :

OK, back to you, sorry for the delay. Modified the code according to https://paste.ubuntu.com/p/SgnhHZkyKB/ just a style topic as far as I am concerned.

Revision history for this message
Dan Watkins (oddbloke) wrote :

Thanks Robert! I absolutely disagree that it's just a style topic, though; arbitrarily setting class-level attributes outside of the class definition makes the code much harder to reason about.

review: Approve
Revision history for this message
Ryan Harper (raharper) :
review: Approve
Revision history for this message
Server Team CI bot (server-team-bot) wrote :

PASSED: Continuous integration, rev:15205e8382e2959a3b11fa8c721c810a949a0539
https://jenkins.ubuntu.com/server/job/cloud-init-ci/632/
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/632/rebuild

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

FAILED: Autolanding.
More details in the following jenkins job:
https://jenkins.ubuntu.com/server/job/cloud-init-autoland-test/186/
Executed test runs:
    SUCCESS: Checkout
    FAILED: Unit & Style Tests

review: Needs Fixing (continuous-integration)
Revision history for this message
Dan Watkins (oddbloke) wrote :

Robert, it looks like your tests are no longer correct due to modifications to master; could you rebase onto/merge from master and re-push, please?

~rjschwei/cloud-init:setDefRoute updated
c7eece4... by Robert Schweikert

- Rebase and fix tests

Revision history for this message
Robert Schweikert (rjschwei) wrote :

Should be all better now

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

PASSED: Continuous integration, rev:c7eece435f5a32b6ece9d7c01bcf0f1d0892d4af
https://jenkins.ubuntu.com/server/job/cloud-init-ci/637/
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/637/rebuild

review: Approve (continuous-integration)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
2index 539b76d..4d19f56 100644
3--- a/cloudinit/net/network_state.py
4+++ b/cloudinit/net/network_state.py
5@@ -148,6 +148,7 @@ class NetworkState(object):
6 self._network_state = copy.deepcopy(network_state)
7 self._version = version
8 self.use_ipv6 = network_state.get('use_ipv6', False)
9+ self._has_default_route = None
10
11 @property
12 def config(self):
13@@ -157,14 +158,6 @@ class NetworkState(object):
14 def version(self):
15 return self._version
16
17- def iter_routes(self, filter_func=None):
18- for route in self._network_state.get('routes', []):
19- if filter_func is not None:
20- if filter_func(route):
21- yield route
22- else:
23- yield route
24-
25 @property
26 def dns_nameservers(self):
27 try:
28@@ -179,6 +172,12 @@ class NetworkState(object):
29 except KeyError:
30 return []
31
32+ @property
33+ def has_default_route(self):
34+ if self._has_default_route is None:
35+ self._has_default_route = self._maybe_has_default_route()
36+ return self._has_default_route
37+
38 def iter_interfaces(self, filter_func=None):
39 ifaces = self._network_state.get('interfaces', {})
40 for iface in six.itervalues(ifaces):
41@@ -188,6 +187,32 @@ class NetworkState(object):
42 if filter_func(iface):
43 yield iface
44
45+ def iter_routes(self, filter_func=None):
46+ for route in self._network_state.get('routes', []):
47+ if filter_func is not None:
48+ if filter_func(route):
49+ yield route
50+ else:
51+ yield route
52+
53+ def _maybe_has_default_route(self):
54+ for route in self.iter_routes():
55+ if self._is_default_route(route):
56+ return True
57+ for iface in self.iter_interfaces():
58+ for subnet in iface.get('subnets', []):
59+ for route in subnet.get('routes', []):
60+ if self._is_default_route(route):
61+ return True
62+ return False
63+
64+ def _is_default_route(self, route):
65+ default_nets = ('::', '0.0.0.0')
66+ return (
67+ route.get('prefix') == 0
68+ and route.get('network') in default_nets
69+ )
70+
71
72 @six.add_metaclass(CommandHandlerMeta)
73 class NetworkStateInterpreter(object):
74diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
75index 19b3e60..e59753d 100644
76--- a/cloudinit/net/sysconfig.py
77+++ b/cloudinit/net/sysconfig.py
78@@ -322,7 +322,7 @@ class Renderer(renderer.Renderer):
79 iface_cfg[new_key] = old_value
80
81 @classmethod
82- def _render_subnets(cls, iface_cfg, subnets):
83+ def _render_subnets(cls, iface_cfg, subnets, has_default_route):
84 # setting base values
85 iface_cfg['BOOTPROTO'] = 'none'
86
87@@ -331,6 +331,7 @@ class Renderer(renderer.Renderer):
88 mtu_key = 'MTU'
89 subnet_type = subnet.get('type')
90 if subnet_type == 'dhcp6':
91+ # TODO need to set BOOTPROTO to dhcp6 on SUSE
92 iface_cfg['IPV6INIT'] = True
93 iface_cfg['DHCPV6C'] = True
94 elif subnet_type in ['dhcp4', 'dhcp']:
95@@ -375,9 +376,9 @@ class Renderer(renderer.Renderer):
96 ipv6_index = -1
97 for i, subnet in enumerate(subnets, start=len(iface_cfg.children)):
98 subnet_type = subnet.get('type')
99- if subnet_type == 'dhcp6':
100- continue
101- elif subnet_type in ['dhcp4', 'dhcp']:
102+ if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']:
103+ if has_default_route and iface_cfg['BOOTPROTO'] != 'none':
104+ iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False
105 continue
106 elif subnet_type == 'static':
107 if subnet_is_ipv6(subnet):
108@@ -443,6 +444,8 @@ class Renderer(renderer.Renderer):
109 # TODO(harlowja): add validation that no other iface has
110 # also provided the default route?
111 iface_cfg['DEFROUTE'] = True
112+ if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'):
113+ iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True
114 if 'gateway' in route:
115 if is_ipv6 or is_ipv6_addr(route['gateway']):
116 iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
117@@ -493,7 +496,9 @@ class Renderer(renderer.Renderer):
118 iface_cfg = iface_contents[iface_name]
119 route_cfg = iface_cfg.routes
120
121- cls._render_subnets(iface_cfg, iface_subnets)
122+ cls._render_subnets(
123+ iface_cfg, iface_subnets, network_state.has_default_route
124+ )
125 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
126
127 @classmethod
128@@ -518,7 +523,9 @@ class Renderer(renderer.Renderer):
129
130 iface_subnets = iface.get("subnets", [])
131 route_cfg = iface_cfg.routes
132- cls._render_subnets(iface_cfg, iface_subnets)
133+ cls._render_subnets(
134+ iface_cfg, iface_subnets, network_state.has_default_route
135+ )
136 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
137
138 # iter_interfaces on network-state is not sorted to produce
139@@ -547,7 +554,9 @@ class Renderer(renderer.Renderer):
140
141 iface_subnets = iface.get("subnets", [])
142 route_cfg = iface_cfg.routes
143- cls._render_subnets(iface_cfg, iface_subnets)
144+ cls._render_subnets(
145+ iface_cfg, iface_subnets, network_state.has_default_route
146+ )
147 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
148
149 @staticmethod
150@@ -608,7 +617,9 @@ class Renderer(renderer.Renderer):
151
152 iface_subnets = iface.get("subnets", [])
153 route_cfg = iface_cfg.routes
154- cls._render_subnets(iface_cfg, iface_subnets)
155+ cls._render_subnets(
156+ iface_cfg, iface_subnets, network_state.has_default_route
157+ )
158 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
159
160 @classmethod
161@@ -620,7 +631,9 @@ class Renderer(renderer.Renderer):
162 iface_cfg.kind = 'infiniband'
163 iface_subnets = iface.get("subnets", [])
164 route_cfg = iface_cfg.routes
165- cls._render_subnets(iface_cfg, iface_subnets)
166+ cls._render_subnets(
167+ iface_cfg, iface_subnets, network_state.has_default_route
168+ )
169 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
170
171 @classmethod
172diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
173index e3b9e02..468d544 100644
174--- a/tests/unittests/test_net.py
175+++ b/tests/unittests/test_net.py
176@@ -860,6 +860,7 @@ NETWORK_CONFIGS = {
177 BOOTPROTO=dhcp
178 DEFROUTE=yes
179 DEVICE=eth99
180+ DHCLIENT_SET_DEFAULT_ROUTE=yes
181 DNS1=8.8.8.8
182 DNS2=8.8.4.4
183 DOMAIN="barley.maas sach.maas"
184@@ -1234,6 +1235,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
185 'ifcfg-bond0.200': textwrap.dedent("""\
186 BOOTPROTO=dhcp
187 DEVICE=bond0.200
188+ DHCLIENT_SET_DEFAULT_ROUTE=no
189 NM_CONTROLLED=no
190 ONBOOT=yes
191 PHYSDEV=bond0
192@@ -1333,6 +1335,7 @@ pre-down route del -net 10.0.0.0 netmask 255.0.0.0 gw 11.0.0.1 metric 3 || true
193 'ifcfg-eth5': textwrap.dedent("""\
194 BOOTPROTO=dhcp
195 DEVICE=eth5
196+ DHCLIENT_SET_DEFAULT_ROUTE=no
197 HWADDR=98:bb:9f:2c:e8:8a
198 NM_CONTROLLED=no
199 ONBOOT=no
200@@ -1988,6 +1991,23 @@ CONFIG_V1_SIMPLE_SUBNET = {
201 'type': 'static'}],
202 'type': 'physical'}]}
203
204+CONFIG_V1_MULTI_IFACE = {
205+ 'version': 1,
206+ 'config': [{'type': 'physical',
207+ 'mtu': 1500,
208+ 'subnets': [{'type': 'static',
209+ 'netmask': '255.255.240.0',
210+ 'routes': [{'netmask': '0.0.0.0',
211+ 'network': '0.0.0.0',
212+ 'gateway': '51.68.80.1'}],
213+ 'address': '51.68.89.122',
214+ 'ipv4': True}],
215+ 'mac_address': 'fa:16:3e:25:b4:59',
216+ 'name': 'eth0'},
217+ {'type': 'physical',
218+ 'mtu': 9000,
219+ 'subnets': [{'type': 'dhcp4'}],
220+ 'mac_address': 'fa:16:3e:b1:ca:29', 'name': 'eth1'}]}
221
222 DEFAULT_DEV_ATTRS = {
223 'eth1000': {
224@@ -2460,6 +2480,49 @@ USERCTL=no
225 respath = '/etc/resolv.conf'
226 self.assertNotIn(respath, found.keys())
227
228+ def test_network_config_v1_multi_iface_samples(self):
229+ ns = network_state.parse_net_config_data(CONFIG_V1_MULTI_IFACE)
230+ render_dir = self.tmp_path("render")
231+ os.makedirs(render_dir)
232+ renderer = self._get_renderer()
233+ renderer.render_network_state(ns, target=render_dir)
234+ found = dir2dict(render_dir)
235+ nspath = '/etc/sysconfig/network-scripts/'
236+ self.assertNotIn(nspath + 'ifcfg-lo', found.keys())
237+ expected_i1 = """\
238+# Created by cloud-init on instance boot automatically, do not edit.
239+#
240+BOOTPROTO=none
241+DEFROUTE=yes
242+DEVICE=eth0
243+GATEWAY=51.68.80.1
244+HWADDR=fa:16:3e:25:b4:59
245+IPADDR=51.68.89.122
246+MTU=1500
247+NETMASK=255.255.240.0
248+NM_CONTROLLED=no
249+ONBOOT=yes
250+STARTMODE=auto
251+TYPE=Ethernet
252+USERCTL=no
253+"""
254+ self.assertEqual(expected_i1, found[nspath + 'ifcfg-eth0'])
255+ expected_i2 = """\
256+# Created by cloud-init on instance boot automatically, do not edit.
257+#
258+BOOTPROTO=dhcp
259+DEVICE=eth1
260+DHCLIENT_SET_DEFAULT_ROUTE=no
261+HWADDR=fa:16:3e:b1:ca:29
262+MTU=9000
263+NM_CONTROLLED=no
264+ONBOOT=yes
265+STARTMODE=auto
266+TYPE=Ethernet
267+USERCTL=no
268+"""
269+ self.assertEqual(expected_i2, found[nspath + 'ifcfg-eth1'])
270+
271 def test_config_with_explicit_loopback(self):
272 ns = network_state.parse_net_config_data(CONFIG_V1_EXPLICIT_LOOPBACK)
273 render_dir = self.tmp_path("render")

Subscribers

People subscribed via source and target branches