neutron/neutron/tests/functional/cmd/test_netns_cleanup.py

158 lines
6.4 KiB
Python

# 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 mock
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)
common_utils.wait_until_true(
lambda: self._get_num_spawned_procs() == procs_launched,
timeout=15,
exception=Exception("Didn't spawn expected number of processes"))
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
"""
commands = [['python', process_spawn.__file__,
'-n', NUM_SUBPROCESSES,
'-f', n_const.IPv4,
'-p', n_const.PROTO_NAME_TCP,
'--noignore_sigterm',
'--parent_listen'],
['python', process_spawn.__file__,
'-n', NUM_SUBPROCESSES,
'-f', process_spawn.UNIX_FAMILY,
'-p', n_const.PROTO_NAME_TCP,
'--noignore_sigterm',
'--noparent_listen'],
['python', 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)
self.assertIsNone(self.conf.get('agent_type'))
# to unregister opts
self.conf.reset()
self.conf.unregister_opts(cmd.netns_opts)