diff --git a/behave_framework/pyproject.toml b/behave_framework/pyproject.toml index 263750d9f6..e96c86dffc 100644 --- a/behave_framework/pyproject.toml +++ b/behave_framework/pyproject.toml @@ -1,6 +1,6 @@ [project] name = "minifi-test-framework" -version = "0.1.0" +version = "0.2.0" requires-python = ">= 3.10" description = "A testing framework for MiNiFi extensions." dependencies = [ @@ -8,11 +8,14 @@ dependencies = [ "docker==7.1.0", "PyYAML==6.0.3", "humanfriendly==10.0", - "m2crypto==0.46.2", - "pyopenssl==25.0.0", + "cryptography==46.0.5", "pyjks==20.0.0" ] [tool.setuptools] package-dir = {"" = "src"} -packages = ["minifi_test_framework"] + +[tool.setuptools.packages.find] +where = ["src"] +include = ["minifi_test_framework*"] + diff --git a/behave_framework/src/minifi_test_framework/containers/container.py b/behave_framework/src/minifi_test_framework/containers/container_linux.py similarity index 93% rename from behave_framework/src/minifi_test_framework/containers/container.py rename to behave_framework/src/minifi_test_framework/containers/container_linux.py index 24f941319c..ad0fd49dfa 100644 --- a/behave_framework/src/minifi_test_framework/containers/container.py +++ b/behave_framework/src/minifi_test_framework/containers/container_linux.py @@ -15,29 +15,31 @@ # limitations under the License. # from __future__ import annotations -from typing import TYPE_CHECKING import json import logging import os import shlex -import tempfile import tarfile +import tempfile import uuid - -import docker from docker.models.networks import Network - +from minifi_test_framework.containers.container_protocol import ContainerProtocol from minifi_test_framework.containers.directory import Directory from minifi_test_framework.containers.file import File from minifi_test_framework.containers.host_file import HostFile +from typing import TYPE_CHECKING + +import docker if TYPE_CHECKING: from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class Container: - def __init__(self, image_name: str, container_name: str, network: Network, command: str | None = None, entrypoint: str | None = None): +class LinuxContainer(ContainerProtocol): + def __init__(self, image_name: str, container_name: str, network: Network, command: str | None = None, + entrypoint: str | None = None): + super().__init__() self.image_name: str = image_name self.container_name: str = container_name self.network: Network = network @@ -120,10 +122,11 @@ def deploy(self, context: MinifiTestContext | None) -> bool: pass try: logging.info(f"Creating and starting container '{self.container_name}'...") - self.container = self.client.containers.run( - image=self.image_name, name=self.container_name, ports=self.ports, - environment=self.environment, volumes=self.volumes, network=self.network.name, - command=self.command, entrypoint=self.entrypoint, user=self.user, detach=True) + self.container = self.client.containers.run(image=self.image_name, name=self.container_name, + ports=self.ports, environment=self.environment, + volumes=self.volumes, network=self.network.name, + command=self.command, entrypoint=self.entrypoint, + user=self.user, detach=True) except Exception as e: logging.error(f"Error starting container: {e}") raise @@ -211,7 +214,8 @@ def directory_contains_file_with_regex(self, directory_path: str, regex_str: str exit_code, output = self.exec_run("sh -c {}".format(shlex.quote(command))) if exit_code != 0: - logging.debug("While looking for regex %s in directory %s, grep returned exit code %d, output: %s", regex_str, directory_path, exit_code, output) + logging.debug("While looking for regex %s in directory %s, grep returned exit code %d, output: %s", + regex_str, directory_path, exit_code, output) return exit_code == 0 def path_with_content_exists(self, path: str, content: str) -> bool: @@ -375,7 +379,8 @@ def _extract_directory_from_container(self, directory_path: str, temp_dir: str) return os.path.join(temp_dir, os.path.basename(directory_path.strip('/'))) except Exception as e: - logging.error(f"Error extracting files from directory path '{directory_path}' from container '{self.container_name}': {e}") + logging.error( + f"Error extracting files from directory path '{directory_path}' from container '{self.container_name}': {e}") return None def _read_files_from_directory(self, directory_path: str) -> list[str] | None: @@ -478,3 +483,11 @@ def directory_contains_file_with_minimum_size(self, directory_path: str, expecte return True return False + + def get_memory_usage(self) -> int | None: + exit_code, output = self.exec_run(["awk", "/VmRSS/ { printf \"%d\\n\", $2 }", "/proc/1/status"]) + if exit_code != 0: + return None + memory_usage_in_bytes = int(output.strip()) * 1024 + logging.info(f"{self.container_name} memory usage: {memory_usage_in_bytes} bytes") + return memory_usage_in_bytes diff --git a/behave_framework/src/minifi_test_framework/containers/container_protocol.py b/behave_framework/src/minifi_test_framework/containers/container_protocol.py new file mode 100644 index 0000000000..5eb19f088e --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/container_protocol.py @@ -0,0 +1,47 @@ +from typing import Protocol, List + +from minifi_test_framework.containers.directory import Directory +from minifi_test_framework.containers.file import File +from minifi_test_framework.containers.host_file import HostFile + + +class ContainerProtocol(Protocol): + image_name: str + container_name: str + dirs: List[Directory] + files: List[File] + host_files: List[HostFile] + + def deploy(self, context) -> bool: + ... + + def clean_up(self): + ... + + def exec_run(self, command): + ... + + def directory_contains_file_with_content(self, directory_path: str, expected_content: str) -> bool: + ... + + def directory_contains_file_with_regex(self, directory_path: str, regex_str: str) -> bool: + ... + + def path_with_content_exists(self, path: str, content: str) -> bool: + ... + + def get_logs(self) -> str: + ... + + @property + def exited(self) -> bool: + ... + + def get_number_of_files(self, directory_path: str) -> int: + ... + + def verify_file_contents(self, directory_path: str, expected_contents: list[str]) -> bool: + ... + + def log_app_output(self) -> bool: + ... diff --git a/behave_framework/src/minifi_test_framework/containers/container_windows.py b/behave_framework/src/minifi_test_framework/containers/container_windows.py new file mode 100644 index 0000000000..c1a9c755aa --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/container_windows.py @@ -0,0 +1,392 @@ +# +# 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, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +from __future__ import annotations +import logging +import os +import tempfile +import base64 +import tarfile +import io +from typing import Union, Optional, Tuple, List, Dict, TYPE_CHECKING + +import docker +from docker.models.networks import Network +from docker.models.containers import Container + +from minifi_test_framework.containers.container_protocol import ContainerProtocol +from minifi_test_framework.containers.directory import Directory +from minifi_test_framework.containers.file import File +from minifi_test_framework.containers.host_file import HostFile + +if TYPE_CHECKING: + from minifi_test_framework.core.minifi_test_context import MinifiTestContext + + +class WindowsContainer(ContainerProtocol): + def __init__(self, image_name: str, container_name: str, network: Network, command: str | None = None, entrypoint: str | None = None): + super().__init__() + self.image_name: str = image_name + self.container_name: str = container_name + self.network: Network = network + + self.client: docker.DockerClient = docker.from_env() + self.container: Optional[Container] = None + self.files: List[File] = [] + self.dirs: List[Directory] = [] + self.host_files: List[HostFile] = [] + self.volumes: Dict = {} + self.command: str | None = command + self.entrypoint: str | None = entrypoint + self._temp_dir: Optional[tempfile.TemporaryDirectory] = None + self.ports: Optional[Dict[str, int]] = None + self.environment: List[str] = [] + + def _normalize_path(self, path: str) -> str: + clean_path = path.strip().replace("/", "\\") + if clean_path.startswith("\\"): + clean_path = clean_path[1:] + + # If it doesn't already have a drive letter, assume C: + if ":" not in clean_path: + return f"C:\\{clean_path}" + return clean_path + + def deploy(self, context: MinifiTestContext | None) -> bool: + if self._temp_dir: + self._temp_dir.cleanup() + self._temp_dir = tempfile.TemporaryDirectory() + + for directory in self.dirs: + rel_path = directory.path.strip("/\\") + temp_subdir = os.path.join(self._temp_dir.name, rel_path) + os.makedirs(temp_subdir, exist_ok=True) + + for file_name, content in directory.files.items(): + file_path = os.path.join(temp_subdir, file_name) + with open(file_path, "w", encoding="utf-8") as temp_file: + logging.info(f"writing content into {temp_file.name}") + temp_file.write(content) + + container_bind_path = self._normalize_path(directory.path) + self.volumes[temp_subdir] = { + "bind": container_bind_path, + "mode": directory.mode + } + + for host_file in self.host_files: + container_bind_path = self._normalize_path(host_file.container_path) + self.volumes[host_file.host_path] = {"bind": container_bind_path, "mode": host_file.mode} + + try: + existing_container = self.client.containers.get(self.container_name) + logging.warning(f"Found existing container '{self.container_name}'. Removing it first.") + existing_container.remove(force=True) + except docker.errors.NotFound: + pass + + try: + logging.info(f"Creating and starting container '{self.container_name}'...") + self.container = self.client.containers.create( + image=self.image_name, + name=self.container_name, + ports=self.ports, + environment=self.environment, + volumes=self.volumes, + network=self.network.name, + command=self.command, + entrypoint=self.entrypoint, + detach=True, + tty=False + ) + + self.container.start() + + for file in self.files: + self._copy_content_to_container(file.content, file.path) + + except Exception as e: + logging.error(f"Error starting container: {e}") + self.clean_up() + raise + return True + + def _copy_content_to_container(self, content: str | bytes, target_path: str): + if not self.container: + return + + win_path = self._normalize_path(target_path) + dir_name = os.path.dirname(win_path) + file_name = os.path.basename(win_path) + + self._run_powershell(f"New-Item -ItemType Directory -Force -Path '{dir_name}'") + + tar_stream = io.BytesIO() + with tarfile.open(fileobj=tar_stream, mode='w') as tar: + if isinstance(content, str): + encoded_data = content.encode('utf-8') + else: + encoded_data = content + tarinfo = tarfile.TarInfo(name=file_name) + tarinfo.size = len(encoded_data) + tar.addfile(tarinfo, io.BytesIO(encoded_data)) + + tar_stream.seek(0) + + self.container.put_archive(path=dir_name, data=tar_stream) + + def clean_up(self): + if self._temp_dir: + try: + self._temp_dir.cleanup() + self._temp_dir = None + except Exception as e: + logging.warning(f"Failed to cleanup temp dir: {e}") + + if self.container: + try: + self.container.remove(force=True) + except docker.errors.NotFound: + pass + except Exception as e: + logging.warning(f"Failed to remove container: {e}") + finally: + self.container = None + + def exec_run(self, command: Union[str, list]) -> Tuple[int | None, str]: + logging.debug(f"Running command: {command}") + if self.container: + (code, output) = self.container.exec_run(command, detach=False) + decoded_output = output.decode("utf-8", errors='replace') + logging.debug(f"Result {code}, output: {decoded_output}") + return code, decoded_output + return None, "Container not running." + + def _run_powershell(self, ps_script: str) -> Tuple[int | None, str]: + if not self.container: + return None, "Container not running" + + encoded_command = base64.b64encode(ps_script.encode('utf_16_le')).decode('utf-8') + + cmd_parts = ["powershell", "-NonInteractive", "-NoProfile", "-EncodedCommand", encoded_command] + + return self.exec_run(cmd_parts) + + def not_empty_dir_exists(self, directory_path: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + ps_script = ( + f"if (Test-Path -Path '{win_path}' -PathType Container) {{ " + f" if (Get-ChildItem -Path '{win_path}') {{ exit 0 }} else {{ exit 1 }} " + f"}} else {{ exit 2 }}" + ) + + exit_code, _ = self._run_powershell(ps_script) + return exit_code == 0 + + def directory_contains_file_with_content(self, directory_path: str, expected_content: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + escaped_content = expected_content.replace("'", "''") + + ps_script = ( + f"if (-not (Test-Path '{win_path}')) {{ exit 2 }}; " + f"$matches = Get-ChildItem -Path '{win_path}' -File -Depth 0 | " + f"Select-String -Pattern '{escaped_content}' -SimpleMatch -List; " + f"if ($matches) {{ exit 0 }} else {{ exit 1 }}" + ) + + exit_code, _ = self._run_powershell(ps_script) + return exit_code == 0 + + def directory_contains_file_with_regex(self, directory_path: str, regex_str: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + escaped_regex = regex_str.replace("'", "''") + + ps_script = ( + f"if (-not (Test-Path '{win_path}')) {{ exit 2 }}; " + f"$matches = Get-ChildItem -Path '{win_path}' -File -Depth 0 | " + f"Select-String -Pattern '{escaped_regex}' -List; " + f"if ($matches) {{ exit 0 }} else {{ exit 1 }}" + ) + + exit_code, _ = self._run_powershell(ps_script) + return exit_code == 0 + + def path_with_content_exists(self, path: str, content: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(path) + + escaped_content = content.replace("'", "''").replace("\n", "\r\n") + + ps_script = ( + f"try {{ " + f" $found = Select-String -Path '{win_path}' -Pattern '^{escaped_content}$'; " + f" if ($found.Count -eq 1) {{ exit 0 }} else {{ exit 1 }} " + f"}} catch {{ exit 2 }}" + ) + + exit_code, output = self._run_powershell(ps_script) + if exit_code != 0: + logging.debug(f"path_with_content_exists failed for {win_path}. Output: {output}") + + return exit_code == 0 + + def directory_has_single_file_with_content(self, directory_path: str, expected_content: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + escaped_content = expected_content.strip().replace("'", "''").replace("\n", "\r\n") + + ps_script = ( + f"$files = Get-ChildItem -Path '{win_path}' -File -Depth 0; " + f"if ($files.Count -ne 1) {{ exit 1 }}; " + f"$actual_content = Get-Content -Path $files[0].FullName -Raw; " + f"if ($actual_content.Trim() -eq '{escaped_content}') {{ exit 0 }} else {{ exit 2 }};" + ) + + exit_code, output = self._run_powershell(ps_script) + if exit_code != 0: + logging.debug(f"Check for single file failed in {win_path}. Output: {output}") + + return exit_code == 0 + + def get_logs(self) -> str: + logging.debug("Getting logs from container '%s'", self.container_name) + if not self.container: + return "" + logs_as_bytes = self.container.logs() + return logs_as_bytes.decode('utf-8', errors='replace') + + def log_app_output(self) -> bool: + logs = self.get_logs() + logging.info("Logs of container '%s':", self.container_name) + for line in logs.splitlines(): + logging.info(line) + return False + + @property + def exited(self) -> bool: + if not self.container: + return False + try: + self.container.reload() + return self.container.status == 'exited' + except docker.errors.NotFound: + self.container = None + return False + except Exception: + return False + + def get_number_of_files(self, directory_path: str) -> int: + if not self.container: + return -1 + + win_path = self._normalize_path(directory_path) + ps_script = f"(Get-ChildItem -Path '{win_path}' -File -Depth 0).Count" + + exit_code, output = self._run_powershell(ps_script) + + if exit_code != 0: + logging.error(f"Error counting files in '{win_path}': {output}") + return -1 + + try: + return int(output.strip()) + except (ValueError, IndexError): + return -1 + + def verify_file_contents(self, directory_path: str, expected_contents: List[str]) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + + ps_list = f"Get-ChildItem -Path '{win_path}' -File -Depth 0 | Select-Object -ExpandProperty FullName" + exit_code, output = self._run_powershell(ps_list) + + if exit_code != 0: + logging.error(f"Error listing files in '{win_path}': {output}") + return False + + actual_filepaths = [path.strip() for path in output.splitlines() if path.strip()] + + if len(actual_filepaths) != len(expected_contents): + return False + + actual_file_contents = [] + for path in actual_filepaths: + ps_read = ( + f"$c = Get-Content -Path '{path}' -Raw; " + f"if ($c) {{ ($c -replace '\\r\\n', '\\n' -replace '\\r', '\\n').Trim() }}" + ) + + exit_code, content = self._run_powershell(ps_read) + if exit_code != 0: + return False + actual_file_contents.append(content) + + normalized_expected = [ + s.strip().replace("\r\n", "\n").replace("\r", "\n") for s in expected_contents + ] + + return sorted(actual_file_contents) == sorted(normalized_expected) + + def nonempty_dir_exists(self, directory_path: str) -> bool: + if not self.container: + return False + + win_path = self._normalize_path(directory_path) + ps_script = ( + f"if ((Test-Path -LiteralPath '{win_path}' -PathType Container) " + f"-and (Get-ChildItem -LiteralPath '{win_path}' -Force | Select-Object -First 1)) " + f"{{ exit 0 }} else {{ exit 1 }}" + ) + + exit_code, _ = self._run_powershell(ps_script) + + return exit_code == 0 + + def directory_contains_file_with_minimum_size(self, directory_path: str, expected_size: int) -> bool: + if not self.container or not self.nonempty_dir_exists(directory_path): + return False + + win_path = self._normalize_path(directory_path) + ps_script = ( + f"Get-ChildItem -LiteralPath '{win_path}' -File " + f"| Where-Object {{ $_.Length -gt {expected_size} }}" + ) + + exit_code, output = self._run_powershell(ps_script) + + if exit_code != 0: + logging.error(f"Error running command to get file sizes: {output}") + return False + + if output and len(output.strip()) > 0: + return True + + return False diff --git a/behave_framework/src/minifi_test_framework/containers/http_proxy_container.py b/behave_framework/src/minifi_test_framework/containers/http_proxy_container.py index da0cd4c2b6..430e2abff1 100644 --- a/behave_framework/src/minifi_test_framework/containers/http_proxy_container.py +++ b/behave_framework/src/minifi_test_framework/containers/http_proxy_container.py @@ -17,13 +17,13 @@ from textwrap import dedent -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class HttpProxy(Container): +class HttpProxy(LinuxContainer): def __init__(self, test_context: MinifiTestContext): dockerfile = dedent("""\ FROM {base_image} diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_container.py b/behave_framework/src/minifi_test_framework/containers/minifi_container.py deleted file mode 100644 index 8ce0f9b688..0000000000 --- a/behave_framework/src/minifi_test_framework/containers/minifi_container.py +++ /dev/null @@ -1,231 +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 -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 - - -class MinifiContainer(Container): - def __init__(self, container_name: str, test_context: MinifiTestContext): - super().__init__(test_context.minifi_container_image, f"{container_name}-{test_context.scenario_id}", test_context.network) - self.flow_definition = MinifiFlowDefinition() - self.properties: dict[str, str] = {} - self.log_properties: dict[str, str] = {} - self.scenario_id = test_context.scenario_id - self.deploy_timeout_seconds = 20 - - minifi_client_cert, minifi_client_key = make_cert_without_extended_usage(common_name=self.container_name, ca_cert=test_context.root_ca_cert, ca_key=test_context.root_ca_key) - self.files.append(File("/usr/local/share/certs/ca-root-nss.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert))) - self.files.append(File("/tmp/resources/root_ca.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert))) - self.files.append(File("/tmp/resources/minifi_client.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=minifi_client_cert))) - self.files.append(File("/tmp/resources/minifi_client.key", crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=minifi_client_key))) - self.files.append(File("/tmp/resources/minifi_merged_cert.crt", - crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=minifi_client_cert) + crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=minifi_client_key))) - - clientuser_cert, clientuser_key = make_client_cert("clientuser", ca_cert=test_context.root_ca_cert, ca_key=test_context.root_ca_key) - self.files.append(File("/tmp/resources/clientuser.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=clientuser_cert))) - self.files.append(File("/tmp/resources/clientuser.key", crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=clientuser_key))) - - minifi_server_cert, minifi_server_key = make_server_cert(common_name=f"server-{test_context.scenario_id}", ca_cert=test_context.root_ca_cert, ca_key=test_context.root_ca_key) - self.files.append(File("/tmp/resources/minifi_server.crt", - 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() - - def deploy(self, context: MinifiTestContext | None) -> bool: - flow_config = self.flow_definition.to_yaml() - logging.info(f"Deploying MiNiFi container '{self.container_name}' with flow configuration:\n{flow_config}") - if self.is_fhs: - self.files.append(File("/etc/nifi-minifi-cpp/config.yml", flow_config)) - self.files.append(File("/etc/nifi-minifi-cpp/minifi.properties", self._get_properties_file_content())) - self.files.append( - File("/etc/nifi-minifi-cpp/minifi-log.properties", self._get_log_properties_file_content())) - else: - self.files.append(File("/opt/minifi/minifi-current/conf/config.yml", flow_config)) - self.files.append( - File("/opt/minifi/minifi-current/conf/minifi.properties", self._get_properties_file_content())) - self.files.append(File("/opt/minifi/minifi-current/conf/minifi-log.properties", - self._get_log_properties_file_content())) - - 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"))) - - if not super().deploy(context): - return False - - finished_str = "MiNiFi started" - return wait_for_condition( - condition=lambda: finished_str in self.get_logs(), - timeout_seconds=self.deploy_timeout_seconds, - bail_condition=lambda: self.exited, - context=context) - - def set_deploy_timeout_seconds(self, timeout_seconds: int): - self.deploy_timeout_seconds = timeout_seconds - - def set_property(self, key: str, value: str): - self.properties[key] = value - - def set_log_property(self, key: str, value: str): - self.log_properties[key] = value - - def enable_openssl_fips_mode(self): - self.properties["nifi.openssl.fips.support.enable"] = "true" - - def enable_log_metrics_publisher(self): - self.properties["nifi.metrics.publisher.LogMetricsPublisher.metrics"] = "RepositoryMetrics" - self.properties["nifi.metrics.publisher.LogMetricsPublisher.logging.interval"] = "1s" - self.properties["nifi.metrics.publisher.class"] = "LogMetricsPublisher" - - def fetch_flow_config_from_flow_url(self): - self.properties["nifi.c2.flow.url"] = f"http://minifi-c2-server-{self.scenario_id}:10090/c2/config?class=minifi-test-class" - - def set_up_ssl_properties(self): - self.properties["nifi.remote.input.secure"] = "true" - self.properties["nifi.security.client.certificate"] = "/tmp/resources/minifi_client.crt" - self.properties["nifi.security.client.private.key"] = "/tmp/resources/minifi_client.key" - self.properties["nifi.security.client.ca.certificate"] = "/tmp/resources/root_ca.crt" - - def _fill_default_properties(self): - if self.is_fhs: - self.properties["nifi.flow.configuration.file"] = "/etc/nifi-minifi-cpp/config.yml" - self.properties["nifi.extension.path"] = "/usr/lib64/nifi-minifi-cpp/extensions/*" - self.properties["nifi.python.processor.dir"] = '/var/lib/nifi-minifi-cpp/minifi-python' - else: - self.properties["nifi.flow.configuration.file"] = "./conf/config.yml" - self.properties["nifi.extension.path"] = "../extensions/*" - self.properties["nifi.python.processor.dir"] = '/opt/minifi/minifi-current/minifi-python' - self.properties["nifi.administrative.yield.duration"] = "1 sec" - self.properties["nifi.bored.yield.duration"] = "100 millis" - self.properties["nifi.openssl.fips.support.enable"] = "false" - self.properties["nifi.provenance.repository.class.name"] = "NoOpRepository" - - def _fill_default_log_properties(self): - self.log_properties["spdlog.pattern"] = "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v" - - self.log_properties["appender.stderr"] = "stderr" - self.log_properties["logger.root"] = "DEBUG, stderr" - self.log_properties["logger.org::apache::nifi::minifi"] = "DEBUG, stderr" - - def _get_properties_file_content(self): - lines = (f"{key}={value}" for key, value in self.properties.items()) - return "\n".join(lines) - - def _get_log_properties_file_content(self): - lines = (f"{key}={value}" for key, value in self.log_properties.items()) - return "\n".join(lines) - - def get_memory_usage(self) -> int | None: - exit_code, output = self.exec_run(["awk", "/VmRSS/ { printf \"%d\\n\", $2 }", "/proc/1/status"]) - if exit_code != 0: - return None - 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 - - def add_example_python_processors(self): - run_minifi_cmd = '/opt/minifi/minifi-current/bin/minifi.sh run' if not self.is_fhs else '/usr/bin/minifi' - minifi_python_dir_path = '/opt/minifi/minifi-current/minifi-python' if not self.is_fhs else '/var/lib/nifi-minifi-cpp/minifi-python' - minifi_python_examples = '/opt/minifi/minifi-current/minifi-python-examples' if not self.is_fhs else '/usr/share/doc/nifi-minifi-cpp/pythonprocessor-examples' - self.command = f'sh -c "cp -r {minifi_python_examples} {minifi_python_dir_path}/examples && {run_minifi_cmd}"' diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_controller.py b/behave_framework/src/minifi_test_framework/containers/minifi_controller.py new file mode 100644 index 0000000000..22f51d2348 --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/minifi_controller.py @@ -0,0 +1,80 @@ +import logging +from minifi_test_framework.core.helpers import retry_check + + +class MinifiController: + def __init__(self, minifi_container, config_path, bin_path): + self.minifi_container = minifi_container + self.config_path = config_path + self.bin_path = bin_path + + def set_controller_socket_properties(self): + self.minifi_container.properties["controller.socket.enable"] = "true" + self.minifi_container.properties["controller.socket.host"] = "localhost" + self.minifi_container.properties["controller.socket.port"] = "9998" + self.minifi_container.properties["controller.socket.local.any.interface"] = "false" + + def update_flow_config_through_controller(self): + self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--updateflow", "/tmp/resources/minifi-controller/config.yml"]) + + def updated_config_is_persisted(self) -> bool: + exit_code, output = self.minifi_container.exec_run(["cat", self.config_path]) + if exit_code != 0: + logging.error(f"Failed to read MiNiFi config file to check if updated config is persisted: {exit_code} {output}") + return False + return "2f2a3b47-f5ba-49f6-82b5-bc1c86b96f38" in output + + def stop_component_through_controller(self, component: str): + self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--stop", component]) + + def start_component_through_controller(self, component: str): + self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--start", component]) + + @retry_check(max_tries=10, retry_interval_seconds=1) + def is_component_running(self, component: str) -> bool: + (code, output) = self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--list", "components"]) + return code == 0 and component + ", running: true" in output + + def get_connections(self): + (_, output) = self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--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(max_tries=10, retry_interval_seconds=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.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--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.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--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.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--manifest"]) + manifest = "" + for line in output.split('\n'): + if not line.startswith('['): + manifest += line + return manifest + + def create_debug_bundle(self) -> bool: + (code, _) = self.minifi_container.exec_run([f"{self.bin_path}/minifi-controller", "--debug", "/tmp"]) + if code != 0: + logging.error("Minifi controller debug command failed with code: %d", code) + return False + + (code, _) = self.minifi_container.exec_run(["test", "-f", "/tmp/debug.tar.gz"]) + return code == 0 diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_linux_container.py b/behave_framework/src/minifi_test_framework/containers/minifi_linux_container.py new file mode 100644 index 0000000000..62311d9abb --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/minifi_linux_container.py @@ -0,0 +1,146 @@ +# +# 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 +import os +from minifi_test_framework.containers.file import File +from minifi_test_framework.containers.host_file import HostFile +from minifi_test_framework.core.helpers import wait_for_condition +from minifi_test_framework.core.minifi_test_context import MinifiTestContext +from minifi_test_framework.core.ssl_utils import make_cert_without_extended_usage, make_client_cert, make_server_cert, \ + dump_cert, dump_key +from minifi_test_framework.minifi.minifi_flow_definition import MinifiFlowDefinition +from pathlib import Path + +from .container_linux import LinuxContainer +from .minifi_controller import MinifiController +from .minifi_protocol import MinifiProtocol + + +class NormalDeployment: + def __init__(self): + self.conf_path = "/opt/minifi/minifi-current/conf" + self.bin_path = "/opt/minifi/minifi-current/bin" + self.extension_pattern = "../extensions/*" + self.minifi_python_path = "/opt/minifi/minifi-current/minifi-python" + self.minifi_python_examples_path = "/opt/minifi/minifi-current/minifi-python-examples" + + +class FHSDeployment: + def __init__(self): + self.conf_path = "/etc/nifi-minifi-cpp/" + self.bin_path = "/usr/bin" + self.extension_pattern = "/usr/lib64/nifi-minifi-cpp/extensions/*" + self.minifi_python_path = "/var/lib/nifi-minifi-cpp/minifi-python" + self.minifi_python_examples_path = "/usr/share/doc/nifi-minifi-cpp/pythonprocessor-examples" + + +class MinifiLinuxContainer(LinuxContainer, MinifiProtocol): + def __init__(self, container_name: str, test_context: MinifiTestContext, + deployment: NormalDeployment | FHSDeployment): + super().__init__(test_context.minifi_container_image, f"{container_name}-{test_context.scenario_id}", + test_context.network) + self.flow_definition = MinifiFlowDefinition() + self.properties: dict[str, str] = {} + self.log_properties: dict[str, str] = {} + self.scenario_id = test_context.scenario_id + self.deploy_timeout_seconds = 20 + self.deployment_type = deployment + + self.controller = MinifiController(self, f"{self.deployment_type.conf_path}/config.yml", + self.deployment_type.bin_path) + + minifi_client_cert, minifi_client_key = make_cert_without_extended_usage(common_name=self.container_name, + ca_cert=test_context.root_ca_cert, + ca_key=test_context.root_ca_key) + self.files.append(File("/usr/local/share/certs/ca-root-nss.crt", dump_cert(test_context.root_ca_cert))) + self.files.append(File("/tmp/resources/root_ca.crt", dump_cert(test_context.root_ca_cert))) + self.files.append(File("/tmp/resources/minifi_client.crt", dump_cert(minifi_client_cert))) + self.files.append(File("/tmp/resources/minifi_client.key", dump_key(minifi_client_key))) + self.files.append( + File("/tmp/resources/minifi_merged_cert.crt", dump_cert(minifi_client_cert) + dump_key(minifi_client_key))) + + clientuser_cert, clientuser_key = make_client_cert("clientuser", ca_cert=test_context.root_ca_cert, + ca_key=test_context.root_ca_key) + self.files.append(File("/tmp/resources/clientuser.crt", dump_cert(clientuser_cert))) + self.files.append(File("/tmp/resources/clientuser.key", dump_key(clientuser_key))) + + minifi_server_cert, minifi_server_key = make_server_cert(common_name=f"server-{test_context.scenario_id}", + ca_cert=test_context.root_ca_cert, + ca_key=test_context.root_ca_key) + self.files.append( + File("/tmp/resources/minifi_server.crt", dump_cert(cert=minifi_server_cert) + dump_key(minifi_server_key))) + + self._fill_default_properties() + self._fill_default_log_properties() + + def deploy(self, context: MinifiTestContext | None) -> bool: + flow_config = self.flow_definition.to_yaml() + logging.info(f"Deploying MiNiFi container '{self.container_name}' with flow configuration:\n{flow_config}") + self.files.append(File(f"{self.deployment_type.conf_path}/config.yml", flow_config)) + self.files.append( + File(f"{self.deployment_type.conf_path}/minifi.properties", self._get_properties_file_content())) + self.files.append( + File(f"{self.deployment_type.conf_path}/minifi-log.properties", self._get_log_properties_file_content())) + 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"))) + + if not super().deploy(context): + return False + + finished_str = "MiNiFi started" + return wait_for_condition(condition=lambda: finished_str in self.get_logs(), + timeout_seconds=self.deploy_timeout_seconds, bail_condition=lambda: self.exited, + context=context) + + def set_deploy_timeout_seconds(self, timeout_seconds: int): + self.deploy_timeout_seconds = timeout_seconds + + def set_property(self, key: str, value: str): + self.properties[key] = value + + def set_log_property(self, key: str, value: str): + self.log_properties[key] = value + + def _fill_default_properties(self): + self.properties["nifi.flow.configuration.file"] = f"{self.deployment_type.conf_path}/config.yml" + self.properties["nifi.extension.path"] = self.deployment_type.extension_pattern + self.properties["nifi.administrative.yield.duration"] = "1 sec" + self.properties["nifi.bored.yield.duration"] = "100 millis" + self.properties["nifi.openssl.fips.support.enable"] = "false" + self.properties["nifi.provenance.repository.class.name"] = "NoOpRepository" + self.properties["nifi.python.processor.dir"] = self.deployment_type.minifi_python_path + + def _fill_default_log_properties(self): + self.log_properties["spdlog.pattern"] = "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v" + + self.log_properties["appender.stderr"] = "stderr" + self.log_properties["logger.root"] = "DEBUG, stderr" + self.log_properties["logger.org::apache::nifi::minifi"] = "DEBUG, stderr" + + def _get_properties_file_content(self): + lines = (f"{key}={value}" for key, value in self.properties.items()) + return "\n".join(lines) + + def _get_log_properties_file_content(self): + lines = (f"{key}={value}" for key, value in self.log_properties.items()) + return "\n".join(lines) + + def add_example_python_processors(self): + run_minifi_cmd = f'{self.deployment_type.bin_path}/minifi.sh run' + self.command = f'sh -c "cp -r {self.deployment_type.minifi_python_examples_path} {self.deployment_type.minifi_python_path}/examples && {run_minifi_cmd}"' diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_protocol.py b/behave_framework/src/minifi_test_framework/containers/minifi_protocol.py new file mode 100644 index 0000000000..4e6d658684 --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/minifi_protocol.py @@ -0,0 +1,40 @@ +from typing import Protocol + + +class MinifiProtocol(Protocol): + def set_property(self, key: str, value: str): + ... + + def set_log_property(self, key: str, value: str): + ... + + def set_deploy_timeout_seconds(self, timeout_seconds: int): + ... + + +def set_controller_socket_properties(minifi: MinifiProtocol): + minifi.set_property("controller.socket.enable", "true") + minifi.set_property("controller.socket.host", "localhost") + minifi.set_property("controller.socket.port", "9998") + minifi.set_property("controller.socket.local.any.interface", "false") + + +def enable_openssl_fips_mode(minifi: MinifiProtocol): + minifi.set_property("nifi.openssl.fips.support.enable", "true") + + +def enable_log_metrics_publisher(minifi: MinifiProtocol): + minifi.set_property("nifi.metrics.publisher.LogMetricsPublisher.metrics", "RepositoryMetrics") + minifi.set_property("nifi.metrics.publisher.LogMetricsPublisher.logging.interval", "1s") + minifi.set_property("nifi.metrics.publisher.class", "LogMetricsPublisher") + + +def fetch_flow_config_from_flow_url(minifi: MinifiProtocol, scenario_id: str): + minifi.set_property("nifi.c2.flow.url", f"http://minifi-c2-server-{scenario_id}:10090/c2/config?class=minifi-test-class") + + +def set_up_ssl_properties(minifi: MinifiProtocol): + minifi.set_property("nifi.remote.input.secure", "true") + minifi.set_property("nifi.security.client.certificate", "/tmp/resources/minifi_client.crt") + minifi.set_property("nifi.security.client.private.key", "/tmp/resources/minifi_client.key") + minifi.set_property("nifi.security.client.ca.certificate", "/tmp/resources/root_ca.crt") diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_win_container.py b/behave_framework/src/minifi_test_framework/containers/minifi_win_container.py new file mode 100644 index 0000000000..2e8952cf3a --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/minifi_win_container.py @@ -0,0 +1,59 @@ +from typing import Dict + +from minifi_test_framework.core.minifi_test_context import MinifiTestContext +from minifi_test_framework.minifi.minifi_flow_definition import MinifiFlowDefinition +from minifi_test_framework.containers.directory import Directory +from .container_windows import WindowsContainer +from .minifi_protocol import MinifiProtocol +import logging + + +class MinifiWindowsContainer(WindowsContainer, MinifiProtocol): + def __init__(self, container_name: str, test_context: MinifiTestContext): + super().__init__(test_context.minifi_container_image, f"{container_name}-{test_context.scenario_id}", test_context.network) + self.flow_config_str: str = "" + self.flow_definition = MinifiFlowDefinition() + self.properties: Dict[str, str] = {} + self.log_properties: Dict[str, str] = {} + + self._fill_default_properties() + self._fill_default_log_properties() + + def deploy(self, context: MinifiTestContext | None) -> bool: + logging.info(self.flow_definition.to_yaml()) + conf_dir = Directory("\\Program Files\\ApacheNiFiMiNiFi\\nifi-minifi-cpp\\conf") + conf_dir.add_file("config.yml", self.flow_definition.to_yaml()) + conf_dir.add_file("minifi.properties", self._get_properties_file_content()) + conf_dir.add_file("minifi-log.properties", self._get_log_properties_file_content()) + + self.dirs.append(conf_dir) + return super().deploy(context) + + def set_property(self, key: str, value: str): + self.properties[key] = value + + def set_log_property(self, key: str, value: str): + self.log_properties[key] = value + + def _fill_default_properties(self): + self.properties["nifi.flow.configuration.file"] = "./conf/config.yml" + self.properties["nifi.extension.path"] = "../extensions/*" + self.properties["nifi.administrative.yield.duration"] = "1 sec" + self.properties["nifi.bored.yield.duration"] = "100 millis" + self.properties["nifi.openssl.fips.support.enable"] = "false" + self.properties["nifi.provenance.repository.class.name"] = "NoOpRepository" + + def _fill_default_log_properties(self): + self.log_properties["spdlog.pattern"] = "[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v" + + self.log_properties["appender.stderr"] = "stderr" + self.log_properties["logger.root"] = "DEBUG, stderr" + self.log_properties["logger.org::apache::nifi::minifi"] = "DEBUG, stderr" + + def _get_properties_file_content(self): + lines = (f"{key}={value}" for key, value in self.properties.items()) + return "\n".join(lines) + + def _get_log_properties_file_content(self): + lines = (f"{key}={value}" for key, value in self.log_properties.items()) + return "\n".join(lines) diff --git a/behave_framework/src/minifi_test_framework/containers/nifi_container.py b/behave_framework/src/minifi_test_framework/containers/nifi_container.py index ae0f9d0f84..3d589a8fec 100644 --- a/behave_framework/src/minifi_test_framework/containers/nifi_container.py +++ b/behave_framework/src/minifi_test_framework/containers/nifi_container.py @@ -18,18 +18,19 @@ import logging import os from pathlib import Path -from OpenSSL import crypto from minifi_test_framework.containers.file import File -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.minifi.nifi_flow_definition import NifiFlowDefinition from minifi_test_framework.containers.host_file import HostFile from minifi_test_framework.core.ssl_utils import make_server_cert +from minifi_test_framework.core.ssl_utils import dump_cert, dump_key -class NifiContainer(Container): + +class NifiContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext, command: list[str] | None = None, use_ssl: bool = False): self.flow_definition = NifiFlowDefinition() name = f"nifi-{test_context.scenario_id}" @@ -78,9 +79,9 @@ def __init__(self, test_context: MinifiTestContext, command: list[str] | None = self.host_files.append(HostFile("/scripts/convert_cert_to_jks.sh", os.path.join(resource_dir, "convert_cert_to_jks.sh"))) nifi_client_cert, nifi_client_key = make_server_cert(common_name=f"nifi-{test_context.scenario_id}", ca_cert=test_context.root_ca_cert, ca_key=test_context.root_ca_key) - self.files.append(File("/tmp/resources/root_ca.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert))) - self.files.append(File("/tmp/resources/nifi_client.crt", crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=nifi_client_cert))) - self.files.append(File("/tmp/resources/nifi_client.key", crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=nifi_client_key))) + self.files.append(File("/tmp/resources/root_ca.crt", dump_cert(test_context.root_ca_cert))) + self.files.append(File("/tmp/resources/nifi_client.crt", dump_cert(nifi_client_cert))) + self.files.append(File("/tmp/resources/nifi_client.key", dump_key(nifi_client_key))) def deploy(self, context: MinifiTestContext | None) -> bool: flow_config = self.flow_definition.to_json() diff --git a/behave_framework/src/minifi_test_framework/core/__init__.py b/behave_framework/src/minifi_test_framework/core/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/behave_framework/src/minifi_test_framework/core/hooks.py b/behave_framework/src/minifi_test_framework/core/hooks.py index 3e4e69a95c..ea440cc30d 100644 --- a/behave_framework/src/minifi_test_framework/core/hooks.py +++ b/behave_framework/src/minifi_test_framework/core/hooks.py @@ -46,6 +46,10 @@ def inject_scenario_id(context: MinifiTestContext, step): def common_before_scenario(context: Context, scenario: Scenario): + if "SUPPORTS_WINDOWS" not in scenario.effective_tags and os.name == 'nt': + scenario.skip("No windows support") + return + if not hasattr(context, "minifi_container_image"): context.minifi_container_image = get_minifi_container_image() @@ -93,6 +97,8 @@ def common_after_scenario(context: MinifiTestContext, scenario: Scenario): with open(scenario_info_path, "w") as f: f.write(header) - for container in context.containers.values(): - container.clean_up() - context.network.remove() + if hasattr(context, 'containers'): + for container in context.containers.values(): + container.clean_up() + if hasattr(context, 'network'): + context.network.remove() diff --git a/behave_framework/src/minifi_test_framework/core/minifi_test_context.py b/behave_framework/src/minifi_test_framework/core/minifi_test_context.py index 959f2e1fac..171aff2633 100644 --- a/behave_framework/src/minifi_test_framework/core/minifi_test_context.py +++ b/behave_framework/src/minifi_test_framework/core/minifi_test_context.py @@ -16,32 +16,46 @@ # from __future__ import annotations -from typing import TYPE_CHECKING + +import os +import docker from behave.runner import Context +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.x509 import Certificate from docker.models.networks import Network -from minifi_test_framework.containers.container import Container -from OpenSSL import crypto +from minifi_test_framework.containers.container_protocol import ContainerProtocol +from minifi_test_framework.containers.minifi_protocol import MinifiProtocol -if TYPE_CHECKING: - from minifi_test_framework.containers.minifi_container import MinifiContainer DEFAULT_MINIFI_CONTAINER_NAME = "minifi-primary" +class MinifiContainer(ContainerProtocol, MinifiProtocol): + pass + + class MinifiTestContext(Context): - containers: dict[str, Container] + containers: dict[str, ContainerProtocol] scenario_id: str network: Network minifi_container_image: str resource_dir: str | None - root_ca_key: crypto.PKey - root_ca_cert: crypto.X509 + root_ca_key: Certificate + root_ca_cert: RSAPrivateKey def get_or_create_minifi_container(self, container_name: str) -> MinifiContainer: if container_name not in self.containers: - from minifi_test_framework.containers.minifi_container import MinifiContainer - self.containers[container_name] = MinifiContainer(container_name, self) + if os.name == 'nt': + from minifi_test_framework.containers.minifi_win_container import MinifiWindowsContainer + minifi_container = MinifiWindowsContainer(container_name, self) + elif 'MINIFI_INSTALLATION_TYPE=FHS' in str(docker.from_env().images.get(self.minifi_container_image).history()): + from minifi_test_framework.containers.minifi_linux_container import MinifiLinuxContainer, FHSDeployment + minifi_container = MinifiLinuxContainer(container_name, self, FHSDeployment()) + else: + from minifi_test_framework.containers.minifi_linux_container import MinifiLinuxContainer, NormalDeployment + minifi_container = MinifiLinuxContainer(container_name, self, NormalDeployment()) + self.containers[container_name] = minifi_container return self.containers[container_name] def get_or_create_default_minifi_container(self) -> MinifiContainer: diff --git a/behave_framework/src/minifi_test_framework/core/ssl_utils.py b/behave_framework/src/minifi_test_framework/core/ssl_utils.py index d39cf96460..e17e9b004a 100644 --- a/behave_framework/src/minifi_test_framework/core/ssl_utils.py +++ b/behave_framework/src/minifi_test_framework/core/ssl_utils.py @@ -13,149 +13,79 @@ # See the License for the specific language governing permissions and # limitations under the License. +import datetime -import time -import logging -import random - -from M2Crypto import X509, EVP, RSA, ASN1 -from OpenSSL import crypto - - -def gen_cert(): - """ - Generate TLS certificate request for testing - """ - - req, key = gen_req() - pub_key = req.get_pubkey() - subject = req.get_subject() - cert = X509.X509() - # noinspection PyTypeChecker - cert.set_serial_number(1) - cert.set_version(2) - cert.set_subject(subject) - t = int(time.time()) - now = ASN1.ASN1_UTCTIME() - now.set_time(t) - now_plus_year = ASN1.ASN1_UTCTIME() - now_plus_year.set_time(t + 60 * 60 * 24 * 365) - cert.set_not_before(now) - cert.set_not_after(now_plus_year) - issuer = X509.X509_Name() - issuer.C = 'US' - issuer.CN = 'minifi-listen' - cert.set_issuer(issuer) - cert.set_pubkey(pub_key) - cert.sign(key, 'sha256') - - return cert, key - - -def rsa_gen_key_callback(): - pass - - -def gen_req(): - """ - Generate TLS certificate request for testing - """ - - logging.info('Generating test certificate request') - key = EVP.PKey() - req = X509.Request() - rsa = RSA.gen_key(1024, 65537, rsa_gen_key_callback) - key.assign_rsa(rsa) - req.set_pubkey(key) - name = req.get_subject() - name.C = 'US' - name.CN = 'minifi-listen' - req.sign(key, 'sha256') - - return req, key +from cryptography import x509 +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives import serialization +from cryptography.hazmat.primitives.asymmetric import rsa +from cryptography.hazmat.primitives.asymmetric.rsa import RSAPrivateKey +from cryptography.x509 import Certificate, ExtendedKeyUsage +from cryptography.x509.oid import NameOID -def make_self_signed_cert(common_name): - ca_key = crypto.PKey() - ca_key.generate_key(crypto.TYPE_RSA, 2048) +def gen_cert() -> tuple[Certificate, RSAPrivateKey]: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) - ca_cert = crypto.X509() - ca_cert.set_version(2) - ca_cert.set_serial_number(random.randint(50000000, 100000000)) + subject = issuer = x509.Name([x509.NameAttribute(NameOID.COUNTRY_NAME, u"US"), x509.NameAttribute(NameOID.COMMON_NAME, u"minifi-listen"), ]) - ca_subj = ca_cert.get_subject() - ca_subj.commonName = common_name + cert = x509.CertificateBuilder().subject_name(subject).issuer_name(issuer).public_key(key.public_key()).serial_number( + x509.random_serial_number()).not_valid_before(datetime.datetime.now(datetime.UTC)).not_valid_after( + datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=365)).sign(key, hashes.SHA256()) - ca_cert.add_extensions([ - crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=ca_cert), - ]) - - ca_cert.add_extensions([ - crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid:always", issuer=ca_cert), - ]) - - ca_cert.add_extensions([ - crypto.X509Extension(b"basicConstraints", False, b"CA:TRUE"), - crypto.X509Extension(b"keyUsage", False, b"keyCertSign, cRLSign"), - ]) - - ca_cert.set_issuer(ca_subj) - ca_cert.set_pubkey(ca_key) + return cert, key - ca_cert.gmtime_adj_notBefore(0) - ca_cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) - ca_cert.sign(ca_key, 'sha256') +def make_self_signed_cert(common_name: str) -> tuple[Certificate, RSAPrivateKey]: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) - return ca_cert, ca_key + subject = issuer = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, common_name), ]) + cert = x509.CertificateBuilder().subject_name(subject).issuer_name(issuer).public_key(key.public_key()).serial_number( + x509.random_serial_number()).not_valid_before(datetime.datetime.now(datetime.UTC)).not_valid_after( + datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=3650)).add_extension( + x509.SubjectKeyIdentifier.from_public_key(key.public_key()), critical=False, ).add_extension(x509.BasicConstraints(ca=True, path_length=None), + critical=True, ).sign(key, hashes.SHA256()) -def _make_cert(common_name, ca_cert, ca_key, extended_key_usage=None): - key = crypto.PKey() - key.generate_key(crypto.TYPE_RSA, 2048) + return cert, key - cert = crypto.X509() - cert.set_version(2) - cert.set_serial_number(random.randint(50000000, 100000000)) - client_subj = cert.get_subject() - client_subj.commonName = common_name +def _make_cert(common_name: str, ca_cert: Certificate, ca_key: RSAPrivateKey, extended_key_usage: ExtendedKeyUsage | None) -> tuple[ + Certificate, RSAPrivateKey]: + key = rsa.generate_private_key(public_exponent=65537, key_size=2048) - cert.add_extensions([ - crypto.X509Extension(b"basicConstraints", False, b"CA:FALSE"), - crypto.X509Extension(b"subjectKeyIdentifier", False, b"hash", subject=cert), - ]) + subject = x509.Name([x509.NameAttribute(NameOID.COMMON_NAME, common_name), ]) - extensions = [crypto.X509Extension(b"authorityKeyIdentifier", False, b"keyid:always", issuer=ca_cert), - crypto.X509Extension(b"keyUsage", False, b"digitalSignature")] + builder = x509.CertificateBuilder().subject_name(subject).issuer_name(ca_cert.subject).public_key(key.public_key()).serial_number( + x509.random_serial_number()).not_valid_before(datetime.datetime.now(datetime.UTC)).not_valid_after( + datetime.datetime.now(datetime.UTC) + datetime.timedelta(days=3650)).add_extension(x509.BasicConstraints(ca=False, path_length=None), + critical=True, ).add_extension( + x509.SubjectKeyIdentifier.from_public_key(key.public_key()), critical=False, ).add_extension( + x509.SubjectAlternativeName([x509.DNSName(common_name)]), critical=False, ) if extended_key_usage: - extensions.append(crypto.X509Extension(b"extendedKeyUsage", False, extended_key_usage)) - - cert.add_extensions([ - crypto.X509Extension(b"subjectAltName", False, b"DNS.1:" + common_name.encode()) - ]) + builder = builder.add_extension(x509.ExtendedKeyUsage(extended_key_usage), critical=False) - cert.add_extensions(extensions) + cert = builder.sign(ca_key, hashes.SHA256()) + return cert, key - cert.set_issuer(ca_cert.get_subject()) - cert.set_pubkey(key) - cert.gmtime_adj_notBefore(0) - cert.gmtime_adj_notAfter(10 * 365 * 24 * 60 * 60) +def make_client_cert(common_name: str, ca_cert: Certificate, ca_key: RSAPrivateKey) -> tuple[Certificate, RSAPrivateKey]: + return _make_cert(common_name, ca_cert, ca_key, x509.ExtendedKeyUsage([x509.OID_CLIENT_AUTH])) - cert.sign(ca_key, 'sha256') - return cert, key +def make_server_cert(common_name: str, ca_cert: Certificate, ca_key: RSAPrivateKey) -> tuple[Certificate, RSAPrivateKey]: + return _make_cert(common_name, ca_cert, ca_key, x509.ExtendedKeyUsage(([x509.OID_SERVER_AUTH]))) -def make_client_cert(common_name, ca_cert, ca_key): - return _make_cert(common_name=common_name, ca_cert=ca_cert, ca_key=ca_key, extended_key_usage=b"clientAuth") +def make_cert_without_extended_usage(common_name: str, ca_cert: Certificate, ca_key: RSAPrivateKey) -> tuple[Certificate, RSAPrivateKey]: + return _make_cert(common_name, ca_cert, ca_key, None) -def make_server_cert(common_name, ca_cert, ca_key): - return _make_cert(common_name=common_name, ca_cert=ca_cert, ca_key=ca_key, extended_key_usage=b"serverAuth") +def dump_cert(cert: Certificate, encoding_type: serialization.Encoding = serialization.Encoding.PEM) -> bytes: + return cert.public_bytes(encoding_type) -def make_cert_without_extended_usage(common_name, ca_cert, ca_key): - return _make_cert(common_name=common_name, ca_cert=ca_cert, ca_key=ca_key, extended_key_usage=None) +def dump_key(key: RSAPrivateKey, encoding_type: serialization.Encoding = serialization.Encoding.PEM) -> bytes: + return key.private_bytes(encoding=encoding_type, format=serialization.PrivateFormat.TraditionalOpenSSL, + encryption_algorithm=serialization.NoEncryption()) diff --git a/behave_framework/src/minifi_test_framework/steps/__init__.py b/behave_framework/src/minifi_test_framework/steps/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/behave_framework/src/minifi_test_framework/steps/configuration_steps.py b/behave_framework/src/minifi_test_framework/steps/configuration_steps.py index 80f555275f..0018eff1de 100644 --- a/behave_framework/src/minifi_test_framework/steps/configuration_steps.py +++ b/behave_framework/src/minifi_test_framework/steps/configuration_steps.py @@ -18,6 +18,10 @@ from behave import step, given from minifi_test_framework.core.minifi_test_context import MinifiTestContext +from minifi_test_framework.containers.minifi_protocol import enable_openssl_fips_mode +from minifi_test_framework.containers.minifi_protocol import set_up_ssl_properties +from minifi_test_framework.containers.minifi_protocol import enable_log_metrics_publisher +from minifi_test_framework.containers.minifi_protocol import fetch_flow_config_from_flow_url @step('MiNiFi configuration "{config_key}" is set to "{config_value}"') @@ -27,7 +31,7 @@ def step_impl(context: MinifiTestContext, config_key: str, config_value: str): @step("log metrics publisher is enabled in MiNiFi") def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().enable_log_metrics_publisher() + enable_log_metrics_publisher(context.get_or_create_default_minifi_container()) @step('log property "{log_property_key}" is set to "{log_property_value}"') @@ -37,14 +41,14 @@ def step_impl(context: MinifiTestContext, log_property_key: str, log_property_va @given("OpenSSL FIPS mode is enabled in MiNiFi") def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().enable_openssl_fips_mode() + enable_openssl_fips_mode(context.get_or_create_default_minifi_container()) @given("flow configuration path is set up in flow url property") def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().fetch_flow_config_from_flow_url() + fetch_flow_config_from_flow_url(context.get_or_create_default_minifi_container(), context.scenario_id) @given("SSL properties are set in MiNiFi") def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().set_up_ssl_properties() + set_up_ssl_properties(context.get_or_create_default_minifi_container()) diff --git a/docker/RunBehaveTests.sh b/docker/RunBehaveTests.sh index bcfe346400..6492921e29 100755 --- a/docker/RunBehaveTests.sh +++ b/docker/RunBehaveTests.sh @@ -155,22 +155,6 @@ pip install --trusted-host pypi.python.org --upgrade pip setuptools # Install test dependencies echo "Installing test dependencies..." 1>&2 -# hint include/library paths if homewbrew is in use -if brew list 2> /dev/null | grep openssl > /dev/null 2>&1; then - echo "Using homebrew paths for openssl" 1>&2 - LDFLAGS="-L$(brew --prefix openssl@1.1)/lib" - export LDFLAGS - CFLAGS="-I$(brew --prefix openssl@1.1)/include" - export CFLAGS - SWIG_FEATURES="-cpperraswarn -includeall -I$(brew --prefix openssl@1.1)/include" - export SWIG_FEATURES -fi - -if ! command swig -version &> /dev/null; then - echo "Swig could not be found on your system (dependency of m2crypto python library). Please install swig to continue." - exit 1 -fi - pip install -e "${docker_dir}/../behave_framework" export TMPDIR="/tmp/behavex_ci_${RANDOM}" @@ -192,6 +176,9 @@ fi echo "${BEHAVE_OPTS[@]}" -mapfile -t FEATURE_FILES < <(find "${docker_dir}/../extensions" -type f -name '*.feature') +FEATURE_FILES=() +while IFS= read -r -d '' file; do + FEATURE_FILES+=("$file") +done < <(find "${docker_dir}/../extensions" -type f -name '*.feature' -print0) behavex "${BEHAVE_OPTS[@]}" "${FEATURE_FILES[@]}" diff --git a/docker/installed/win.Dockerfile b/docker/installed/win.Dockerfile new file mode 100644 index 0000000000..b413b68b9b --- /dev/null +++ b/docker/installed/win.Dockerfile @@ -0,0 +1,24 @@ +#escape=` + +FROM mcr.microsoft.com/windows/servercore:ltsc2022 + +LABEL maintainer="Apache NiFi " + +ARG MSI_SOURCE="nifi-minifi-cpp.msi" + +ENV MINIFI_HOME="C:\Program Files\ApacheNiFiMiNiFi\nifi-minifi-cpp" + +SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop'; $ProgressPreference = 'SilentlyContinue';"] + +RUN Set-ExecutionPolicy Bypass -Scope Process -Force; [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.ServicePointManager]::SecurityProtocol -bor 3072; iex ((New-Object System.Net.WebClient).DownloadString('https://community.chocolatey.org/install.ps1')) + +RUN choco install git -y +RUN choco install vcredist140 -y + +COPY ${MSI_SOURCE} C:\temp\minifi.msi + +SHELL ["cmd", "/S", "/C"] + +RUN C:\Windows\System32\msiexec.exe /i C:\temp\minifi.msi /qn /norestart /L*V C:\minifi_install.log + +CMD ["C:\\Program Files\\ApacheNiFiMiNiFi\\nifi-minifi-cpp\\bin\\minifi.exe"] \ No newline at end of file diff --git a/extensions/aws/tests/features/containers/kinesis_server_container.py b/extensions/aws/tests/features/containers/kinesis_server_container.py index 01b6dced2f..d97bb5c834 100644 --- a/extensions/aws/tests/features/containers/kinesis_server_container.py +++ b/extensions/aws/tests/features/containers/kinesis_server_container.py @@ -16,13 +16,13 @@ import logging from pathlib import Path -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition, retry_check from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder -class KinesisServerContainer(Container): +class KinesisServerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): builder = DockerImageBuilder( image_tag="minifi-kinesis-mock:latest", diff --git a/extensions/aws/tests/features/containers/s3_server_container.py b/extensions/aws/tests/features/containers/s3_server_container.py index 8f00b2644c..2364b2d0c6 100644 --- a/extensions/aws/tests/features/containers/s3_server_container.py +++ b/extensions/aws/tests/features/containers/s3_server_container.py @@ -16,12 +16,12 @@ import json import logging -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class S3ServerContainer(Container): +class S3ServerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("adobe/s3mock:3.12.0", f"s3-server-{test_context.scenario_id}", test_context.network) self.environment.append("initialBuckets=test_bucket") diff --git a/extensions/azure/tests/features/containers/azure_server_container.py b/extensions/azure/tests/features/containers/azure_server_container.py index 4caeb786b1..cb0d2e5ebd 100644 --- a/extensions/azure/tests/features/containers/azure_server_container.py +++ b/extensions/azure/tests/features/containers/azure_server_container.py @@ -18,13 +18,13 @@ import logging from docker.errors import ContainerError -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import run_cmd_in_docker_image from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class AzureServerContainer(Container): +class AzureServerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("mcr.microsoft.com/azure-storage/azurite:3.35.0", f"azure-storage-server-{test_context.scenario_id}", diff --git a/extensions/couchbase/tests/features/containers/couchbase_server_container.py b/extensions/couchbase/tests/features/containers/couchbase_server_container.py index f1cce49911..3f4f1401f7 100644 --- a/extensions/couchbase/tests/features/containers/couchbase_server_container.py +++ b/extensions/couchbase/tests/features/containers/couchbase_server_container.py @@ -15,26 +15,25 @@ import logging -from OpenSSL import crypto from minifi_test_framework.core.helpers import wait_for_condition, retry_check -from minifi_test_framework.core.ssl_utils import make_server_cert -from minifi_test_framework.containers.container import Container +from minifi_test_framework.core.ssl_utils import make_server_cert, dump_cert, dump_key +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.file import File from minifi_test_framework.core.minifi_test_context import MinifiTestContext from docker.errors import ContainerError -class CouchbaseServerContainer(Container): +class CouchbaseServerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("couchbase:enterprise-7.2.5", f"couchbase-server-{test_context.scenario_id}", test_context.network) couchbase_cert, couchbase_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - root_ca_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/opt/couchbase/var/lib/couchbase/inbox/CA/root_ca.crt", root_ca_content, permissions=0o666)) - couchbase_cert_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=couchbase_cert) + couchbase_cert_content = dump_cert(couchbase_cert) self.files.append(File("/opt/couchbase/var/lib/couchbase/inbox/chain.pem", couchbase_cert_content, permissions=0o666)) - couchbase_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=couchbase_key) + couchbase_key_content = dump_key(couchbase_key) self.files.append(File("/opt/couchbase/var/lib/couchbase/inbox/pkey.key", couchbase_key_content, permissions=0o666)) def deploy(self, context: MinifiTestContext | None) -> bool: @@ -66,7 +65,7 @@ def run_post_startup_commands(self): return True - @retry_check(max_tries=12, retry_interval=5) + @retry_check(max_tries=12, retry_interval_seconds=5) def _run_couchbase_cli_command(self, command): (code, output) = self.exec_run(command) if code != 0: @@ -90,7 +89,7 @@ def _run_python_in_couchbase_helper_docker(self, command: str): logging.error(f"Unexpected error while running python command '{command}' in couchbase helper docker: '{e}'") return False - @retry_check(max_tries=15, retry_interval=2) + @retry_check(max_tries=15, retry_interval_seconds=2) def _load_couchbase_certs(self): python_command = f""" import requests diff --git a/extensions/elasticsearch/tests/features/containers/elastic_base_container.py b/extensions/elasticsearch/tests/features/containers/elastic_base_container.py index 5db6e61dc1..2287278c4a 100644 --- a/extensions/elasticsearch/tests/features/containers/elastic_base_container.py +++ b/extensions/elasticsearch/tests/features/containers/elastic_base_container.py @@ -14,11 +14,11 @@ # limitations under the License. from minifi_test_framework.core.helpers import wait_for_condition, retry_check -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class ElasticBaseContainer(Container): +class ElasticBaseContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext, image: str, container_name: str): super().__init__(image, container_name, test_context.network) self.user = None diff --git a/extensions/elasticsearch/tests/features/containers/elasticsearch_container.py b/extensions/elasticsearch/tests/features/containers/elasticsearch_container.py index 037711d75b..aec9280266 100644 --- a/extensions/elasticsearch/tests/features/containers/elasticsearch_container.py +++ b/extensions/elasticsearch/tests/features/containers/elasticsearch_container.py @@ -19,8 +19,7 @@ from elastic_base_container import ElasticBaseContainer from pathlib import Path -from OpenSSL import crypto -from minifi_test_framework.core.ssl_utils import make_server_cert, make_cert_without_extended_usage +from minifi_test_framework.core.ssl_utils import make_server_cert, make_cert_without_extended_usage, dump_cert, dump_key from minifi_test_framework.containers.file import File from minifi_test_framework.containers.host_file import HostFile from minifi_test_framework.core.minifi_test_context import MinifiTestContext @@ -33,19 +32,19 @@ def __init__(self, test_context: MinifiTestContext): http_cert, http_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) transport_cert, transport_key = make_cert_without_extended_usage(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - root_ca_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/usr/share/elasticsearch/config/certs/root_ca.crt", root_ca_content, permissions=0o644)) - http_cert_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=http_cert) + http_cert_content = dump_cert(http_cert) self.files.append(File("/usr/share/elasticsearch/config/certs/elastic_http.crt", http_cert_content, permissions=0o644)) - http_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=http_key) + http_key_content = dump_key(http_key) self.files.append(File("/usr/share/elasticsearch/config/certs/elastic_http.key", http_key_content, permissions=0o644)) - transport_cert_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=transport_cert) + transport_cert_content = dump_cert(transport_cert) self.files.append(File("/usr/share/elasticsearch/config/certs/elastic_transport.crt", transport_cert_content, permissions=0o644)) - transport_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=transport_key) + transport_key_content = dump_key(transport_key) self.files.append(File("/usr/share/elasticsearch/config/certs/elastic_transport.key", transport_key_content, permissions=0o644)) features_dir = Path(__file__).resolve().parent.parent diff --git a/extensions/elasticsearch/tests/features/containers/opensearch_container.py b/extensions/elasticsearch/tests/features/containers/opensearch_container.py index cdf5008d59..234a26d12d 100644 --- a/extensions/elasticsearch/tests/features/containers/opensearch_container.py +++ b/extensions/elasticsearch/tests/features/containers/opensearch_container.py @@ -18,8 +18,7 @@ from elastic_base_container import ElasticBaseContainer from pathlib import Path -from OpenSSL import crypto -from minifi_test_framework.core.ssl_utils import make_server_cert +from minifi_test_framework.core.ssl_utils import make_server_cert, dump_cert, dump_key from minifi_test_framework.containers.file import File from minifi_test_framework.containers.host_file import HostFile from minifi_test_framework.core.minifi_test_context import MinifiTestContext @@ -31,13 +30,13 @@ def __init__(self, test_context: MinifiTestContext): admin_pem, admin_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - root_ca_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/usr/share/opensearch/config/root-ca.pem", root_ca_content, permissions=0o644)) - admin_pem_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=admin_pem) + admin_pem_content = dump_cert(admin_pem) self.files.append(File("/usr/share/opensearch/config/admin.pem", admin_pem_content, permissions=0o644)) - admin_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=admin_key) + admin_key_content = dump_key(admin_key) self.files.append(File("/usr/share/opensearch/config/admin-key.pem", admin_key_content, permissions=0o644)) features_dir = Path(__file__).resolve().parent.parent diff --git a/extensions/gcp/tests/features/containers/fake_gcs_server_container.py b/extensions/gcp/tests/features/containers/fake_gcs_server_container.py index a9733ba4e3..5689065bb4 100644 --- a/extensions/gcp/tests/features/containers/fake_gcs_server_container.py +++ b/extensions/gcp/tests/features/containers/fake_gcs_server_container.py @@ -15,12 +15,12 @@ import logging from minifi_test_framework.core.helpers import wait_for_condition, retry_check -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.directory import Directory from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class FakeGcsServerContainer(Container): +class FakeGcsServerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("fsouza/fake-gcs-server:1.45.1", f"fake-gcs-server-{test_context.scenario_id}", test_context.network, command=f'-scheme http -host fake-gcs-server-{test_context.scenario_id}') diff --git a/extensions/grafana-loki/tests/features/containers/grafana_loki_container.py b/extensions/grafana-loki/tests/features/containers/grafana_loki_container.py index 32f1cf1db1..af296c5978 100644 --- a/extensions/grafana-loki/tests/features/containers/grafana_loki_container.py +++ b/extensions/grafana-loki/tests/features/containers/grafana_loki_container.py @@ -14,13 +14,12 @@ # limitations under the License. import logging -from OpenSSL import crypto from minifi_test_framework.core.helpers import wait_for_condition, retry_check -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.file import File from minifi_test_framework.core.minifi_test_context import MinifiTestContext -from minifi_test_framework.core.ssl_utils import make_server_cert +from minifi_test_framework.core.ssl_utils import make_server_cert, dump_cert, dump_key from docker.errors import ContainerError @@ -30,20 +29,20 @@ def __init__(self, enable_ssl: bool = False, enable_multi_tenancy: bool = False) self.enable_multi_tenancy = enable_multi_tenancy -class GrafanaLokiContainer(Container): +class GrafanaLokiContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext, options: GrafanaLokiOptions): super().__init__("grafana/loki:3.2.1", f"grafana-loki-server-{test_context.scenario_id}", test_context.network) extra_ssl_settings = "" if options.enable_ssl: grafana_loki_cert, grafana_loki_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - root_ca_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/etc/loki/root_ca.crt", root_ca_content, permissions=0o644)) - grafana_loki_cert_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=grafana_loki_cert) + grafana_loki_cert_content = dump_cert(grafana_loki_cert) self.files.append(File("/etc/loki/cert.pem", grafana_loki_cert_content, permissions=0o644)) - grafana_loki_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=grafana_loki_key) + grafana_loki_key_content = dump_key(grafana_loki_key) self.files.append(File("/etc/loki/key.pem", grafana_loki_key_content, permissions=0o644)) extra_ssl_settings = """ diff --git a/extensions/grafana-loki/tests/features/containers/reverse_proxy_container.py b/extensions/grafana-loki/tests/features/containers/reverse_proxy_container.py index 409a9b9e69..43efd49c5d 100644 --- a/extensions/grafana-loki/tests/features/containers/reverse_proxy_container.py +++ b/extensions/grafana-loki/tests/features/containers/reverse_proxy_container.py @@ -14,11 +14,11 @@ # limitations under the License. from minifi_test_framework.core.minifi_test_context import MinifiTestContext -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition -class ReverseProxyContainer(Container): +class ReverseProxyContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("minifi-reverse-proxy:latest", f"reverse-proxy-{test_context.scenario_id}", test_context.network) self.environment = [ diff --git a/extensions/kafka/tests/features/containers/kafka_server_container.py b/extensions/kafka/tests/features/containers/kafka_server_container.py index 30073186fb..09e35e131e 100644 --- a/extensions/kafka/tests/features/containers/kafka_server_container.py +++ b/extensions/kafka/tests/features/containers/kafka_server_container.py @@ -15,17 +15,18 @@ import logging import re + +from cryptography.hazmat.primitives import serialization import jks -from OpenSSL import crypto from minifi_test_framework.core.helpers import wait_for_condition -from minifi_test_framework.core.ssl_utils import make_server_cert -from minifi_test_framework.containers.container import Container +from minifi_test_framework.core.ssl_utils import make_server_cert, dump_cert, dump_key +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.file import File from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class KafkaServer(Container): +class KafkaServer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("apache/kafka:4.1.0", f"kafka-server-{test_context.scenario_id}", test_context.network) self.environment.append("KAFKA_NODE_ID=1") @@ -55,7 +56,7 @@ def __init__(self, test_context: MinifiTestContext): kafka_cert, kafka_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - pke = jks.PrivateKeyEntry.new('kafka-broker-cert', [crypto.dump_certificate(crypto.FILETYPE_ASN1, kafka_cert)], crypto.dump_privatekey(crypto.FILETYPE_ASN1, kafka_key), 'rsa_raw') + pke = jks.PrivateKeyEntry.new('kafka-broker-cert', [dump_cert(kafka_cert, encoding_type=serialization.Encoding.DER)], dump_key(kafka_key, encoding_type=serialization.Encoding.DER), 'rsa_raw') server_keystore = jks.KeyStore.new('jks', [pke]) server_keystore_content = server_keystore.saves('abcdefgh') self.files.append(File("/etc/kafka/secrets/kafka.keystore.jks", server_keystore_content, permissions=0o644)) @@ -63,7 +64,7 @@ def __init__(self, test_context: MinifiTestContext): trusted_cert = jks.TrustedCertEntry.new( 'root-ca', # Alias for the certificate - crypto.dump_certificate(crypto.FILETYPE_ASN1, test_context.root_ca_cert) + dump_cert(test_context.root_ca_cert, encoding_type=serialization.Encoding.DER) ) # Create a JKS keystore that will serve as a truststore with the trusted cert entry. diff --git a/extensions/mqtt/tests/features/containers/mqtt_broker_container.py b/extensions/mqtt/tests/features/containers/mqtt_broker_container.py index b309ff3093..c4f4b74163 100644 --- a/extensions/mqtt/tests/features/containers/mqtt_broker_container.py +++ b/extensions/mqtt/tests/features/containers/mqtt_broker_container.py @@ -17,14 +17,14 @@ import re from textwrap import dedent -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder from docker.errors import ContainerError -class MqttBrokerContainer(Container): +class MqttBrokerContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): dockerfile = dedent("""\ FROM {base_image} diff --git a/extensions/opc/tests/features/containers/opc_ua_server_container.py b/extensions/opc/tests/features/containers/opc_ua_server_container.py index 3dfb4626d0..3740b77bdb 100644 --- a/extensions/opc/tests/features/containers/opc_ua_server_container.py +++ b/extensions/opc/tests/features/containers/opc_ua_server_container.py @@ -14,12 +14,12 @@ # limitations under the License. from typing import List, Optional -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class OPCUAServerContainer(Container): +class OPCUAServerContainer(LinuxContainer): OPC_SERVER_IMAGE = "lordgamez/open62541:1.5.2" def __init__(self, test_context: MinifiTestContext, command: Optional[List[str]] = None): diff --git a/extensions/prometheus/tests/features/containers/prometheus_container.py b/extensions/prometheus/tests/features/containers/prometheus_container.py index 09053693e5..829bd892ed 100644 --- a/extensions/prometheus/tests/features/containers/prometheus_container.py +++ b/extensions/prometheus/tests/features/containers/prometheus_container.py @@ -14,15 +14,15 @@ # limitations under the License. import logging -from OpenSSL import crypto -from minifi_test_framework.containers.container import Container from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.core.ssl_utils import make_cert_without_extended_usage from minifi_test_framework.containers.file import File +from minifi_test_framework.core.ssl_utils import dump_cert, dump_key +from minifi_test_framework.containers.container_linux import LinuxContainer -class PrometheusContainer(Container): +class PrometheusContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext, ssl: bool = False): super().__init__("prom/prometheus:v3.9.1", f"prometheus-{test_context.scenario_id}", test_context.network) self.scenario_id = test_context.scenario_id @@ -30,13 +30,13 @@ def __init__(self, test_context: MinifiTestContext, ssl: bool = False): if ssl: prometheus_cert, prometheus_key = make_cert_without_extended_usage(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - root_ca_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=test_context.root_ca_cert) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/etc/prometheus/certs/root-ca.pem", root_ca_content, permissions=0o644)) - prometheus_cert_content = crypto.dump_certificate(type=crypto.FILETYPE_PEM, cert=prometheus_cert) + prometheus_cert_content = dump_cert(prometheus_cert) self.files.append(File("/etc/prometheus/certs/prometheus.crt", prometheus_cert_content, permissions=0o644)) - prometheus_key_content = crypto.dump_privatekey(type=crypto.FILETYPE_PEM, pkey=prometheus_key) + prometheus_key_content = dump_key(prometheus_key) self.files.append(File("/etc/prometheus/certs/prometheus.key", prometheus_key_content, permissions=0o644)) extra_ssl_settings = """ diff --git a/extensions/splunk/tests/features/containers/splunk_container.py b/extensions/splunk/tests/features/containers/splunk_container.py index 4106bb63e3..9a1488e634 100644 --- a/extensions/splunk/tests/features/containers/splunk_container.py +++ b/extensions/splunk/tests/features/containers/splunk_container.py @@ -15,15 +15,14 @@ import json -from OpenSSL import crypto -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.core.helpers import wait_for_condition, retry_check from minifi_test_framework.containers.file import File -from minifi_test_framework.core.ssl_utils import make_server_cert +from minifi_test_framework.core.ssl_utils import make_server_cert, dump_cert, dump_key -class SplunkContainer(Container): +class SplunkContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): super().__init__("splunk/splunk:9.2.1-patch2", f"splunk-{test_context.scenario_id}", test_context.network) self.user = None @@ -43,9 +42,9 @@ def __init__(self, test_context: MinifiTestContext): self.files.append(File("/tmp/defaults/default.yml", splunk_config_content, mode="rw", permissions=0o644)) splunk_cert, splunk_key = make_server_cert(self.container_name, test_context.root_ca_cert, test_context.root_ca_key) - splunk_cert_content = crypto.dump_certificate(crypto.FILETYPE_PEM, splunk_cert) - splunk_key_content = crypto.dump_privatekey(crypto.FILETYPE_PEM, splunk_key) - root_ca_content = crypto.dump_certificate(crypto.FILETYPE_PEM, test_context.root_ca_cert) + splunk_cert_content = dump_cert(splunk_cert) + splunk_key_content = dump_key(splunk_key) + root_ca_content = dump_cert(test_context.root_ca_cert) self.files.append(File("/opt/splunk/etc/auth/splunk_cert.pem", splunk_cert_content.decode() + splunk_key_content.decode() + root_ca_content.decode(), permissions=0o644)) self.files.append(File("/opt/splunk/etc/auth/root_ca.pem", root_ca_content.decode(), permissions=0o644)) diff --git a/extensions/sql/tests/features/containers/postgress_server_container.py b/extensions/sql/tests/features/containers/postgress_server_container.py index f1df20e4bb..2dea3058e7 100644 --- a/extensions/sql/tests/features/containers/postgress_server_container.py +++ b/extensions/sql/tests/features/containers/postgress_server_container.py @@ -17,13 +17,13 @@ import logging from textwrap import dedent -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class PostgresContainer(Container): +class PostgresContainer(LinuxContainer): def __init__(self, context): dockerfile = dedent("""\ FROM {base_image} diff --git a/extensions/standard-processors/tests/features/containers/diag_slave_container.py b/extensions/standard-processors/tests/features/containers/diag_slave_container.py index 27b4259588..4741441c4f 100644 --- a/extensions/standard-processors/tests/features/containers/diag_slave_container.py +++ b/extensions/standard-processors/tests/features/containers/diag_slave_container.py @@ -18,13 +18,13 @@ import logging from textwrap import dedent -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class DiagSlave(Container): +class DiagSlave(LinuxContainer): def __init__(self, test_context: MinifiTestContext): dockerfile = dedent("""\ FROM panterdsd/diagslave:latest diff --git a/extensions/standard-processors/tests/features/containers/syslog_container.py b/extensions/standard-processors/tests/features/containers/syslog_container.py index f2da956f59..c831af6f14 100644 --- a/extensions/standard-processors/tests/features/containers/syslog_container.py +++ b/extensions/standard-processors/tests/features/containers/syslog_container.py @@ -15,10 +15,10 @@ # limitations under the License. # -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer -class SyslogContainer(Container): +class SyslogContainer(LinuxContainer): def __init__(self, protocol, context): super(SyslogContainer, self).__init__("ubuntu:24.04", f"syslog-{protocol}-{context.scenario_id}", context.network) self.command = f'/bin/bash -c "echo Syslog {protocol} client started; while true; do logger --{protocol} -n minifi-primary-{context.scenario_id} -P 514 sample_log; sleep 1; done"' diff --git a/extensions/standard-processors/tests/features/containers/tcp_client_container.py b/extensions/standard-processors/tests/features/containers/tcp_client_container.py index 5da7525235..74fcf1351e 100644 --- a/extensions/standard-processors/tests/features/containers/tcp_client_container.py +++ b/extensions/standard-processors/tests/features/containers/tcp_client_container.py @@ -13,12 +13,12 @@ # See the License for the specific language governing permissions and # limitations under the License. -from minifi_test_framework.containers.container import Container +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext -class TcpClientContainer(Container): +class TcpClientContainer(LinuxContainer): def __init__(self, test_context: MinifiTestContext): cmd = ( "/bin/sh -c 'apk add netcat-openbsd && " diff --git a/extensions/standard-processors/tests/features/core_functionality.feature b/extensions/standard-processors/tests/features/core_functionality.feature index bab09863ff..bae88e4cce 100644 --- a/extensions/standard-processors/tests/features/core_functionality.feature +++ b/extensions/standard-processors/tests/features/core_functionality.feature @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -@CORE +@CORE @SUPPORTS_WINDOWS Feature: Core flow functionalities Test core flow configuration functionalities @@ -33,8 +33,8 @@ Feature: Core flow functionalities When all instances start up - Then at least one file with the content "first_custom_text" is placed in the "/tmp/output" directory in less than 20 seconds - And at least one file with the content "second_custom_text" is placed in the "/tmp/output" directory in less than 20 seconds + Then at least one file with the content "first_custom_text" is placed in the "/tmp/output" directory in less than 200 seconds + And at least one file with the content "second_custom_text" is placed in the "/tmp/output" directory in less than 200 seconds Scenario: A funnel can be used as a terminator Given a GenerateFlowFile processor with the "Data Format" property set to "Text" diff --git a/extensions/standard-processors/tests/features/environment.py b/extensions/standard-processors/tests/features/environment.py index 1edb297a3d..a2c3531cef 100644 --- a/extensions/standard-processors/tests/features/environment.py +++ b/extensions/standard-processors/tests/features/environment.py @@ -12,8 +12,9 @@ # 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 platform + import docker from minifi_test_framework.core.hooks import common_before_scenario from minifi_test_framework.core.hooks import common_after_scenario @@ -30,6 +31,8 @@ def before_feature(context, feature): def is_minifi_image_alpine_based(context): + if os.name == 'nt': + return False client: docker.DockerClient = docker.from_env() container = client.containers.create( image=context.minifi_container_image, diff --git a/extensions/standard-processors/tests/features/replace_text.feature b/extensions/standard-processors/tests/features/replace_text.feature index f8a3fdb05e..b24a0e8738 100644 --- a/extensions/standard-processors/tests/features/replace_text.feature +++ b/extensions/standard-processors/tests/features/replace_text.feature @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -@CORE +@CORE @SUPPORTS_WINDOWS Feature: Changing flowfile contents using the ReplaceText processor Scenario Outline: Replace text using Entire text mode diff --git a/extensions/standard-processors/tests/features/steps/minifi_c2_server_container.py b/extensions/standard-processors/tests/features/steps/minifi_c2_server_container.py index 75c4f4f3bd..7f73ae9b11 100644 --- a/extensions/standard-processors/tests/features/steps/minifi_c2_server_container.py +++ b/extensions/standard-processors/tests/features/steps/minifi_c2_server_container.py @@ -17,32 +17,35 @@ import jks import os -from OpenSSL import crypto -from cryptography.hazmat.primitives.serialization import pkcs12, BestAvailableEncryption, load_pem_private_key + +from cryptography.hazmat.primitives import serialization from cryptography import x509 from pathlib import Path -from minifi_test_framework.containers.container import Container +from cryptography.hazmat.primitives._serialization import BestAvailableEncryption +from cryptography.hazmat.primitives.serialization import load_pem_private_key, pkcs12 +from minifi_test_framework.containers.container_linux import LinuxContainer from minifi_test_framework.core.helpers import wait_for_condition from minifi_test_framework.core.minifi_test_context import MinifiTestContext from minifi_test_framework.core.ssl_utils import make_server_cert from minifi_test_framework.containers.file import File from minifi_test_framework.containers.host_file import HostFile +from minifi_test_framework.core.ssl_utils import dump_key, dump_cert -class MinifiC2Server(Container): +class MinifiC2Server(LinuxContainer): def __init__(self, test_context: MinifiTestContext, ssl: bool = False): super().__init__("apache/nifi-minifi-c2:1.27.0", f"minifi-c2-server-{test_context.scenario_id}", test_context.network) if ssl: c2_cert, c2_key = make_server_cert(f"minifi-c2-server-{test_context.scenario_id}", test_context.root_ca_cert, test_context.root_ca_key) - pke = jks.PrivateKeyEntry.new('c2-server-cert', [crypto.dump_certificate(crypto.FILETYPE_ASN1, c2_cert)], crypto.dump_privatekey(crypto.FILETYPE_ASN1, c2_key), 'rsa_raw') + pke = jks.PrivateKeyEntry.new('c2-server-cert', [dump_cert(c2_cert, encoding_type=serialization.Encoding.DER)], dump_key(c2_key, encoding_type=serialization.Encoding.DER), 'rsa_raw') server_keystore = jks.KeyStore.new('jks', [pke]) server_keystore_content = server_keystore.saves('abcdefgh') self.files.append(File("/opt/minifi-c2/minifi-c2-current/certs/minifi-c2-server-keystore.jks", server_keystore_content, permissions=0o644)) - private_key_pem = crypto.dump_privatekey(crypto.FILETYPE_PEM, test_context.root_ca_key) + private_key_pem = dump_key(test_context.root_ca_key) private_key = load_pem_private_key(private_key_pem, password=None) - certificate_pem = crypto.dump_certificate(crypto.FILETYPE_PEM, test_context.root_ca_cert) + certificate_pem = dump_cert(test_context.root_ca_cert) certificate = x509.load_pem_x509_certificate(certificate_pem) pkcs12_data = pkcs12.serialize_key_and_certificates( name=None, diff --git a/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py b/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py index c65d67e0f4..940636e727 100644 --- a/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py +++ b/extensions/standard-processors/tests/features/steps/minifi_controller_steps.py @@ -22,52 +22,52 @@ @given('controller socket properties are set up') def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().set_controller_socket_properties() + context.get_or_create_default_minifi_container().controller.set_controller_socket_properties() @when('MiNiFi config is updated through MiNiFi controller') def step_impl(context: MinifiTestContext): - context.get_or_create_default_minifi_container().update_flow_config_through_controller() + context.get_or_create_default_minifi_container().controller.update_flow_config_through_controller() @then('the updated config is persisted') def step_impl(context: MinifiTestContext): - assert context.get_or_create_default_minifi_container().updated_config_is_persisted() + assert context.get_or_create_default_minifi_container().controller.updated_config_is_persisted() @when('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) + context.get_or_create_default_minifi_container().controller.stop_component_through_controller(component) @when('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) + context.get_or_create_default_minifi_container().controller.start_component_through_controller(component) @then('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) + assert not context.get_or_create_default_minifi_container().controller.is_component_running(component) @then('the {component} component is running') def step_impl(context: MinifiTestContext, component: str): - assert context.get_or_create_default_minifi_container().is_component_running(component) + assert context.get_or_create_default_minifi_container().controller.is_component_running(component) @then('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) + assert context.get_or_create_default_minifi_container().controller.connection_found_through_controller(connection) @then('{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 + assert context.get_or_create_default_minifi_container().controller.get_full_connection_count() == connection_count -@retry_check(10, 1) +@retry_check(max_tries=5, retry_interval_seconds=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) + return context.get_or_create_default_minifi_container().controller.get_connection_size(connection) == (size, max_size) @then('connection \"{connection}\" has {size:d} size and {max_size:d} max size through MiNiFi controller') @@ -75,9 +75,9 @@ def step_impl(context: MinifiTestContext, connection: str, size: int, max_size: assert check_connection_size_through_controller(context, connection, size, max_size) -@retry_check(10, 1) +@retry_check(max_tries=10, retry_interval_seconds=1) def manifest_can_be_retrieved_through_minifi_controller(context: MinifiTestContext) -> bool: - manifest = context.get_or_create_default_minifi_container().get_manifest() + manifest = context.get_or_create_default_minifi_container().controller.get_manifest() return '"agentManifest": {' in manifest and '"componentManifest": {' in manifest and '"agentType": "cpp"' in manifest @@ -88,4 +88,4 @@ def step_impl(context: MinifiTestContext): @then('debug bundle can be retrieved through MiNiFi controller') def step_impl(context: MinifiTestContext): - assert context.get_or_create_default_minifi_container().create_debug_bundle() + assert context.get_or_create_default_minifi_container().controller.create_debug_bundle() diff --git a/extensions/standard-processors/tests/features/steps/steps.py b/extensions/standard-processors/tests/features/steps/steps.py index 6ef8df1bb9..bd82621e7d 100644 --- a/extensions/standard-processors/tests/features/steps/steps.py +++ b/extensions/standard-processors/tests/features/steps/steps.py @@ -27,6 +27,8 @@ from containers.syslog_container import SyslogContainer from containers.diag_slave_container import DiagSlave from containers.tcp_client_container import TcpClientContainer + +from minifi_test_framework.containers.minifi_protocol import set_up_ssl_properties from minifi_c2_server_container import MinifiC2Server @@ -78,7 +80,7 @@ def step_impl(context: MinifiTestContext): context.get_or_create_default_minifi_container().set_property("nifi.c2.full.heartbeat", "false") context.get_or_create_default_minifi_container().set_property("nifi.c2.agent.class", "minifi-test-class") context.get_or_create_default_minifi_container().set_property("nifi.c2.agent.identifier", "minifi-test-id") - context.get_or_create_default_minifi_container().set_up_ssl_properties() + set_up_ssl_properties(context.get_or_create_default_minifi_container()) @given("a MiNiFi C2 server is set up")