summaryrefslogtreecommitdiff
path: root/tests/integration_tests/datasources/test_lxd_discovery.py
blob: f3ca71582e675a1322cd06bd6800596ffcfc3d98 (plain)
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
import json

import pytest
import yaml

from tests.integration_tests.clouds import ImageSpecification
from tests.integration_tests.instances import IntegrationInstance
from tests.integration_tests.util import lxd_has_nocloud, verify_clean_log


def _customize_environment(client: IntegrationInstance):
    # Assert our platform can detect LXD during systemd generator timeframe.
    ds_id_log = client.execute("cat /run/cloud-init/ds-identify.log").stdout
    assert "check for 'LXD' returned found" in ds_id_log

    if client.settings.PLATFORM == "lxd_vm":
        # ds-identify runs at systemd generator time before /dev/lxd/sock.
        # Assert we can expected artifact which indicates LXD is viable.
        result = client.execute("cat /sys/class/dmi/id/board_name")
        if not result.ok:
            raise AssertionError(
                "Missing expected /sys/class/dmi/id/board_name"
            )
        if "LXD" != result.stdout:
            raise AssertionError(f"DMI board_name is not LXD: {result.stdout}")

    # Having multiple datasources prevents ds-identify from short-circuiting
    # detection logic with a log like:
    #     single entry in datasource_list (LXD) use that.
    # Also, NoCloud is detected during init-local timeframe.

    # If there is a race on VMs where /dev/lxd/sock is not setup in init-local
    # cloud-init will fallback to NoCloud and fail this test.
    client.write_to_file(
        "/etc/cloud/cloud.cfg.d/99-detect-lxd-first.cfg",
        "datasource_list: [LXD, NoCloud]\n",
    )
    # This is also to ensure that NoCloud can be detected
    if ImageSpecification.from_os_image().release == "jammy":
        # Add nocloud-net seed files because Jammy no longer delivers NoCloud
        # (LP: #1958460).
        client.execute("mkdir -p /var/lib/cloud/seed/nocloud-net")
        client.write_to_file("/var/lib/cloud/seed/nocloud-net/meta-data", "")
        client.write_to_file(
            "/var/lib/cloud/seed/nocloud-net/user-data", "#cloud-config\n{}"
        )
    client.execute("cloud-init clean --logs")
    client.restart()


@pytest.mark.lxd_container
@pytest.mark.lxd_vm
@pytest.mark.ubuntu  # Because netplan
def test_lxd_datasource_discovery(client: IntegrationInstance):
    """Test that DataSourceLXD is detected instead of NoCloud."""

    _customize_environment(client)
    result = client.execute("cloud-init status --wait --long")
    if not result.ok:
        raise AssertionError("cloud-init failed:\n%s", result.stderr)
    if "DataSourceLXD" not in result.stdout:
        raise AssertionError(
            "cloud-init did not discover DataSourceLXD", result.stdout
        )
    netplan_yaml = client.execute("cat /etc/netplan/50-cloud-init.yaml")
    netplan_cfg = yaml.safe_load(netplan_yaml)

    platform = client.settings.PLATFORM
    nic_dev = "eth0" if platform == "lxd_container" else "enp5s0"
    assert {
        "network": {"ethernets": {nic_dev: {"dhcp4": True}}, "version": 2}
    } == netplan_cfg
    log = client.read_from_file("/var/log/cloud-init.log")
    verify_clean_log(log)
    result = client.execute("cloud-id")
    if result.stdout != "lxd":
        raise AssertionError(
            "cloud-id didn't report lxd. Result: %s", result.stdout
        )
    # Validate config instance data represented
    data = json.loads(
        client.read_from_file("/run/cloud-init/instance-data.json")
    )
    v1 = data["v1"]
    ds_cfg = data["ds"]
    assert "lxd" == v1["platform"]
    assert "LXD socket API v. 1.0 (/dev/lxd/sock)" == v1["subplatform"]
    ds_cfg = json.loads(client.execute("cloud-init query ds").stdout)
    assert [
        "_doc",
        "_metadata_api_version",
        "config",
        "devices",
        "meta-data",
    ] == sorted(list(ds_cfg.keys()))
    if (
        client.settings.PLATFORM == "lxd_vm"
        and ImageSpecification.from_os_image().release == "bionic"
    ):
        # pycloudlib injects user.vendor_data for lxd_vm on bionic
        # to start the lxd-agent.
        # https://github.com/canonical/pycloudlib/blob/main/pycloudlib/\
        #    lxd/defaults.py#L13-L27
        # Underscore-delimited aliases exist for any keys containing hyphens or
        # dots.
        lxd_config_keys = ["user.meta-data", "user.vendor-data"]
    else:
        lxd_config_keys = ["user.meta-data"]
    assert "1.0" == ds_cfg["_metadata_api_version"]
    assert lxd_config_keys == list(ds_cfg["config"].keys())
    assert {"public-keys": v1["public_ssh_keys"][0]} == (
        yaml.safe_load(ds_cfg["config"]["user.meta-data"])
    )
    assert "#cloud-config\ninstance-id" in ds_cfg["meta-data"]

    # Some series no longer provide nocloud-net seed files (LP: #1958460)
    if lxd_has_nocloud(client):
        # Assert NoCloud seed files are still present in non-Jammy images
        # and that NoCloud seed files provide the same content as LXD socket.
        nocloud_metadata = yaml.safe_load(
            client.read_from_file("/var/lib/cloud/seed/nocloud-net/meta-data")
        )
        assert client.instance.name == nocloud_metadata["instance-id"]
        assert (
            nocloud_metadata["instance-id"]
            == nocloud_metadata["local-hostname"]
        )
        assert v1["public_ssh_keys"][0] == nocloud_metadata["public-keys"]