diff --git a/etc/neutron/rootwrap.d/debug.filters b/etc/neutron/rootwrap.d/debug.filters
index 8d72ce2b1e3..8d7a2dc69e4 100644
--- a/etc/neutron/rootwrap.d/debug.filters
+++ b/etc/neutron/rootwrap.d/debug.filters
@@ -15,4 +15,8 @@
 ping: RegExpFilter, ping, root, ping, -w, \d+, -c, \d+, [0-9\.]+
 ping_alt: RegExpFilter, ping, root, ping, -c, \d+, -w, \d+, [0-9\.]+
 ping6: RegExpFilter, ping6, root, ping6, -w, \d+, -c, \d+, [0-9A-Fa-f:]+
-ping6_alt: RegExpFilter, ping6, root, ping6, -c, \d+, -w, \d+, [0-9A-Fa-f:]+
\ No newline at end of file
+ping6_alt: RegExpFilter, ping6, root, ping6, -c, \d+, -w, \d+, [0-9A-Fa-f:]+
+
+# "sleep" command, only for testing
+sleep: RegExpFilter, sleep, root, sleep, \d+
+kill_sleep: KillFilter, root, sleep, -9
diff --git a/neutron/agent/linux/ip_lib.py b/neutron/agent/linux/ip_lib.py
index 7e008b67e11..81d0acca866 100644
--- a/neutron/agent/linux/ip_lib.py
+++ b/neutron/agent/linux/ip_lib.py
@@ -932,6 +932,15 @@ def network_namespace_exists(namespace, try_is_ready=False, **kwargs):
     return False
 
 
+def list_namespace_pids(namespace):
+    """List namespace process PIDs
+
+    :param namespace: (string) the name of the namespace
+    :return: (tuple)
+    """
+    return privileged.list_ns_pids(namespace)
+
+
 def ensure_device_is_ready(device_name, namespace=None):
     dev = IPDevice(device_name, namespace=namespace)
     try:
diff --git a/neutron/privileged/__init__.py b/neutron/privileged/__init__.py
index 43bb3c2228e..296dba5c777 100644
--- a/neutron/privileged/__init__.py
+++ b/neutron/privileged/__init__.py
@@ -25,5 +25,6 @@ default = priv_context.PrivContext(
     capabilities=[caps.CAP_SYS_ADMIN,
                   caps.CAP_NET_ADMIN,
                   caps.CAP_DAC_OVERRIDE,
-                  caps.CAP_DAC_READ_SEARCH],
+                  caps.CAP_DAC_READ_SEARCH,
+                  caps.CAP_SYS_PTRACE],
 )
diff --git a/neutron/privileged/agent/linux/ip_lib.py b/neutron/privileged/agent/linux/ip_lib.py
index 65122b32ea3..e4c58bf8929 100644
--- a/neutron/privileged/agent/linux/ip_lib.py
+++ b/neutron/privileged/agent/linux/ip_lib.py
@@ -187,6 +187,12 @@ def open_namespace(namespace):
         pass
 
 
+@privileged.default.entrypoint
+def list_ns_pids(namespace):
+    """List namespace process PIDs"""
+    return netns.ns_pids().get(namespace, [])
+
+
 def _translate_ip_device_exception(e, device=None, namespace=None):
     if e.code == errno.ENODEV:
         raise NetworkInterfaceNotFound(device=device, namespace=namespace)
diff --git a/neutron/tests/common/net_helpers.py b/neutron/tests/common/net_helpers.py
index 5a4c9110bb5..4f274935448 100644
--- a/neutron/tests/common/net_helpers.py
+++ b/neutron/tests/common/net_helpers.py
@@ -585,6 +585,8 @@ class NamespaceFixture(fixtures.Fixture):
 
     def destroy(self):
         if self.ip_wrapper.netns.exists(self.name):
+            for pid in ip_lib.list_namespace_pids(self.name):
+                utils.kill_process(pid, signal.SIGKILL, run_as_root=True)
             self.ip_wrapper.netns.delete(self.name)
 
 
diff --git a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
index 6ec96a96928..32fa9a9abff 100644
--- a/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
+++ b/neutron/tests/functional/privileged/agent/linux/test_ip_lib.py
@@ -12,6 +12,9 @@
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
+import functools
+
+import eventlet
 import netaddr
 from neutron_lib import constants as n_cons
 from oslo_utils import uuidutils
@@ -618,3 +621,35 @@ class GetLinkAttributesTestCase(functional_base.BaseSudoTestCase):
                          'alias', 'allmulticast', 'link_kind']
         attr = self.device.link.attributes
         self.assertSetEqual(set(expected_attr), set(attr.keys()))
+
+
+class ListNamespacePids(functional_base.BaseSudoTestCase):
+
+    def setUp(self):
+        super(ListNamespacePids, self).setUp()
+        self.namespace = self.useFixture(net_helpers.NamespaceFixture()).name
+
+    @staticmethod
+    def _run_sleep(namespace):
+        ip_wrapper = ip_lib.IPWrapper(namespace=namespace)
+        ip_wrapper.netns.execute(['sleep', '100'], check_exit_code=False)
+
+    def _check_pids(self, num_pids, namespace=None):
+        namespace = self.namespace if not namespace else namespace
+        self.pids = priv_ip_lib.list_ns_pids(namespace)
+        return len(self.pids) == num_pids
+
+    def test_list_namespace_pids(self):
+        eventlet.spawn_n(self._run_sleep, self.namespace)
+
+        try:
+            check_pids = functools.partial(self._check_pids, 1)
+            common_utils.wait_until_true(check_pids, timeout=5)
+        except common_utils.WaitTimeout:
+            self.fail('Process no found in namespace %s' % self.namespace)
+
+    def test_list_namespace_pids_nothing_running_inside(self):
+        self.assertTrue(self._check_pids(0))
+
+    def test_list_namespace_not_created(self):
+        self.assertTrue(self._check_pids(0, namespace='othernamespace'))