Skip to content

Commit 8057b7a

Browse files
committed
feat(oracle): set keep_configuration to true for iscsi instances
This is necessary for Oracle Baremetal instances on IPv6-only networks which rely on the iscsi connection. Without this, after shutting down the instance, the instance is not recoverable. This setting will be automatically set for all iscsi instances via the Oracle Datasource based on metadata/config files left behind by initramfs during boot on ISCSI instances. PR: #6097
1 parent 62ea4a8 commit 8057b7a

File tree

3 files changed

+88
-148
lines changed

3 files changed

+88
-148
lines changed

cloudinit/sources/DataSourceOracle.py

Lines changed: 41 additions & 59 deletions
Original file line numberDiff line numberDiff line change
@@ -293,9 +293,15 @@ def network_config(self):
293293
return self._network_config
294294

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

383-
if self._network_config["version"] == 1:
384-
if is_primary:
385-
if is_ipv6_only:
386-
subnets = [{"type": "dhcp6"}]
387-
else:
388-
subnets = [{"type": "dhcp"}]
389+
if is_primary:
390+
if is_ipv6_only:
391+
subnets = [{"type": "dhcp6"}]
389392
else:
390-
subnets = []
391-
if vnic_dict.get("privateIp"):
392-
subnets.append(
393-
{
394-
"type": "static",
395-
"address": (
396-
f"{vnic_dict['privateIp']}/"
397-
f"{network.prefixlen}"
398-
),
399-
}
400-
)
401-
if vnic_dict.get("ipv6Addresses"):
402-
subnets.append(
403-
{
404-
"type": "static",
405-
"address": (
406-
f"{vnic_dict['ipv6Addresses'][0]}/"
407-
f"{network.prefixlen}"
408-
),
409-
}
410-
)
411-
interface_config = {
412-
"name": name,
413-
"type": "physical",
414-
"mac_address": mac_address,
415-
"mtu": MTU,
416-
"subnets": subnets,
417-
}
418-
self._network_config["config"].append(interface_config)
419-
elif self._network_config["version"] == 2:
420-
# Why does this elif exist???
421-
# Are there plans to switch to v2?
422-
interface_config = {
423-
"mtu": MTU,
424-
"match": {"macaddress": mac_address},
425-
}
426-
self._network_config["ethernets"][name] = interface_config
427-
428-
interface_config["dhcp6"] = is_primary and is_ipv6_only
429-
interface_config["dhcp4"] = is_primary and not is_ipv6_only
430-
if not is_primary:
431-
interface_config["addresses"] = []
432-
if vnic_dict.get("privateIp"):
433-
interface_config["addresses"].append(
434-
f"{vnic_dict['privateIp']}/{network.prefixlen}"
435-
)
436-
if vnic_dict.get("ipv6Addresses"):
437-
interface_config["addresses"].append(
438-
f"{vnic_dict['ipv6Addresses'][0]}/"
439-
f"{network.prefixlen}"
440-
)
441-
self._network_config["ethernets"][name] = interface_config
393+
subnets = [{"type": "dhcp"}]
394+
else:
395+
subnets = []
396+
if vnic_dict.get("privateIp"):
397+
subnets.append(
398+
{
399+
"type": "static",
400+
"address": (
401+
f"{vnic_dict['privateIp']}/"
402+
f"{network.prefixlen}"
403+
),
404+
}
405+
)
406+
if vnic_dict.get("ipv6Addresses"):
407+
subnets.append(
408+
{
409+
"type": "static",
410+
"address": (
411+
f"{vnic_dict['ipv6Addresses'][0]}/"
412+
f"{network.prefixlen}"
413+
),
414+
}
415+
)
416+
interface_config = {
417+
"name": name,
418+
"type": "physical",
419+
"mac_address": mac_address,
420+
"mtu": MTU,
421+
"subnets": subnets,
422+
}
423+
self._network_config["config"].append(interface_config)
442424

443425

444426
class DataSourceOracleNet(DataSourceOracle):

tests/integration_tests/datasources/test_oci_networking.py

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,3 +157,50 @@ def test_oci_networking_system_cfg(client: IntegrationInstance, tmpdir):
157157
netplan_cfg = yaml.safe_load(netplan_yaml)
158158
expected_netplan_cfg = yaml.safe_load(SYSTEM_CFG)
159159
assert expected_netplan_cfg == netplan_cfg
160+
161+
162+
@pytest.mark.skipif(PLATFORM != "oci", reason="Test is OCI specific")
163+
def test_oci_keep_configuration_networking_config(
164+
session_cloud: IntegrationCloud,
165+
):
166+
"""
167+
Test to ensure the keep_configuration is applied on Oracle ISCSI instances.
168+
169+
This test launches a Baremetal OCI instance so that ISCSI is used, and
170+
checks that the primary systemd network configuration file contains the
171+
'KeepConfiguration=true' directive, which indicates that the network
172+
configuration is preserved as expected.
173+
174+
Assertions:
175+
- At least one netplan file exists under '/run/systemd/network'.
176+
- The primary systemd network configuration file includes the
177+
'KeepConfiguration=true' directive.
178+
- The netplan configuration includes the 'critical: true' directive.
179+
"""
180+
with session_cloud.launch(
181+
launch_kwargs={
182+
"instance_type": "BM.Optimized3.36",
183+
},
184+
) as client:
185+
r = client.execute("ls /run/systemd/network/10-netplan-*.network")
186+
assert r.ok, (
187+
"No netplan files found under /run/systemd/network. We are looking"
188+
" for netplan files here to check that the underlying "
189+
"'KeepConfiguration=true' directive is actually being applied to "
190+
"the systemd network configuration."
191+
)
192+
primary_systemd_file: str = r.stdout.strip().splitlines()[0]
193+
systemd_config = client.read_from_file(primary_systemd_file)
194+
assert (
195+
"KeepConfiguration=true" in systemd_config
196+
or "CriticalConnection=true" in systemd_config
197+
), (
198+
f"Neither 'KeepConfiguration=true' nor 'CriticalConnection=true' "
199+
f"found in '{primary_systemd_file}':\n{primary_systemd_file}"
200+
)
201+
netplan_config = client.read_from_file(
202+
"/etc/netplan/50-cloud-init.yaml",
203+
)
204+
assert (
205+
"critical: true" in netplan_config
206+
), "critical: true not found in netplan config"

