You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by lo...@apache.org on 2025/11/27 15:49:38 UTC

(nifi-minifi-cpp) branch MINIFICPP-2684 created (now 31b7dccc5)

This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a change to branch MINIFICPP-2684
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git


      at 31b7dccc5 MINIFICPP-2684 Move MiNiFi controller tests to modular docker tests

This branch includes the following new commits:

     new 31b7dccc5 MINIFICPP-2684 Move MiNiFi controller tests to modular docker tests

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



(nifi-minifi-cpp) 01/01: MINIFICPP-2684 Move MiNiFi controller tests to modular docker tests

Posted by lo...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

lordgamez pushed a commit to branch MINIFICPP-2684
in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git

commit 31b7dccc508d4baae591354af7c83749a981d4f9
Author: Gabor Gyimesi <ga...@gmail.com>
AuthorDate: Thu Nov 27 11:35:25 2025 +0100

    MINIFICPP-2684 Move MiNiFi controller tests to modular docker tests
---
 .../containers/minifi_container.py                 |  90 +++++++++++++++-
 .../resources/minifi-controller/config.yml         |   0
 docker/test/integration/cluster/ContainerStore.py  |   3 -
 .../test/integration/cluster/DockerTestCluster.py  |  84 +--------------
 .../cluster/MinifiControllerExecutor.py            |  78 --------------
 .../cluster/containers/MinifiContainer.py          |   7 --
 .../features/MiNiFi_integration_test_driver.py     |  36 -------
 docker/test/integration/features/steps/steps.py    | 116 ---------------------
 .../tests}/features/minifi_controller.feature      |   5 +-
 .../features/steps/minifi_controller_steps.py      |  91 ++++++++++++++++
 .../tests/features/steps/steps.py                  |   8 +-
 11 files changed, 186 insertions(+), 332 deletions(-)

diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_container.py b/behave_framework/src/minifi_test_framework/containers/minifi_container.py
index 8db562e04..6787ccaa5 100644
--- a/behave_framework/src/minifi_test_framework/containers/minifi_container.py
+++ b/behave_framework/src/minifi_test_framework/containers/minifi_container.py
@@ -16,12 +16,16 @@
 #
 
 import logging
+import os
+from pathlib import Path
 from OpenSSL import crypto
 
 from minifi_test_framework.core.minifi_test_context import MinifiTestContext
 from minifi_test_framework.containers.file import File
+from minifi_test_framework.containers.host_file import HostFile
 from minifi_test_framework.minifi.minifi_flow_definition import MinifiFlowDefinition
 from minifi_test_framework.core.ssl_utils import make_cert_without_extended_usage, make_client_cert, make_server_cert
+from minifi_test_framework.core.helpers import wait_for_condition, retry_check
 from .container import Container
 
 
@@ -48,6 +52,10 @@ class MinifiContainer(Container):
                                crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=minifi_server_cert) + crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=minifi_server_key)))
 
         self.is_fhs = 'MINIFI_INSTALLATION_TYPE=FHS' in str(self.client.images.get(test_context.minifi_container_image).history())
+        if self.is_fhs:
+            self.minifi_controller_path = '/usr/bin/minifi-controller'
+        else:
+            self.minifi_controller_path = '/opt/minifi/minifi-current/bin/minifi-controller'
 
         self._fill_default_properties()
         self._fill_default_log_properties()
@@ -67,7 +75,16 @@ class MinifiContainer(Container):
             self.files.append(File("/opt/minifi/minifi-current/conf/minifi-log.properties",
                                    self._get_log_properties_file_content()))
 
-        return super().deploy()
+        resource_dir = Path(__file__).resolve().parent / "resources" / "minifi-controller"
+        self.host_files.append(HostFile("/tmp/resources/minifi-controller/config.yml", os.path.join(resource_dir, "config.yml")))
+
+        super().deploy()
+        finished_str = "MiNiFi started"
+        return wait_for_condition(
+            condition=lambda: finished_str in self.get_logs(),
+            timeout_seconds=15,
+            bail_condition=lambda: self.exited,
+            context=None)
 
     def set_property(self, key: str, value: str):
         self.properties[key] = value
