# This file is part of cloud-init. See LICENSE file for license information.

import textwrap
from unittest import mock

import yaml

from cloudinit.net import network_manager, network_state
from tests.unittests.helpers import dir2dict


def assert_equal_dict(expected_d, found_d):
    for p, c in expected_d.items():
        if p not in found_d:
            continue
        assert c == found_d[p]


class TestNetworkManagerRenderNetworkState:
    def _parse_network_state_from_config(self, config):
        with mock.patch("cloudinit.net.network_state.get_interfaces_by_mac"):
            config = yaml.safe_load(config)
            return network_state.parse_net_config_data(config)

    def test_subnet_route_metric(self, tmpdir):
        """Test subnet metric renders as route-metric in NM config."""
        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                name: ens1f0np0
                type: physical
                subnets:
                  - type: static
                    address: 192.168.1.10/24
                    metric: 100
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mac-address=XX:XX:XX:XX:XX:00

                [ipv4]
                method=manual
                may-fail=false
                address1=192.168.1.10/24
                route-metric=100

                """
            ),
        }

        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)

    def test_bond_dns_baseline(self, tmpdir):

        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: ens1f0np0
                subnets: []
                type: physical
              - mac_address: 'xx:xx:xx:xx:xx:01'
                mtu: 9000
                name: ens1f1np1
                subnets: []
                type: physical
              - bond_interfaces:
                  - ens1f0np0
                  - ens1f1np1
                mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: bond0
                params:
                  bond-ad_actor_sys_prio: 65535
                  bond-ad_actor_system: 00:00:00:00:00:00
                  bond-ad_select: bandwidth
                  bond-ad_user_port_key: 2
                  bond-all_slaves_active: 0
                  bond-arp_all_targets: any
                  bond-arp_interval: 100
                  bond-arp_ip_target: 127.0.0.1
                  bond-arp_validate: none
                  bond-downdelay: 0
                  bond-fail_over_mac: xx:xx:xx:xx:xx:01
                  bond-lacp_rate: slow
                  bond-miimon: 100
                  bond-min_links: 2
                  bond-mode: 802.3ad
                  bond-updelay: 0
                  bond-use_carrier: 1
                  bond-xmit_hash_policy: layer3+4
                subnets: []
                type: bond
              - name: bond0.123
                subnets:
                  - address: 0.0.0.0
                    ipv4: true
                    netmask: 255.255.255.0
                    prefix: 24
                    routes:
                      - gateway: 0.0.0.1
                        netmask: 0.0.0.0
                        network: 0.0.0.0
                    type: static
                type: vlan
                vlan_id: 123
                vlan_link: bond0
              - address: 1.1.1.1
                search: hostname1
                type: nameserver
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:00

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-ens1f1np1.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f1np1
                uuid=2685ec2b-1c26-583d-a660-0ab24201fef3
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:01

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0
                uuid=54317911-f840-516b-a10d-82cb4c1f075c
                autoconnect-priority=120
                type=bond
                interface-name=bond0

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [bond]
                mode=802.3ad
                miimon=100
                xmit_hash_policy=layer3+4
                downdelay=0
                updelay=0
                fail_over_mac=xx:xx:xx:xx:xx:01
                ad_actor_sys_prio=65535
                ad_actor_system=00:00:00:00:00:00
                ad_select=bandwidth
                ad_user_port_key=2
                all_slaves_active=0
                arp_all_targets=any
                arp_interval=100
                arp_ip_target=127.0.0.1
                arp_validate=none
                lacp_rate=slow
                min_links=2
                use_carrier=1

                [ipv4]
                method=disabled
                may-fail=false

                [ipv6]
                method=disabled
                may-fail=false

                [ethernet]
                mtu=9000

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.123.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0.123
                uuid=7541e7a5-450b-570b-b3e8-a7f9eced114a
                autoconnect-priority=120
                type=vlan
                interface-name=bond0.123

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [vlan]
                id=123
                parent=54317911-f840-516b-a10d-82cb4c1f075c

                [ipv4]
                method=manual
                may-fail=false
                address1=0.0.0.0/24
                route1=0.0.0.0/0,0.0.0.1
                dns=1.1.1.1;
                dns-search=hostname1;

                """
            ),
        }
        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)

    def test_bond_dns_redacted_with_method_disabled(self, tmpdir):

        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: ens1f0np0
                subnets: []
                type: physical
              - mac_address: 'xx:xx:xx:xx:xx:01'
                mtu: 9000
                name: ens1f1np1
                subnets: []
                type: physical
              - bond_interfaces:
                  - ens1f0np0
                  - ens1f1np1
                mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: bond0
                params:
                  bond-ad_actor_sys_prio: 65535
                  bond-ad_actor_system: 00:00:00:00:00:00
                  bond-ad_select: bandwidth
                  bond-ad_user_port_key: 2
                  bond-all_slaves_active: 0
                  bond-arp_all_targets: any
                  bond-arp_interval: 100
                  bond-arp_ip_target: 127.0.0.1
                  bond-arp_validate: none
                  bond-downdelay: 0
                  bond-fail_over_mac: xx:xx:xx:xx:xx:01
                  bond-lacp_rate: slow
                  bond-miimon: 100
                  bond-min_links: 2
                  bond-mode: 802.3ad
                  bond-updelay: 0
                  bond-use_carrier: 1
                  bond-xmit_hash_policy: layer3+4
                subnets: []
                type: bond
              - name: bond0.123
                subnets:
                  - address: 0.0.0.0
                    ipv4: true
                    netmask: 255.255.255.0
                    prefix: 24
                    routes:
                      - gateway: 0.0.0.1
                        netmask: 0.0.0.0
                        network: 0.0.0.0
                    type: ipv6_slaac  # !! to force ipvx.method to be disabled
                type: vlan
                vlan_id: 123
                vlan_link: bond0
              - address: 1.1.1.1
                search: hostname1
                type: nameserver
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:00

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-ens1f1np1.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f1np1
                uuid=2685ec2b-1c26-583d-a660-0ab24201fef3
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:01

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0
                uuid=54317911-f840-516b-a10d-82cb4c1f075c
                autoconnect-priority=120
                type=bond
                interface-name=bond0

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [bond]
                mode=802.3ad
                miimon=100
                xmit_hash_policy=layer3+4
                downdelay=0
                updelay=0
                fail_over_mac=xx:xx:xx:xx:xx:01
                ad_actor_sys_prio=65535
                ad_actor_system=00:00:00:00:00:00
                ad_select=bandwidth
                ad_user_port_key=2
                all_slaves_active=0
                arp_all_targets=any
                arp_interval=100
                arp_ip_target=127.0.0.1
                arp_validate=none
                lacp_rate=slow
                min_links=2
                use_carrier=1

                [ipv4]
                method=disabled
                may-fail=false

                [ipv6]
                method=disabled
                may-fail=false

                [ethernet]
                mtu=9000

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.123.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0.123
                uuid=7541e7a5-450b-570b-b3e8-a7f9eced114a
                autoconnect-priority=120
                type=vlan
                interface-name=bond0.123

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [vlan]
                id=123
                parent=54317911-f840-516b-a10d-82cb4c1f075c

                [ipv6]
                method=auto
                may-fail=false
                address1=0.0.0.0/24
                dns-search=hostname1;

                [ipv4]
                method=disabled
                route1=0.0.0.0/0,0.0.0.1

                """
            ),
        }
        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)

    def test_bond_balance_tlb_baseline(self, tmpdir):

        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: ens1f0np0
                subnets: []
                type: physical
              - mac_address: 'xx:xx:xx:xx:xx:01'
                mtu: 9000
                name: ens1f1np1
                subnets: []
                type: physical
              - bond_interfaces:
                  - ens1f0np0
                  - ens1f1np1
                mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: bond0
                params:
                  bond-active_slave: ens1f0np0
                  bond-all_slaves_active: 0
                  bond-arp_all_targets: any
                  bond-arp_interval: 100
                  bond-arp_ip_target: 127.0.0.1
                  bond-arp_validate: none
                  bond-downdelay: 0
                  bond-fail_over_mac: xx:xx:xx:xx:xx:01
                  bond-lp_interval: 1
                  bond-miimon: 100
                  bond-mode: balance-tlb
                  bond-primary: ens1f0np0
                  bond-primary_reselect: always
                  bond-resend_igmp: 1
                  bond-tlb_dynamic_lb: 1
                  bond-updelay: 0
                  bond-use_carrier: 1
                  bond-xmit_hash_policy: layer3+4
                subnets: []
                type: bond
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:00

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-ens1f1np1.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f1np1
                uuid=2685ec2b-1c26-583d-a660-0ab24201fef3
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:01

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0
                uuid=54317911-f840-516b-a10d-82cb4c1f075c
                autoconnect-priority=120
                type=bond
                interface-name=bond0

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [bond]
                mode=balance-tlb
                miimon=100
                xmit_hash_policy=layer3+4
                downdelay=0
                updelay=0
                fail_over_mac=xx:xx:xx:xx:xx:01
                primary_reselect=always
                primary=ens1f0np0
                active_slave=ens1f0np0
                all_slaves_active=0
                arp_all_targets=any
                arp_interval=100
                arp_ip_target=127.0.0.1
                arp_validate=none
                lp_interval=1
                resend_igmp=1
                tlb_dynamic_lb=1
                use_carrier=1

                [ipv4]
                method=disabled
                may-fail=false

                [ipv6]
                method=disabled
                may-fail=false

                [ethernet]
                mtu=9000

                """
            ),
        }
        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)

    def test_bond_balance_rr_baseline(self, tmpdir):

        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: ens1f0np0
                subnets: []
                type: physical
              - mac_address: 'xx:xx:xx:xx:xx:01'
                mtu: 9000
                name: ens1f1np1
                subnets: []
                type: physical
              - bond_interfaces:
                  - ens1f0np0
                  - ens1f1np1
                mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: bond0
                params:
                  bond-all_slaves_active: 0
                  bond-arp_all_targets: any
                  bond-arp_interval: 100
                  bond-arp_ip_target: 127.0.0.1
                  bond-arp_validate: none
                  bond-downdelay: 0
                  bond-fail_over_mac: xx:xx:xx:xx:xx:01
                  bond-miimon: 100
                  bond-mode: balance-tlb
                  bond-packets_per_slave: 1
                  bond-resend_igmp: 1
                  bond-updelay: 0
                  bond-use_carrier: 1
                subnets: []
                type: bond
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:00

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-ens1f1np1.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f1np1
                uuid=2685ec2b-1c26-583d-a660-0ab24201fef3
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:01

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0
                uuid=54317911-f840-516b-a10d-82cb4c1f075c
                autoconnect-priority=120
                type=bond
                interface-name=bond0

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [bond]
                mode=balance-tlb
                miimon=100
                downdelay=0
                updelay=0
                fail_over_mac=xx:xx:xx:xx:xx:01
                all_slaves_active=0
                arp_all_targets=any
                arp_interval=100
                arp_ip_target=127.0.0.1
                arp_validate=none
                packets_per_slave=1
                resend_igmp=1
                use_carrier=1

                [ipv4]
                method=disabled
                may-fail=false

                [ipv6]
                method=disabled
                may-fail=false

                [ethernet]
                mtu=9000

                """
            ),
        }
        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)

    def test_bond_active_backup_baseline(self, tmpdir):

        config = textwrap.dedent(
            """\
            version: 1
            config:
              - mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: ens1f0np0
                subnets: []
                type: physical
              - mac_address: 'xx:xx:xx:xx:xx:01'
                mtu: 9000
                name: ens1f1np1
                subnets: []
                type: physical
              - bond_interfaces:
                  - ens1f0np0
                  - ens1f1np1
                mac_address: 'xx:xx:xx:xx:xx:00'
                mtu: 9000
                name: bond0
                params:
                  bond-active_slave: ens1f0np0
                  bond-all_slaves_active: 0
                  bond-arp_all_targets: any
                  bond-arp_interval: 100
                  bond-arp_ip_target: 127.0.0.1
                  bond-arp_validate: none
                  bond-downdelay: 0
                  bond-fail_over_mac: xx:xx:xx:xx:xx:01
                  bond-miimon: 100
                  bond-mode: balance-tlb
                  bond-num_grat_arp: 1
                  bond-num_unsol_na: 1
                  bond-resend_igmp: 1
                  bond-updelay: 0
                  bond-use_carrier: 1
                subnets: []
                type: bond
            """
        )

        expected_config = {
            "/etc/NetworkManager/system-connections/cloud-init-ens1f0np0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f0np0
                uuid=99c4bf6c-1691-53c4-bfe8-abdcb90b278a
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:00

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-ens1f1np1.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init ens1f1np1
                uuid=2685ec2b-1c26-583d-a660-0ab24201fef3
                autoconnect-priority=120
                type=ethernet
                slave-type=bond
                master=54317911-f840-516b-a10d-82cb4c1f075c

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [ethernet]
                mtu=9000
                mac-address=XX:XX:XX:XX:XX:01

                """
            ),
            "/etc/NetworkManager/system-connections/cloud-init-bond0.nmconnection": textwrap.dedent(  # noqa: E501
                """\
                # Generated by cloud-init. Changes will be lost.

                [connection]
                id=cloud-init bond0
                uuid=54317911-f840-516b-a10d-82cb4c1f075c
                autoconnect-priority=120
                type=bond
                interface-name=bond0

                [user]
                org.freedesktop.NetworkManager.origin=cloud-init

                [bond]
                mode=balance-tlb
                miimon=100
                num_grat_arp=1
                downdelay=0
                updelay=0
                fail_over_mac=xx:xx:xx:xx:xx:01
                active_slave=ens1f0np0
                all_slaves_active=0
                arp_all_targets=any
                arp_interval=100
                arp_ip_target=127.0.0.1
                arp_validate=none
                num_unsol_na=1
                resend_igmp=1
                use_carrier=1

                [ipv4]
                method=disabled
                may-fail=false

                [ipv6]
                method=disabled
                may-fail=false

                [ethernet]
                mtu=9000

                """
            ),
        }
        with mock.patch("cloudinit.net.get_interfaces_by_mac"):
            ns = self._parse_network_state_from_config(config)
            target = str(tmpdir)
            network_manager.Renderer().render_network_state(ns, target=target)
            rendered_content = dir2dict(target)
            assert_equal_dict(expected_config, rendered_content)
