Skip to content

Commit

Permalink
Add an env variable "PROCESS_TAG" in ProcessManager
Browse files Browse the repository at this point in the history
Added a new environment variable "PROCESS_TAG" in ``ProcessManager``.
This environment variable could be read by the process executed and
is unique per process. This environment variable can be used to tag
the running process; for example, a container manager can use this
tag to mark the a container.

This feature will be used by TripleO to identify the running containers
with a unique tag. This will make the "kill" process easier; it will
be needed just to find the container running with this tag.

Closes-Bug: #1991000
Change-Id: I234c661720a8b1ceadb5333181890806f79dc21a
  • Loading branch information
ralonsoh committed Dec 24, 2022
1 parent 2979cd1 commit 3d575f8
Show file tree
Hide file tree
Showing 6 changed files with 115 additions and 18 deletions.
6 changes: 6 additions & 0 deletions etc/neutron/rootwrap.d/rootwrap.filters
Original file line number Diff line number Diff line change
Expand Up @@ -40,17 +40,23 @@ ip_exec: IpNetnsExecFilter, ip, root

# METADATA PROXY
haproxy: RegExpFilter, haproxy, root, haproxy, -f, .*
haproxy_env: EnvFilter, env, root, PROCESS_TAG=, haproxy, -f, .*

# DHCP
dnsmasq: CommandFilter, dnsmasq, root
dnsmasq_env: EnvFilter, env, root, PROCESS_TAG=, dnsmasq

# DIBBLER
dibbler-client: CommandFilter, dibbler-client, root
dibbler-client_env: EnvFilter, env, root, PROCESS_TAG=, dibbler-client

# L3
radvd: CommandFilter, radvd, root
radvd_env: EnvFilter, env, root, PROCESS_TAG=, radvd
keepalived: CommandFilter, keepalived, root
keepalived_env: EnvFilter, env, root, PROCESS_TAG=, keepalived
keepalived_state_change: CommandFilter, neutron-keepalived-state-change, root
keepalived_state_change_env: EnvFilter, env, root, PROCESS_TAG=, neutron-keepalived-state-change

# OPEN VSWITCH
ovs-ofctl: CommandFilter, ovs-ofctl, root
Expand Down
12 changes: 9 additions & 3 deletions neutron/agent/linux/external_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@


LOG = logging.getLogger(__name__)
PROCESS_TAG = 'PROCESS_TAG'
DEFAULT_SERVICE_NAME = 'default-service'