@@ -112,3 +129,74 @@ class MinifiContainer(Container):
         memory_usage_in_bytes = int(output.strip()) * 1024
         logging.info(f"MiNiFi memory usage: {memory_usage_in_bytes} bytes")
         return memory_usage_in_bytes
+
+    def set_controller_socket_properties(self):
+        self.properties["controller.socket.enable"] = "true"
+        self.properties["controller.socket.host"] = "localhost"
+        self.properties["controller.socket.port"] = "9998"
+        self.properties["controller.socket.local.any.interface"] = "false"
+
+    def update_flow_config_through_controller(self):
+        self.exec_run([self.minifi_controller_path, "--updateflow", "/tmp/resources/minifi-controller/config.yml"])
+
+    def updated_config_is_persisted(self) -> bool:
+        exit_code, output = self.exec_run(["cat", "/opt/minifi/minifi-current/conf/config.yml" if not self.is_fhs else "/etc/nifi-minifi-cpp/config.yml"])
+        if exit_code != 0:
+            logging.error("Failed to read MiNiFi config file to check if updated config is persisted")
+            return False
+        return "2f2a3b47-f5ba-49f6-82b5-bc1c86b96f38" in output
+
+    def stop_component_through_controller(self, component: str):
+        self.exec_run([self.minifi_controller_path, "--stop", component])
+
+    def start_component_through_controller(self, component: str):
+        self.exec_run([self.minifi_controller_path, "--start", component])
+
+    @retry_check(10, 1)
+    def is_component_running(self, component: str) -> bool:
+        (code, output) = self.exec_run([self.minifi_controller_path, "--list", "components"])
+        return code == 0 and component + ", running: true" in output
+
+    def get_connections(self):
+        (_, output) = self.exec_run([self.minifi_controller_path, "--list", "connections"])
+        connections = []
+        for line in output.split('\n'):
+            if not line.startswith('[') and not line.startswith('Connection Names'):
+                connections.append(line)
+        return connections
+
+    @retry_check(10, 1)
+    def connection_found_through_controller(self, connection: str) -> bool:
+        return connection in self.get_connections()
+
+    def get_full_connection_count(self) -> int:
+        (_, output) = self.exec_run([self.minifi_controller_path, "--getfull"])
+        for line in output.split('\n'):
+            if "are full" in line:
+                return int(line.split(' ')[0])
+        return -1
+
+    def get_connection_size(self, connection: str):
+        (_, output) = self.exec_run([self.minifi_controller_path, "--getsize", connection])
+        for line in output.split('\n'):
+            if "Size/Max of " + connection in line:
+                size_and_max = line.split(connection)[1].split('/')
+                return (int(size_and_max[0].strip()), int(size_and_max[1].strip()))
+        return (-1, -1)
+
+    def get_manifest(self) -> str:
+        (_, output) = self.exec_run([self.minifi_controller_path, "--manifest"])
+        manifest = ""
+        for line in output.split('\n'):
+            if not line.startswith('['):
+                manifest += line
+        return manifest
+
+    def create_debug_bundle(self) -> bool:
+        (code, _) = self.exec_run([self.minifi_controller_path, "--debug", "/tmp"])
+        if code != 0:
+            logging.error("Minifi controller debug command failed with code: %d", code)
+            return False
+
+        (code, _) = self.exec_run(["test", "-f", "/tmp/debug.tar.gz"])
+        return code == 0
diff --git a/docker/test/integration/resources/minifi-controller/config.yml b/behave_framework/src/minifi_test_framework/containers/resources/minifi-controller/config.yml
similarity index 100%
rename from docker/test/integration/resources/minifi-controller/config.yml
rename to behave_framework/src/minifi_test_framework/containers/resources/minifi-controller/config.yml
diff --git a/docker/test/integration/cluster/ContainerStore.py b/docker/test/integration/cluster/ContainerStore.py
index 6d87739bb..084bbc45d 100644
--- a/docker/test/integration/cluster/ContainerStore.py
+++ b/docker/test/integration/cluster/ContainerStore.py
@@ -273,9 +273,6 @@ class ContainerStore:
     def set_json_in_minifi(self):
         self.minifi_options.config_format = "json"
 
