Merge ~raharper/cloud-init:sysconfig-handle-global-static-routes into cloud-init:master

Proposed by Ryan Harper
Status: Work in progress
Proposed branch: ~raharper/cloud-init:sysconfig-handle-global-static-routes
Merge into: cloud-init:master
Diff against target: 151 lines (+58/-0)
2 files modified
cloudinit/net/sysconfig.py (+22/-0)
tests/unittests/test_net.py (+36/-0)
Reviewer Review Type Date Requested Status
Server Team CI bot continuous-integration Approve
cloud-init Commiters Pending
Review via email: mp+342102@code.launchpad.net

Commit message

net/sysconfig: handle global static routes

Cloud-init network-config V1 format allows configuration of "global"
static routes which are not directly associated with any interface
in particular. These routes were not rendered under sysconfig which
generally prefers routes to be part of an interface subnet configuration.
If the configuration includes global routes, sysconfig will now render
these static routes to /etc/sysconfig/static-routes file with the 'any'
parameter indicating it will be active for any network interface.

This change does not affect existing subnet routes which continue to
be rendered into /etc/sysconfig/network-scripts/route-$iface and
route6-$iface as needed.

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:8fe387770dda040419468007f5f279f502ad32c1
https://jenkins.ubuntu.com/server/job/cloud-init-ci/930/
Executed test runs:
    SUCCESS: Checkout
    SUCCESS: Unit & Style Tests
    SUCCESS: Ubuntu LTS: Build
    SUCCESS: Ubuntu LTS: Integration
    SUCCESS: MAAS Compatability Testing
    IN_PROGRESS: Declarative: Post Actions

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

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

one useful comment/question and some nits.

5afdc68... by Ryan Harper

net/sysconfig refactors

- move ip-route keyword mapping outside of loop
- move prefix 'any' to entry construction

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

Thanks for the review, fixing up most.

We do have some discussion to be had re: sysconfig capture and redeploy w.r.t what files cloud-init will leave around, also cloud-init clean for network related configuration files.

2b459ab... by Ryan Harper

reduce vertical spacing some more

- move regex out of loop and compress to two lines
- keep list comprehension to filter non route entries from regex search
- convert match into dictionary in one line
- use ternary operator to append matched route when found
- network_config is not needed in _assert_static_routes
- no longer break out network_config to pass to static_routes
- add an else clause to assert_static_routes to make sure we don't
  have rendered static routes file when we shouldnt

c79e5fc... by Ryan Harper

Make tox happy

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

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

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

review: Approve (continuous-integration)
Revision history for this message
Scott Moser (smoser) wrote :

I filed a bug
 https://bugs.launchpad.net/cloud-init/+bug/1759324
to handle "make network rendering cleanup after itself."

Unmerged commits

c79e5fc... by Ryan Harper

Make tox happy

2b459ab... by Ryan Harper

reduce vertical spacing some more

- move regex out of loop and compress to two lines
- keep list comprehension to filter non route entries from regex search
- convert match into dictionary in one line
- use ternary operator to append matched route when found
- network_config is not needed in _assert_static_routes
- no longer break out network_config to pass to static_routes
- add an else clause to assert_static_routes to make sure we don't
  have rendered static routes file when we shouldnt

5afdc68... by Ryan Harper

net/sysconfig refactors

- move ip-route keyword mapping outside of loop
- move prefix 'any' to entry construction

194f739... by Ryan Harper

net/sysconfig: handle global static routes

Cloud-init network-config V1 format allows configuration of "global"
static routes which are not directly associated with any interface
in particular. These routes were not rendered under sysconfig which
generally prefers routes to be part of an interface subnet configuration.
If the configuration includes global routes, sysconfig will now render
these static routes to /etc/sysconfig/static-routes file with the 'any'
parameter indicating it will be active for any network interface.