agent_cfg.register_external_process_opts()
Expand Down Expand Up @@ -61,7 +63,6 @@ def __init__(self, conf, uuid, namespace=None, service=None,
self.uuid = uuid
self.namespace = namespace
self.default_cmd_callback = default_cmd_callback
self.cmd_addl_env = cmd_addl_env
self.pids_path = pids_path or self.conf.external_pids
self.pid_file = pid_file
self.run_as_root = run_as_root or self.namespace is not None
Expand All @@ -73,7 +74,11 @@ def __init__(self, conf, uuid, namespace=None, service=None,
self.service = service
else:
self.service_pid_fname = 'pid'
self.service = 'default-service'
self.service = DEFAULT_SERVICE_NAME

process_tag = '%s-%s' % (self.service, self.uuid)
self.cmd_addl_env = cmd_addl_env or {}
self.cmd_addl_env[PROCESS_TAG] = process_tag

fileutils.ensure_tree(os.path.dirname(self.get_pid_file_name()),
mode=0o755)
Expand Down Expand Up @@ -110,7 +115,8 @@ def disable(self, sig='9', get_stop_command=None):
privsep_exec=True)
else:
cmd = self.get_kill_cmd(sig, pid)
utils.execute(cmd, run_as_root=self.run_as_root,
utils.execute(cmd, addl_env=self.cmd_addl_env,
run_as_root=self.run_as_root,
privsep_exec=True)
# In the case of shutting down, remove the pid file
if sig == '9':
Expand Down
88 changes: 79 additions & 9 deletions neutron/tests/unit/agent/linux/test_external_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,13 @@
# under the License.

import os.path
import tempfile
from unittest import mock

from neutron_lib import fixture as lib_fixtures
from oslo_config import cfg
from oslo_utils import fileutils
from oslo_utils import uuidutils
import psutil

from neutron.agent.linux import external_process as ep
Expand All @@ -29,6 +31,15 @@
TEST_SERVICE = 'testsvc'
TEST_PID = 1234
TEST_CMDLINE = 'python foo --router_id=%s'
SCRIPT = """#!/bin/bash
output_file=$1
if [ -z "${PROCESS_TAG}" ] ; then
echo "Variable PROCESS_TAG not set" > $output_file
else
echo "Variable PROCESS_TAG set: $PROCESS_TAG" > $output_file
fi
"""
DEFAULT_ENVVAR = ep.PROCESS_TAG + '=' + ep.DEFAULT_SERVICE_NAME


class BaseTestProcessMonitor(base.BaseTestCase):
Expand Down Expand Up @@ -124,7 +135,8 @@ def test_processmanager_ensures_pid_dir(self):

def test_enable_no_namespace(self):
callback = mock.Mock()
callback.return_value = ['the', 'cmd']
cmd = ['the', 'cmd']
callback.return_value = cmd

with mock.patch.object(ep.ProcessManager, 'get_pid_file_name') as name:
name.return_value = 'pidfile'
Expand All @@ -134,7 +146,8 @@ def test_enable_no_namespace(self):
manager = ep.ProcessManager(self.conf, 'uuid')
manager.enable(callback)
callback.assert_called_once_with('pidfile')
self.execute.assert_called_once_with(['the', 'cmd'],
cmd = ['env', DEFAULT_ENVVAR + '-uuid'] + cmd
self.execute.assert_called_once_with(cmd,
check_exit_code=True,
extra_ok_codes=None,
run_as_root=False,
Expand All @@ -154,10 +167,11 @@ def test_enable_with_namespace(self):
with mock.patch.object(ep, 'ip_lib') as ip_lib:
manager.enable(callback)
callback.assert_called_once_with('pidfile')
env = {ep.PROCESS_TAG: ep.DEFAULT_SERVICE_NAME + '-uuid'}
ip_lib.assert_has_calls([
mock.call.IPWrapper(namespace='ns'),
mock.call.IPWrapper().netns.execute(
['the', 'cmd'], addl_env=None, run_as_root=True)])
['the', 'cmd'], addl_env=env, run_as_root=True)])

def test_enable_with_namespace_process_active(self):
callback = mock.Mock()
Expand Down Expand Up @@ -189,6 +203,56 @@ def _create_cmd(*args):
except common_utils.WaitTimeout:
self.fail('ProcessManager.enable() raised WaitTimeout')

def _create_env_var_testing_environment(self, script_content, _create_cmd):
with tempfile.NamedTemporaryFile('w+', dir='/tmp/',
delete=False) as script:
script.write(script_content)
output = tempfile.NamedTemporaryFile('w+', dir='/tmp/', delete=False)
os.chmod(script.name, 0o777)
service_name = 'my_new_service'
uuid = uuidutils.generate_uuid()
pm = ep.ProcessManager(self.conf, uuid, service=service_name,
default_cmd_callback=_create_cmd)
return script, output, service_name, uuid, pm

def test_enable_check_process_id_env_var(self):
def _create_cmd(*args):
return [script.name, output.name]

self.execute_p.stop()
script, output, service_name, uuid, pm = (
self._create_env_var_testing_environment(SCRIPT, _create_cmd))
with mock.patch.object(ep.ProcessManager, 'active') as active:
active.__get__ = mock.Mock(return_value=False)
pm.enable()

with open(output.name, 'r') as f:
ret_value = f.readline().strip()
expected_value = ('Variable PROCESS_TAG set: %s-%s' %
(service_name, uuid))
self.assertEqual(expected_value, ret_value)

def test_disable_check_process_id_env_var(self):
def _create_cmd(*args):
return [script.name, output.name]

self.execute_p.stop()
script, output, service_name, uuid, pm = (
self._create_env_var_testing_environment(SCRIPT, _create_cmd))
with mock.patch.object(ep.ProcessManager, 'active') as active, \
mock.patch.object(pm, 'get_kill_cmd') as mock_kill_cmd:
active.__get__ = mock.Mock(return_value=True)
# NOTE(ralonsoh): the script we are using for testing does not
# expect to receive the SIG number as the first argument.
mock_kill_cmd.return_value = [script.name, output.name]
pm.disable(sig='15')

with open(output.name, 'r') as f:
ret_value = f.readline().strip()
expected_value = ('Variable PROCESS_TAG set: %s-%s' %
(service_name, uuid))
self.assertEqual(expected_value, ret_value)

def test_reload_cfg_without_custom_reload_callback(self):
with mock.patch.object(ep.ProcessManager, 'disable') as disable:
manager = ep.ProcessManager(self.conf, 'uuid', namespace='ns')
Expand Down Expand Up @@ -216,6 +280,7 @@ def test_disable_get_stop_command(self):
custom_reload_callback=reload_callback)
manager.disable(
get_stop_command=manager.custom_reload_callback)
cmd = ['env', DEFAULT_ENVVAR + '-uuid'] + cmd
self.assertIn(cmd, self.execute.call_args[0])

