From 417ee5515c3b4c15b454a10eb8ec694c7dfcf4a5 Mon Sep 17 00:00:00 2001
From: James Falcon <james.falcon@canonical.com>
Date: Thu, 23 May 2024 15:30:04 -0500
Subject: [PATCH] fix(ec2): Ensure metadata exists before configuring PBR
 (#5287)

Fixes GH-5283
---
 cloudinit/sources/DataSourceEc2.py  | 19 +++++--
 tests/unittests/sources/test_ec2.py | 88 +++++++++++++++++++++++++++++
 2 files changed, 102 insertions(+), 5 deletions(-)

--- a/cloudinit/sources/DataSourceEc2.py
+++ b/cloudinit/sources/DataSourceEc2.py
@@ -963,11 +963,23 @@ def _configure_policy_routing(
     @param: is_ipv4: Boolean indicating if we are acting over ipv4 or not.
     @param: table: Routing table id.
     """
+    if is_ipv4:
+        subnet_prefix_routes = nic_metadata.get("subnet-ipv4-cidr-block")
+        ips = nic_metadata.get("local-ipv4s")
+    else:
+        subnet_prefix_routes = nic_metadata.get("subnet-ipv6-cidr-blocks")
+        ips = nic_metadata.get("ipv6s")
+    if not (subnet_prefix_routes and ips):
+        LOG.debug(
+            "Not enough IMDS information to configure policy routing "
+            "for IPv%s",
+            "4" if is_ipv4 else "6",
+        )
+        return
+
     if not dev_config.get("routes"):
         dev_config["routes"] = []
     if is_ipv4:
-        subnet_prefix_routes = nic_metadata["subnet-ipv4-cidr-block"]
-        ips = nic_metadata["local-ipv4s"]
         try:
             lease = distro.dhcp_client.dhcp_discovery(nic_name, distro=distro)
             gateway = lease["routers"]
@@ -988,9 +1000,6 @@ def _configure_policy_routing(
                     "table": table,
                 },
             )
-    else:
-        subnet_prefix_routes = nic_metadata["subnet-ipv6-cidr-blocks"]
-        ips = nic_metadata["ipv6s"]
 
     subnet_prefix_routes = (
         [subnet_prefix_routes]
--- a/tests/unittests/sources/test_ec2.py
+++ b/tests/unittests/sources/test_ec2.py
@@ -194,6 +194,43 @@ NIC2_MD_IPV4_IPV6_MULTI_IP = {
     "vpc-ipv6-cidr-blocks": "2600:1f16:292:100::/56",
 }
 
+MULTI_NIC_V6_ONLY_MD = {
+    "macs": {
+        "02:6b:df:a2:4b:2b": {
+            "device-number": "1",
+            "interface-id": "eni-0669816d0cf606123",
+            "ipv6s": "2600:1f16:67f:f201:8d2e:4d1f:9e80:4ab9",
+            "local-hostname": "i-0951b6d0b66337123.us-east-2.compute.internal",
+            "mac": "02:6b:df:a2:4b:2b",
+            "owner-id": "483410185123",
+            "security-group-ids": "sg-0bf34e5c3cde1d123",
+            "security-groups": "default",
+            "subnet-id": "subnet-0903f279682c66123",
+            "subnet-ipv6-cidr-blocks": "2600:1f16:67f:f201:0:0:0:0/64",
+            "vpc-id": "vpc-0ac1befb8c824a123",
+            "vpc-ipv4-cidr-block": "192.168.0.0/20",
+            "vpc-ipv4-cidr-blocks": "192.168.0.0/20",
+            "vpc-ipv6-cidr-blocks": "2600:1f16:67f:f200:0:0:0:0/56",
+        },
+        "02:7c:03:b8:5c:af": {
+            "device-number": "0",
+            "interface-id": "eni-0f3cddb84c16e1123",
+            "ipv6s": "2600:1f16:67f:f201:6613:29a2:dbf7:2f1f",
+            "local-hostname": "i-0951b6d0b66337123.us-east-2.compute.internal",
+            "mac": "02:7c:03:b8:5c:af",
+            "owner-id": "483410185123",
+            "security-group-ids": "sg-0bf34e5c3cde1d123",
+            "security-groups": "default",
+            "subnet-id": "subnet-0903f279682c66123",
+            "subnet-ipv6-cidr-blocks": "2600:1f16:67f:f201:0:0:0:0/64",
+            "vpc-id": "vpc-0ac1befb8c824a123",
+            "vpc-ipv4-cidr-block": "192.168.0.0/20",
+            "vpc-ipv4-cidr-blocks": "192.168.0.0/20",
+            "vpc-ipv6-cidr-blocks": "2600:1f16:67f:f200:0:0:0:0/56",
+        },
+    }
+}
+
 SECONDARY_IP_METADATA_2018_09_24 = {
     "ami-id": "ami-0986c2ac728528ac2",
     "ami-launch-index": "0",
@@ -1396,6 +1433,57 @@ class TestConvertEc2MetadataNetworkConfi
             ),
         )
 
+    def test_convert_ec2_metadata_network_config_multi_nics_ipv6_only(self):
+        """Like above, but only ipv6s are present in metadata."""
+        macs_to_nics = {
+            "02:7c:03:b8:5c:af": "eth0",
+            "02:6b:df:a2:4b:2b": "eth1",
+        }
+        mac_data = copy.deepcopy(MULTI_NIC_V6_ONLY_MD)
+        network_metadata = {"interfaces": mac_data}
+        expected = {
+            "version": 2,
+            "ethernets": {
+                "eth0": {
+                    "dhcp4": True,
+                    "dhcp4-overrides": {"route-metric": 100},
+                    "dhcp6": True,
+                    "match": {"macaddress": "02:7c:03:b8:5c:af"},
+                    "set-name": "eth0",
+                    "dhcp6-overrides": {"route-metric": 100},
+                },
+                "eth1": {
+                    "dhcp4": True,
+                    "dhcp4-overrides": {
+                        "route-metric": 200,
+                        "use-routes": True,
+                    },
+                    "dhcp6": True,
+                    "match": {"macaddress": "02:6b:df:a2:4b:2b"},
+                    "set-name": "eth1",
+                    "routes": [
+                        {"to": "2600:1f16:67f:f201:0:0:0:0/64", "table": 101},
+                    ],
+                    "routing-policy": [
+                        {
+                            "from": "2600:1f16:67f:f201:8d2e:4d1f:9e80:4ab9",
+                            "table": 101,
+                        },
+                    ],
+                    "dhcp6-overrides": {
+                        "route-metric": 200,
+                        "use-routes": True,
+                    },
+                },
+            },
+        }
+        distro = mock.Mock()
+        distro.network_activator = activators.NetplanActivator
+        assert expected == ec2.convert_ec2_metadata_network_config(
+            network_metadata, distro, macs_to_nics
+        )
+        distro.dhcp_client.dhcp_discovery.assert_not_called()
+
     def test_convert_ec2_metadata_network_config_handles_dhcp4_and_dhcp6(self):
         """Config both dhcp4 and dhcp6 when both vpc-ipv6 and ipv4 exists."""
         macs_to_nics = {self.mac1: "eth9"}
