Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions cloudinit/config/schemas/schema-network-config-v1.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@
"accept-ra": {
"type": "boolean",
"description": "Whether to accept IPv6 Router Advertisements (RA) on this interface. If unset, it will not be rendered"
},
"keep_configuration": {
"type": "boolean",
"description": "Designate the connection as 'critical to the system', meaning that special care will be taken not to release the assigned IP when the daemon is restarted. (only recognized by Netplan renderer)."
}
}
},
Expand Down
2 changes: 2 additions & 0 deletions cloudinit/net/netplan.py
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ def _render_content(self, network_state: NetworkState) -> str:
"set-name": ifname,
"match": ifcfg.get("match", None),
}
if "keep_configuration" in ifcfg:
eth["critical"] = ifcfg["keep_configuration"]
if eth["match"] is None:
macaddr = ifcfg.get("mac_address", None)
if macaddr is not None:
Expand Down
1 change: 1 addition & 0 deletions cloudinit/net/network_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -428,6 +428,7 @@ def handle_physical(self, command):
"accept-ra": accept_ra,
"wakeonlan": wakeonlan,
"optional": optional,
"keep_configuration": command.get("keep_configuration"),
}
)
iface_key = command.get("config_id", command.get("name"))
Expand Down
100 changes: 41 additions & 59 deletions cloudinit/sources/DataSourceOracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -293,9 +293,15 @@ def network_config(self):
return self._network_config