def test_disable_no_namespace(self):
Expand All @@ -227,8 +292,10 @@ def test_disable_no_namespace(self):

with mock.patch.object(ep, 'utils') as utils:
manager.disable()
env = {ep.PROCESS_TAG: ep.DEFAULT_SERVICE_NAME + '-uuid'}
utils.assert_has_calls([
mock.call.execute(['kill', '-9', 4],
addl_env=env,
run_as_root=False,
privsep_exec=True)])

Expand All @@ -242,10 +309,11 @@ def test_disable_namespace(self):

with mock.patch.object(ep, 'utils') as utils:
manager.disable()
env = {ep.PROCESS_TAG: ep.DEFAULT_SERVICE_NAME + '-uuid'}
utils.assert_has_calls([
mock.call.execute(['kill', '-9', 4],
run_as_root=True,
privsep_exec=True)])
mock.call.execute(
['kill', '-9', 4], addl_env=env, run_as_root=True,
privsep_exec=True)])

def test_disable_not_active(self):
with mock.patch.object(ep.ProcessManager, 'pid') as pid:
Expand Down Expand Up @@ -280,16 +348,18 @@ def _test_disable_custom_kill_script(self, kill_script_exists, namespace,
pid.__get__ = mock.Mock(return_value=4)
with mock.patch.object(ep.ProcessManager, 'active') as active:
active.__get__ = mock.Mock(return_value=True)
service_name = 'test-service'
manager = ep.ProcessManager(
self.conf, 'uuid', namespace=namespace,
service='test-service')
service=service_name)
with mock.patch.object(ep, 'utils') as utils, \
mock.patch.object(os.path, 'isfile',
return_value=kill_script_exists):
manager.disable()
addl_env = {ep.PROCESS_TAG: service_name + '-uuid'}
utils.execute.assert_called_with(
expected_cmd, run_as_root=bool(namespace),
privsep_exec=True)
expected_cmd, addl_env=addl_env,
run_as_root=bool(namespace), privsep_exec=True)