-    def set_controller_socket_properties_in_minifi(self):
-        self.minifi_options.enable_controller_socket = True
-
     def enable_log_metrics_publisher_in_minifi(self):
         self.minifi_options.enable_log_metrics_publisher = True
 
diff --git a/docker/test/integration/cluster/DockerTestCluster.py b/docker/test/integration/cluster/DockerTestCluster.py
index 93801e83e..458e0bd88 100644
--- a/docker/test/integration/cluster/DockerTestCluster.py
+++ b/docker/test/integration/cluster/DockerTestCluster.py
@@ -15,22 +15,16 @@
 import logging
 import time
 import re
-import tarfile
-import tempfile
-import os
-import gzip
-import shutil
 
 from .LogSource import LogSource
 from .ContainerStore import ContainerStore
 from .DockerCommunicator import DockerCommunicator
-from .MinifiControllerExecutor import MinifiControllerExecutor
 from .checkers.AzureChecker import AzureChecker
 from .checkers.PostgresChecker import PostgresChecker
 from .checkers.PrometheusChecker import PrometheusChecker
 from .checkers.ModbusChecker import ModbusChecker
 from .checkers.MqttHelper import MqttHelper
-from utils import get_peak_memory_usage, get_minifi_pid, get_memory_usage, retry_check
+from utils import get_peak_memory_usage, get_minifi_pid, get_memory_usage
 
 
 class DockerTestCluster:
@@ -42,7 +36,6 @@ class DockerTestCluster:
         self.azure_checker = AzureChecker(self.container_communicator)
         self.postgres_checker = PostgresChecker(self.container_communicator)
         self.prometheus_checker = PrometheusChecker()
-        self.minifi_controller_executor = MinifiControllerExecutor(self.container_communicator)
         self.modbus_checker = ModbusChecker(self.container_communicator)
         self.mqtt_helper = MqttHelper()
 
@@ -124,9 +117,6 @@ class DockerTestCluster:
     def set_json_in_minifi(self):
         self.container_store.set_json_in_minifi()
 
-    def set_controller_socket_properties_in_minifi(self):
-        self.container_store.set_controller_socket_properties_in_minifi()
-
     def enable_log_metrics_publisher_in_minifi(self):
         self.container_store.enable_log_metrics_publisher_in_minifi()
 
@@ -283,78 +273,6 @@ class DockerTestCluster:
         logging.warning(f"Memory usage ({current_memory_usage}) is more than the maximum asserted memory usage ({max_memory_usage})")
         return False
 