set_primary = False
# this is v1
if self._is_iscsi_root():
self._network_config = self._get_iscsi_config()
logging.debug(
"Instance is using iSCSI root, setting primary NIC as critical"
)
# This is necessary for Oracle baremetal instances in case they are
# running on an IPv6-only network. Without this, they become
# unreachable/unrecoverable after a shutdown.
self._network_config["config"][0]["keep_configuration"] = True
if not self._has_network_config():
LOG.debug(
"Could not obtain network configuration from initramfs. "
Expand Down Expand Up @@ -380,65 +386,41 @@ def _add_network_config_from_opc_imds(self, set_primary: bool = False):
else:
network = ipaddress.ip_network(vnic_dict["subnetCidrBlock"])

if self._network_config["version"] == 1:
if is_primary:
if is_ipv6_only:
subnets = [{"type": "dhcp6"}]
else:
subnets = [{"type": "dhcp"}]
if is_primary:
if is_ipv6_only:
subnets = [{"type": "dhcp6"}]
else:
subnets = []
if vnic_dict.get("privateIp"):
subnets.append(
{
"type": "static",
"address": (
f"{vnic_dict['privateIp']}/"
f"{network.prefixlen}"
),
}
)
if vnic_dict.get("ipv6Addresses"):
subnets.append(
{
"type": "static",
"address": (
f"{vnic_dict['ipv6Addresses'][0]}/"
f"{network.prefixlen}"
),
}
)
interface_config = {
"name": name,
"type": "physical",
"mac_address": mac_address,
"mtu": MTU,
"subnets": subnets,
}
self._network_config["config"].append(interface_config)
elif self._network_config["version"] == 2:
# Why does this elif exist???
# Are there plans to switch to v2?
interface_config = {
"mtu": MTU,
"match": {"macaddress": mac_address},
}
self._network_config["ethernets"][name] = interface_config

interface_config["dhcp6"] = is_primary and is_ipv6_only
interface_config["dhcp4"] = is_primary and not is_ipv6_only
if not is_primary:
interface_config["addresses"] = []
if vnic_dict.get("privateIp"):
interface_config["addresses"].append(
f"{vnic_dict['privateIp']}/{network.prefixlen}"
)
if vnic_dict.get("ipv6Addresses"):
interface_config["addresses"].append(
f"{vnic_dict['ipv6Addresses'][0]}/"
f"{network.prefixlen}"
)
self._network_config["ethernets"][name] = interface_config
subnets = [{"type": "dhcp"}]
else:
subnets = []
if vnic_dict.get("privateIp"):
subnets.append(
{
"type": "static",
"address": (
f"{vnic_dict['privateIp']}/"
f"{network.prefixlen}"
),
}
)
if vnic_dict.get("ipv6Addresses"):
subnets.append(
{
"type": "static",
"address": (
f"{vnic_dict['ipv6Addresses'][0]}/"
f"{network.prefixlen}"
),
}
)
interface_config = {
"name": name,
"type": "physical",
"mac_address": mac_address,
"mtu": MTU,
"subnets": subnets,
}
self._network_config["config"].append(interface_config)


class DataSourceOracleNet(DataSourceOracle):
Expand Down
9 changes: 9 additions & 0 deletions doc/rtd/reference/network-config-format-v1.rst
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,15 @@ Physical example
.. literalinclude:: ../../examples/network-config-v1-physical-3-nic.yaml
:language: yaml

``keep_configuration: <boolean>``
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Designate the connection as 'critical to the system', meaning that special care
will be taken not to release the assigned IP when the daemon is restarted.

.. note::
This is only recognized by Netplan renderer.

Bond
----

Expand Down
47 changes: 47 additions & 0 deletions tests/integration_tests/datasources/test_oci_networking.py
Original file line number Diff line number Diff line change
Expand Up @@ -157,3 +157,50 @@ def test_oci_networking_system_cfg(client: IntegrationInstance, tmpdir):
netplan_cfg = yaml.safe_load(netplan_yaml)
expected_netplan_cfg = yaml.safe_load(SYSTEM_CFG)
assert expected_netplan_cfg == netplan_cfg


@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific")
def test_oci_keep_configuration_networking_config(
session_cloud: IntegrationCloud,
):
"""
Test to ensure the keep_configuration is applied on Oracle ISCSI instances.

This test launches a Baremetal OCI instance so that ISCSI is used, and
checks that the primary systemd network configuration file contains the
'KeepConfiguration=true' directive, which indicates that the network
configuration is preserved as expected.

Assertions:
- At least one netplan file exists under '/run/systemd/network'.
- The primary systemd network configuration file includes the
'KeepConfiguration=true' directive.
- The netplan configuration includes the 'critical: true' directive.
"""
with session_cloud.launch(
launch_kwargs={
"instance_type": "BM.Optimized3.36",
},
) as client:
r = client.execute("ls /run/systemd/network/10-netplan-*.network")
assert r.ok, (
"No netplan files found under /run/systemd/network. We are looking"
" for netplan files here to check that the underlying "
"'KeepConfiguration=true' directive is actually being applied to "
"the systemd network configuration."
)
primary_systemd_file: str = r.stdout.strip().splitlines()[0]
systemd_config = client.read_from_file(primary_systemd_file)
assert (
"KeepConfiguration=true" in systemd_config
or "CriticalConnection=true" in systemd_config
), (
f"Neither 'KeepConfiguration=true' nor 'CriticalConnection=true' "
f"found in '{primary_systemd_file}':\n{primary_systemd_file}"
)
netplan_config = client.read_from_file(
"/etc/netplan/50-cloud-init.yaml",
)
assert (
"critical: true" in netplan_config
), "critical: true not found in netplan config"
89 changes: 0 additions & 89 deletions tests/unittests/sources/test_oracle.py
Original file line number Diff line number Diff line change
Expand Up @@ -485,48 +485,6 @@ def test_imds_nic_setup_v1(self, set_primary, oracle_ds):
assert "10.0.0.231/24" == secondary_cfg["subnets"][0]["address"]
assert "static" == secondary_cfg["subnets"][0]["type"]

@pytest.mark.parametrize(
"set_primary",
[True, False],
)
def test_secondary_nic_v2(self, set_primary, oracle_ds):
oracle_ds._vnics_data = json.loads(OPC_VM_SECONDARY_VNIC_RESPONSE)
oracle_ds._network_config = {
"version": 2,
"ethernets": {"primary": {"nic": {}}},
}
with mock.patch(
f"{DS_PATH}.get_interfaces_by_mac",
return_value={
"02:00:17:05:d1:db": "ens3",
"00:00:17:02:2b:b1": "ens4",
},
):
oracle_ds._add_network_config_from_opc_imds(
set_primary=set_primary
)

nic_cfg = oracle_ds.network_config["ethernets"]
if set_primary:
assert "ens3" in nic_cfg
primary_cfg = nic_cfg["ens3"]

assert primary_cfg["dhcp4"] is True
assert primary_cfg["dhcp6"] is False
assert "02:00:17:05:d1:db" == primary_cfg["match"]["macaddress"]
assert 9000 == primary_cfg["mtu"]
assert "addresses" not in primary_cfg

assert "ens4" in nic_cfg
secondary_cfg = nic_cfg["ens4"]
assert secondary_cfg["dhcp4"] is False
assert secondary_cfg["dhcp6"] is False
assert "00:00:17:02:2b:b1" == secondary_cfg["match"]["macaddress"]
assert 9000 == secondary_cfg["mtu"]

assert 1 == len(secondary_cfg["addresses"])
assert "10.0.0.231/24" == secondary_cfg["addresses"][0]

@pytest.mark.parametrize(
"set_primary",
[
Expand Down Expand Up @@ -578,53 +536,6 @@ def test_imds_nic_setup_v1_ipv6_only(self, set_primary, oracle_ds):
)
assert "static" == secondary_cfg["subnets"][0]["type"]

@pytest.mark.parametrize(
"set_primary",
[True, False],
)
def test_secondary_nic_v2_ipv6_only(self, set_primary, oracle_ds):
oracle_ds._vnics_data = json.loads(
OPC_VM_IPV6_ONLY_SECONDARY_VNIC_RESPONSE
)
oracle_ds._network_config = {
"version": 2,
"ethernets": {"primary": {"nic": {}}},
}
with mock.patch(
f"{DS_PATH}.get_interfaces_by_mac",
return_value={
"02:00:17:0d:6b:be": "ens3",
"02:00:17:18:f6:ff": "ens4",
},
):
oracle_ds._add_network_config_from_opc_imds(
set_primary=set_primary
)

nic_cfg = oracle_ds.network_config["ethernets"]
if set_primary:
assert "ens3" in nic_cfg
primary_cfg = nic_cfg["ens3"]

assert primary_cfg["dhcp4"] is False
assert primary_cfg["dhcp6"] is True
assert "02:00:17:0d:6b:be" == primary_cfg["match"]["macaddress"]
assert 9000 == primary_cfg["mtu"]
assert "addresses" not in primary_cfg

assert "ens4" in nic_cfg
secondary_cfg = nic_cfg["ens4"]
assert secondary_cfg["dhcp4"] is False
assert secondary_cfg["dhcp6"] is False
assert "02:00:17:18:f6:ff" == secondary_cfg["match"]["macaddress"]
assert 9000 == secondary_cfg["mtu"]

assert 1 == len(secondary_cfg["addresses"])
assert (
"2603:c020:400d:5d7e:aacc:8e5f:3b1b:3a4a/128"
== secondary_cfg["addresses"][0]
)

@pytest.mark.parametrize("error_add_network", [None, Exception])
@pytest.mark.parametrize(
"configure_secondary_nics",
Expand Down