summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRodolfo Alonso Hernandez <ralonsoh@redhat.com>2020-06-11 13:26:56 +0000
committerRodolfo Alonso Hernandez <ralonsoh@redhat.com>2020-06-19 14:59:11 +0000
commit0c1818fbb02b5ff6a9a5157d4bbb58314242c806 (patch)
tree806afc3907093e68a7a6e0f7f1261b974eef52bd
parent2592fdb5844e14c352824081511d728345d40641 (diff)
downloadneutron-0c1818fbb02b5ff6a9a5157d4bbb58314242c806.tar.gz
Migrate "netstat" to oslo.privsep
Change-Id: If9e4c1513553c4bd10fd3b91c28c4d3f806ed816 Story: #2007686 Task: #40047
-rw-r--r--etc/neutron/rootwrap.d/netns-cleanup.filters12
-rw-r--r--neutron/cmd/netns_cleanup.py22
-rw-r--r--neutron/privileged/agent/linux/utils.py42
-rw-r--r--neutron/tests/functional/privileged/agent/linux/test_utils.py39
-rw-r--r--neutron/tests/unit/cmd/test_netns_cleanup.py58
-rw-r--r--neutron/tests/unit/privileged/agent/linux/test_utils.py76
6 files changed, 162 insertions, 87 deletions
diff --git a/etc/neutron/rootwrap.d/netns-cleanup.filters b/etc/neutron/rootwrap.d/netns-cleanup.filters
deleted file mode 100644
index 1ee142e54c..0000000000
--- a/etc/neutron/rootwrap.d/netns-cleanup.filters
+++ /dev/null
@@ -1,12 +0,0 @@
-# neutron-rootwrap command filters for nodes on which neutron is
-# expected to control network
-#
-# This file should be owned by (and only-writeable by) the root user
-
-# format seems to be
-# cmd-name: filter-name, raw-command, user, args
-
-[Filters]
-
-# netns-cleanup
-netstat: CommandFilter, netstat, root
diff --git a/neutron/cmd/netns_cleanup.py b/neutron/cmd/netns_cleanup.py
index 4c38cd222f..a12987228a 100644
--- a/neutron/cmd/netns_cleanup.py
+++ b/neutron/cmd/netns_cleanup.py
@@ -35,6 +35,7 @@ from neutron.common import config
from neutron.conf.agent import cmd
from neutron.conf.agent import common as agent_config
from neutron.conf.agent import dhcp as dhcp_config
+from neutron.privileged.agent.linux import utils as priv_utils
LOG = logging.getLogger(__name__)
NS_PREFIXES = {
@@ -43,7 +44,6 @@ NS_PREFIXES = {
dvr_fip_ns.FIP_NS_PREFIX],
}
SIGTERM_WAITTIME = 10
-NETSTAT_PIDS_REGEX = re.compile(r'.* (?P<pid>\d{2,6})/.*')
class PidsInNamespaceException(Exception):
@@ -134,22 +134,6 @@ def unplug_device(device):
device.set_log_fail_as_error(orig_log_fail_as_error)
-def find_listen_pids_namespace(namespace):
- """Retrieve a list of pids of listening processes within the given netns.
-
- It executes netstat -nlp and returns a set of unique pairs
- """
- ip = ip_lib.IPWrapper(namespace=namespace)
- pids = set()
- cmd = ['netstat', '-nlp']
- output = ip.netns.execute(cmd, run_as_root=True)
- for line in output.splitlines():
- m = NETSTAT_PIDS_REGEX.match(line)
- if m:
- pids.add(m.group('pid'))
- return pids
-
-
def wait_until_no_listen_pids_namespace(namespace, timeout=SIGTERM_WAITTIME):
"""Poll listening processes within the given namespace.
@@ -164,7 +148,7 @@ def wait_until_no_listen_pids_namespace(namespace, timeout=SIGTERM_WAITTIME):
# previous command
start = end = time.time()
while end - start < timeout:
- if not find_listen_pids_namespace(namespace):
+ if not priv_utils.find_listen_pids_namespace(namespace):
return
time.sleep(1)
end = time.time()
@@ -179,7 +163,7 @@ def _kill_listen_processes(namespace, force=False):
then a SIGKILL will be issued to all parents and all their children. Also,
this function returns the number of listening processes.
"""
- pids = find_listen_pids_namespace(namespace)
+ pids = priv_utils.find_listen_pids_namespace(namespace)
pids_to_kill = {utils.find_fork_top_parent(pid) for pid in pids}
kill_signal = signal.SIGTERM
if force:
diff --git a/neutron/privileged/agent/linux/utils.py b/neutron/privileged/agent/linux/utils.py
new file mode 100644
index 0000000000..c038814ace
--- /dev/null
+++ b/neutron/privileged/agent/linux/utils.py
@@ -0,0 +1,42 @@
+# Copyright 2020 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import re
+
+from oslo_concurrency import processutils
+
+from neutron import privileged
+
+
+NETSTAT_PIDS_REGEX = re.compile(r'.* (?P<pid>\d{2,6})/.*')
+
+
+@privileged.default.entrypoint
+def find_listen_pids_namespace(namespace):
+ return _find_listen_pids_namespace(namespace)
+
+
+def _find_listen_pids_namespace(namespace):
+ """Retrieve a list of pids of listening processes within the given netns
+
+ This method is implemented separately to allow unit testing.
+ """
+ pids = set()
+ cmd = ['ip', 'netns', 'exec', namespace, 'netstat', '-nlp']
+ output = processutils.execute(*cmd)
+ for line in output[0].splitlines():
+ m = NETSTAT_PIDS_REGEX.match(line)
+ if m:
+ pids.add(m.group('pid'))
+ return list(pids)
diff --git a/neutron/tests/functional/privileged/agent/linux/test_utils.py b/neutron/tests/functional/privileged/agent/linux/test_utils.py
new file mode 100644
index 0000000000..ae316630b8
--- /dev/null
+++ b/neutron/tests/functional/privileged/agent/linux/test_utils.py
@@ -0,0 +1,39 @@
+# Copyright 2020 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from neutron.agent.linux import ip_lib
+from neutron.privileged.agent.linux import utils as priv_utils
+from neutron.tests.common import net_helpers
+from neutron.tests.functional import base as functional_base
+
+
+class FindListenPidsNamespaceTestCase(functional_base.BaseSudoTestCase):
+
+ def test_find_listen_pids_namespace(self):
+ ns = self.useFixture(net_helpers.NamespaceFixture()).name
+ ip_wrapper = ip_lib.IPWrapper(namespace=ns)
+ ip_wrapper.add_dummy('device')
+ device = ip_lib.IPDevice('device', namespace=ns)
+ device.addr.add('10.20.30.40/24')
+ device.link.set_up()
+
+ self.assertEqual(tuple(), priv_utils.find_listen_pids_namespace(ns))
+
+ netcat = net_helpers.NetcatTester(ns, ns, '10.20.30.40', 12345, 'udp')
+ proc = netcat.server_process
+ self.assertEqual((str(proc.child_pid), ),
+ priv_utils.find_listen_pids_namespace(ns))
+
+ netcat.stop_processes()
+ self.assertEqual(tuple(), priv_utils.find_listen_pids_namespace(ns))
diff --git a/neutron/tests/unit/cmd/test_netns_cleanup.py b/neutron/tests/unit/cmd/test_netns_cleanup.py
index 3753b7d945..f2408827dc 100644
--- a/neutron/tests/unit/cmd/test_netns_cleanup.py
+++ b/neutron/tests/unit/cmd/test_netns_cleanup.py
@@ -19,41 +19,9 @@ from unittest import mock
import testtools
from neutron.cmd import netns_cleanup as util
+from neutron.privileged.agent.linux import utils as priv_utils
from neutron.tests import base
-NETSTAT_NETNS_OUTPUT = ("""
-Active Internet connections (only servers)
-Proto Recv-Q Send-Q Local Address Foreign Address State\
- PID/Program name
-tcp 0 0 0.0.0.0:9697 0.0.0.0:* LISTEN\
- 1347/python
-raw 0 0 0.0.0.0:112 0.0.0.0:* 7\
- 1279/keepalived
-raw 0 0 0.0.0.0:112 0.0.0.0:* 7\
- 1279/keepalived
-raw6 0 0 :::58 :::* 7\
- 1349/radvd
-Active UNIX domain sockets (only servers)
-Proto RefCnt Flags Type State I-Node PID/Program name\
- Path
-unix 2 [ ACC ] STREAM LISTENING 82039530 1353/python\
- /tmp/rootwrap-VKSm8a/rootwrap.sock
-""")
-
-NETSTAT_NO_NAMESPACE = ("""
-Cannot open network namespace "qrouter-e6f206b2-4e8d-4597-a7e1-c3a20337e9c6":\
- No such file or directory
-""")
-
-NETSTAT_NO_LISTEN_PROCS = ("""
-Active Internet connections (only servers)
-Proto Recv-Q Send-Q Local Address Foreign Address State\
- PID/Program name
-Active UNIX domain sockets (only servers)
-Proto RefCnt Flags Type State I-Node PID/Program name\
- Path
-""")
-
class TestNetnsCleanup(base.BaseTestCase):
def setUp(self):
@@ -189,28 +157,6 @@ class TestNetnsCleanup(base.BaseTestCase):
self.assertEqual([], ovs_br_cls.mock_calls)
self.assertTrue(debug.called)
- def _test_find_listen_pids_namespace_helper(self, expected,
- netstat_output=None):
- with mock.patch('neutron.agent.linux.ip_lib.IPWrapper') as ip_wrap:
- ip_wrap.return_value.netns.execute.return_value = netstat_output
- observed = util.find_listen_pids_namespace(mock.ANY)
- self.assertEqual(expected, observed)
-
- def test_find_listen_pids_namespace_correct_output(self):
- expected = set(['1347', '1279', '1349', '1353'])
- self._test_find_listen_pids_namespace_helper(expected,
- NETSTAT_NETNS_OUTPUT)
-
- def test_find_listen_pids_namespace_no_procs(self):
- expected = set()
- self._test_find_listen_pids_namespace_helper(expected,
- NETSTAT_NO_LISTEN_PROCS)
-
- def test_find_listen_pids_namespace_no_namespace(self):
- expected = set()
- self._test_find_listen_pids_namespace_helper(expected,
- NETSTAT_NO_NAMESPACE)
-
def _test__kill_listen_processes_helper(self, pids, parents, children,
kills_expected, force):
def _get_element(dct, x):
@@ -234,7 +180,7 @@ class TestNetnsCleanup(base.BaseTestCase):
mocks['find_fork_top_parent'].side_effect = _find_parent
mocks['find_child_pids'].side_effect = _find_childs
- with mock.patch.object(util, 'find_listen_pids_namespace',
+ with mock.patch.object(priv_utils, 'find_listen_pids_namespace',
return_value=pids):
calls = []
for pid, sig in kills_expected:
diff --git a/neutron/tests/unit/privileged/agent/linux/test_utils.py b/neutron/tests/unit/privileged/agent/linux/test_utils.py
new file mode 100644
index 0000000000..6141731b28
--- /dev/null
+++ b/neutron/tests/unit/privileged/agent/linux/test_utils.py
@@ -0,0 +1,76 @@
+# Copyright 2020 Red Hat, Inc.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from unittest import mock
+
+from oslo_concurrency import processutils
+
+from neutron.privileged.agent.linux import utils as priv_utils
+from neutron.tests import base
+
+
+NETSTAT_NETNS_OUTPUT = ("""
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address Foreign Address State\
+ PID/Program name
+tcp 0 0 0.0.0.0:9697 0.0.0.0:* LISTEN\
+ 1347/python
+raw 0 0 0.0.0.0:112 0.0.0.0:* 7\
+ 1279/keepalived
+raw 0 0 0.0.0.0:112 0.0.0.0:* 7\
+ 1279/keepalived
+raw6 0 0 :::58 :::* 7\
+ 1349/radvd
+Active UNIX domain sockets (only servers)
+Proto RefCnt Flags Type State I-Node PID/Program name\
+ Path
+unix 2 [ ACC ] STREAM LISTENING 82039530 1353/python\
+ /tmp/rootwrap-VKSm8a/rootwrap.sock
+""")
+
+NETSTAT_NO_NAMESPACE = ("""
+Cannot open network namespace "qrouter-e6f206b2-4e8d-4597-a7e1-c3a20337e9c6":\
+ No such file or directory
+""")
+
+NETSTAT_NO_LISTEN_PROCS = ("""
+Active Internet connections (only servers)
+Proto Recv-Q Send-Q Local Address Foreign Address State\
+ PID/Program name
+Active UNIX domain sockets (only servers)
+Proto RefCnt Flags Type State I-Node PID/Program name\
+ Path
+""")
+
+
+class FindListenPidsNamespaceTestCase(base.BaseTestCase):
+
+ def _test_find_listen_pids_namespace_helper(self, expected,
+ netstat_output=None):
+ with mock.patch.object(processutils, 'execute') as mock_execute:
+ mock_execute.return_value = (netstat_output, mock.ANY)
+ observed = priv_utils._find_listen_pids_namespace(mock.ANY)
+ self.assertEqual(sorted(expected), sorted(observed))
+
+ def test_find_listen_pids_namespace_correct_output(self):
+ expected = ['1347', '1279', '1349', '1353']
+ self._test_find_listen_pids_namespace_helper(expected,
+ NETSTAT_NETNS_OUTPUT)
+
+ def test_find_listen_pids_namespace_no_procs(self):
+ self._test_find_listen_pids_namespace_helper([],
+ NETSTAT_NO_LISTEN_PROCS)
+
+ def test_find_listen_pids_namespace_no_namespace(self):
+ self._test_find_listen_pids_namespace_helper([], NETSTAT_NO_NAMESPACE)