sailesh1993 / rpms / cloud-init

Forked from rpms/cloud-init a year ago
Clone
69219a
From f14ed869e5784b1d5a3dfbabc1484eb266e8c3ab Mon Sep 17 00:00:00 2001
69219a
From: Eduardo Otubo <otubo@redhat.com>
69219a
Date: Mon, 4 May 2020 12:40:13 +0200
69219a
Subject: [PATCH 6/6] net: IPv6, accept_ra, slaac, stateless (#51)
69219a
69219a
RH-Author: Eduardo Otubo <otubo@redhat.com>
69219a
Message-id: <20200327152826.13343-7-otubo@redhat.com>
69219a
Patchwork-id: 94458
69219a
O-Subject: [RHEL-8.1.z/RHEL-8.2.z cloud-init PATCHv2 6/6] net: IPv6, accept_ra, slaac, stateless (#51)
69219a
Bugzilla: 1811753
69219a
RH-Acked-by: Cathy Avery <cavery@redhat.com>
69219a
RH-Acked-by: Vitaly Kuznetsov <vkuznets@redhat.com>
69219a
69219a
commit 62bbc262c3c7f633eac1d09ec78c055eef05166a
69219a
Author: Harald <hjensas@redhat.com>
69219a
Date:   Wed Nov 20 18:55:27 2019 +0100
69219a
69219a
    net: IPv6, accept_ra, slaac, stateless (#51)
69219a
69219a
    Router advertisements are required for the default route
69219a
    to be set up, thus accept_ra should be enabled for
69219a
    dhcpv6-stateful.
69219a
69219a
    sysconf: IPV6_FORCE_ACCEPT_RA controls accept_ra sysctl.
69219a
    eni: mode static and mode dhcp 'accept_ra' controls sysctl.
69219a
69219a
    Add 'accept-ra: true|false' parameter to config v1 and
69219a
    v2. When True: accept_ra is set to '1'. When False:
69219a
    accept_ra is set to '0'. When not defined in config the
69219a
    value is left to the operating system default.
69219a
69219a
    This change also extend the IPv6 support to distinguish
69219a
    between slaac and dhcpv6-stateless. SLAAC is autoconfig
69219a
    without any options from DHCP, while stateless auto-configures
69219a
    the address and the uses DHCP for other options.
69219a
69219a
    LP: #1806014
69219a
    LP: #1808647
69219a
69219a
Signed-off-by: Eduardo Otubo <otubo@redhat.com>
69219a
Signed-off-by: Miroslav Rezanina <mrezanin@redhat.com>
69219a
---
69219a
 cloudinit/net/eni.py                               |  15 ++
69219a
 cloudinit/net/netplan.py                           |   9 +-
69219a
 cloudinit/net/network_state.py                     |  21 +-
69219a
 cloudinit/net/sysconfig.py                         |  34 ++-
69219a
 cloudinit/sources/helpers/openstack.py             |  21 +-
69219a
 .../unittests/test_datasource/test_configdrive.py  |   3 +-
69219a
 tests/unittests/test_net.py                        | 249 +++++++++++++++++++++
69219a
 7 files changed, 334 insertions(+), 18 deletions(-)
69219a
69219a
diff --git a/cloudinit/net/eni.py b/cloudinit/net/eni.py
69219a
index 896a39b..c70435a 100644
69219a
--- a/cloudinit/net/eni.py
69219a
+++ b/cloudinit/net/eni.py
69219a
@@ -393,6 +393,7 @@ class Renderer(renderer.Renderer):
69219a
     def _render_iface(self, iface, render_hwaddress=False):
69219a
         sections = []
69219a
         subnets = iface.get('subnets', {})
69219a
+        accept_ra = iface.pop('accept-ra', None)
69219a
         if subnets:
69219a
             for index, subnet in enumerate(subnets):
69219a
                 ipv4_subnet_mtu = None
69219a
@@ -409,9 +410,23 @@ class Renderer(renderer.Renderer):
69219a
                         subnet['type'] == 'ipv6_dhcpv6-stateful'):
69219a
                     # Configure network settings using DHCP or DHCPv6
69219a
                     iface['mode'] = 'dhcp'
69219a
+                    if accept_ra is not None:
69219a
+                        # Accept router advertisements (0=off, 1=on)
69219a
+                        iface['accept_ra'] = '1' if accept_ra else '0'
69219a
                 elif subnet['type'] == 'ipv6_dhcpv6-stateless':
69219a
                     # Configure network settings using SLAAC from RAs
69219a
                     iface['mode'] = 'auto'
69219a
+                    # Use stateless DHCPv6 (0=off, 1=on)
69219a
+                    iface['dhcp'] = '1'
69219a
+                elif subnet['type'] == 'ipv6_slaac':
69219a
+                    # Configure network settings using SLAAC from RAs
69219a
+                    iface['mode'] = 'auto'
69219a
+                    # Use stateless DHCPv6 (0=off, 1=on)
69219a
+                    iface['dhcp'] = '0'
69219a
+                elif subnet_is_ipv6(subnet) and subnet['type'] == 'static':
69219a
+                    if accept_ra is not None:
69219a
+                        # Accept router advertisements (0=off, 1=on)
69219a
+                        iface['accept_ra'] = '1' if accept_ra else '0'
69219a
 
69219a
                 # do not emit multiple 'auto $IFACE' lines as older (precise)
69219a
                 # ifupdown complains
69219a
diff --git a/cloudinit/net/netplan.py b/cloudinit/net/netplan.py
69219a
index 21517fd..78ec38c 100644
69219a
--- a/cloudinit/net/netplan.py
69219a
+++ b/cloudinit/net/netplan.py
69219a
@@ -4,7 +4,7 @@ import copy
69219a
 import os
69219a
 
69219a
 from . import renderer
69219a
-from .network_state import subnet_is_ipv6, NET_CONFIG_TO_V2
69219a
+from .network_state import subnet_is_ipv6, NET_CONFIG_TO_V2, IPV6_DYNAMIC_TYPES
69219a
 
69219a
 from cloudinit import log as logging
69219a
 from cloudinit import util
69219a
@@ -51,7 +51,8 @@ def _extract_addresses(config, entry, ifname):
69219a
          'mtu': 1480,
69219a
          'netmask': 64,
69219a
          'type': 'static'}],