tests/unittests/sources/test_oracle.py

Lines changed: 0 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -485,48 +485,6 @@ def test_imds_nic_setup_v1(self, set_primary, oracle_ds):
485485
assert "10.0.0.231/24" == secondary_cfg["subnets"][0]["address"]
486486
assert "static" == secondary_cfg["subnets"][0]["type"]
487487

488-
@pytest.mark.parametrize(
489-
"set_primary",
490-
[True, False],
491-
)
492-
def test_secondary_nic_v2(self, set_primary, oracle_ds):
493-
oracle_ds._vnics_data = json.loads(OPC_VM_SECONDARY_VNIC_RESPONSE)
494-
oracle_ds._network_config = {
495-
"version": 2,
496-
"ethernets": {"primary": {"nic": {}}},
497-
}
498-
with mock.patch(
499-
f"{DS_PATH}.get_interfaces_by_mac",
500-
return_value={
501-
"02:00:17:05:d1:db": "ens3",
502-
"00:00:17:02:2b:b1": "ens4",
503-
},
504-
):
505-
oracle_ds._add_network_config_from_opc_imds(
506-
set_primary=set_primary
507-
)
508-
509-
nic_cfg = oracle_ds.network_config["ethernets"]
510-
if set_primary:
511-
assert "ens3" in nic_cfg
512-
primary_cfg = nic_cfg["ens3"]
513-
514-
assert primary_cfg["dhcp4"] is True
515-
assert primary_cfg["dhcp6"] is False
516-
assert "02:00:17:05:d1:db" == primary_cfg["match"]["macaddress"]
517-
assert 9000 == primary_cfg["mtu"]
518-
assert "addresses" not in primary_cfg
519-
520-
assert "ens4" in nic_cfg
521-
secondary_cfg = nic_cfg["ens4"]
522-
assert secondary_cfg["dhcp4"] is False
523-
assert secondary_cfg["dhcp6"] is False
524-
assert "00:00:17:02:2b:b1" == secondary_cfg["match"]["macaddress"]
525-
assert 9000 == secondary_cfg["mtu"]
526-
527-
assert 1 == len(secondary_cfg["addresses"])
528-
assert "10.0.0.231/24" == secondary_cfg["addresses"][0]
529-
530488
@pytest.mark.parametrize(
531489
"set_primary",
532490
[
@@ -578,53 +536,6 @@ def test_imds_nic_setup_v1_ipv6_only(self, set_primary, oracle_ds):
578536
)
579537
assert "static" == secondary_cfg["subnets"][0]["type"]
580538

581-
@pytest.mark.parametrize(
582-
"set_primary",
583-
[True, False],
584-
)
585-
def test_secondary_nic_v2_ipv6_only(self, set_primary, oracle_ds):
586-
oracle_ds._vnics_data = json.loads(
587-
OPC_VM_IPV6_ONLY_SECONDARY_VNIC_RESPONSE
588-
)
589-
oracle_ds._network_config = {
590-
"version": 2,
591-
"ethernets": {"primary": {"nic": {}}},
592-
}
593-
with mock.patch(
594-
f"{DS_PATH}.get_interfaces_by_mac",
595-
return_value={
596-
"02:00:17:0d:6b:be": "ens3",
597-
"02:00:17:18:f6:ff": "ens4",
598-
},
599-
):
600-
oracle_ds._add_network_config_from_opc_imds(
601-
set_primary=set_primary
602-
)
603-
604-
nic_cfg = oracle_ds.network_config["ethernets"]
605-
if set_primary:
606-
assert "ens3" in nic_cfg
607-
primary_cfg = nic_cfg["ens3"]
608-
609-
assert primary_cfg["dhcp4"] is False
610-
assert primary_cfg["dhcp6"] is True
611-
assert "02:00:17:0d:6b:be" == primary_cfg["match"]["macaddress"]
612-
assert 9000 == primary_cfg["mtu"]
613-
assert "addresses" not in primary_cfg
614-
615-
assert "ens4" in nic_cfg
616-
secondary_cfg = nic_cfg["ens4"]
617-
assert secondary_cfg["dhcp4"] is False
618-
assert secondary_cfg["dhcp6"] is False
619-
assert "02:00:17:18:f6:ff" == secondary_cfg["match"]["macaddress"]
620-
assert 9000 == secondary_cfg["mtu"]
621-
622-
assert 1 == len(secondary_cfg["addresses"])
623-
assert (
624-
"2603:c020:400d:5d7e:aacc:8e5f:3b1b:3a4a/128"
625-
== secondary_cfg["addresses"][0]
626-
)
627-
628539
@pytest.mark.parametrize("error_add_network", [None, Exception])
629540
@pytest.mark.parametrize(
630541
"configure_secondary_nics",

0 commit comments

Comments
 (0)