File: test_net_rendering.py

package info (click to toggle)
cloud-init 25.3-2
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 12,412 kB
  • sloc: python: 135,894; sh: 3,883; makefile: 141; javascript: 30; xml: 22
file content (130 lines) | stat: -rw-r--r-- 4,536 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
"""Home of the tests for end-to-end net rendering

Tests defined here should take a v1 or v2 yaml config as input, and verify
that the rendered network config is as expected. Input files are defined
under `tests/unittests/net/artifacts` with the format of

<test_name><format>.yaml

For example, if my test name is "test_all_the_things" and I'm testing a
v2 format, I should have a file named test_all_the_things_v2.yaml.

If a renderer outputs multiple files, the expected files should live in
the artifacts directory under the given test name. For example, if I'm
expecting NetworkManager to output a file named eth0.nmconnection as
part of my "test_all_the_things" test, then in the artifacts directory
there should be a
`test_all_the_things/etc/NetworkManager/system-connections/eth0.nmconnection`
file.

To add a new nominal test, create the input and output files, then add the test
name to the `test_convert` test along with it's supported renderers.

Before adding a test here, check that it is not already represented
in `unittests/test_net.py`. While that file contains similar tests, it has
become too large to be maintainable.
"""

import glob
from enum import Flag, auto
from pathlib import Path

import pytest
import yaml

from cloudinit.net.netplan import Renderer as NetplanRenderer
from cloudinit.net.network_manager import Renderer as NetworkManagerRenderer
from cloudinit.net.network_state import NetworkState, parse_net_config_data
from cloudinit.net.networkd import Renderer as NetworkdRenderer
from tests.unittests.helpers import mock

ARTIFACT_DIR = Path(__file__).parent.absolute() / "artifacts"


class Renderer(Flag):
    Netplan = auto()
    NetworkManager = auto()
    Networkd = auto()


@pytest.fixture(autouse=True)
def setup(mocker):
    mocker.patch("cloudinit.net.network_state.get_interfaces_by_mac")


def _check_file_diff(expected_paths: list, tmp_path: Path):
    for expected_path in expected_paths:
        expected_contents = Path(expected_path).read_text()
        actual_path = tmp_path / expected_path.split(
            str(ARTIFACT_DIR), maxsplit=1
        )[1].lstrip("/")
        assert (
            actual_path.exists()
        ), f"Expected {actual_path} to exist, but it does not"
        actual_contents = actual_path.read_text()
        assert expected_contents.strip() == actual_contents.strip()


def _check_netplan(
    network_state: NetworkState, netplan_path: Path, expected_config
):
    if network_state.version == 2:
        renderer = NetplanRenderer(config={"netplan_path": netplan_path})
        renderer.render_network_state(network_state)
        assert yaml.safe_load(netplan_path.read_text()) == expected_config, (
            f"Netplan config generated at {netplan_path} does not match v2 "
            "config defined for this test."
        )
    else:
        raise NotImplementedError


def _check_network_manager(network_state: NetworkState, tmp_path: Path):
    renderer = NetworkManagerRenderer()
    renderer.render_network_state(
        network_state, target=str(tmp_path / "no_matching_mac")
    )
    expected_paths = glob.glob(
        str(ARTIFACT_DIR / "no_matching_mac" / "**/*.nmconnection"),
        recursive=True,
    )
    _check_file_diff(expected_paths, tmp_path)


@mock.patch("cloudinit.net.util.chownbyname", return_value=True)
def _check_networkd_renderer(
    network_state: NetworkState, tmp_path: Path, m_chown
):
    renderer = NetworkdRenderer()
    renderer.render_network_state(
        network_state, target=str(tmp_path / "photon_net_config")
    )
    expected_paths = glob.glob(
        str(ARTIFACT_DIR / "photon_net_config" / "**/*.net*"),
        recursive=True,
    )
    _check_file_diff(expected_paths, tmp_path)


@pytest.mark.parametrize(
    "test_name, renderers",
    [
        ("no_matching_mac_v2", Renderer.Netplan | Renderer.NetworkManager),
        ("photon_net_config_v2", Renderer.Networkd),
    ],
)
def test_convert(test_name, renderers, tmp_path):
    network_config = yaml.safe_load(
        Path(ARTIFACT_DIR, f"{test_name}.yaml").read_text()
    )
    network_state = parse_net_config_data(network_config["network"])
    if Renderer.Netplan in renderers:
        _check_netplan(
            network_state, tmp_path / "netplan.yaml", network_config
        )
    if Renderer.NetworkManager in renderers:
        _check_network_manager(network_state, tmp_path)
    if Renderer.Networkd in renderers:
        _check_networkd_renderer(  # pylint: disable=E1120
            network_state, tmp_path
        )