chore: Switch to new images (#830)

Co-authored-by: Oleksandr K <okozachenko@vexxhost.com>
Co-authored-by: okozachenko1203 <okozachenko1203@users.noreply.github.com>
diff --git a/images/neutron/Earthfile b/images/neutron/Earthfile
new file mode 100644
index 0000000..e8fbf6d
--- /dev/null
+++ b/images/neutron/Earthfile
@@ -0,0 +1,21 @@
+VERSION 0.7
+
+platform-image:
+  ARG PROJECT=neutron
+  ARG RELEASE=zed
+  ARG REF=4575136fe99f67dc140987601c90493cf62c0330
+  FROM ../openstack-service+image \
+    --PROJECT ${PROJECT} \
+    --RELEASE ${RELEASE} \
+    --PROJECT_REF ${REF} \
+    --PIP_PACKAGES "git+https://github.com/openstack/neutron-vpnaas.git@256464aea691f8b4957ba668a117963353f34e4c"
+  DO \
+    ../+APT_INSTALL \
+    --PACKAGES "conntrack dnsmasq dnsmasq-utils ebtables ethtool haproxy iproute2 ipset iptables iputils-arping jq keepalived lshw openvswitch-switch strongswan uuid-runtime"
+  DO ../+APPLY_PATCHES
+  SAVE IMAGE --push \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${RELEASE} \
+    ghcr.io/vexxhost/atmosphere/${PROJECT}:${REF}
+
+image:
+  BUILD --platform linux/amd64 --platform linux/arm64 +platform-image
diff --git a/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch b/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch
new file mode 100644
index 0000000..a1540ad
--- /dev/null
+++ b/images/neutron/patches/0000-fix-netns-deletion-of-broken-namespaces.patch
@@ -0,0 +1,138 @@
+From f8130f36e3cdb67fd9be64a61ac22b487200e2bc Mon Sep 17 00:00:00 2001
+From: Felix Huettner <felix.huettner@mail.schwarz>
+Date: Fri, 22 Sep 2023 16:25:10 +0200
+Subject: [PATCH] fix netns deletion of broken namespaces
+
+normal network namespaces are bind-mounted to files under
+/var/run/netns. If a process deleting a network namespace gets killed
+during that operation there is the chance that the bind mount to the
+netns has been removed, but the file under /var/run/netns still exists.
+
+When the neutron-ovn-metadata-agent tries to clean up such network
+namespaces it first tires to validate that the network namespace is
+empty. For the cases described above this fails, as this network
+namespace no longer really exists, but is just a stray file laying
+around.
+
+To fix this we treat network namespaces where we get an `OSError` with
+errno 22 (Invalid Argument) as empty. The calls to pyroute2 to delete
+the namespace will then clean up the file.
+
+Additionally we add a guard to teardown_datapath to continue even if
+this fails. failing to remove a datapath is not critical and leaves in
+the worst case a process and a network namespace running, however
+previously it would have also prevented the creation of new datapaths
+which is critical for VM startup.
+
+Closes-Bug: #2037102
+Change-Id: I7c43812fed5903f98a2e491076c24a8d926a59b4
+---
+
+diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py
+index 9953729..196dc17 100644
+--- a/neutron/agent/linux/ip_lib.py
++++ b/neutron/agent/linux/ip_lib.py
+@@ -259,7 +259,22 @@
+         return ip
+ 
+     def namespace_is_empty(self):
+-        return not self.get_devices()
++        try:
++            return not self.get_devices()
++        except OSError as e:
++            # This can happen if we previously got terminated in the middle of
++            # removing this namespace. In this case the bind mount of the
++            # namespace under /var/run/netns will be removed, but the namespace
++            # file is still there. As the bind mount is gone we can no longer
++            # access the namespace to validate that it is empty. But since it
++            # should have already been removed we are sure that the check has
++            # passed the last time and since the namespace is unuseable that
++            # can not have changed.
++            # Future calls to pyroute2 to remove that namespace will clean up
++            # the leftover file.
++            if e.errno == errno.EINVAL:
++                return True
++            raise e
+ 
+     def garbage_collect_namespace(self):
+         """Conditionally destroy the namespace if it is empty."""
+diff --git a/neutron/agent/ovn/metadata/agent.py b/neutron/agent/ovn/metadata/agent.py
+index 7a09145..888ab15 100644
+--- a/neutron/agent/ovn/metadata/agent.py
++++ b/neutron/agent/ovn/metadata/agent.py
+@@ -478,7 +478,10 @@
+                              ns.startswith(NS_PREFIX) and
+                              ns not in metadata_namespaces]
+         for ns in unused_namespaces:
+-            self.teardown_datapath(self._get_datapath_name(ns))
++            try:
++                self.teardown_datapath(self._get_datapath_name(ns))
++            except Exception:
++                LOG.exception('Error unable to destroy namespace: %s', ns)
+ 
+         # resync all network namespaces based on the associated datapaths,
+         # even those that are already running. This is to make sure
+diff --git a/neutron/tests/unit/agent/linux/test_ip_lib.py b/neutron/tests/unit/agent/linux/test_ip_lib.py
+index c488e90..3956142 100644
+--- a/neutron/tests/unit/agent/linux/test_ip_lib.py
++++ b/neutron/tests/unit/agent/linux/test_ip_lib.py
+@@ -357,6 +357,23 @@
+                 self.assertNotIn(mock.call().delete('ns'),
+                                  ip_ns_cmd_cls.mock_calls)
+ 
++    def test_garbage_collect_namespace_existing_broken(self):
++        with mock.patch.object(ip_lib, 'IpNetnsCommand') as ip_ns_cmd_cls:
++            ip_ns_cmd_cls.return_value.exists.return_value = True
++
++            ip = ip_lib.IPWrapper(namespace='ns')
++
++            with mock.patch.object(ip, 'get_devices',
++                                   side_effect=OSError(errno.EINVAL, None)
++                                   ) as mock_get_devices:
++                self.assertTrue(ip.garbage_collect_namespace())
++
++                mock_get_devices.assert_called_once_with()
++                expected = [mock.call(ip),
++                            mock.call().exists('ns'),
++                            mock.call().delete('ns')]
++                self.assertEqual(ip_ns_cmd_cls.mock_calls, expected)
++
+     @mock.patch.object(priv_lib, 'create_interface')
+     def test_add_vlan(self, create):
+         retval = ip_lib.IPWrapper().add_vlan('eth0.1', 'eth0', '1')
+diff --git a/neutron/tests/unit/agent/ovn/metadata/test_agent.py b/neutron/tests/unit/agent/ovn/metadata/test_agent.py
+index 73487e1..cb6ee43 100644
+--- a/neutron/tests/unit/agent/ovn/metadata/test_agent.py
++++ b/neutron/tests/unit/agent/ovn/metadata/test_agent.py
+@@ -138,6 +138,31 @@
+             lnn.assert_called_once_with()
+             tdp.assert_called_once_with('3')
+ 
++    def test_sync_teardown_namespace_does_not_crash_on_error(self):
++        """Test that sync tears down unneeded metadata namespaces.
++        Even if that fails it continues to provision other datapaths
++        """
++        with mock.patch.object(
++                self.agent, 'provision_datapath') as pdp,\
++                mock.patch.object(
++                    ip_lib, 'list_network_namespaces',
++                    return_value=['ovnmeta-1', 'ovnmeta-2', 'ovnmeta-3',
++                                  'ns1', 'ns2']) as lnn,\
++                mock.patch.object(
++                    self.agent, 'teardown_datapath',
++                    side_effect=Exception()) as tdp:
++            self.agent.sync()
++
++            pdp.assert_has_calls(
++                [
++                    mock.call(p.datapath)
++                    for p in self.ports
++                ],
++                any_order=True
++            )
++            lnn.assert_called_once_with()
++            tdp.assert_called_once_with('3')
++
+     def test_get_networks_datapaths(self):
+         """Test get_networks_datapaths returns only datapath objects for the
+         networks containing vif ports of type ''(blank) and 'external'.