-    def update_flow_config_through_controller(self, container_name: str):
-        self.minifi_controller_executor.update_flow(container_name)
-
-    @retry_check(10, 1)
-    def check_minifi_controller_updated_config_is_persisted(self, container_name: str) -> bool:
-        return self.minifi_controller_executor.updated_config_is_persisted(container_name)
-
-    def stop_component_through_controller(self, component: str, container_name: str):
-        self.minifi_controller_executor.stop_component(component, container_name)
-
-    def start_component_through_controller(self, component: str, container_name: str):
-        self.minifi_controller_executor.start_component(component, container_name)
-
-    @retry_check(10, 1)
-    def check_component_not_running_through_controller(self, component: str, container_name: str) -> bool:
-        return not self.minifi_controller_executor.is_component_running(component, container_name)
-
-    @retry_check(10, 1)
-    def check_component_running_through_controller(self, component: str, container_name: str) -> bool:
-        return self.minifi_controller_executor.is_component_running(component, container_name)
-
-    @retry_check(10, 1)
-    def connection_found_through_controller(self, connection: str, container_name: str) -> bool:
-        return connection in self.minifi_controller_executor.get_connections(container_name)
-
-    @retry_check(10, 1)
-    def check_connections_full_through_controller(self, connection_count: int, container_name: str) -> bool:
-        return self.minifi_controller_executor.get_full_connection_count(container_name) == connection_count
-
-    @retry_check(10, 1)
-    def check_connection_size_through_controller(self, connection: str, size: int, max_size: int, container_name: str) -> bool:
-        return self.minifi_controller_executor.get_connection_size(connection, container_name) == (size, max_size)
-
-    @retry_check(10, 1)
-    def manifest_can_be_retrieved_through_minifi_controller(self, container_name: str) -> bool:
-        manifest = self.minifi_controller_executor.get_manifest(container_name)
-        return '"agentManifest": {' in manifest and '"componentManifest": {' in manifest and '"agentType": "cpp"' in manifest
-
-    @retry_check(10, 1)
-    def debug_bundle_can_be_retrieved_through_minifi_controller(self, container_name: str) -> bool:
-        with tempfile.TemporaryDirectory() as td:
-            result = self.minifi_controller_executor.get_debug_bundle(container_name, td)
-            if not result:
-                logging.error("Failed to get debug bundle")
-                return False
-
-            with tarfile.open(os.path.join(td, "debug.tar.gz")) as file:
-                file.extractall(td)
-
-            if not os.path.exists(os.path.join(td, "config.yml")):
-                logging.error("config.yml file was not found in debug bundle")
-                return False
-
-            if not os.path.exists(os.path.join(td, "minifi.properties")):
-                logging.error("minifi.properties file was not found in debug bundle")
-                return False
-
-            if not os.path.exists(os.path.join(td, "minifi.log.gz")):
-                logging.error("minifi.log.gz file was not found in debug bundle")
-                return False
-
-            with gzip.open(os.path.join(td, "minifi.log.gz"), 'rb') as f_in:
-                with open(os.path.join(td, "minifi.log"), 'wb') as f_out:
-                    shutil.copyfileobj(f_in, f_out)
-
-            with open(os.path.join(td, "minifi.log")) as f:
-                if 'MiNiFi started' not in f.read():
-                    logging.error("'MiNiFi started' log entry was not found in minifi.log file")
-                    return False
-
-            return True
-
     def set_value_on_plc_with_modbus(self, container_name, modbus_cmd):
         return self.modbus_checker.set_value_on_plc_with_modbus(container_name, modbus_cmd)
 
diff --git a/docker/test/integration/cluster/MinifiControllerExecutor.py b/docker/test/integration/cluster/MinifiControllerExecutor.py
deleted file mode 100644
index 880e43e74..000000000
--- a/docker/test/integration/cluster/MinifiControllerExecutor.py
+++ /dev/null
@@ -1,78 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one or more
-# contributor license agreements.  See the NOTICE file distributed with
-# this work for additional information regarding copyright ownership.
-# The ASF licenses this file to You 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 logging
-from .DockerCommunicator import DockerCommunicator
-from .containers.MinifiContainer import MinifiContainer
-
-
-class MinifiControllerExecutor:
-    def __init__(self, container_communicator: DockerCommunicator):
-        self.container_communicator = container_communicator
-
-    def update_flow(self, container_name: str):
-        self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--updateflow", "/tmp/resources/minifi-controller/config.yml"])
-
-    def updated_config_is_persisted(self, container_name: str) -> bool:
-        (code, output) = self.container_communicator.execute_command(container_name, ["cat", MinifiContainer.MINIFI_LOCATIONS.config_path])
-        return code == 0 and "2f2a3b47-f5ba-49f6-82b5-bc1c86b96f38" in output
-
-    def stop_component(self, component: str, container_name: str):
-        self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--stop", component])
-
-    def start_component(self, component: str, container_name: str):
-        self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--start", component])
-
-    def is_component_running(self, component: str, container_name: str) -> bool:
-        (code, output) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--list", "components"])
-        return code == 0 and component + ", running: true" in output
-
-    def get_connections(self, container_name: str):
-        (_, output) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--list", "connections"])
-        connections = []
-        for line in output.split('\n'):
-            if not line.startswith('[') and not line.startswith('Connection Names'):
-                connections.append(line)
-        return connections
-
-    def get_full_connection_count(self, container_name: str) -> int:
-        (_, output) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--getfull"])
-        for line in output.split('\n'):
-            if "are full" in line:
-                return int(line.split(' ')[0])
-        return -1
-
-    def get_connection_size(self, connection: str, container_name: str):
-        (_, output) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--getsize", connection])
-        for line in output.split('\n'):
-            if "Size/Max of " + connection in line:
-                size_and_max = line.split(connection)[1].split('/')
-                return (int(size_and_max[0].strip()), int(size_and_max[1].strip()))
-        return (-1, -1)
-
-    def get_manifest(self, container_name: str) -> str:
-        (_, output) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--manifest"])
-        manifest = ""
-        for line in output.split('\n'):
-            if not line.startswith('['):
-                manifest += line
-        return manifest
-
-    def get_debug_bundle(self, container_name: str, dest: str) -> bool:
-        (code, _) = self.container_communicator.execute_command(container_name, [MinifiContainer.MINIFI_LOCATIONS.minifi_controller_path, "--debug", "/tmp"])
-        if code != 0:
-            logging.error("Minifi controller debug command failed with code: %d", code)
-            return False
-
-        return self.container_communicator.copy_file_from_container(container_name, "/tmp/debug.tar.gz", dest)
diff --git a/docker/test/integration/cluster/containers/MinifiContainer.py b/docker/test/integration/cluster/containers/MinifiContainer.py
index 387a3bd0d..f5185d1be 100644
--- a/docker/test/integration/cluster/containers/MinifiContainer.py
+++ b/docker/test/integration/cluster/containers/MinifiContainer.py
@@ -41,7 +41,6 @@ class MinifiOptions:
         self.config_format = "json"
         self.use_flow_config_from_url = False
         self.set_ssl_context_properties = False