69219a
-      'type: physical'
69219a
+      'type: physical',
69219a
+      'accept-ra': 'true'
69219a
     }
69219a
 
69219a
     An entry dictionary looks like:
69219a
@@ -92,6 +93,8 @@ def _extract_addresses(config, entry, ifname):
69219a
             if sn_type == 'dhcp':
69219a
                 sn_type += '4'
69219a
             entry.update({sn_type: True})
69219a
+        elif sn_type in IPV6_DYNAMIC_TYPES:
69219a
+            entry.update({'dhcp6': True})
69219a
         elif sn_type in ['static']:
69219a
             addr = "%s" % subnet.get('address')
69219a
             if 'prefix' in subnet:
69219a
@@ -144,6 +147,8 @@ def _extract_addresses(config, entry, ifname):
69219a
         ns = entry.get('nameservers', {})
69219a
         ns.update({'search': searchdomains})
69219a
         entry.update({'nameservers': ns})
69219a
+    if 'accept-ra' in config and config['accept-ra'] is not None:
69219a
+        entry.update({'accept-ra': util.is_true(config.get('accept-ra'))})
69219a
 
69219a
 
69219a
 def _extract_bond_slaves_by_name(interfaces, entry, bond_master):
69219a
diff --git a/cloudinit/net/network_state.py b/cloudinit/net/network_state.py
69219a
index 571eb57..82cfa42 100644
69219a
--- a/cloudinit/net/network_state.py
69219a
+++ b/cloudinit/net/network_state.py
69219a
@@ -17,13 +17,17 @@ from cloudinit import util
69219a
 LOG = logging.getLogger(__name__)
69219a
 
69219a
 NETWORK_STATE_VERSION = 1
