Use transient systemd units in Process fixture
This makes the damon processes spawned by fullstack tests more similar to those used in a real deployment. It creates a systemd unit for each daemon on the fly and combines all daemons of the same host in a systemd slice. The units are automatically removed on service shutdown. This also better ensures that there are no left over processes after failed test cases as all processes started in a systemd unit are cleaned up when the unit is stopped. This change is a prerequisite to implement methods to kill or shutdown a host. Without this change it's impossible to find out which processes belong to a agent and to kill all these processes. This is important to eg. kill keepalived processes started by L3 agents. The timeout for the L2 agent restart has to be extended slightly because the service is no longer killed with SIGKILL but properly shut down. Change-Id: Ieb21de8a370aca24abc216f55dbd98e553a65d05
This commit is contained in:
parent
4769811451
commit
4a02848228
@ -54,3 +54,7 @@ l3_agent: CommandFilter, l3_agent.py, root
|
|||||||
|
|
||||||
#needed to capture and analyze traffic in fullstack tests (f.e. in DSCP scenarios)
|
#needed to capture and analyze traffic in fullstack tests (f.e. in DSCP scenarios)
|
||||||
tcpdump: CommandFilter, tcpdump, root
|
tcpdump: CommandFilter, tcpdump, root
|
||||||
|
|
||||||
|
#needed to create transient systemd services for fullstack tests
|
||||||
|
systemd_run: CommandFilter, systemd-run, root
|
||||||
|
systemctl: CommandFilter, systemctl, root
|
@ -16,7 +16,6 @@ import datetime
|
|||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
import signal
|
|
||||||
|
|
||||||
import fixtures
|
import fixtures
|
||||||
from neutron_lib import constants
|
from neutron_lib import constants
|
||||||
@ -25,7 +24,6 @@ from neutronclient.v2_0 import client
|
|||||||
from oslo_log import log as logging
|
from oslo_log import log as logging
|
||||||
from oslo_utils import fileutils
|
from oslo_utils import fileutils
|
||||||
|
|
||||||
from neutron.agent.common import async_process
|
|
||||||
from neutron.agent.linux import ip_lib
|
from neutron.agent.linux import ip_lib
|
||||||
from neutron.agent.linux import utils
|
from neutron.agent.linux import utils
|
||||||
from neutron.common import utils as common_utils
|
from neutron.common import utils as common_utils
|
||||||
@ -39,16 +37,25 @@ CMD_FOLDER = 'agents'
|
|||||||
|
|
||||||
class ProcessFixture(fixtures.Fixture):
|
class ProcessFixture(fixtures.Fixture):
|
||||||
def __init__(self, test_name, process_name, exec_name, config_filenames,
|
def __init__(self, test_name, process_name, exec_name, config_filenames,
|
||||||
namespace=None, kill_signal=signal.SIGKILL):
|
namespace=None, slice_name=None):
|
||||||
super(ProcessFixture, self).__init__()
|
super(ProcessFixture, self).__init__()
|
||||||
self.test_name = test_name
|
self.test_name = test_name
|
||||||
self.process_name = process_name
|
self.process_name = process_name
|
||||||
self.exec_name = exec_name
|
self.exec_name = exec_name
|
||||||
self.config_filenames = config_filenames
|
self.config_filenames = config_filenames
|
||||||
self.process = None
|
self.process = None
|
||||||
self.kill_signal = kill_signal
|
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
|
||||||
|
self.slice_name = slice_name
|
||||||
|
self.unit_name = f'{self.test_name}-{self.process_name}'
|
||||||
|
if self.namespace:
|
||||||
|
self.unit_name += f'-{self.namespace}'
|
||||||
|
|
||||||
|
# Escape special characters in unit names, see man systemd.unit
|
||||||
|
self.unit_name = utils.execute(
|
||||||
|
['systemd-escape', self.unit_name],
|
||||||
|
).strip()
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
self.start()
|
self.start()
|
||||||
self.addCleanup(self.stop)
|
self.addCleanup(self.stop)
|
||||||
@ -66,25 +73,58 @@ class ProcessFixture(fixtures.Fixture):
|
|||||||
if run_as_root
|
if run_as_root
|
||||||
else shutil.which(self.exec_name))
|
else shutil.which(self.exec_name))
|
||||||
cmd = [exec_name, '--log-dir', log_dir, '--log-file', log_file]
|
cmd = [exec_name, '--log-dir', log_dir, '--log-file', log_file]
|
||||||
|
if self.namespace:
|
||||||
|
cmd = ip_lib.add_namespace_to_cmd(cmd, self.namespace)
|
||||||
for filename in self.config_filenames:
|
for filename in self.config_filenames:
|
||||||
cmd += ['--config-file', filename]
|
cmd += ['--config-file', filename]
|
||||||
self.process = async_process.AsyncProcess(
|
|
||||||
cmd, run_as_root=run_as_root, namespace=self.namespace,
|
systemd_run = [
|
||||||
process_name=self.process_name
|
'systemd-run',
|
||||||
|
'--service-type', 'exec',
|
||||||
|
'--property', 'TimeoutStopSec=30s',
|
||||||
|
'--unit', self.unit_name,
|
||||||
|
'--setenv', f'PATH={os.environ["PATH"]}',
|
||||||
|
'--same-dir',
|
||||||
|
'--collect',
|
||||||
|
]
|
||||||
|
|
||||||
|
if not run_as_root:
|
||||||
|
systemd_run += [
|
||||||
|
'--uid', os.getuid(),
|
||||||
|
'--gid', os.getgid(),
|
||||||
|
]
|
||||||
|
|
||||||
|
if self.slice_name:
|
||||||
|
systemd_run += ['--slice', self.slice_name]
|
||||||
|
|
||||||
|
utils.execute(
|
||||||
|
systemd_run + cmd,
|
||||||
|
# Always create the systemd unit as root, the process itself will
|
||||||
|
# run unprivileged if run_as_root is False.
|
||||||
|
run_as_root=True,
|
||||||
)
|
)
|
||||||
self.process.start(block=True)
|
|
||||||
LOG.debug("Process started: %s", self.process_name)
|
LOG.debug("Process started: %s", self.process_name)
|
||||||
|
|
||||||
def stop(self, kill_signal=None):
|
def stop(self, kill_signal=None):
|
||||||
kill_signal = kill_signal or self.kill_signal
|
if self.process_is_not_running():
|
||||||
try:
|
return
|
||||||
self.process.stop(
|
|
||||||
block=True, kill_signal=kill_signal,
|
if kill_signal:
|
||||||
kill_timeout=15)
|
stop_cmd = [
|
||||||
except async_process.AsyncProcessException as e:
|
'systemctl',
|
||||||
if "Process is not running" not in str(e):
|
'kill',
|
||||||
raise
|
'--signal', kill_signal.value,
|
||||||
LOG.debug("Process stopped: %s", self.process_name)
|
'--kill-who', 'all',
|
||||||
|
self.unit_name,
|
||||||
|
]
|
||||||
|
msg = (f'Process killed with signal {kill_signal}: '
|
||||||
|
f'{self.process_name}')
|
||||||
|
else:
|
||||||
|
stop_cmd = ['systemctl', 'stop', self.unit_name]
|
||||||
|
msg = f'Process stopped: {self.process_name}'
|
||||||
|
|
||||||
|
utils.execute(stop_cmd, run_as_root=True)
|
||||||
|
LOG.debug(msg)
|
||||||
|
|
||||||
def restart(self, executor=None):
|
def restart(self, executor=None):
|
||||||
def _restart():
|
def _restart():
|
||||||
@ -99,7 +139,13 @@ class ProcessFixture(fixtures.Fixture):
|
|||||||
return executor.submit(_restart)
|
return executor.submit(_restart)
|
||||||
|
|
||||||
def process_is_running(self):
|
def process_is_running(self):
|
||||||
return self.process.is_running
|
cmd = ['systemctl', 'is-active', self.unit_name]
|
||||||
|
return utils.execute(
|
||||||
|
cmd,
|
||||||
|
run_as_root=True,
|
||||||
|
log_fail_as_error=False,
|
||||||
|
check_exit_code=False,
|
||||||
|
) == 'active\n'
|
||||||
|
|
||||||
def process_is_not_running(self):
|
def process_is_not_running(self):
|
||||||
return not self.process_is_running()
|
return not self.process_is_running()
|
||||||
@ -156,6 +202,7 @@ class NeutronServerFixture(ServiceFixture):
|
|||||||
self.neutron_cfg_fixture = neutron_cfg_fixture
|
self.neutron_cfg_fixture = neutron_cfg_fixture
|
||||||
self.plugin_cfg_fixture = plugin_cfg_fixture
|
self.plugin_cfg_fixture = plugin_cfg_fixture
|
||||||
self.service_cfg_fixtures = service_cfg_fixtures
|
self.service_cfg_fixtures = service_cfg_fixtures
|
||||||
|
self.hostname = self.neutron_cfg_fixture.config['DEFAULT']['host']
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
config_filenames = [self.neutron_cfg_fixture.filename,
|
config_filenames = [self.neutron_cfg_fixture.filename,
|
||||||
@ -167,10 +214,9 @@ class NeutronServerFixture(ServiceFixture):
|
|||||||
|
|
||||||
self.process_fixture = self.useFixture(ProcessFixture(
|
self.process_fixture = self.useFixture(ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=self.NEUTRON_SERVER,
|
process_name=f'{self.NEUTRON_SERVER}-{self.hostname}',
|
||||||
exec_name=self.NEUTRON_SERVER,
|
exec_name=self.NEUTRON_SERVER,
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames))
|
||||||
kill_signal=signal.SIGTERM))
|
|
||||||
|
|
||||||
common_utils.wait_until_true(self.server_is_live)
|
common_utils.wait_until_true(self.server_is_live)
|
||||||
|
|
||||||
@ -200,6 +246,7 @@ class OVSAgentFixture(ServiceFixture):
|
|||||||
self.neutron_config = self.neutron_cfg_fixture.config
|
self.neutron_config = self.neutron_cfg_fixture.config
|
||||||
self.agent_cfg_fixture = agent_cfg_fixture
|
self.agent_cfg_fixture = agent_cfg_fixture
|
||||||
self.agent_config = agent_cfg_fixture.config
|
self.agent_config = agent_cfg_fixture.config
|
||||||
|
self.hostname = self.neutron_config['DEFAULT']['host']
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
self.br_int = self.useFixture(
|
self.br_int = self.useFixture(
|
||||||
@ -211,15 +258,16 @@ class OVSAgentFixture(ServiceFixture):
|
|||||||
|
|
||||||
self.process_fixture = self.useFixture(ProcessFixture(
|
self.process_fixture = self.useFixture(ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=constants.AGENT_PROCESS_OVS,
|
process_name=f'{constants.AGENT_PROCESS_OVS}-{self.hostname}',
|
||||||
|
slice_name=self.hostname,
|
||||||
exec_name=shutil.which(
|
exec_name=shutil.which(
|
||||||
'ovs_agent.py',
|
'ovs_agent.py',
|
||||||
path=os.path.join(fullstack_base.ROOTDIR, CMD_FOLDER)),
|
path=os.path.join(fullstack_base.ROOTDIR, CMD_FOLDER)),
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames,
|
||||||
kill_signal=signal.SIGTERM))
|
))
|
||||||
|
|
||||||
|
|
||||||
class PlacementFixture(fixtures.Fixture):
|
class PlacementFixture(ServiceFixture):
|
||||||
|
|
||||||
def __init__(self, env_desc, host_desc, test_name, placement_cfg_fixture):
|
def __init__(self, env_desc, host_desc, test_name, placement_cfg_fixture):
|
||||||
super(PlacementFixture, self).__init__()
|
super(PlacementFixture, self).__init__()
|
||||||
@ -237,8 +285,7 @@ class PlacementFixture(fixtures.Fixture):
|
|||||||
'placement.py', path=os.path.join(fullstack_base.ROOTDIR,
|
'placement.py', path=os.path.join(fullstack_base.ROOTDIR,
|
||||||
'servers')
|
'servers')
|
||||||
),
|
),
|
||||||
config_filenames=[self.placement_cfg_fixture.filename],
|
config_filenames=[self.placement_cfg_fixture.filename]))
|
||||||
kill_signal=signal.SIGTERM))
|
|
||||||
|
|
||||||
|
|
||||||
class SRIOVAgentFixture(ServiceFixture):
|
class SRIOVAgentFixture(ServiceFixture):
|
||||||
@ -253,16 +300,18 @@ class SRIOVAgentFixture(ServiceFixture):
|
|||||||
self.neutron_config = self.neutron_cfg_fixture.config
|
self.neutron_config = self.neutron_cfg_fixture.config
|
||||||
self.agent_cfg_fixture = agent_cfg_fixture
|
self.agent_cfg_fixture = agent_cfg_fixture
|
||||||
self.agent_config = agent_cfg_fixture.config
|
self.agent_config = agent_cfg_fixture.config
|
||||||
|
self.hostname = self.neutron_config['DEFAULT']['host']
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
config_filenames = [self.neutron_cfg_fixture.filename,
|
config_filenames = [self.neutron_cfg_fixture.filename,
|
||||||
self.agent_cfg_fixture.filename]
|
self.agent_cfg_fixture.filename]
|
||||||
|
process_name = f'{constants.AGENT_PROCESS_NIC_SWITCH}-{self.hostname}'
|
||||||
self.process_fixture = self.useFixture(ProcessFixture(
|
self.process_fixture = self.useFixture(ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=constants.AGENT_PROCESS_NIC_SWITCH,
|
process_name=process_name,
|
||||||
|
slice_name=self.hostname,
|
||||||
exec_name=constants.AGENT_PROCESS_NIC_SWITCH,
|
exec_name=constants.AGENT_PROCESS_NIC_SWITCH,
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames))
|
||||||
kill_signal=signal.SIGTERM))
|
|
||||||
|
|
||||||
|
|
||||||
class LinuxBridgeAgentFixture(ServiceFixture):
|
class LinuxBridgeAgentFixture(ServiceFixture):
|
||||||
@ -279,15 +328,18 @@ class LinuxBridgeAgentFixture(ServiceFixture):
|
|||||||
self.agent_cfg_fixture = agent_cfg_fixture
|
self.agent_cfg_fixture = agent_cfg_fixture
|
||||||
self.agent_config = agent_cfg_fixture.config
|
self.agent_config = agent_cfg_fixture.config
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
self.hostname = self.neutron_config['DEFAULT']['host']
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
config_filenames = [self.neutron_cfg_fixture.filename,
|
config_filenames = [self.neutron_cfg_fixture.filename,
|
||||||
self.agent_cfg_fixture.filename]
|
self.agent_cfg_fixture.filename]
|
||||||
|
process_name = f'{constants.AGENT_PROCESS_LINUXBRIDGE}-{self.hostname}'
|
||||||
|
|
||||||
self.process_fixture = self.useFixture(
|
self.process_fixture = self.useFixture(
|
||||||
ProcessFixture(
|
ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=constants.AGENT_PROCESS_LINUXBRIDGE,
|
process_name=process_name,
|
||||||
|
slice_name=self.hostname,
|
||||||
exec_name=constants.AGENT_PROCESS_LINUXBRIDGE,
|
exec_name=constants.AGENT_PROCESS_LINUXBRIDGE,
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames,
|
||||||
namespace=self.namespace
|
namespace=self.namespace
|
||||||
@ -307,6 +359,7 @@ class L3AgentFixture(ServiceFixture):
|
|||||||
self.neutron_cfg_fixture = neutron_cfg_fixture
|
self.neutron_cfg_fixture = neutron_cfg_fixture
|
||||||
self.l3_agent_cfg_fixture = l3_agent_cfg_fixture
|
self.l3_agent_cfg_fixture = l3_agent_cfg_fixture
|
||||||
self.namespace = namespace
|
self.namespace = namespace
|
||||||
|
self.hostname = self.neutron_cfg_fixture.config['DEFAULT']['host']
|
||||||
|
|
||||||
def _setUp(self):
|
def _setUp(self):
|
||||||
self.plugin_config = self.l3_agent_cfg_fixture.config
|
self.plugin_config = self.l3_agent_cfg_fixture.config
|
||||||
@ -326,7 +379,8 @@ class L3AgentFixture(ServiceFixture):
|
|||||||
self.process_fixture = self.useFixture(
|
self.process_fixture = self.useFixture(
|
||||||
ProcessFixture(
|
ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=constants.AGENT_PROCESS_L3,
|
process_name=f'{constants.AGENT_PROCESS_L3}-{self.hostname}',
|
||||||
|
slice_name=self.hostname,
|
||||||
exec_name=exec_name,
|
exec_name=exec_name,
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames,
|
||||||
namespace=self.namespace
|
namespace=self.namespace
|
||||||
@ -337,7 +391,7 @@ class L3AgentFixture(ServiceFixture):
|
|||||||
return self.plugin_config.DEFAULT.test_namespace_suffix
|
return self.plugin_config.DEFAULT.test_namespace_suffix
|
||||||
|
|
||||||
|
|
||||||
class DhcpAgentFixture(fixtures.Fixture):
|
class DhcpAgentFixture(ServiceFixture):
|
||||||
|
|
||||||
def __init__(self, env_desc, host_desc, test_name,
|
def __init__(self, env_desc, host_desc, test_name,
|
||||||
neutron_cfg_fixture, agent_cfg_fixture, namespace=None):
|
neutron_cfg_fixture, agent_cfg_fixture, namespace=None):
|
||||||
@ -363,11 +417,13 @@ class DhcpAgentFixture(fixtures.Fixture):
|
|||||||
exec_name = shutil.which(
|
exec_name = shutil.which(
|
||||||
'dhcp_agent.py',
|
'dhcp_agent.py',
|
||||||
path=os.path.join(fullstack_base.ROOTDIR, CMD_FOLDER))
|
path=os.path.join(fullstack_base.ROOTDIR, CMD_FOLDER))
|
||||||
|
hostname = self.get_agent_hostname()
|
||||||
|
|
||||||
self.process_fixture = self.useFixture(
|
self.process_fixture = self.useFixture(
|
||||||
ProcessFixture(
|
ProcessFixture(
|
||||||
test_name=self.test_name,
|
test_name=self.test_name,
|
||||||
process_name=constants.AGENT_PROCESS_DHCP,
|
process_name=f'{constants.AGENT_PROCESS_DHCP}-{hostname}',
|
||||||
|
slice_name=hostname,
|
||||||
exec_name=exec_name,
|
exec_name=exec_name,
|
||||||
config_filenames=config_filenames,
|
config_filenames=config_filenames,
|
||||||
namespace=self.namespace
|
namespace=self.namespace
|
||||||
|
@ -147,7 +147,7 @@ class _TestUninterruptedConnectivityOnL2AgentRestart(
|
|||||||
'l2_pop': False}),
|
'l2_pop': False}),
|
||||||
]
|
]
|
||||||
|
|
||||||
def _test_l2_agent_restart(self, agent_restart_timeout=20):
|
def _test_l2_agent_restart(self, agent_restart_timeout=30):
|
||||||
# Environment preparation is effectively the same as connectivity test
|
# Environment preparation is effectively the same as connectivity test
|
||||||
vms = self._prepare_vms_in_single_network()
|
vms = self._prepare_vms_in_single_network()
|
||||||
vms.ping_all()
|
vms.ping_all()
|
||||||
@ -175,5 +175,5 @@ class TestUninterruptedConnectivityOnL2AgentRestartOvs(
|
|||||||
scenario,
|
scenario,
|
||||||
_TestUninterruptedConnectivityOnL2AgentRestart.network_scenarios))
|
_TestUninterruptedConnectivityOnL2AgentRestart.network_scenarios))
|
||||||
|
|
||||||
def test_l2_agent_restart(self, agent_restart_timeout=20):
|
def test_l2_agent_restart(self, agent_restart_timeout=30):
|
||||||
self._test_l2_agent_restart(agent_restart_timeout)
|
self._test_l2_agent_restart(agent_restart_timeout)
|
||||||
|
@ -34,12 +34,13 @@ class MTUNetworkTestSetup(base.BaseFullStackTestCase):
|
|||||||
self.tenant_id = uuidutils.generate_uuid()
|
self.tenant_id = uuidutils.generate_uuid()
|
||||||
|
|
||||||
def _restart_neutron_server(self, global_mtu):
|
def _restart_neutron_server(self, global_mtu):
|
||||||
env = environment.Environment(
|
neutron_server = self.environment.neutron_server
|
||||||
environment.EnvironmentDescription(global_mtu=global_mtu),
|
neutron_server.neutron_cfg_fixture.config['DEFAULT'].update(
|
||||||
self.host_desc)
|
{'global_physnet_mtu': str(global_mtu)}
|
||||||
env.test_name = self.get_name()
|
)
|
||||||
self.useFixture(env)
|
neutron_server.neutron_cfg_fixture.write_config_to_configfile()
|
||||||
env.neutron_server.restart()
|
neutron_server.restart()
|
||||||
|
self.environment.wait_until_env_is_up()
|
||||||
|
|
||||||
|
|
||||||
class TestMTUScenarios(MTUNetworkTestSetup):
|
class TestMTUScenarios(MTUNetworkTestSetup):
|
||||||
|
Loading…
x
Reference in New Issue
Block a user