-        self.enable_controller_socket = False
         self.enable_log_metrics_publisher = False
         self.enable_example_minifi_python_processors = False
         if "true" in os.environ['MINIFI_FIPS']:
@@ -186,12 +185,6 @@ class MinifiContainer(FlowContainer):
             if self.options.use_flow_config_from_url:
                 f.write(f"nifi.c2.flow.url=http://minifi-c2-server-{self.feature_context.id}:10090/c2/config?class=minifi-test-class\n")
 
-            if self.options.enable_controller_socket:
-                f.write("controller.socket.enable=true\n")
-                f.write("controller.socket.host=localhost\n")
-                f.write("controller.socket.port=9998\n")
-                f.write("controller.socket.local.any.interface=false\n")
-
             if self.options.use_nifi_python_processors_with_virtualenv or self.options.remove_python_requirements_txt or self.options.use_nifi_python_processors_without_dependencies:
                 f.write("nifi.python.virtualenv.directory={minifi_python_venv_parent}/venv\n".format(minifi_python_venv_parent=MinifiContainer.MINIFI_LOCATIONS.minifi_python_venv_parent))
             elif self.options.use_nifi_python_processors_with_virtualenv_packages_installed:
diff --git a/docker/test/integration/features/MiNiFi_integration_test_driver.py b/docker/test/integration/features/MiNiFi_integration_test_driver.py
index d3dd8dd5c..73941ecda 100644
--- a/docker/test/integration/features/MiNiFi_integration_test_driver.py
+++ b/docker/test/integration/features/MiNiFi_integration_test_driver.py
@@ -374,42 +374,9 @@ class MiNiFi_integration_test:
     def set_json_in_minifi(self):
         self.cluster.set_json_in_minifi()
 
-    def set_controller_socket_properties_in_minifi(self):
-        self.cluster.set_controller_socket_properties_in_minifi()
-
     def llama_model_is_downloaded_in_minifi(self):
         self.cluster.llama_model_is_downloaded_in_minifi()
 