This change does not affect existing subnet routes which continue to
be rendered into /etc/sysconfig/network-scripts/route-$iface and
route6-$iface as needed.

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 39d89c4..f8627f3 100644
--- a/cloudinit/net/sysconfig.py
+++ b/cloudinit/net/sysconfig.py
@@ -550,6 +550,26 @@ class Renderer(renderer.Renderer):
550 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)550 cls._render_subnet_routes(iface_cfg, route_cfg, iface_subnets)
551551
552 @classmethod552 @classmethod
553 def _render_static_routes(cls, base_sysconf_dir, network_state, contents):
554 """Render global routes into /etc/sysconfig/static-routes file"""
555 sr_content = []
556 ipr_mapping = {'network': 'net', 'netmask': 'netmask',
557 'metric': 'metric', 'gateway': 'gw'}
558 for route in network_state.iter_routes():
559 route_fmt = []
560 # use a specific order to generate a net-tools/route command
561 for kw in ['network', 'netmask', 'metric', 'gateway']:
562 if kw in route:
563 route_fmt.extend([ipr_mapping[kw], str(route[kw])])
564 if route_fmt:
565 sr_content.append("any " + " ".join(route_fmt))
566
567 if sr_content:
568 static_routes_fn = os.path.join(base_sysconf_dir, 'static-routes')
569 sr_content = [_make_header()] + sr_content
570 contents[static_routes_fn] = "\n".join(sr_content) + "\n"
571
572 @classmethod
553 def _render_sysconfig(cls, base_sysconf_dir, network_state):573 def _render_sysconfig(cls, base_sysconf_dir, network_state):
554 '''Given state, return /etc/sysconfig files + contents'''574 '''Given state, return /etc/sysconfig files + contents'''
555 iface_contents = {}575 iface_contents = {}
@@ -576,6 +596,8 @@ class Renderer(renderer.Renderer):
576 iface_cfg.routes.to_string("ipv4")596 iface_cfg.routes.to_string("ipv4")
577 contents[iface_cfg.routes.path_ipv6] = \597 contents[iface_cfg.routes.path_ipv6] = \
578 iface_cfg.routes.to_string("ipv6")598 iface_cfg.routes.to_string("ipv6")
599 cls._render_static_routes(base_sysconf_dir, network_state, contents)
600
579 return contents601 return contents
580602
581 def render_network_state(self, network_state, target=None):603 def render_network_state(self, network_state, target=None):
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
index c12a487..21bb672 100644
--- a/tests/unittests/test_net.py
+++ b/tests/unittests/test_net.py
@@ -21,6 +21,7 @@ import gzip
21import io21import io
22import json22import json
23import os23import os
24import re
24import textwrap25import textwrap
25import yaml26import yaml
2627
@@ -1575,6 +1576,7 @@ class TestSysConfigRendering(CiTestCase):
1575 def _render_and_read(self, network_config=None, state=None, dir=None):1576 def _render_and_read(self, network_config=None, state=None, dir=None):
1576 if dir is None:1577 if dir is None:
1577 dir = self.tmp_dir()1578 dir = self.tmp_dir()
1579 self.render_dir = dir
15781580
1579 if network_config:1581 if network_config:
1580 ns = network_state.parse_net_config_data(network_config)1582 ns = network_state.parse_net_config_data(network_config)
@@ -1582,6 +1584,7 @@ class TestSysConfigRendering(CiTestCase):
1582 ns = state1584 ns = state
1583 else:1585 else:
1584 raise ValueError("Expected data or state, got neither")1586 raise ValueError("Expected data or state, got neither")
1587 self.network_state = ns
15851588
1586 renderer = sysconfig.Renderer()1589 renderer = sysconfig.Renderer()
1587 renderer.render_network_state(ns, dir)1590 renderer.render_network_state(ns, dir)
@@ -1610,6 +1613,32 @@ class TestSysConfigRendering(CiTestCase):
1610 if missing:1613 if missing:
1611 raise AssertionError("Missing headers in: %s" % missing)1614 raise AssertionError("Missing headers in: %s" % missing)
16121615
1616 def _assert_static_routes(self, found):
1617 static_routes = list(self.network_state.iter_routes())
1618 snet_keys = ['network', 'netmask', 'metric', 'gateway']
1619 if len(static_routes) > 0:
1620 self.assertIn('/etc/sysconfig/static-routes', found)
1621 sroute_fn = os.path.join(self.render_dir,
1622 'etc/sysconfig/static-routes')
1623 self.assertTrue(os.path.exists(sroute_fn))
1624 sroute_content = util.load_file(sroute_fn)
1625 sr_re = (r'^any\snet\s(?P<network>\S+)\snetmask\s(?P<netmask>\S+)'
1626 r'\smetric\s(?P<metric>\S+)\sgw\s(?P<gateway>\S+)')
1627 for line in [line for line in sroute_content.splitlines()
1628 if line.startswith('any')]:
1629 found_route = re.search(sr_re, line).groupdict('')
1630 if 'metric' in found_route:
1631 found_route['metric'] = int(found_route['metric'])
1632 found_match = []
1633 for sroute in static_routes:
1634 missing = [key for key in snet_keys
1635 if found_route[key] != sroute[key]]
1636 if not missing:
1637 found_match.append(sroute)
1638 self.assertEqual(1, len(found_match))
1639 else:
1640 self.assertNotIn('/etc/sysconfig/static-routes', found)
1641
1613 @mock.patch("cloudinit.net.sys_dev_path")1642 @mock.patch("cloudinit.net.sys_dev_path")
1614 @mock.patch("cloudinit.net.read_sys_net")1643 @mock.patch("cloudinit.net.read_sys_net")
1615 @mock.patch("cloudinit.net.get_devicelist")1644 @mock.patch("cloudinit.net.get_devicelist")
@@ -1792,42 +1821,49 @@ USERCTL=no
1792 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1821 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1793 self._compare_files_to_expected(entry['expected_sysconfig'], found)1822 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1794 self._assert_headers(found)1823 self._assert_headers(found)
1824 self._assert_static_routes(found)
17951825
1796 def test_vlan_config(self):1826 def test_vlan_config(self):
1797 entry = NETWORK_CONFIGS['vlan']1827 entry = NETWORK_CONFIGS['vlan']
1798 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1828 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1799 self._compare_files_to_expected(entry['expected_sysconfig'], found)1829 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1800 self._assert_headers(found)1830 self._assert_headers(found)
1831 self._assert_static_routes(found)
18011832
1802 def test_bridge_config(self):1833 def test_bridge_config(self):
1803 entry = NETWORK_CONFIGS['bridge']1834 entry = NETWORK_CONFIGS['bridge']
1804 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1835 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1805 self._compare_files_to_expected(entry['expected_sysconfig'], found)1836 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1806 self._assert_headers(found)1837 self._assert_headers(found)
1838 self._assert_static_routes(found)
18071839
1808 def test_manual_config(self):1840 def test_manual_config(self):
1809 entry = NETWORK_CONFIGS['manual']1841 entry = NETWORK_CONFIGS['manual']
1810 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1842 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1811 self._compare_files_to_expected(entry['expected_sysconfig'], found)1843 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1812 self._assert_headers(found)1844 self._assert_headers(found)
1845 self._assert_static_routes(found)
18131846
1814 def test_all_config(self):1847 def test_all_config(self):
1815 entry = NETWORK_CONFIGS['all']1848 entry = NETWORK_CONFIGS['all']
1816 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1849 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1817 self._compare_files_to_expected(entry['expected_sysconfig'], found)1850 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1818 self._assert_headers(found)1851 self._assert_headers(found)
1852 self._assert_static_routes(found)
18191853
1820 def test_small_config(self):1854 def test_small_config(self):
1821 entry = NETWORK_CONFIGS['small']1855 entry = NETWORK_CONFIGS['small']
1822 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1856 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1823 self._compare_files_to_expected(entry['expected_sysconfig'], found)1857 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1824 self._assert_headers(found)1858 self._assert_headers(found)
1859 self._assert_static_routes(found)
18251860
1826 def test_v4_and_v6_static_config(self):1861 def test_v4_and_v6_static_config(self):
1827 entry = NETWORK_CONFIGS['v4_and_v6_static']1862 entry = NETWORK_CONFIGS['v4_and_v6_static']
1828 found = self._render_and_read(network_config=yaml.load(entry['yaml']))1863 found = self._render_and_read(network_config=yaml.load(entry['yaml']))
1829 self._compare_files_to_expected(entry['expected_sysconfig'], found)1864 self._compare_files_to_expected(entry['expected_sysconfig'], found)
1830 self._assert_headers(found)1865 self._assert_headers(found)
1866 self._assert_static_routes(found)
18311867
18321868
1833class TestEniNetRendering(CiTestCase):1869class TestEniNetRendering(CiTestCase):

Subscribers

People subscribed via source and target branches