def test_disable_custom_kill_script_no_namespace(self):
self._test_disable_custom_kill_script(
Expand Down
7 changes: 5 additions & 2 deletions neutron/tests/unit/agent/metadata/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@

from neutron.agent.l3 import agent as l3_agent
from neutron.agent.l3 import router_info
from neutron.agent.linux import external_process as ep
from neutron.agent.linux import iptables_manager
from neutron.agent.linux import utils as linux_utils
from neutron.agent.metadata import driver as metadata_driver
Expand Down Expand Up @@ -142,6 +143,7 @@ def test_after_router_updated_should_not_call_add_metadata_rules(self):
def test_spawn_metadata_proxy(self):
router_id = _uuid()
router_ns = 'qrouter-%s' % router_id
service_name = 'haproxy'
ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'

cfg.CONF.set_override('metadata_proxy_user', self.EUNAME)
Expand Down Expand Up @@ -181,7 +183,7 @@ def test_spawn_metadata_proxy(self):
router_id=router_id)

netns_execute_args = [
'haproxy',
service_name,
'-f', cfg_file]

log_tag = ("haproxy-" + metadata_driver.METADATA_SERVICE_NAME +
Expand All @@ -205,9 +207,10 @@ def test_spawn_metadata_proxy(self):
mock.call().write(cfg_contents)],
any_order=True)

env = {ep.PROCESS_TAG: service_name + '-' + router_id}
ip_mock.assert_has_calls([
mock.call(namespace=router_ns),
mock.call().netns.execute(netns_execute_args, addl_env=None,
mock.call().netns.execute(netns_execute_args, addl_env=env,
run_as_root=True)
])

Expand Down
12 changes: 8 additions & 4 deletions neutron/tests/unit/agent/ovn/metadata/test_driver.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
from oslo_config import cfg
from oslo_utils import uuidutils

from neutron.agent.linux import external_process as ep
from neutron.agent.ovn.metadata import agent as metadata_agent
from neutron.agent.ovn.metadata import driver as metadata_driver
from neutron.common import metadata as comm_meta
Expand Down Expand Up @@ -52,6 +53,7 @@ def test_spawn_metadata_proxy(self):
datapath_id = _uuid()
metadata_ns = metadata_agent.NS_PREFIX + datapath_id
ip_class_path = 'neutron.agent.linux.ip_lib.IPWrapper'
service_name = 'haproxy'

cfg.CONF.set_override('metadata_proxy_user', self.EUNAME)
cfg.CONF.set_override('metadata_proxy_group', self.EGNAME)
Expand Down Expand Up @@ -84,11 +86,12 @@ def test_spawn_metadata_proxy(self):
network_id=datapath_id)

netns_execute_args = [
'haproxy',
service_name,
'-f', cfg_file]

log_tag = 'haproxy-{}-{}'.format(
metadata_driver.METADATA_SERVICE_NAME, datapath_id)
log_tag = '{}-{}-{}'.format(
service_name, metadata_driver.METADATA_SERVICE_NAME,
datapath_id)

cfg_contents = metadata_driver._HAPROXY_CONFIG_TEMPLATE % {
'user': self.EUNAME,
Expand All @@ -107,9 +110,10 @@ def test_spawn_metadata_proxy(self):
mock.call().write(cfg_contents)],
any_order=True)

env = {ep.PROCESS_TAG: service_name + '-' + datapath_id}
ip_mock.assert_has_calls([
mock.call(namespace=metadata_ns),
mock.call().netns.execute(netns_execute_args, addl_env=None,
mock.call().netns.execute(netns_execute_args, addl_env=env,
run_as_root=True)
])

Expand Down
8 changes: 8 additions & 0 deletions releasenotes/notes/process-manager-tag-2181918518972004.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
other:
- |
The ``ProcessManager`` class will now, by default, add an environment
variable when starting a new process. This default tag is named
"PROCESS_TAG" and will contain a unique identifier for this specific
process. It could be used, for example, by TripleO to univocally tag
any new container spawned and find it using the same tag.

0 comments on commit 3d575f8

Please sign in to comment.