-    def update_flow_config_through_controller(self, container_name: str):
-        self.cluster.update_flow_config_through_controller(container_name)
-
-    def check_minifi_controller_updated_config_is_persisted(self, container_name: str):
-        assert self.cluster.check_minifi_controller_updated_config_is_persisted(container_name) or self.cluster.log_app_output()
-
-    def stop_component_through_controller(self, component: str, container_name: str):
-        self.cluster.stop_component_through_controller(component, container_name)
-
-    def start_component_through_controller(self, component: str, container_name: str):
-        self.cluster.start_component_through_controller(component, container_name)
-
-    def check_component_not_running_through_controller(self, component: str, container_name: str):
-        assert self.cluster.check_component_not_running_through_controller(component, container_name) or self.cluster.log_app_output()
-
-    def check_component_running_through_controller(self, component: str, container_name: str):
-        assert self.cluster.check_component_running_through_controller(component, container_name) or self.cluster.log_app_output()
-
-    def connection_found_through_controller(self, connection: str, container_name: str):
-        assert self.cluster.connection_found_through_controller(connection, container_name) or self.cluster.log_app_output()
-
-    def check_connections_full_through_controller(self, connection_count: int, container_name: str):
-        assert self.cluster.check_connections_full_through_controller(connection_count, container_name) or self.cluster.log_app_output()
-
-    def check_connection_size_through_controller(self, connection: str, size: int, max_size: int, container_name: str):
-        assert self.cluster.check_connection_size_through_controller(connection, size, max_size, container_name) or self.cluster.log_app_output()
-
-    def manifest_can_be_retrieved_through_minifi_controller(self, container_name: str):
-        assert self.cluster.manifest_can_be_retrieved_through_minifi_controller(container_name) or self.cluster.log_app_output()
-
     def enable_log_metrics_publisher_in_minifi(self):
         self.cluster.enable_log_metrics_publisher_in_minifi()
 
@@ -422,9 +389,6 @@ class MiNiFi_integration_test:
     def disable_openssl_fips_mode_in_minifi(self):
         self.cluster.disable_openssl_fips_mode_in_minifi()
 
-    def debug_bundle_can_be_retrieved_through_minifi_controller(self, container_name: str):
-        assert self.cluster.debug_bundle_can_be_retrieved_through_minifi_controller(container_name) or self.cluster.log_app_output()
-
     def set_value_on_plc_with_modbus(self, container_name, modbus_cmd):
         assert self.cluster.set_value_on_plc_with_modbus(container_name, modbus_cmd)
 
diff --git a/docker/test/integration/features/steps/steps.py b/docker/test/integration/features/steps/steps.py
index 9757441dd..606d5d76d 100644
--- a/docker/test/integration/features/steps/steps.py
+++ b/docker/test/integration/features/steps/steps.py
@@ -896,122 +896,6 @@ def step_impl(context):
     context.test.set_json_in_minifi()
 
 
