This is an automated email from the ASF dual-hosted git repository. szaszm pushed a commit to branch main in repository https://gitbox.apache.org/repos/asf/nifi-minifi-cpp.git
commit bc9a9ea1cc34886d5d6afcaa2ba193ec4b915eca Author: Gabor Gyimesi <[email protected]> AuthorDate: Thu Jan 29 16:16:42 2026 +0100 MINIFICPP-2679 Move HTTP tests to modular docker tests Depends on github.com/apache/nifi-minifi-cpp/pull/2075 Closes #2077 Signed-off-by: Marton Szasz <[email protected]> --- .../minifi_test_framework/containers/container.py | 20 ++- .../containers/minifi_container.py | 11 +- .../containers/nifi_container.py | 89 ++++++++++++ .../src/minifi_test_framework/core/hooks.py | 4 +- .../core/minifi_test_context.py | 8 -- .../minifi/flow_definition.py | 42 +----- ...low_definition.py => minifi_flow_definition.py} | 37 +---- .../minifi/nifi_flow_definition.py | 158 +++++++++++++++++++++ .../minifi_test_framework/steps/checking_steps.py | 104 +++++++++----- .../src/minifi_test_framework/steps/core_steps.py | 12 ++ .../steps/flow_building_steps.py | 73 ++++++++-- docker/RunBehaveTests.sh | 3 +- extensions/civetweb/tests/features/environment.py | 25 ++++ .../civetweb/tests}/features/http.feature | 116 +++++++++------ .../civetweb/tests}/features/https.feature | 122 +++++++++------- extensions/civetweb/tests/features/steps/steps.py | 19 +++ 16 files changed, 613 insertions(+), 230 deletions(-) diff --git a/behave_framework/src/minifi_test_framework/containers/container.py b/behave_framework/src/minifi_test_framework/containers/container.py index 07abded28..4b6de8634 100644 --- a/behave_framework/src/minifi_test_framework/containers/container.py +++ b/behave_framework/src/minifi_test_framework/containers/container.py @@ -30,7 +30,7 @@ from minifi_test_framework.containers.host_file import HostFile class Container: - def __init__(self, image_name: str, container_name: str, network: Network, command: str | None = None): + def __init__(self, image_name: str, container_name: str, network: Network, command: str | None = None, entrypoint: str | None = None): self.image_name: str = image_name self.container_name: str = container_name self.network: Network = network @@ -42,6 +42,7 @@ class Container: self.host_files: list[HostFile] = [] self.volumes = {} self.command: str | None = command + self.entrypoint: str | None = entrypoint self._temp_dir: tempfile.TemporaryDirectory | None = None self.ports: dict[str, int] | None = None self.environment: list[str] = [] @@ -91,7 +92,7 @@ class Container: 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, user=self.user, detach=True) + command=self.command, entrypoint=self.entrypoint, user=self.user, detach=True) except Exception as e: logging.error(f"Error starting container: {e}") raise @@ -430,3 +431,18 @@ class Container: continue return False + + 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 + + command = f"find \"{directory_path}\" -maxdepth 1 -type f -size +{expected_size}c" + + exit_code, output = self.exec_run(command) + if exit_code != 0: + logging.error(f"Error running command to get file sizes: {output}") + return False + if len(output.strip()) > 0: + return True + + return False diff --git a/behave_framework/src/minifi_test_framework/containers/minifi_container.py b/behave_framework/src/minifi_test_framework/containers/minifi_container.py index 4d1a1a5b3..f6951519f 100644 --- a/behave_framework/src/minifi_test_framework/containers/minifi_container.py +++ b/behave_framework/src/minifi_test_framework/containers/minifi_container.py @@ -20,19 +20,20 @@ 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.minifi.flow_definition import FlowDefinition -from minifi_test_framework.core.ssl_utils import make_cert_without_extended_usage, make_client_cert +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 .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 = FlowDefinition() + self.flow_definition = MinifiFlowDefinition() self.properties: dict[str, str] = {} self.log_properties: dict[str, str] = {} 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))) @@ -41,6 +42,10 @@ class MinifiContainer(Container): 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()) self._fill_default_properties() diff --git a/behave_framework/src/minifi_test_framework/containers/nifi_container.py b/behave_framework/src/minifi_test_framework/containers/nifi_container.py new file mode 100644 index 000000000..396b1e1a7 --- /dev/null +++ b/behave_framework/src/minifi_test_framework/containers/nifi_container.py @@ -0,0 +1,89 @@ +# 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 io +import gzip +from typing import List, Optional +from minifi_test_framework.containers.file import File +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.minifi.nifi_flow_definition import NifiFlowDefinition + + +class NifiContainer(Container): + NIFI_VERSION = '2.7.2' + + def __init__(self, test_context: MinifiTestContext, command: Optional[List[str]] = None, use_ssl: bool = False): + self.flow_definition = NifiFlowDefinition() + name = f"nifi-{test_context.scenario_id}" + if use_ssl: + entry_command = (r"sed -i -e 's/^\(nifi.remote.input.host\)=.*/\1={name}/' " + r"-e 's/^\(nifi.remote.input.secure\)=.*/\1=true/' " + r"-e 's/^\(nifi.sensitive.props.key\)=.*/\1=secret_key_12345/' " + r"-e 's/^\(nifi.web.https.port\)=.*/\1=8443/' " + r"-e 's/^\(nifi.web.https.host\)=.*/\1={name}/' " + r"-e 's/^\(nifi.security.keystore\)=.*/\1=\/tmp\/resources\/keystore.jks/' " + r"-e 's/^\(nifi.security.keystoreType\)=.*/\1=jks/' " + r"-e 's/^\(nifi.security.keystorePasswd\)=.*/\1=passw0rd1!/' " + r"-e 's/^\(nifi.security.keyPasswd\)=.*/#\1=passw0rd1!/' " + r"-e 's/^\(nifi.security.truststore\)=.*/\1=\/tmp\/resources\/truststore.jks/' " + r"-e 's/^\(nifi.security.truststoreType\)=.*/\1=jks/' " + r"-e 's/^\(nifi.security.truststorePasswd\)=.*/\1=passw0rd1!/' " + r"-e 's/^\(nifi.remote.input.socket.port\)=.*/\1=10443/' /opt/nifi/nifi-current/conf/nifi.properties && " + r"cp /tmp/nifi_config/flow.json.gz /opt/nifi/nifi-current/conf && /opt/nifi/nifi-current/bin/nifi.sh run & " + r"nifi_pid=$! &&" + r"tail -F --pid=${{nifi_pid}} /opt/nifi/nifi-current/logs/nifi-app.log").format(name=name) + else: + entry_command = (r"sed -i -e 's/^\(nifi.remote.input.host\)=.*/\1={name}/' " + r"-e 's/^\(nifi.sensitive.props.key\)=.*/\1=secret_key_12345/' " + r"-e 's/^\(nifi.remote.input.secure\)=.*/\1=false/' " + r"-e 's/^\(nifi.web.http.port\)=.*/\1=8080/' " + r"-e 's/^\(nifi.web.https.port\)=.*/\1=/' " + r"-e 's/^\(nifi.web.https.host\)=.*/\1=/' " + r"-e 's/^\(nifi.web.http.host\)=.*/\1={name}/' " + r"-e 's/^\(nifi.security.keystore\)=.*/\1=/' " + r"-e 's/^\(nifi.security.keystoreType\)=.*/\1=/' " + r"-e 's/^\(nifi.security.keystorePasswd\)=.*/\1=/' " + r"-e 's/^\(nifi.security.keyPasswd\)=.*/\1=/' " + r"-e 's/^\(nifi.security.truststore\)=.*/\1=/' " + r"-e 's/^\(nifi.security.truststoreType\)=.*/\1=/' " + r"-e 's/^\(nifi.security.truststorePasswd\)=.*/\1=/' " + r"-e 's/^\(nifi.remote.input.socket.port\)=.*/\1=10000/' /opt/nifi/nifi-current/conf/nifi.properties && " + r"cp /tmp/nifi_config/flow.json.gz /opt/nifi/nifi-current/conf && /opt/nifi/nifi-current/bin/nifi.sh run & " + r"nifi_pid=$! &&" + r"tail -F --pid=${{nifi_pid}} /opt/nifi/nifi-current/logs/nifi-app.log").format(name=name) + if not command: + command = ["/bin/sh", "-c", entry_command] + + super().__init__("apache/nifi:" + self.NIFI_VERSION, name, test_context.network, entrypoint=command) + + def deploy(self): + flow_config = self.flow_definition.to_json() + buffer = io.BytesIO() + + with gzip.GzipFile(fileobj=buffer, mode='wb') as gz_file: + gz_file.write(flow_config.encode()) + + gzipped_bytes = buffer.getvalue() + self.files.append(File("/tmp/nifi_config/flow.json.gz", gzipped_bytes)) + + super().deploy() + finished_str = "Started Application in" + return wait_for_condition( + condition=lambda: finished_str in self.get_logs(), + timeout_seconds=300, + bail_condition=lambda: self.exited, + context=None) diff --git a/behave_framework/src/minifi_test_framework/core/hooks.py b/behave_framework/src/minifi_test_framework/core/hooks.py index 80528fa96..298f27d85 100644 --- a/behave_framework/src/minifi_test_framework/core/hooks.py +++ b/behave_framework/src/minifi_test_framework/core/hooks.py @@ -51,9 +51,7 @@ def common_before_scenario(context: Context, scenario: Scenario): method_map = { "get_or_create_minifi_container": MinifiTestContext.get_or_create_minifi_container, - "get_or_create_default_minifi_container": MinifiTestContext.get_or_create_default_minifi_container, - "get_minifi_container": MinifiTestContext.get_minifi_container, - "get_default_minifi_container": MinifiTestContext.get_default_minifi_container, + "get_or_create_default_minifi_container": MinifiTestContext.get_or_create_default_minifi_container } for attr, method in method_map.items(): if not hasattr(context, attr): 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 3cfb0264e..959f2e1fa 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 @@ -46,11 +46,3 @@ class MinifiTestContext(Context): def get_or_create_default_minifi_container(self) -> MinifiContainer: return self.get_or_create_minifi_container(DEFAULT_MINIFI_CONTAINER_NAME) - - def get_minifi_container(self, container_name: str) -> MinifiContainer: - if container_name not in self.containers: - raise KeyError(f"MiNiFi container '{container_name}' does not exist in the test context.") - return self.containers[container_name] - - def get_default_minifi_container(self) -> MinifiContainer: - return self.get_minifi_container(DEFAULT_MINIFI_CONTAINER_NAME) diff --git a/behave_framework/src/minifi_test_framework/minifi/flow_definition.py b/behave_framework/src/minifi_test_framework/minifi/flow_definition.py index e53e8d0e7..cbaa4b8de 100644 --- a/behave_framework/src/minifi_test_framework/minifi/flow_definition.py +++ b/behave_framework/src/minifi_test_framework/minifi/flow_definition.py @@ -15,7 +15,7 @@ # limitations under the License. # -import yaml +from abc import ABC from .connection import Connection from .controller_service import ControllerService @@ -24,7 +24,7 @@ from .parameter_context import ParameterContext from .processor import Processor -class FlowDefinition: +class FlowDefinition(ABC): def __init__(self, flow_name: str = "MiNiFi Flow"): self.flow_name = flow_name self.processors: list[Processor] = [] @@ -53,42 +53,10 @@ class FlowDefinition: self.connections.append(connection) def to_yaml(self) -> str: - """Serializes the entire flow definition into the MiNiFi YAML format.""" + raise NotImplementedError("to_yaml method must be implemented in subclasses") - # Create a quick lookup map of processor names to their objects - # This is crucial for finding the source/destination IDs for connections - processors_by_name = {p.name: p for p in self.processors} - funnels_by_name = {f.name: f for f in self.funnels} - - connectables_by_name = {**processors_by_name, **funnels_by_name} - - if len(self.parameter_contexts) > 0: - parameter_context_name = self.parameter_contexts[0].name - else: - parameter_context_name = '' - # Build the final dictionary structure - config = {'MiNiFi Config Version': 3, 'Flow Controller': {'name': self.flow_name}, - 'Parameter Contexts': [p.to_yaml_dict() for p in self.parameter_contexts], - 'Processors': [p.to_yaml_dict() for p in self.processors], - 'Funnels': [f.to_yaml_dict() for f in self.funnels], 'Connections': [], - 'Controller Services': [c.to_yaml_dict() for c in self.controller_services], - 'Remote Processing Groups': [], 'Parameter Context Name': parameter_context_name} - - # Build the connections list by looking up processor IDs - for conn in self.connections: - source_proc = connectables_by_name.get(conn.source_name) - dest_proc = connectables_by_name.get(conn.target_name) - - if not source_proc or not dest_proc: - raise ValueError( - f"Could not find processors for connection from '{conn.source_name}' to '{conn.target_name}'") - - config['Connections'].append( - {'name': f"{conn.source_name}/{conn.source_relationship}/{conn.target_name}", 'id': conn.id, - 'source id': source_proc.id, 'source relationship name': conn.source_relationship, - 'destination id': dest_proc.id}) - - return yaml.dump(config, sort_keys=False, indent=2, width=120) + def to_json(self) -> str: + raise NotImplementedError("to_json method must be implemented in subclasses") def __repr__(self): return f"FlowDefinition(Processors: {self.processors}, Controller Services: {self.controller_services})" diff --git a/behave_framework/src/minifi_test_framework/minifi/flow_definition.py b/behave_framework/src/minifi_test_framework/minifi/minifi_flow_definition.py similarity index 64% copy from behave_framework/src/minifi_test_framework/minifi/flow_definition.py copy to behave_framework/src/minifi_test_framework/minifi/minifi_flow_definition.py index e53e8d0e7..469a3b8c1 100644 --- a/behave_framework/src/minifi_test_framework/minifi/flow_definition.py +++ b/behave_framework/src/minifi_test_framework/minifi/minifi_flow_definition.py @@ -17,40 +17,12 @@ import yaml -from .connection import Connection -from .controller_service import ControllerService -from .funnel import Funnel -from .parameter_context import ParameterContext -from .processor import Processor +from .flow_definition import FlowDefinition -class FlowDefinition: +class MinifiFlowDefinition(FlowDefinition): def __init__(self, flow_name: str = "MiNiFi Flow"): - self.flow_name = flow_name - self.processors: list[Processor] = [] - self.controller_services: list[ControllerService] = [] - self.funnels: list[Funnel] = [] - self.connections: list[Connection] = [] - self.parameter_contexts: list[ParameterContext] = [] - - def add_processor(self, processor: Processor): - self.processors.append(processor) - - def get_processor(self, processor_name: str) -> Processor | None: - return next((proc for proc in self.processors if proc.name == processor_name), None) - - def get_controller_service(self, controller_service_name: str) -> ControllerService | None: - return next((controller for controller in self.controller_services if controller.name == controller_service_name), None) - - def get_parameter_context(self, parameter_context_name: str) -> ParameterContext | None: - return next((parameter_context for parameter_context in self.parameter_contexts if - parameter_context.name == parameter_context_name), None) - - def add_funnel(self, funnel: Funnel): - self.funnels.append(funnel) - - def add_connection(self, connection: Connection): - self.connections.append(connection) + super().__init__(flow_name) def to_yaml(self) -> str: """Serializes the entire flow definition into the MiNiFi YAML format.""" @@ -89,6 +61,3 @@ class FlowDefinition: 'destination id': dest_proc.id}) return yaml.dump(config, sort_keys=False, indent=2, width=120) - - def __repr__(self): - return f"FlowDefinition(Processors: {self.processors}, Controller Services: {self.controller_services})" diff --git a/behave_framework/src/minifi_test_framework/minifi/nifi_flow_definition.py b/behave_framework/src/minifi_test_framework/minifi/nifi_flow_definition.py new file mode 100644 index 000000000..8e7faab5a --- /dev/null +++ b/behave_framework/src/minifi_test_framework/minifi/nifi_flow_definition.py @@ -0,0 +1,158 @@ +# +# 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 json +import uuid +from .flow_definition import FlowDefinition + + +class NifiFlowDefinition(FlowDefinition): + NIFI_VERSION: str = '2.2.0' + + def __init__(self, flow_name: str = "NiFi Flow"): + super().__init__(flow_name) + + def to_json(self) -> str: + config = { + "encodingVersion": { + "majorVersion": 2, + "minorVersion": 0 + }, + "maxTimerDrivenThreadCount": 10, + "maxEventDrivenThreadCount": 1, + "registries": [], + "parameterContexts": [], + "parameterProviders": [], + "controllerServices": [], + "reportingTasks": [], + "templates": [], + "rootGroup": { + "identifier": "9802c873-3322-3b60-a71d-732d02bd60f8", + "instanceIdentifier": str(uuid.uuid4()), + "name": "NiFi Flow", + "comments": "", + "position": { + "x": 0, + "y": 0 + }, + "processGroups": [], + "remoteProcessGroups": [], + "processors": [], + "inputPorts": [], + "outputPorts": [], + "connections": [], + "labels": [], + "funnels": [], + "controllerServices": [], + "defaultFlowFileExpiration": "0 sec", + "defaultBackPressureObjectThreshold": 10000, + "defaultBackPressureDataSizeThreshold": "1 GB", + "scheduledState": "RUNNING", + "executionEngine": "INHERITED", + "maxConcurrentTasks": 1, + "statelessFlowTimeout": "1 min", + "flowFileConcurrency": "UNBOUNDED", + "flowFileOutboundPolicy": "STREAM_WHEN_AVAILABLE", + "componentType": "PROCESS_GROUP" + } + } + + processors_by_name = {p.name: p for p in self.processors} + processors_node = config["rootGroup"]["processors"] + + for proc in self.processors: + processors_node.append({ + "identifier": str(proc.id), + "instanceIdentifier": str(proc.id), + "name": proc.name, + "comments": "", + "position": { + "x": 0, + "y": 0 + }, + "type": 'org.apache.nifi.processors.standard.' + proc.class_name, + "bundle": { + "group": "org.apache.nifi", + "artifact": "nifi-standard-nar", + "version": self.NIFI_VERSION + }, + "properties": {key: value for key, value in proc.properties.items() if key}, + "propertyDescriptors": {}, + "style": {}, + "schedulingPeriod": "0 sec" if proc.scheduling_strategy == "EVENT_DRIVEN" else proc.scheduling_period, + "schedulingStrategy": "TIMER_DRIVEN", + "executionNode": "ALL", + "penaltyDuration": "5 sec", + "yieldDuration": "1 sec", + "bulletinLevel": "WARN", + "runDurationMillis": "0", + "concurrentlySchedulableTaskCount": proc.max_concurrent_tasks if proc.max_concurrent_tasks is not None else 1, + "autoTerminatedRelationships": proc.auto_terminated_relationships, + "scheduledState": "RUNNING", + "retryCount": 10, + "retriedRelationships": [], + "backoffMechanism": "PENALIZE_FLOWFILE", + "maxBackoffPeriod": "10 mins", + "componentType": "PROCESSOR", + "groupIdentifier": "9802c873-3322-3b60-a71d-732d02bd60f8" + }) + + connections_node = config["rootGroup"]["connections"] + + for conn in self.connections: + source_proc = processors_by_name.get(conn.source_name) + dest_proc = processors_by_name.get(conn.target_name) + if not source_proc or not dest_proc: + raise ValueError( + f"Could not find processors for connection from '{conn.source_name}' to '{conn.target_name}'") + + connections_node.append({ + "identifier": conn.id, + "instanceIdentifier": conn.id, + "name": f"{conn.source_name}/{conn.source_relationship}/{conn.target_name}", + "source": { + "id": source_proc.id, + "type": "PROCESSOR", + "groupId": "9802c873-3322-3b60-a71d-732d02bd60f8", + "name": conn.source_name, + "comments": "", + "instanceIdentifier": source_proc.id + }, + "destination": { + "id": dest_proc.id, + "type": "PROCESSOR", + "groupId": "9802c873-3322-3b60-a71d-732d02bd60f8", + "name": dest_proc.name, + "comments": "", + "instanceIdentifier": dest_proc.id + }, + "labelIndex": 1, + "zIndex": 0, + "selectedRelationships": [conn.source_relationship], + "backPressureObjectThreshold": 10, + "backPressureDataSizeThreshold": "50 B", + "flowFileExpiration": "0 sec", + "prioritizers": [], + "bends": [], + "loadBalanceStrategy": "DO_NOT_LOAD_BALANCE", + "partitioningAttribute": "", + "loadBalanceCompression": "DO_NOT_COMPRESS", + "componentType": "CONNECTION", + "groupIdentifier": "9802c873-3322-3b60-a71d-732d02bd60f8" + }) + + return json.dumps(config) diff --git a/behave_framework/src/minifi_test_framework/steps/checking_steps.py b/behave_framework/src/minifi_test_framework/steps/checking_steps.py index c964aea46..8a2eafae1 100644 --- a/behave_framework/src/minifi_test_framework/steps/checking_steps.py +++ b/behave_framework/src/minifi_test_framework/steps/checking_steps.py @@ -32,8 +32,8 @@ def step_impl(context: MinifiTestContext, content: str, path: str, duration: str new_content = content.replace("\\n", "\n") timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().path_with_content_exists(path, new_content), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].path_with_content_exists(path, new_content), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @then('a single file with the content "{content}" is placed in the "{directory}" directory in less than {duration}') @@ -41,16 +41,16 @@ def step_impl(context: MinifiTestContext, content: str, directory: str, duration new_content = content.replace("\\n", "\n") timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().directory_has_single_file_with_content(directory, new_content), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].directory_has_single_file_with_content(directory, new_content), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @then('in the "{container_name}" container at least one file with the content "{content}" is placed in the "{directory}" directory in less than {duration}') def step_impl(context: MinifiTestContext, container_name: str, content: str, directory: str, duration: str): timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_minifi_container(container_name).directory_contains_file_with_content(directory, content), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_minifi_container(container_name).exited, context=context) + condition=lambda: context.containers[container_name].directory_contains_file_with_content(directory, content), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[container_name].exited, context=context) @then('at least one file with the content "{content}" is placed in the "{directory}" directory in less than {duration}') @@ -62,25 +62,25 @@ def step_impl(context: MinifiTestContext, content: str, directory: str, duration def step_impl(context: MinifiTestContext, message: str, duration: str): duration_seconds = humanfriendly.parse_timespan(duration) time.sleep(duration_seconds) - assert message not in context.get_default_minifi_container().get_logs() + assert message not in context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs() @then("the Minifi logs do not contain errors") def step_impl(context: MinifiTestContext): - assert "[error]" not in context.get_default_minifi_container().get_logs() or context.get_default_minifi_container().log_app_output() + assert "[error]" not in context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs() or context.containers[DEFAULT_MINIFI_CONTAINER_NAME].log_app_output() @then("the Minifi logs do not contain warnings") def step_impl(context: MinifiTestContext): - assert "[warning]" not in context.get_default_minifi_container().get_logs() or context.get_default_minifi_container().log_app_output() + assert "[warning]" not in context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs() or context.containers[DEFAULT_MINIFI_CONTAINER_NAME].log_app_output() @then("the Minifi logs contain the following message: '{message}' in less than {duration}") @then('the Minifi logs contain the following message: "{message}" in less than {duration}') def step_impl(context: MinifiTestContext, message: str, duration: str): duration_seconds = humanfriendly.parse_timespan(duration) - assert wait_for_condition(condition=lambda: message in context.get_default_minifi_container().get_logs(), - timeout_seconds=duration_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: message in context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs(), + timeout_seconds=duration_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -88,14 +88,14 @@ def step_impl(context: MinifiTestContext, message: str, duration: str): def step_impl(context, log_message, count, duration): duration_seconds = humanfriendly.parse_timespan(duration) time.sleep(duration_seconds) - assert context.get_default_minifi_container().get_logs().count(log_message) == count or context.get_default_minifi_container().log_app_output() + assert context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs().count(log_message) == count or context.containers[DEFAULT_MINIFI_CONTAINER_NAME].log_app_output() @then("the Minifi logs match the following regex: \"{regex}\" in less than {duration}") def step_impl(context, regex, duration): duration_seconds = humanfriendly.parse_timespan(duration) - assert wait_for_condition(condition=lambda: re.search(regex, context.get_default_minifi_container().get_logs()), - timeout_seconds=duration_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: re.search(regex, context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_logs()), + timeout_seconds=duration_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -105,13 +105,18 @@ def step_impl(context: MinifiTestContext, url: str): assert http_proxy_container.check_http_proxy_access(url) or http_proxy_container.log_app_output() -@then('no files are placed in the "{directory}" directory in {duration} of running time') -def step_impl(context, directory, duration): +@then('in the "{container}" container no files are placed in the "{directory}" directory in {duration} of running time') +def step_impl(context, container, directory, duration): duration_seconds = humanfriendly.parse_timespan(duration) - assert check_condition_after_wait(condition=lambda: context.get_default_minifi_container().get_number_of_files(directory) == 0, + assert check_condition_after_wait(condition=lambda: context.containers[container].get_number_of_files(directory) == 0, context=context, wait_time=duration_seconds) +@then('no files are placed in the "{directory}" directory in {duration} of running time') +def step_impl(context, directory, duration): + context.execute_steps(f'then in the "{DEFAULT_MINIFI_CONTAINER_NAME}" container no files are placed in the "{directory}" directory in {duration} of running time') + + @then('{num:d} files are placed in the "{directory}" directory in less than {duration}') @then('{num:d} file is placed in the "{directory}" directory in less than {duration}') def step_impl(context: MinifiTestContext, num: int, directory: str, duration: str): @@ -119,8 +124,8 @@ def step_impl(context: MinifiTestContext, num: int, directory: str, duration: st if num == 0: context.execute_steps(f'then no files are placed in the "{directory}" directory in {duration} of running time') return - assert wait_for_condition(condition=lambda: context.get_default_minifi_container().get_number_of_files(directory) == num, - timeout_seconds=duration_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) == num, + timeout_seconds=duration_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -128,8 +133,8 @@ def step_impl(context: MinifiTestContext, num: int, directory: str, duration: st @then('at least {num:d} file is placed in the "{directory}" directory in less than {duration}') def step_impl(context: MinifiTestContext, num: int, directory: str, duration: str): duration_seconds = humanfriendly.parse_timespan(duration) - assert wait_for_condition(condition=lambda: context.get_default_minifi_container().get_number_of_files(directory) >= num, - timeout_seconds=duration_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) >= num, + timeout_seconds=duration_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -137,8 +142,8 @@ def step_impl(context: MinifiTestContext, num: int, directory: str, duration: st def step_impl(context: MinifiTestContext, directory: str, regex_str: str, duration: str): duration_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().directory_contains_file_with_regex(directory, regex_str), - timeout_seconds=duration_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].directory_contains_file_with_regex(directory, regex_str), + timeout_seconds=duration_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @then('files with contents "{content_one}" and "{content_two}" are placed in the "{directory}" directory in less than {timeout}') @@ -147,8 +152,8 @@ def step_impl(context: MinifiTestContext, directory: str, timeout: str, content_ c1 = content_one.replace("\\n", "\n") c2 = content_two.replace("\\n", "\n") contents_arr = [c1, c2] - assert wait_for_condition(condition=lambda: context.get_default_minifi_container().verify_file_contents(directory, contents_arr), - timeout_seconds=timeout_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].verify_file_contents(directory, contents_arr), + timeout_seconds=timeout_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -157,8 +162,8 @@ def step_impl(context: MinifiTestContext, directory: str, timeout: str, contents timeout_seconds = humanfriendly.parse_timespan(timeout) new_contents = contents.replace("\\n", "\n") contents_arr = new_contents.split(",") - assert wait_for_condition(condition=lambda: context.get_default_minifi_container().verify_file_contents(directory, contents_arr), - timeout_seconds=timeout_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, + assert wait_for_condition(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].verify_file_contents(directory, contents_arr), + timeout_seconds=timeout_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @@ -167,17 +172,17 @@ def step_impl(context: MinifiTestContext, directory: str, timeout: str, contents def step_impl(context: MinifiTestContext, content: str, directory: str, duration: str): timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().verify_path_with_json_content(directory, content), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].verify_path_with_json_content(directory, content), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @then('MiNiFi\'s memory usage does not increase by more than {max_increase} after {duration}') def step_impl(context: MinifiTestContext, max_increase: str, duration: str): time_in_seconds = humanfriendly.parse_timespan(duration) max_increase_in_bytes = humanfriendly.parse_size(max_increase) - initial_memory_usage = context.get_default_minifi_container().get_memory_usage() + initial_memory_usage = context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_memory_usage() time.sleep(time_in_seconds) - final_memory_usage = context.get_default_minifi_container().get_memory_usage() + final_memory_usage = context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_memory_usage() assert final_memory_usage - initial_memory_usage <= max_increase_in_bytes @@ -186,15 +191,15 @@ def step_impl(context: MinifiTestContext, max_increase: str, duration: str): def step_impl(context: MinifiTestContext, content: str, directory: str, duration: str): timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().directory_contains_file_with_json_content(directory, content), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].directory_contains_file_with_json_content(directory, content), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) @then('after a wait of {duration}, at least {lower_bound:d} and at most {upper_bound:d} files are produced and placed in the "{directory}" directory') def step_impl(context, lower_bound, upper_bound, duration, directory): duration_seconds = humanfriendly.parse_timespan(duration) - assert check_condition_after_wait(condition=lambda: context.get_default_minifi_container().get_number_of_files(directory) >= lower_bound - and context.get_default_minifi_container().get_number_of_files(directory) <= upper_bound, + assert check_condition_after_wait(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) >= lower_bound + and context.containers[DEFAULT_MINIFI_CONTAINER_NAME].get_number_of_files(directory) <= upper_bound, context=context, wait_time=duration_seconds) @@ -205,7 +210,7 @@ def step_impl(context, directory, duration, contents): return contents_arr = contents.split(",") timeout_in_seconds = humanfriendly.parse_timespan(duration) - assert wait_for_condition(condition=lambda: context.get_default_minifi_container().verify_file_contents(directory, contents_arr), + assert wait_for_condition(condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].verify_file_contents(directory, contents_arr), timeout_seconds=timeout_in_seconds, bail_condition=lambda: False, context=context) @@ -219,5 +224,28 @@ def step_impl(context, directory, duration): def step_impl(context, directory, duration): timeout_in_seconds = humanfriendly.parse_timespan(duration) assert wait_for_condition( - condition=lambda: context.get_default_minifi_container().directory_contains_empty_file(directory), - timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.get_default_minifi_container().exited, context=context) + condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].directory_contains_empty_file(directory), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[DEFAULT_MINIFI_CONTAINER_NAME].exited, context=context) + + +@then("in the \"{container_name}\" container at least one empty file is placed in the \"{directory}\" directory in less than {duration}") +def step_impl(context, container_name, directory, duration): + timeout_in_seconds = humanfriendly.parse_timespan(duration) + assert wait_for_condition( + condition=lambda: context.containers[container_name].directory_contains_empty_file(directory), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[container_name].exited, context=context) + + +@then("in the \"{container_name}\" container at least one file with minimum size of \"{size}\" is placed in the \"{directory}\" directory in less than {duration}") +def step_impl(context, container_name: str, directory: str, size: str, duration: str): + timeout_in_seconds = humanfriendly.parse_timespan(duration) + size_in_bytes = humanfriendly.parse_size(size) + assert wait_for_condition( + condition=lambda: context.containers[container_name].directory_contains_file_with_minimum_size(directory, size_in_bytes), + timeout_seconds=timeout_in_seconds, bail_condition=lambda: context.containers[container_name].exited, context=context) + + +@then("at least one file with minimum size of \"{size}\" is placed in the \"{directory}\" directory in less than {duration}") +def step_impl(context, directory: str, size: str, duration: str): + context.execute_steps( + f'Then in the "{DEFAULT_MINIFI_CONTAINER_NAME}" container at least one file with minimum size of "{size}" is placed in the "{directory}" directory in less than {duration}') diff --git a/behave_framework/src/minifi_test_framework/steps/core_steps.py b/behave_framework/src/minifi_test_framework/steps/core_steps.py index eb4050ab6..836b58834 100644 --- a/behave_framework/src/minifi_test_framework/steps/core_steps.py +++ b/behave_framework/src/minifi_test_framework/steps/core_steps.py @@ -25,6 +25,8 @@ import uuid import humanfriendly from behave import when, step, given +from minifi_test_framework.containers.http_proxy_container import HttpProxy +from minifi_test_framework.containers.nifi_container import NifiContainer from minifi_test_framework.containers.directory import Directory from minifi_test_framework.containers.file import File from minifi_test_framework.core.minifi_test_context import DEFAULT_MINIFI_CONTAINER_NAME, MinifiTestContext @@ -100,3 +102,13 @@ def step_impl(context: MinifiTestContext): @given("OpenSSL FIPS mode is enabled in MiNiFi") def step_impl(context): context.get_or_create_default_minifi_container().enable_openssl_fips_mode() + + +@step("the http proxy server is set up") +def step_impl(context): + context.containers["http-proxy"] = HttpProxy(context) + + +@step("a NiFi container is set up") +def step_impl(context): + context.containers["nifi"] = NifiContainer(context) diff --git a/behave_framework/src/minifi_test_framework/steps/flow_building_steps.py b/behave_framework/src/minifi_test_framework/steps/flow_building_steps.py index d780673a4..474dd5a0a 100644 --- a/behave_framework/src/minifi_test_framework/steps/flow_building_steps.py +++ b/behave_framework/src/minifi_test_framework/steps/flow_building_steps.py @@ -20,7 +20,6 @@ import uuid from behave import given, step from minifi_test_framework.containers.directory import Directory -from minifi_test_framework.containers.http_proxy_container import HttpProxy from minifi_test_framework.core.minifi_test_context import DEFAULT_MINIFI_CONTAINER_NAME, MinifiTestContext from minifi_test_framework.minifi.connection import Connection from minifi_test_framework.minifi.controller_service import ControllerService @@ -64,6 +63,13 @@ def step_impl(context: MinifiTestContext, processor_type: str, property_name: st context.get_or_create_minifi_container(minifi_container_name).flow_definition.add_processor(processor) +@step('a {processor_type} processor with the "{property_name}" property set to "{property_value}" in the NiFi flow') +def step_impl(context: MinifiTestContext, processor_type: str, property_name: str, property_value: str): + processor = Processor(processor_type, processor_type) + processor.add_property(property_name, property_value) + context.containers["nifi"].flow_definition.add_processor(processor) + + @given('a {processor_type} processor with the name "{processor_name}"') def step_impl(context: MinifiTestContext, processor_type: str, processor_name: str): processor = Processor(processor_type, processor_name) @@ -76,20 +82,41 @@ def step_impl(context: MinifiTestContext, processor_type: str, minifi_container_ context.get_or_create_minifi_container(minifi_container_name).flow_definition.add_processor(processor) +@given("a {processor_type} processor in the NiFi flow") +def step_impl(context: MinifiTestContext, processor_type: str): + processor = Processor(processor_type, processor_type) + context.containers["nifi"].flow_definition.add_processor(processor) + + @given("a {processor_type} processor") def step_impl(context: MinifiTestContext, processor_type: str): context.execute_steps(f'given a {processor_type} processor in the "{DEFAULT_MINIFI_CONTAINER_NAME}" flow') -@step('the "{property_name}" property of the {processor_name} processor is set to "{property_value}"') +@given('the "{property_name}" property of the {processor_name} processor is set to "{property_value}" in the "{minifi_container_name}" flow') +def step_impl(context: MinifiTestContext, property_name: str, processor_name: str, property_value: str, minifi_container_name: str): + processor = context.get_or_create_minifi_container(minifi_container_name).flow_definition.get_processor(processor_name) + if property_value == "(not set)": + processor.remove_property(property_name) + else: + processor.add_property(property_name, property_value) + + +@given('the "{property_name}" property of the {processor_name} processor is set to "{property_value}" in the NiFi flow') def step_impl(context: MinifiTestContext, property_name: str, processor_name: str, property_value: str): - processor = context.get_or_create_default_minifi_container().flow_definition.get_processor(processor_name) + processor = context.containers["nifi"].flow_definition.get_processor(processor_name) if property_value == "(not set)": processor.remove_property(property_name) else: processor.add_property(property_name, property_value) +@given('the "{property_name}" property of the {processor_name} processor is set to "{property_value}"') +def step_impl(context: MinifiTestContext, property_name: str, processor_name: str, property_value: str): + context.execute_steps( + f'given the "{property_name}" property of the {processor_name} processor is set to "{property_value}" in the "{DEFAULT_MINIFI_CONTAINER_NAME}" flow') + + @step('the "{property_name}" property of the {controller_name} controller service is set to "{property_value}"') def step_impl(context: MinifiTestContext, property_name: str, controller_name: str, property_value: str): controller_service = context.get_or_create_default_minifi_container().flow_definition.get_controller_service(controller_name) @@ -110,6 +137,12 @@ def step_impl(context: MinifiTestContext, relationship_name: str, source: str, t context.get_or_create_minifi_container(minifi_container_name).flow_definition.add_connection(connection) +@step('in the NiFi flow the "{relationship_name}" relationship of the {source} processor is connected to the {target}') +def step_impl(context: MinifiTestContext, relationship_name: str, source: str, target: str): + connection = Connection(source_name=source, source_relationship=relationship_name, target_name=target) + context.containers["nifi"].flow_definition.add_connection(connection) + + @step('the "{relationship_name}" relationship of the {source} processor is connected to the {target}') def step_impl(context: MinifiTestContext, relationship_name: str, source: str, target: str): context.execute_steps(f'given in the "{DEFAULT_MINIFI_CONTAINER_NAME}" flow the "{relationship_name}" relationship of the {source} processor is connected to the {target}') @@ -127,6 +160,11 @@ def step_impl(context: MinifiTestContext, processor_name: str, relationship: str relationship) +@step("{processor_name}'s {relationship} relationship is auto-terminated in the NiFi flow") +def step_impl(context: MinifiTestContext, processor_name: str, relationship: str): + context.containers["nifi"].flow_definition.get_processor(processor_name).auto_terminated_relationships.append(relationship) + + @step("{processor_name}'s {relationship} relationship is auto-terminated") def step_impl(context: MinifiTestContext, processor_name: str, relationship: str): context.execute_steps(f'given {processor_name}\'s {relationship} relationship is auto-terminated in the "{DEFAULT_MINIFI_CONTAINER_NAME}" flow') @@ -137,6 +175,16 @@ def step_impl(context: MinifiTestContext): context.get_or_create_default_minifi_container().command = ["/bin/sh", "-c", "timeout 10s ./bin/minifi.sh run && sleep 100"] +@step('the scheduling period of the {processor_name} processor is set to "{duration_str}" in the "{minifi_container_name}" flow') +def step_impl(context: MinifiTestContext, processor_name: str, duration_str: str, minifi_container_name: str): + context.get_or_create_minifi_container(minifi_container_name).flow_definition.get_processor(processor_name).scheduling_period = duration_str + + +@step('the scheduling period of the {processor_name} processor is set to "{duration_str}" in the NiFi flow') +def step_impl(context: MinifiTestContext, processor_name: str, duration_str: str, minifi_container_name: str): + context.containers["nifi"].flow_definition.get_processor(processor_name).scheduling_period = duration_str + + @step('the scheduling period of the {processor_name} processor is set to "{duration_str}"') def step_impl(context: MinifiTestContext, processor_name: str, duration_str: str): context.get_or_create_default_minifi_container().flow_definition.get_processor(processor_name).scheduling_period = duration_str @@ -191,11 +239,6 @@ def step_impl(context: MinifiTestContext): processor.add_property(row["property name"], row["property value"]) -@step("the http proxy server is set up") -def step_impl(context): - context.containers["http-proxy"] = HttpProxy(context) - - @step("the processors are connected up as described here") def step_impl(context: MinifiTestContext): for row in context.table: @@ -270,14 +313,17 @@ def step_impl(context, property_name, processor_name_one, processor_name_two): # TLS -def add_ssl_context_service_for_minifi(context: MinifiTestContext, cert_name: str): +def add_ssl_context_service_for_minifi(context: MinifiTestContext, cert_name: str, use_system_cert_store: bool = False): ssl_context_service = context.get_or_create_default_minifi_container().flow_definition.get_controller_service("SSLContextService") if ssl_context_service is not None: return controller_service = ControllerService(class_name="SSLContextService", service_name="SSLContextService") controller_service.add_property("Client Certificate", f"/tmp/resources/{cert_name}.crt") controller_service.add_property("Private Key", f"/tmp/resources/{cert_name}.key") - controller_service.add_property("CA Certificate", "/tmp/resources/root_ca.crt") + if use_system_cert_store: + controller_service.add_property("Use System Cert Store", "true") + else: + controller_service.add_property("CA Certificate", "/tmp/resources/root_ca.crt") context.get_or_create_default_minifi_container().flow_definition.controller_services.append(controller_service) @@ -292,3 +338,10 @@ def step_impl(context, processor_name): add_ssl_context_service_for_minifi(context, "minifi_client") processor = context.get_or_create_default_minifi_container().flow_definition.get_processor(processor_name) processor.add_property('SSL Context Service', 'SSLContextService') + + +@given("an ssl context service using the system CA cert store is set up for {processor_name}") +def step_impl(context: MinifiTestContext, processor_name): + add_ssl_context_service_for_minifi(context, "minifi_client", use_system_cert_store=True) + processor = context.get_or_create_default_minifi_container().flow_definition.get_processor(processor_name) + processor.add_property('SSL Context Service', 'SSLContextService') diff --git a/docker/RunBehaveTests.sh b/docker/RunBehaveTests.sh index b48bbf60c..dff7fccc9 100755 --- a/docker/RunBehaveTests.sh +++ b/docker/RunBehaveTests.sh @@ -206,4 +206,5 @@ exec \ "${docker_dir}/../extensions/splunk/tests/features" \ "${docker_dir}/../extensions/gcp/tests/features" \ "${docker_dir}/../extensions/grafana-loki/tests/features" \ - "${docker_dir}/../extensions/lua/tests/features/" + "${docker_dir}/../extensions/lua/tests/features/" \ + "${docker_dir}/../extensions/civetweb/tests/features/" diff --git a/extensions/civetweb/tests/features/environment.py b/extensions/civetweb/tests/features/environment.py new file mode 100644 index 000000000..d6c856340 --- /dev/null +++ b/extensions/civetweb/tests/features/environment.py @@ -0,0 +1,25 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from minifi_test_framework.core.hooks import common_before_scenario +from minifi_test_framework.core.hooks import common_after_scenario + + +def before_scenario(context, scenario): + common_before_scenario(context, scenario) + + +def after_scenario(context, scenario): + common_after_scenario(context, scenario) diff --git a/docker/test/integration/features/http.feature b/extensions/civetweb/tests/features/http.feature similarity index 61% rename from docker/test/integration/features/http.feature rename to extensions/civetweb/tests/features/http.feature index 19a9d3745..395d068e6 100644 --- a/docker/test/integration/features/http.feature +++ b/extensions/civetweb/tests/features/http.feature @@ -19,134 +19,164 @@ Feature: Sending data using InvokeHTTP to a receiver using ListenHTTP As a user of MiNiFi I need to have ListenHTTP and InvokeHTTP processors - Background: - Given the content of "/tmp/output" is monitored - Scenario: A MiNiFi instance transfers data to another MiNiFi instance with message body Given a GetFile processor with the "Input Directory" property set to "/tmp/input" And the "Keep Source File" property of the GetFile processor is set to "true" And a file with the content "test" is present in "/tmp/input" - And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${feature_id}:8080/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${scenario_id}:8080/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "success" relationship of the GetFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "secondary" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "secondary" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "secondary" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "secondary" flow + And in the "secondary" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "secondary" flow When both instances start up - Then at least one flowfile with the content "test" is placed in the monitored directory in less than 60 seconds + Then in the "secondary" container at least one file with the content "test" is placed in the "/tmp/output" directory in less than 60 seconds Scenario: A MiNiFi instance sends data through a HTTP proxy and another one listens - Given a GetFile processor with the "Input Directory" property set to "/tmp/input" + Given the http proxy server is set up + And a GetFile processor with the "Input Directory" property set to "/tmp/input" And the "Keep Source File" property of the GetFile processor is set to "true" And a file with the content "test" is present in "/tmp/input" - And a InvokeHTTP processor with the "Remote URL" property set to "http://minifi-listen-${feature_id}:8080/contentListener" - And these processor properties are set to match the http proxy: + And a InvokeHTTP processor with the "Remote URL" property set to "http://minifi-listen-${scenario_id}:8080/contentListener" + And InvokeHTTP is EVENT_DRIVEN + And these processor properties are set | processor name | property name | property value | | InvokeHTTP | HTTP Method | POST | - | InvokeHTTP | Proxy Host | http-proxy-${feature_id} | + | InvokeHTTP | Proxy Host | http-proxy-${scenario_id} | | InvokeHTTP | Proxy Port | 3128 | | InvokeHTTP | invokehttp-proxy-username | admin | | InvokeHTTP | invokehttp-proxy-password | test101 | And the "success" relationship of the GetFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a http proxy server is set up accordingly - - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "minifi-listen" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "minifi-listen" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "minifi-listen" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "minifi-listen" flow + And in the "minifi-listen" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "minifi-listen" flow When all instances start up - Then at least one flowfile with the content "test" is placed in the monitored directory in less than 60 seconds - And no errors were generated on the http-proxy regarding "http://minifi-listen-${feature_id}:8080/contentListener" + Then in the "minifi-listen" container at least one file with the content "test" is placed in the "/tmp/output" directory in less than 60 seconds + And no errors were generated on the http-proxy regarding "http://minifi-listen-${scenario_id}:8080/contentListener" Scenario: A MiNiFi instance and transfers hashed data to another MiNiFi instance Given a GetFile processor with the "Input Directory" property set to "/tmp/input" And the "Keep Source File" property of the GetFile processor is set to "true" And a file with the content "test" is present in "/tmp/input" And a HashContent processor with the "Hash Attribute" property set to "hash" - And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${feature_id}:8080/contentListener" + And HashContent is EVENT_DRIVEN + And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${scenario_id}:8080/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "success" relationship of the GetFile processor is connected to the HashContent And the "success" relationship of the HashContent processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "secondary" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "secondary" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "secondary" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "secondary" flow + And in the "secondary" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "secondary" flow When both instances start up - Then at least one flowfile with the content "test" is placed in the monitored directory in less than 60 seconds + Then in the "secondary" container at least one file with the content "test" is placed in the "/tmp/output" directory in less than 60 seconds Scenario: A MiNiFi instance transfers data to another MiNiFi instance without message body Given a GetFile processor with the "Input Directory" property set to "/tmp/input" And the "Keep Source File" property of the GetFile processor is set to "true" And a file with the content "test" is present in "/tmp/input" - And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${feature_id}:8080/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${scenario_id}:8080/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "Send Message Body" property of the InvokeHTTP processor is set to "false" And the "success" relationship of the GetFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "secondary" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "secondary" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "secondary" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "secondary" flow + And in the "secondary" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "secondary" flow When both instances start up - Then at least one empty flowfile is placed in the monitored directory in less than 60 seconds + Then in the "secondary" container at least one empty file is placed in the "/tmp/output" directory in less than 60 seconds Scenario: A MiNiFi instance transfers data to a NiFi instance with message body Given a GetFile processor with the "Input Directory" property set to "/tmp/input" And the "Keep Source File" property of the GetFile processor is set to "true" And a file with the content "test" is present in "/tmp/input" - And a InvokeHTTP processor with the "Remote URL" property set to "http://nifi-${feature_id}:8081/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "http://nifi-${scenario_id}:8081/contentListener" And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "success" relationship of the GetFile processor is connected to the InvokeHTTP - And a NiFi flow with the name "nifi" is set up - And a ListenHTTP processor with the "Listening Port" property set to "8081" in the "nifi" flow - And a PutFile processor with the "Directory" property set to "/tmp/output" in the "nifi" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And a NiFi container is set up + And a ListenHTTP processor with the "Listening Port" property set to "8081" in the NiFi flow + And a PutFile processor with the "Directory" property set to "/tmp/output" in the NiFi flow + And in the NiFi flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the NiFi flow + And PutFile's failure relationship is auto-terminated in the NiFi flow When both instances start up - Then at least one flowfile with the content "test" is placed in the monitored directory in less than 60 seconds + Then in the "nifi" container at least one empty file is placed in the "/tmp/output" directory in less than 60 seconds Scenario: A MiNiFi instance transfers data to another MiNiFi instance with message body and limited speed Given a GenerateFlowFile processor with the "File Size" property set to "10 MB" And the scheduling period of the GenerateFlowFile processor is set to "30 sec" - And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${feature_id}:8080/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${scenario_id}:8080/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "Connection Timeout" property of the InvokeHTTP processor is set to "30 s" And the "Read Timeout" property of the InvokeHTTP processor is set to "30 s" And the "Upload Speed Limit" property of the InvokeHTTP processor is set to "800 KB/s" And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "secondary" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "secondary" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "secondary" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "secondary" flow + And in the "secondary" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "secondary" flow When both instances start up - Then at least one flowfile with minimum size of "1 MB" is placed in the monitored directory in less than 60 seconds + Then in the "secondary" container at least one file with minimum size of "1 MB" is placed in the "/tmp/output" directory in less than 60 seconds And the Minifi logs contain the following message: "[warning] InvokeHTTP::onTrigger has been running for" in less than 10 seconds Scenario: A MiNiFi instance retrieves data from another MiNiFi instance with message body and limited speed - Given a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${feature_id}:8080/contentListener&testfile" + Given a InvokeHTTP processor with the "Remote URL" property set to "http://secondary-${scenario_id}:8080/contentListener&testfile" And the scheduling period of the InvokeHTTP processor is set to "3 sec" And the "HTTP Method" property of the InvokeHTTP processor is set to "GET" And the "Connection Timeout" property of the InvokeHTTP processor is set to "30 s" And the "Read Timeout" property of the InvokeHTTP processor is set to "30 s" And the "Download Speed Limit" property of the InvokeHTTP processor is set to "800 KB/s" + And the "Data Format" property of the InvokeHTTP processor is set to "Text" And a PutFile processor with the "Directory" property set to "/tmp/output" + And PutFile is EVENT_DRIVEN And the "response" relationship of the InvokeHTTP processor is connected to the PutFile + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated And a GenerateFlowFile processor with the "File Size" property set to "10 MB" in the "secondary" flow - And the "Data Format" property of the InvokeHTTP processor is set to "Text" And a UpdateAttribute processor with the "http.type" property set to "response_body" in the "secondary" flow - And the "filename" property of the UpdateAttribute processor is set to "testfile" - And the scheduling period of the GenerateFlowFile processor is set to "30 sec" - And a ListenHTTP processor with the "Listening Port" property set to "8080" in a "secondary" flow - And the "success" relationship of the GenerateFlowFile processor is connected to the UpdateAttribute - And the "success" relationship of the UpdateAttribute processor is connected to the ListenHTTP + And UpdateAttribute is EVENT_DRIVEN in the "secondary" flow + And the "filename" property of the UpdateAttribute processor is set to "testfile" in the "secondary" flow + And the scheduling period of the GenerateFlowFile processor is set to "30 sec" in the "secondary" flow + And a ListenHTTP processor with the "Listening Port" property set to "8080" in the "secondary" flow + And ListenHTTP is EVENT_DRIVEN in the "secondary" flow + And in the "secondary" flow the "success" relationship of the GenerateFlowFile processor is connected to the UpdateAttribute + And in the "secondary" flow the "success" relationship of the UpdateAttribute processor is connected to the ListenHTTP + And ListenHTTP's success relationship is auto-terminated in the "secondary" flow When both instances start up - Then at least one flowfile with minimum size of "10 MB" is placed in the monitored directory in less than 60 seconds + Then at least one file with minimum size of "10 MB" is placed in the "/tmp/output" directory in less than 60 seconds And the Minifi logs contain the following message: "[warning] InvokeHTTP::onTrigger has been running for" in less than 10 seconds diff --git a/docker/test/integration/features/https.feature b/extensions/civetweb/tests/features/https.feature similarity index 61% rename from docker/test/integration/features/https.feature rename to extensions/civetweb/tests/features/https.feature index 863cc782e..b3d4eb6bd 100644 --- a/docker/test/integration/features/https.feature +++ b/extensions/civetweb/tests/features/https.feature @@ -16,135 +16,155 @@ @ENABLE_CIVET Feature: Transfer data from and to MiNiFi using HTTPS - Background: - Given the content of "/tmp/output" is monitored - - Scenario: InvokeHTTP to ListenHTTP without an SSLContextService works (no mutual TLS in this case) Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "Lorem ipsum dolor sit amet" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "server" flow + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then a flowfile with the content "Lorem ipsum dolor sit amet" is placed in the monitored directory in less than 10s - + Then in the "server" container at least one file with the content "Lorem ipsum dolor sit amet" is placed in the "/tmp/output" directory in less than 10 seconds Scenario: InvokeHTTP to ListenHTTP without an SSLContextService requires a server cert signed by a CA Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "consectetur adipiscing elit" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then no files are placed in the monitored directory in 10s of running time - + Then in the "server" container no files are placed in the "/tmp/output" directory in 10s of running time Scenario: InvokeHTTP to ListenHTTP with mutual TLS, using certificate files Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "ut labore et dolore magna aliqua" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And an ssl context service with a manual CA cert file is set up for InvokeHTTP And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" - And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" - And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" in the "server" flow + And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" in the "server" flow + And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "server" flow + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then a flowfile with the content "ut labore et dolore magna aliqua" is placed in the monitored directory in less than 10s - + Then in the "server" container at least one file with the content "ut labore et dolore magna aliqua" is placed in the "/tmp/output" directory in less than 10s Scenario: InvokeHTTP to ListenHTTP without mutual TLS, using the system certificate store Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "Ut enim ad minim veniam" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And an ssl context service using the system CA cert store is set up for InvokeHTTP And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then a flowfile with the content "Ut enim ad minim veniam" is placed in the monitored directory in less than 10s - + Then in the "server" container at least one file with the content "Ut enim ad minim veniam" is placed in the "/tmp/output" directory in less than 10s Scenario: InvokeHTTP to ListenHTTP with mutual TLS, using the system certificate store Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "quis nostrud exercitation ullamco laboris nisi" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" + And InvokeHTTP is EVENT_DRIVEN And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And an ssl context service using the system CA cert store is set up for InvokeHTTP And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" - And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" - And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/minifi_server.crt" in the "server" flow + And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" in the "server" flow + And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "server" flow + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then a flowfile with the content "quis nostrud exercitation ullamco laboris nisi" is placed in the monitored directory in less than 10s - + Then in the "server" container at least one file with the content "quis nostrud exercitation ullamco laboris nisi" is placed in the "/tmp/output" directory in less than 10s Scenario: InvokeHTTP to ListenHTTP without mutual TLS, using the system certificate store, requires a server cert signed by a CA Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "ut aliquip ex ea commodo consequat" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And an ssl context service using the system CA cert store is set up for InvokeHTTP And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then no files are placed in the monitored directory in 10s of running time - + Then in the "server" container no files are placed in the "/tmp/output" directory in 10s of running time Scenario: InvokeHTTP to ListenHTTP with mutual TLS, using the system certificate store, requires a server cert signed by a CA Given a GenerateFlowFile processor with the "Data Format" property set to "Text" And the "Unique FlowFiles" property of the GenerateFlowFile processor is set to "false" And the "Custom Text" property of the GenerateFlowFile processor is set to "Duis aute irure dolor in reprehenderit in voluptate" - And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${feature_id}:4430/contentListener" + And a InvokeHTTP processor with the "Remote URL" property set to "https://server-${scenario_id}:4430/contentListener" And the "HTTP Method" property of the InvokeHTTP processor is set to "POST" And an ssl context service using the system CA cert store is set up for InvokeHTTP And the "success" relationship of the GenerateFlowFile processor is connected to the InvokeHTTP + And InvokeHTTP's success relationship is auto-terminated + And InvokeHTTP's response relationship is auto-terminated - And a ListenHTTP processor with the "Listening Port" property set to "4430" in a "server" flow - And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" - And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" - And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" + And a ListenHTTP processor with the "Listening Port" property set to "4430" in the "server" flow + And the "SSL Certificate" property of the ListenHTTP processor is set to "/tmp/resources/self_signed_server.crt" in the "server" flow + And the "SSL Certificate Authority" property of the ListenHTTP processor is set to "/usr/local/share/certs/ca-root-nss.crt" in the "server" flow + And the "SSL Verify Peer" property of the ListenHTTP processor is set to "yes" in the "server" flow And a PutFile processor with the "Directory" property set to "/tmp/output" in the "server" flow - And the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile is EVENT_DRIVEN in the "server" flow + And in the "server" flow the "success" relationship of the ListenHTTP processor is connected to the PutFile + And PutFile's success relationship is auto-terminated in the "server" flow When both instances start up - Then no files are placed in the monitored directory in 10s of running time + Then in the "server" container no files are placed in the "/tmp/output" directory in 10s of running time diff --git a/extensions/civetweb/tests/features/steps/steps.py b/extensions/civetweb/tests/features/steps/steps.py new file mode 100644 index 000000000..90304a7f8 --- /dev/null +++ b/extensions/civetweb/tests/features/steps/steps.py @@ -0,0 +1,19 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +from minifi_test_framework.steps import checking_steps # noqa: F401 +from minifi_test_framework.steps import configuration_steps # noqa: F401 +from minifi_test_framework.steps import core_steps # noqa: F401 +from minifi_test_framework.steps import flow_building_steps # noqa: F401
