# Copyright (c) 2015 Red Hat, Inc. # All Rights Reserved. # # 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 os import sys from unittest import mock import eventlet from neutron_lib import constants as n_const from neutron.agent.l3 import namespaces from neutron.agent.linux import dhcp from neutron.agent.linux import ip_lib from neutron.agent.linux import utils from neutron.cmd import netns_cleanup from neutron.common import utils as common_utils from neutron.conf.agent import cmd from neutron.tests import base as basetest from neutron.tests.common import net_helpers from neutron.tests.functional import base from neutron.tests.functional.cmd import process_spawn GET_NAMESPACES = 'neutron.agent.linux.ip_lib.list_network_namespaces' TEST_INTERFACE_DRIVER = 'neutron.agent.linux.interface.OVSInterfaceDriver' NUM_SUBPROCESSES = 6 class NetnsCleanupTest(base.BaseSudoTestCase): def setUp(self): super(NetnsCleanupTest, self).setUp() self.get_namespaces_p = mock.patch(GET_NAMESPACES) self.get_namespaces = self.get_namespaces_p.start() def setup_config(self, args=None): if args is None: args = [] # force option enabled to make sure non-empty namespaces are # cleaned up and deleted args.append('--force') self.conf = netns_cleanup.setup_conf() self.conf.set_override('interface_driver', TEST_INTERFACE_DRIVER) self.config_parse(conf=self.conf, args=args) def test_cleanup_network_namespaces_cleans_dhcp_and_l3_namespaces(self): dhcp_namespace = self.useFixture( net_helpers.NamespaceFixture(dhcp.NS_PREFIX)).name l3_namespace = self.useFixture( net_helpers.NamespaceFixture(namespaces.NS_PREFIX)).name bridge = self.useFixture( net_helpers.VethPortFixture(namespace=dhcp_namespace)).bridge self.useFixture( net_helpers.VethPortFixture(bridge, l3_namespace)) # we scope the get_namespaces to our own ones not to affect other # tests, as otherwise cleanup will kill them all self.get_namespaces.return_value = [l3_namespace, dhcp_namespace] # launch processes in each namespace to make sure they're # killed during cleanup procs_launched = self._launch_processes([l3_namespace, dhcp_namespace]) self.assertIsNot(procs_launched, 0) try: common_utils.wait_until_true( lambda: self._get_num_spawned_procs() == procs_launched, timeout=15) except eventlet.Timeout: num_spawned_procs = self._get_num_spawned_procs() err_str = ("Expected number/spawned number: {0}/{1}\nProcess " "information:\n".format(num_spawned_procs, procs_launched)) cmd = ['ps', '-f', '-u', 'root'] err_str += utils.execute(cmd, run_as_root=True) raise Exception(err_str) netns_cleanup.cleanup_network_namespaces(self.conf) self.get_namespaces_p.stop() namespaces_now = ip_lib.list_network_namespaces() procs_after = self._get_num_spawned_procs() self.assertEqual(procs_after, 0) self.assertNotIn(l3_namespace, namespaces_now) self.assertNotIn(dhcp_namespace, namespaces_now) @staticmethod def _launch_processes(namespaces): """Launch processes in the specified namespaces. This function will spawn processes inside the given namespaces: - 6 processes listening on tcp ports (parent + 5 children) - 1 process + 5 subprocesses listening on unix sockets - 1 process + 5 subprocesses listening on udp6 sockets First two sets of processes will process SIGTERM so when the parent gets killed, it will kill all spawned children The last set of processes will ignore SIGTERM. This will allow us to test the cleanup functionality which will issue a SIGKILL to all remaining processes after the SIGTERM attempt """ python_exec = os.path.basename(sys.executable) commands = [[python_exec, process_spawn.__file__, '-n', NUM_SUBPROCESSES, '-f', n_const.IPv4, '-p', n_const.PROTO_NAME_TCP, '--noignore_sigterm', '--parent_listen'], [python_exec, process_spawn.__file__, '-n', NUM_SUBPROCESSES, '-f', process_spawn.UNIX_FAMILY, '-p', n_const.PROTO_NAME_TCP, '--noignore_sigterm', '--noparent_listen'], [python_exec, process_spawn.__file__, '-n', NUM_SUBPROCESSES, '-f', n_const.IPv4, '-p', n_const.PROTO_NAME_UDP, '--ignore_sigterm', '--noparent_listen']] proc_count = 0 for netns in namespaces: ip = ip_lib.IPWrapper(namespace=netns) for command in commands: # The total amount of processes per command is # the process itself plus the number of subprocesses spawned by # it proc_count += (1 + NUM_SUBPROCESSES) # We need to pass the PATH env variable so that python # interpreter runs under the same virtual environment. # Otherwise, it won't find the necessary packages such as # oslo_config ip.netns.execute(command, addl_env={'PATH': os.environ.get('PATH')}) return proc_count @staticmethod def _get_num_spawned_procs(): cmd = ['ps', '-f', '-u', 'root'] out = utils.execute(cmd, run_as_root=True) return sum([1 for line in out.splitlines() if 'process_spawn' in line]) class TestNETNSCLIConfig(basetest.BaseTestCase): def setup_config(self, args=None): self.conf = netns_cleanup.setup_conf() super(TestNETNSCLIConfig, self).setup_config(args=args) def test_netns_opts_registration(self): self.assertFalse(self.conf.force) # to unregister opts self.conf.reset() self.conf.unregister_opts(cmd.netns_opts)