69219a
+IPV6_DYNAMIC_TYPES = ['dhcp6',
69219a
+                      'ipv6_slaac',
69219a
+                      'ipv6_dhcpv6-stateless',
69219a
+                      'ipv6_dhcpv6-stateful']
69219a
 NETWORK_STATE_REQUIRED_KEYS = {
69219a
     1: ['version', 'config', 'network_state'],
69219a
 }
69219a
 NETWORK_V2_KEY_FILTER = [
69219a
     'addresses', 'dhcp4', 'dhcp4-overrides', 'dhcp6', 'dhcp6-overrides',
69219a
     'gateway4', 'gateway6', 'interfaces', 'match', 'mtu', 'nameservers',
69219a
-    'renderer', 'set-name', 'wakeonlan'
69219a
+    'renderer', 'set-name', 'wakeonlan', 'accept-ra'
69219a
 ]
69219a
 
69219a
 NET_CONFIG_TO_V2 = {
69219a
@@ -341,7 +345,8 @@ class NetworkStateInterpreter(object):
69219a
             'name': 'eth0',
69219a
             'subnets': [
69219a
                 {'type': 'dhcp4'}
69219a
-             ]
69219a
+             ],
69219a
+            'accept-ra': 'true'
69219a
         }
69219a
         '''
69219a
 
69219a
@@ -361,6 +366,9 @@ class NetworkStateInterpreter(object):
69219a
                     self.use_ipv6 = True
69219a
                     break
69219a
 
69219a
+        accept_ra = command.get('accept-ra', None)
69219a
+        if accept_ra is not None:
69219a
+            accept_ra = util.is_true(accept_ra)
69219a
         iface.update({
69219a
             'name': command.get('name'),
69219a
             'type': command.get('type'),
69219a
@@ -371,6 +379,7 @@ class NetworkStateInterpreter(object):
69219a
             'address': None,
69219a
             'gateway': None,
69219a
             'subnets': subnets,
69219a
+            'accept-ra': accept_ra
69219a
         })
69219a
         self._network_state['interfaces'].update({command.get('name'): iface})
69219a
         self.dump_network_state()
69219a
@@ -614,6 +623,7 @@ class NetworkStateInterpreter(object):
69219a
               driver: ixgbe
69219a
             set-name: lom1
69219a
             dhcp6: true
69219a
+            accept-ra: true
69219a
           switchports:
69219a
             match:
69219a
               name: enp2*
69219a
@@ -642,7 +652,7 @@ class NetworkStateInterpreter(object):
69219a
             driver = match.get('driver', None)
69219a
             if driver:
69219a
                 phy_cmd['params'] = {'driver': driver}
69219a
-            for key in ['mtu', 'match', 'wakeonlan']:
69219a
+            for key in ['mtu', 'match', 'wakeonlan', 'accept-ra']:
69219a
                 if key in cfg:
69219a
                     phy_cmd[key] = cfg[key]
69219a
 
69219a
@@ -915,8 +925,9 @@ def is_ipv6_addr(address):
69219a
 
69219a
 def subnet_is_ipv6(subnet):
69219a
     """Common helper for checking network_state subnets for ipv6."""
69219a
-    # 'static6' or 'dhcp6'
69219a
-    if subnet['type'].endswith('6'):
69219a
+    # 'static6', 'dhcp6', 'ipv6_dhcpv6-stateful', 'ipv6_dhcpv6-stateless' or
69219a
+    # 'ipv6_slaac'
69219a
+    if subnet['type'].endswith('6') or subnet['type'] in IPV6_DYNAMIC_TYPES:
69219a
         # This is a request for DHCPv6.
69219a
         return True
69219a
     elif subnet['type'] == 'static' and is_ipv6_addr(subnet.get('address')):
69219a
diff --git a/cloudinit/net/sysconfig.py b/cloudinit/net/sysconfig.py
69219a
index 8b11dbb..13c0a65 100644
69219a
--- a/cloudinit/net/sysconfig.py
69219a
+++ b/cloudinit/net/sysconfig.py
69219a
@@ -14,7 +14,7 @@ from configobj import ConfigObj
69219a
 
69219a
 from . import renderer
69219a
 from .network_state import (
69219a
-    is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6)
69219a
+    is_ipv6_addr, net_prefix_to_ipv4_mask, subnet_is_ipv6, IPV6_DYNAMIC_TYPES)
69219a
 
69219a
 LOG = logging.getLogger(__name__)
69219a
 NM_CFG_FILE = "/etc/NetworkManager/NetworkManager.conf"
69219a
@@ -319,6 +319,9 @@ class Renderer(renderer.Renderer):
69219a
                     continue
69219a
                 iface_cfg[new_key] = old_value
69219a
 
69219a
+        if iface['accept-ra'] is not None:
69219a
+            iface_cfg['IPV6_FORCE_ACCEPT_RA'] = iface['accept-ra']
69219a
+
69219a
     @classmethod
69219a
     def _render_subnets(cls, iface_cfg, subnets, has_default_route):
69219a
         # setting base values
69219a
@@ -335,6 +338,15 @@ class Renderer(renderer.Renderer):
69219a
                 iface_cfg['DHCPV6C'] = True
69219a
             elif subnet_type == 'ipv6_dhcpv6-stateless':
69219a
                 iface_cfg['IPV6INIT'] = True
69219a
+                # Configure network settings using SLAAC from RAs and optional
69219a
+                # info from dhcp server using DHCPv6
69219a
+                iface_cfg['IPV6_AUTOCONF'] = True
69219a
+                iface_cfg['DHCPV6C'] = True
69219a
+                # Use Information-request to get only stateless configuration
69219a
+                # parameters (i.e., without address).
69219a
+                iface_cfg['DHCPV6C_OPTIONS'] = '-S'
69219a
+            elif subnet_type == 'ipv6_slaac':
69219a
+                iface_cfg['IPV6INIT'] = True
69219a
                 # Configure network settings using SLAAC from RAs
69219a
                 iface_cfg['IPV6_AUTOCONF'] = True
69219a
             elif subnet_type in ['dhcp4', 'dhcp']:
69219a
@@ -381,10 +393,15 @@ class Renderer(renderer.Renderer):
69219a
             # metric may apply to both dhcp and static config
69219a
             if 'metric' in subnet:
69219a
                 iface_cfg['METRIC'] = subnet['metric']
69219a
+            # TODO(hjensas): Including dhcp6 here is likely incorrect. DHCPv6
69219a
+            # does not ever provide a default gateway, the default gateway
69219a
+            # come from RA's. (https://github.com/openSUSE/wicked/issues/570)
69219a
             if subnet_type in ['dhcp', 'dhcp4', 'dhcp6']:
69219a
                 if has_default_route and iface_cfg['BOOTPROTO'] != 'none':
69219a
                     iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = False
69219a
                 continue
69219a
+            elif subnet_type in IPV6_DYNAMIC_TYPES:
69219a
+                continue
69219a
             elif subnet_type == 'static':
69219a
                 if subnet_is_ipv6(subnet):
69219a
                     ipv6_index = ipv6_index + 1
69219a
@@ -424,10 +441,14 @@ class Renderer(renderer.Renderer):
69219a
     @classmethod
69219a
     def _render_subnet_routes(cls, iface_cfg, route_cfg, subnets):
69219a
         for _, subnet in enumerate(subnets, start=len(iface_cfg.children)):
69219a
+            subnet_type = subnet.get('type')
69219a
             for route in subnet.get('routes', []):
69219a
                 is_ipv6 = subnet.get('ipv6') or is_ipv6_addr(route['gateway'])
69219a
 
69219a
-                if _is_default_route(route):
69219a
+                # Any dynamic configuration method, slaac, dhcpv6-stateful/
69219a
+                # stateless should get router information from router RA's.
69219a
+                if (_is_default_route(route) and subnet_type not in
69219a
+                        IPV6_DYNAMIC_TYPES):
69219a
                     if (
69219a
                             (subnet.get('ipv4') and
69219a
                              route_cfg.has_set_default_ipv4) or
69219a
@@ -446,10 +467,17 @@ class Renderer(renderer.Renderer):
69219a
                     # TODO(harlowja): add validation that no other iface has
69219a
                     # also provided the default route?
69219a
                     iface_cfg['DEFROUTE'] = True
69219a
+                    # TODO(hjensas): Including dhcp6 here is likely incorrect.
69219a
+                    # DHCPv6 does not ever provide a default gateway, the
69219a
+                    # default gateway come from RA's.
69219a
+                    # (https://github.com/openSUSE/wicked/issues/570)
69219a
                     if iface_cfg['BOOTPROTO'] in ('dhcp', 'dhcp4', 'dhcp6'):
69219a
+                        # NOTE(hjensas): DHCLIENT_SET_DEFAULT_ROUTE is SuSE
69219a
+                        # only. RHEL, CentOS, Fedora does not implement this
69219a
+                        # option.
69219a
                         iface_cfg['DHCLIENT_SET_DEFAULT_ROUTE'] = True
69219a
                     if 'gateway' in route:
69219a
-                        if is_ipv6 or is_ipv6_addr(route['gateway']):
69219a
+                        if is_ipv6:
69219a
                             iface_cfg['IPV6_DEFAULTGW'] = route['gateway']
69219a
                             route_cfg.has_set_default_ipv6 = True
69219a
                         else:
69219a
diff --git a/cloudinit/sources/helpers/openstack.py b/cloudinit/sources/helpers/openstack.py
69219a
index 9f2fd2d..77fcdd3 100644
69219a
--- a/cloudinit/sources/helpers/openstack.py
69219a
+++ b/cloudinit/sources/helpers/openstack.py
69219a
@@ -584,17 +584,24 @@ def convert_net_json(network_json=None, known_macs=None):
69219a
                         if n['link'] == link['id']]:
69219a
             subnet = dict((k, v) for k, v in network.items()
69219a
                           if k in valid_keys['subnet'])
69219a
-            if 'dhcp' in network['type']:
69219a
-                t = (network['type'] if network['type'].startswith('ipv6')
69219a
-                     else 'dhcp4')
69219a
-                subnet.update({
69219a
-                    'type': t,
69219a
-                })
69219a
-            else:
69219a
+
69219a
+            if network['type'] == 'ipv4_dhcp':
69219a
+                subnet.update({'type': 'dhcp4'})
69219a
+            elif network['type'] == 'ipv6_dhcp':
69219a
+                subnet.update({'type': 'dhcp6'})
69219a
+            elif network['type'] in ['ipv6_slaac', 'ipv6_dhcpv6-stateless',
69219a
+                                     'ipv6_dhcpv6-stateful']:
69219a
+                subnet.update({'type': network['type']})
69219a
+            elif network['type'] in ['ipv4', 'ipv6']:
69219a
                 subnet.update({
69219a
                     'type': 'static',
69219a
                     'address': network.get('ip_address'),
69219a
                 })
69219a
+
69219a
+            # Enable accept_ra for stateful and legacy ipv6_dhcp types
69219a
+            if network['type'] in ['ipv6_dhcpv6-stateful', 'ipv6_dhcp']:
69219a
+                cfg.update({'accept-ra': True})
69219a
+
69219a
             if network['type'] == 'ipv4':
69219a
                 subnet['ipv4'] = True
69219a
             if network['type'] == 'ipv6':
69219a
diff --git a/tests/unittests/test_datasource/test_configdrive.py b/tests/unittests/test_datasource/test_configdrive.py
69219a
index ed4e9d5..bd4c310 100644
69219a
--- a/tests/unittests/test_datasource/test_configdrive.py
69219a
+++ b/tests/unittests/test_datasource/test_configdrive.py
69219a
@@ -536,7 +536,8 @@ class TestNetJson(CiTestCase):
69219a
                  'mtu': None,
69219a
                  'name': 'enp0s2',
69219a
                  'subnets': [{'type': 'ipv6_dhcpv6-stateful'}],
69219a
-                 'type': 'physical'}
69219a
+                 'type': 'physical',
69219a
+                 'accept-ra': True}
69219a
             ],
69219a
         }
69219a
         conv_data = openstack.convert_net_json(in_data, known_macs=KNOWN_MACS)
69219a
diff --git a/tests/unittests/test_net.py b/tests/unittests/test_net.py
69219a
index 70d13f3..21a3f0e 100644
69219a
--- a/tests/unittests/test_net.py
69219a
+++ b/tests/unittests/test_net.py
69219a
@@ -712,6 +712,143 @@ NETWORK_CONFIGS = {
69219a
                 """),
69219a
         },
69219a
     },
69219a
+    'dhcpv6_accept_ra': {
69219a
+        'expected_eni': textwrap.dedent("""\
69219a
+            auto lo
69219a
+            iface lo inet loopback
69219a
+
69219a
+            auto iface0
69219a
+            iface iface0 inet6 dhcp
69219a
+                accept_ra 1
69219a
+        """).rstrip(' '),
69219a
+        'expected_netplan': textwrap.dedent("""
69219a
+            network:
69219a
+                version: 2
69219a
+                ethernets:
69219a
+                    iface0:
69219a
+                        accept-ra: true
69219a
+                        dhcp6: true
69219a
+        """).rstrip(' '),
69219a
+        'yaml_v1': textwrap.dedent("""\
69219a
+            version: 1
69219a
+            config:
69219a
+              - type: 'physical'
69219a
+                name: 'iface0'
69219a
+                subnets:
69219a
+                - {'type': 'dhcp6'}
69219a
+                accept-ra: true
69219a
+        """).rstrip(' '),
69219a
+        'yaml_v2': textwrap.dedent("""\
69219a
+            version: 2
69219a
+            ethernets:
69219a
+                iface0:
69219a
+                    dhcp6: true
69219a
+                    accept-ra: true
69219a
+        """).rstrip(' '),
69219a
+        'expected_sysconfig': {
69219a
+            'ifcfg-iface0': textwrap.dedent("""\
69219a
+                BOOTPROTO=none
69219a
+                DEVICE=iface0
69219a
+                DHCPV6C=yes
69219a
+                IPV6INIT=yes
69219a
+                IPV6_FORCE_ACCEPT_RA=yes
69219a
+                DEVICE=iface0
69219a
+                NM_CONTROLLED=no
69219a
+                ONBOOT=yes
69219a
+                STARTMODE=auto
69219a
+                TYPE=Ethernet
69219a
+                USERCTL=no
69219a
+            """),
69219a
+        },
69219a
+    },
69219a
+    'dhcpv6_reject_ra': {
69219a
+        'expected_eni': textwrap.dedent("""\
69219a
+            auto lo
69219a
+            iface lo inet loopback
69219a
+
69219a
+            auto iface0
69219a
+            iface iface0 inet6 dhcp
69219a
+                accept_ra 0
69219a
+        """).rstrip(' '),
69219a
+        'expected_netplan': textwrap.dedent("""
69219a
+            network:
69219a
+                version: 2
69219a
+                ethernets:
69219a
+                    iface0:
69219a
+                        accept-ra: false
69219a
+                        dhcp6: true
69219a
+        """).rstrip(' '),
69219a
+        'yaml_v1': textwrap.dedent("""\
69219a
+            version: 1
69219a
+            config:
69219a
+            - type: 'physical'
69219a
+              name: 'iface0'
69219a
+              subnets:
69219a
+              - {'type': 'dhcp6'}
69219a
+              accept-ra: false
69219a
+        """).rstrip(' '),
69219a
+        'yaml_v2': textwrap.dedent("""\
69219a
+            version: 2
69219a
+            ethernets:
69219a
+                iface0:
69219a
+                    dhcp6: true
69219a
+                    accept-ra: false
69219a
+        """).rstrip(' '),
69219a
+        'expected_sysconfig': {
69219a
+            'ifcfg-iface0': textwrap.dedent("""\
69219a
+                BOOTPROTO=none
69219a
+                DEVICE=iface0
69219a
+                DHCPV6C=yes
69219a
+                IPV6INIT=yes
69219a
+                IPV6_FORCE_ACCEPT_RA=no
69219a
+                DEVICE=iface0
69219a
+                NM_CONTROLLED=no
69219a
+                ONBOOT=yes
69219a
+                STARTMODE=auto
69219a
+                TYPE=Ethernet
69219a
+                USERCTL=no
69219a
+            """),
69219a
+        },
69219a
+    },
69219a
+    'ipv6_slaac': {
69219a
+        'expected_eni': textwrap.dedent("""\
69219a
+            auto lo
69219a
+            iface lo inet loopback
69219a
+
69219a
+            auto iface0
69219a
+            iface iface0 inet6 auto
69219a
+                dhcp 0
69219a
+        """).rstrip(' '),
69219a
+        'expected_netplan': textwrap.dedent("""
69219a
+            network:
69219a
+                version: 2
69219a
+                ethernets:
69219a
+                    iface0:
69219a
+                        dhcp6: true
69219a
+        """).rstrip(' '),
69219a
+        'yaml': textwrap.dedent("""\
69219a
+            version: 1
69219a
+            config:
69219a
+            - type: 'physical'
69219a
+              name: 'iface0'
69219a
+              subnets:
69219a
+              - {'type': 'ipv6_slaac'}
69219a
+        """).rstrip(' '),
69219a
+        'expected_sysconfig': {
69219a
+            'ifcfg-iface0': textwrap.dedent("""\
69219a
+                BOOTPROTO=none
69219a
+                DEVICE=iface0
69219a
+                IPV6_AUTOCONF=yes
69219a
+                IPV6INIT=yes
69219a
+                DEVICE=iface0
69219a
+                NM_CONTROLLED=no
69219a
+                ONBOOT=yes
69219a
+                STARTMODE=auto
69219a
+                TYPE=Ethernet
69219a
+                USERCTL=no
69219a
+            """),
69219a
+        },
69219a
+    },
69219a
     'dhcpv6_stateless': {
69219a
         'expected_eni': textwrap.dedent("""\
69219a
         auto lo
69219a
@@ -719,6 +856,7 @@ NETWORK_CONFIGS = {
69219a
 
69219a
         auto iface0
69219a
         iface iface0 inet6 auto
69219a
+            dhcp 1
69219a
     """).rstrip(' '),
69219a
         'expected_netplan': textwrap.dedent("""
69219a
         network:
69219a
@@ -739,6 +877,8 @@ NETWORK_CONFIGS = {
69219a
             'ifcfg-iface0': textwrap.dedent("""\
69219a
             BOOTPROTO=none
69219a
             DEVICE=iface0
69219a
+            DHCPV6C=yes
69219a
+            DHCPV6C_OPTIONS=-S
69219a
             IPV6_AUTOCONF=yes
69219a
             IPV6INIT=yes
69219a
             DEVICE=iface0
69219a
@@ -763,6 +903,7 @@ NETWORK_CONFIGS = {
69219a
             version: 2
69219a
             ethernets:
69219a
                 iface0:
69219a
+                    accept-ra: true
69219a
                     dhcp6: true
69219a
     """).rstrip(' '),
69219a
         'yaml': textwrap.dedent("""\
69219a
@@ -772,6 +913,7 @@ NETWORK_CONFIGS = {
69219a
             name: 'iface0'
69219a
             subnets:
69219a
             - {'type': 'ipv6_dhcpv6-stateful'}
69219a
+            accept-ra: true
69219a
     """).rstrip(' '),
69219a
         'expected_sysconfig': {
69219a
             'ifcfg-iface0': textwrap.dedent("""\
69219a
@@ -779,6 +921,7 @@ NETWORK_CONFIGS = {
69219a
             DEVICE=iface0
69219a
             DHCPV6C=yes
69219a
             IPV6INIT=yes
69219a
+            IPV6_FORCE_ACCEPT_RA=yes
69219a
             DEVICE=iface0
69219a
             NM_CONTROLLED=no
69219a
             ONBOOT=yes
69219a
@@ -2376,6 +2519,33 @@ USERCTL=no
69219a
             }
69219a
         }
69219a
 
69219a
+    def test_dhcpv6_accept_ra_config_v1(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_accept_ra']
69219a
+        found = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self._compare_files_to_expected(entry[self.expected_name], found)
69219a
+        self._assert_headers(found)
69219a
+
69219a
+    def test_dhcpv6_accept_ra_config_v2(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_accept_ra']
69219a
+        found = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v2']))
69219a
+        self._compare_files_to_expected(entry[self.expected_name], found)
69219a
+        self._assert_headers(found)
69219a
+
69219a
+    def test_dhcpv6_reject_ra_config_v1(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_reject_ra']
69219a
+        found = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self._compare_files_to_expected(entry[self.expected_name], found)
69219a
+        self._assert_headers(found)
69219a
+
69219a
+    def test_dhcpv6_reject_ra_config_v2(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_reject_ra']
69219a
+        found = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v2']))
69219a
+        self._compare_files_to_expected(entry[self.expected_name], found)
69219a
+        self._assert_headers(found)
69219a
 
69219a
     def test_dhcpv6_stateless_config(self):
69219a
         entry = NETWORK_CONFIGS['dhcpv6_stateless']
69219a
@@ -3267,6 +3437,46 @@ class TestNetplanRoundTrip(CiTestCase):
69219a
             entry['expected_netplan'].splitlines(),
69219a
             files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
 
69219a
+    def testsimple_render_dhcpv6_accept_ra(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_accept_ra']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_netplan'].splitlines(),
69219a
+            files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_reject_ra(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_reject_ra']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_netplan'].splitlines(),
69219a
+            files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
+
69219a
+    def testsimple_render_ipv6_slaac(self):
69219a
+        entry = NETWORK_CONFIGS['ipv6_slaac']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_netplan'].splitlines(),
69219a
+            files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_stateless(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_stateless']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_netplan'].splitlines(),
69219a
+            files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_stateful(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_stateful']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_netplan'].splitlines(),
69219a
+            files['/etc/netplan/50-cloud-init.yaml'].splitlines())
69219a
+
69219a
     def testsimple_render_all(self):
69219a
         entry = NETWORK_CONFIGS['all']
69219a
         files = self._render_and_read(network_config=yaml.load(entry['yaml']))
69219a
@@ -3350,6 +3560,45 @@ class TestEniRoundTrip(CiTestCase):
69219a
             entry['expected_eni'].splitlines(),
69219a
             files['/etc/network/interfaces'].splitlines())
69219a
 
69219a
+    def testsimple_render_dhcpv6_stateless(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_stateless']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml']))
69219a
+        files = self._render_and_read(network_config=yaml.load(entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_eni'].splitlines(),
69219a
+            files['/etc/network/interfaces'].splitlines())
69219a
+
69219a
+    def testsimple_render_ipv6_slaac(self):
69219a
+        entry = NETWORK_CONFIGS['ipv6_slaac']
69219a
+        files = self._render_and_read(network_config=yaml.load(entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_eni'].splitlines(),
69219a
+            files['/etc/network/interfaces'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_stateful(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_stateless']
69219a
+        files = self._render_and_read(network_config=yaml.load(entry['yaml']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_eni'].splitlines(),
69219a
+            files['/etc/network/interfaces'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_accept_ra(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_accept_ra']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_eni'].splitlines(),
69219a
+            files['/etc/network/interfaces'].splitlines())
69219a
+
69219a
+    def testsimple_render_dhcpv6_reject_ra(self):
69219a
+        entry = NETWORK_CONFIGS['dhcpv6_reject_ra']
69219a
+        files = self._render_and_read(network_config=yaml.load(
69219a
+            entry['yaml_v1']))
69219a
+        self.assertEqual(
69219a
+            entry['expected_eni'].splitlines(),
69219a
+            files['/etc/network/interfaces'].splitlines())
69219a
+
69219a
     def testsimple_render_manual(self):
69219a
         """Test rendering of 'manual' for 'type' and 'control'.
69219a
 
69219a
-- 
69219a
1.8.3.1
69219a