-# MiNiFi controller
-@given(u'controller socket properties are set up')
-def step_impl(context):
-    context.test.set_controller_socket_properties_in_minifi()
-
-
-@when(u'MiNiFi config is updated through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str):
-    context.test.update_flow_config_through_controller(minifi_container_name)
-
-
-@when(u'MiNiFi config is updated through MiNiFi controller')
-def step_impl(context):
-    context.execute_steps(f"when MiNiFi config is updated through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'the updated config is persisted in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str):
-    context.test.check_minifi_controller_updated_config_is_persisted(minifi_container_name)
-
-
-@then(u'the updated config is persisted')
-def step_impl(context):
-    context.execute_steps(f"then the updated config is persisted in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@when(u'the {component} component is stopped through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str, component: str):
-    context.test.stop_component_through_controller(component, minifi_container_name)
-
-
-@when(u'the {component} component is stopped through MiNiFi controller')
-def step_impl(context, component: str):
-    context.execute_steps(f"when the {component} component is stopped through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@when(u'the {component} component is started through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str, component: str):
-    context.test.start_component_through_controller(component, minifi_container_name)
-
-
-@when(u'the {component} component is started through MiNiFi controller')
-def step_impl(context, component: str):
-    context.execute_steps(f"when the {component} component is started through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'the {component} component is not running in the \"{minifi_container_name}\" flow')
-def step_impl(context, component: str, minifi_container_name: str):
-    context.test.check_component_not_running_through_controller(component, minifi_container_name)
-
-
-@then(u'the {component} component is not running')
-def step_impl(context, component: str):
-    context.execute_steps(f"then the {component} component is not running in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'the {component} component is running in the \"{minifi_container_name}\" flow')
-def step_impl(context, component: str, minifi_container_name: str):
-    context.test.check_component_running_through_controller(component, minifi_container_name)
-
-
-@then(u'the {component} component is running')
-def step_impl(context, component: str):
-    context.execute_steps(f"then the {component} component is running in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'connection \"{connection}\" can be seen through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, connection: str, minifi_container_name: str):
-    context.test.connection_found_through_controller(connection, minifi_container_name)
-
-
-@then(u'connection \"{connection}\" can be seen through MiNiFi controller')
-def step_impl(context, connection: str):
-    context.execute_steps(f"then connection \"{connection}\" can be seen through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'{connection_count:d} connections can be seen full through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, connection_count: int, minifi_container_name: str):
-    context.test.check_connections_full_through_controller(connection_count, minifi_container_name)
-
-
-@then(u'{connection_count:d} connections can be seen full through MiNiFi controller')
-def step_impl(context, connection_count: int):
-    context.execute_steps(f"then {connection_count:d} connections can be seen full through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'connection \"{connection}\" has {size:d} size and {max_size:d} max size through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, connection: str, size: int, max_size: int, minifi_container_name: str):
-    context.test.check_connection_size_through_controller(connection, size, max_size, minifi_container_name)
-
-
-@then(u'connection \"{connection}\" has {size:d} size and {max_size:d} max size through MiNiFi controller')
-def step_impl(context, connection: str, size: int, max_size: int):
-    context.execute_steps(f"then connection \"{connection}\" has {size:d} size and {max_size:d} max size through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'manifest can be retrieved through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str):
-    context.test.manifest_can_be_retrieved_through_minifi_controller(minifi_container_name)
-
-
-@then(u'manifest can be retrieved through MiNiFi controller')
-def step_impl(context):
-    context.execute_steps(f"then manifest can be retrieved through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
-@then(u'debug bundle can be retrieved through MiNiFi controller in the \"{minifi_container_name}\" flow')
-def step_impl(context, minifi_container_name: str):
-    context.test.debug_bundle_can_be_retrieved_through_minifi_controller(minifi_container_name)
-
-
-@then(u'debug bundle can be retrieved through MiNiFi controller')
-def step_impl(context):
-    context.execute_steps(f"then debug bundle can be retrieved through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow")
-
-
 @given(u'a SSL context service is set up for the following processor: \"{processor_name}\"')
 def step_impl(context, processor_name: str):
     setUpSslContextServiceForProcessor(context, processor_name)
diff --git a/docker/test/integration/features/minifi_controller.feature b/extensions/standard-processors/tests/features/minifi_controller.feature
similarity index 95%
rename from docker/test/integration/features/minifi_controller.feature
rename to extensions/standard-processors/tests/features/minifi_controller.feature
index c78f91ca4..0995c8f95 100644
--- a/docker/test/integration/features/minifi_controller.feature
+++ b/extensions/standard-processors/tests/features/minifi_controller.feature
@@ -17,16 +17,13 @@
 Feature: MiNiFi Controller functionalities
   Test MiNiFi Controller functionalities
 
-  Background:
-    Given the content of "/tmp/output" is monitored
-
   Scenario: Flow config can be updated through MiNiFi controller
     Given a GenerateFlowFile processor
     And a file with the content "test" is present in "/tmp/input"
     And controller socket properties are set up
     When all instances start up
     And MiNiFi config is updated through MiNiFi controller
-    Then a flowfile with the content "test" is placed in the monitored directory in less than 60 seconds
+    Then there is a single file with "test" content in the "/tmp/output" directory in less than 60 seconds
     And the updated config is persisted
 
   Scenario: A component can be stopped
diff --git a/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py b/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py
new file mode 100644
index 000000000..8cefd0762
--- /dev/null
+++ b/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py
@@ -0,0 +1,91 @@
+#
+#  Licensed to the Apache Software Foundation (ASF) under one or more
+#  contributor license agreements.  See the NOTICE file distributed with
+#  this work for additional information regarding copyright ownership.
+#  The ASF licenses this file to You 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 behave import given, when, then
+from minifi_test_framework.core.helpers import retry_check
+from minifi_test_framework.core.minifi_test_context import MinifiTestContext
+
+
+@given(u'controller socket properties are set up')
+def step_impl(context: MinifiTestContext):
+    context.get_or_create_default_minifi_container().set_controller_socket_properties()
+
+
+@when(u'MiNiFi config is updated through MiNiFi controller')
+def step_impl(context: MinifiTestContext):
+    context.get_or_create_default_minifi_container().update_flow_config_through_controller()
+
+
+@then(u'the updated config is persisted')
+def step_impl(context: MinifiTestContext):
+    assert context.get_or_create_default_minifi_container().updated_config_is_persisted()
+
+
+@when(u'the {component} component is stopped through MiNiFi controller')
+def step_impl(context: MinifiTestContext, component: str):
+    context.get_or_create_default_minifi_container().stop_component_through_controller(component)
+
+
+@when(u'the {component} component is started through MiNiFi controller')
+def step_impl(context: MinifiTestContext, component: str):
+    context.get_or_create_default_minifi_container().start_component_through_controller(component)
+
+
+@then(u'the {component} component is not running')
+def step_impl(context: MinifiTestContext, component: str):
+    assert not context.get_or_create_default_minifi_container().is_component_running(component)
+
+
+@then(u'the {component} component is running')
+def step_impl(context: MinifiTestContext, component: str):
+    assert context.get_or_create_default_minifi_container().is_component_running(component)
+
+
+@then(u'connection \"{connection}\" can be seen through MiNiFi controller')
+def step_impl(context: MinifiTestContext, connection: str):
+    assert context.get_or_create_default_minifi_container().connection_found_through_controller(connection)
+
+
+@then(u'{connection_count:d} connections can be seen full through MiNiFi controller')
+def step_impl(context: MinifiTestContext, connection_count: int):
+    assert context.get_or_create_default_minifi_container().get_full_connection_count() == connection_count
+
+
+@retry_check(10, 1)
+def check_connection_size_through_controller(context: MinifiTestContext, connection: str, size: int, max_size: int) -> bool:
+    return context.get_or_create_default_minifi_container().get_connection_size(connection) == (size, max_size)
+
+
+@then(u'connection \"{connection}\" has {size:d} size and {max_size:d} max size through MiNiFi controller')
+def step_impl(context: MinifiTestContext, connection: str, size: int, max_size: int):
+    assert check_connection_size_through_controller(context, connection, size, max_size)
+
+
+@retry_check(10, 1)
+def manifest_can_be_retrieved_through_minifi_controller(context: MinifiTestContext) -> bool:
+    manifest = context.get_or_create_default_minifi_container().get_manifest()
+    return '"agentManifest": {' in manifest and '"componentManifest": {' in manifest and '"agentType": "cpp"' in manifest
+
+
+@then(u'manifest can be retrieved through MiNiFi controller')
+def step_impl(context: MinifiTestContext):
+    assert manifest_can_be_retrieved_through_minifi_controller(context)
+
+
+@then(u'debug bundle can be retrieved through MiNiFi controller')
+def step_impl(context: MinifiTestContext):
+    assert context.get_or_create_default_minifi_container().create_debug_bundle()
diff --git a/extensions/standard-processors/tests/features/steps/steps.py b/extensions/standard-processors/tests/features/steps/steps.py
index 779c8b075..f35234ffa 100644
--- a/extensions/standard-processors/tests/features/steps/steps.py
+++ b/extensions/standard-processors/tests/features/steps/steps.py
@@ -33,21 +33,21 @@ def step_impl(context: MinifiTestContext):
 
 
 @step("a Syslog client with UDP protocol is setup to send logs to minifi")
-def step_impl(context):
+def step_impl(context: MinifiTestContext):
     context.containers["syslog-udp"] = SyslogContainer("udp", context)
 
 
 @step(u'there is an accessible PLC with modbus enabled')
-def step_impl(context):
+def step_impl(context: MinifiTestContext):
     modbus_container = context.containers["diag-slave-tcp"] = DiagSlave(context)
     assert modbus_container.deploy()
 
 
 @step(u'PLC register has been set with {modbus_cmd} command')
-def step_impl(context, modbus_cmd):
+def step_impl(context: MinifiTestContext, modbus_cmd: str):
     assert context.containers["diag-slave-tcp"].set_value_on_plc_with_modbus(modbus_cmd) or context.containers["diag-slave-tcp"].log_app_output()
 
 
 @step('a TCP client is set up to send a test TCP message to minifi')
-def step_impl(context):
+def step_impl(context: MinifiTestContext):
     context.containers["tcp-client"] = TcpClientContainer(context)