diff --git a/behave_framework/src/minifi_test_framework/containers/docker_image_builder.py b/behave_framework/src/minifi_test_framework/containers/docker_image_builder.py index 658ccedb97..1e5d5e602f 100644 --- a/behave_framework/src/minifi_test_framework/containers/docker_image_builder.py +++ b/behave_framework/src/minifi_test_framework/containers/docker_image_builder.py @@ -24,7 +24,7 @@ class DockerImageBuilder: - def __init__(self, image_tag: str, dockerfile_content: str | None = None, build_context_path: str | None = None): + def __init__(self, image_tag: str, dockerfile_content: str | None = None, files_on_context: dict[str, bytes] | None = None, build_context_path: str | None = None): if not dockerfile_content and not build_context_path: raise ValueError("Either 'dockerfile_content' or 'build_context_path' must be provided.") if dockerfile_content and build_context_path: @@ -32,6 +32,7 @@ def __init__(self, image_tag: str, dockerfile_content: str | None = None, build_ self.image_tag: str = image_tag self.dockerfile_content: str | None = dockerfile_content + self.files_on_context: dict[str, str] | None = files_on_context self.build_context_path: str | None = build_context_path self.client = docker.from_env() self.image: Image | None = None @@ -46,6 +47,12 @@ def build(self) -> Image: with open(dockerfile_path, 'w') as f: f.write(self.dockerfile_content) + if self.files_on_context: + for filename, content in self.files_on_context.items(): + file_path = os.path.join(context_path, filename) + with open(file_path, 'wb') as f: + f.write(content) + logging.info(f"Building Docker image '{self.image_tag}' from context '{context_path}'...") try: self.image, build_logs = self.client.images.build(path=context_path, tag=self.image_tag, rm=True, forcerm=True) diff --git a/docker/RunBehaveTests.sh b/docker/RunBehaveTests.sh index 4a6e9b1820..65b5d43d5e 100755 --- a/docker/RunBehaveTests.sh +++ b/docker/RunBehaveTests.sh @@ -204,4 +204,5 @@ exec \ "${docker_dir}/../extensions/couchbase/tests/features" \ "${docker_dir}/../extensions/elasticsearch/tests/features" \ "${docker_dir}/../extensions/splunk/tests/features" \ - "${docker_dir}/../extensions/gcp/tests/features" + "${docker_dir}/../extensions/gcp/tests/features" \ + "${docker_dir}/../extensions/grafana-loki/tests/features" diff --git a/docker/test/integration/cluster/ContainerStore.py b/docker/test/integration/cluster/ContainerStore.py index d7cb787c35..10b093dee4 100644 --- a/docker/test/integration/cluster/ContainerStore.py +++ b/docker/test/integration/cluster/ContainerStore.py @@ -29,9 +29,6 @@ from .containers.MinifiAsPodInKubernetesCluster import MinifiAsPodInKubernetesCluster from .containers.PrometheusContainer import PrometheusContainer from .containers.MinifiC2ServerContainer import MinifiC2ServerContainer -from .containers.GrafanaLokiContainer import GrafanaLokiContainer -from .containers.GrafanaLokiContainer import GrafanaLokiOptions -from .containers.ReverseProxyContainer import ReverseProxyContainer from .FeatureContext import FeatureContext @@ -39,7 +36,6 @@ class ContainerStore: def __init__(self, network, image_store, kubernetes_proxy, feature_id): self.feature_id = feature_id self.minifi_options = MinifiOptions() - self.grafana_loki_options = GrafanaLokiOptions() self.containers = {} self.data_directories = {} self.network = network @@ -212,23 +208,6 @@ def acquire_container(self, context, container_name: str, engine='minifi-cpp', c image_store=self.image_store, command=command, ssl=True)) - elif engine == "grafana-loki-server": - return self.containers.setdefault(container_name, - GrafanaLokiContainer(feature_context=feature_context, - name=container_name, - vols=self.vols, - network=self.network, - image_store=self.image_store, - options=self.grafana_loki_options, - command=command)) - elif engine == "reverse-proxy": - return self.containers.setdefault(container_name, - ReverseProxyContainer(feature_context=feature_context, - name=container_name, - vols=self.vols, - network=self.network, - image_store=self.image_store, - command=command)) else: raise Exception('invalid flow engine: \'%s\'' % engine) @@ -345,12 +324,6 @@ def get_app_log(self, container_name): def get_container_names(self, engine=None): return [key for key in self.containers.keys() if not engine or self.containers[key].get_engine() == engine] - def enable_ssl_in_grafana_loki(self): - self.grafana_loki_options.enable_ssl = True - - def enable_multi_tenancy_in_grafana_loki(self): - self.grafana_loki_options.enable_multi_tenancy = True - def enable_ssl_in_nifi(self): self.nifi_options.use_ssl = True diff --git a/docker/test/integration/cluster/DockerTestCluster.py b/docker/test/integration/cluster/DockerTestCluster.py index 8747084206..15714f5eae 100644 --- a/docker/test/integration/cluster/DockerTestCluster.py +++ b/docker/test/integration/cluster/DockerTestCluster.py @@ -20,7 +20,6 @@ import os import gzip import shutil -from typing import List from .LogSource import LogSource from .ContainerStore import ContainerStore @@ -30,7 +29,6 @@ from .checkers.AzureChecker import AzureChecker from .checkers.PostgresChecker import PostgresChecker from .checkers.PrometheusChecker import PrometheusChecker -from .checkers.GrafanaLokiChecker import GrafanaLokiChecker from .checkers.ModbusChecker import ModbusChecker from .checkers.MqttHelper import MqttHelper from utils import get_peak_memory_usage, get_minifi_pid, get_memory_usage, retry_check @@ -46,7 +44,6 @@ def __init__(self, context, feature_id): self.azure_checker = AzureChecker(self.container_communicator) self.postgres_checker = PostgresChecker(self.container_communicator) self.prometheus_checker = PrometheusChecker() - self.grafana_loki_checker = GrafanaLokiChecker() self.minifi_controller_executor = MinifiControllerExecutor(self.container_communicator) self.modbus_checker = ModbusChecker(self.container_communicator) self.mqtt_helper = MqttHelper() @@ -108,12 +105,6 @@ def disable_openssl_fips_mode_in_minifi(self): def enable_sql_in_minifi(self): self.container_store.enable_sql_in_minifi() - def enable_ssl_in_grafana_loki(self): - self.container_store.enable_ssl_in_grafana_loki() - - def enable_multi_tenancy_in_grafana_loki(self): - self.container_store.enable_multi_tenancy_in_grafana_loki() - def use_nifi_python_processors_with_system_python_packages_installed_in_minifi(self): self.container_store.use_nifi_python_processors_with_system_python_packages_installed_in_minifi() @@ -386,9 +377,6 @@ def debug_bundle_can_be_retrieved_through_minifi_controller(self, container_name return True - def wait_for_lines_on_grafana_loki(self, lines: List[str], timeout_seconds: int, ssl: bool, tenant_id: str): - return self.grafana_loki_checker.wait_for_lines_on_grafana_loki(lines, timeout_seconds, ssl, tenant_id) - def set_value_on_plc_with_modbus(self, container_name, modbus_cmd): return self.modbus_checker.set_value_on_plc_with_modbus(container_name, modbus_cmd) diff --git a/docker/test/integration/cluster/ImageStore.py b/docker/test/integration/cluster/ImageStore.py index 4e52f7deb8..360af8a7cc 100644 --- a/docker/test/integration/cluster/ImageStore.py +++ b/docker/test/integration/cluster/ImageStore.py @@ -67,8 +67,6 @@ def get_image(self, container_engine): image = self.__build_mqtt_broker_image() elif container_engine == "kinesis-server": image = self.__build_kinesis_image() - elif container_engine == "reverse-proxy": - image = self.__build_reverse_proxy_image() else: raise Exception("There is no associated image for " + container_engine) @@ -293,9 +291,6 @@ def __build_mqtt_broker_image(self): def __build_kinesis_image(self): return self.__build_image_by_path(self.test_dir + "/resources/kinesis-mock", 'kinesis-server') - def __build_reverse_proxy_image(self): - return self.__build_image_by_path(self.test_dir + "/resources/reverse-proxy", 'reverse-proxy') - def __build_image(self, dockerfile, context_files=[]): conf_dockerfile_buffer = BytesIO() docker_context_buffer = BytesIO() diff --git a/docker/test/integration/cluster/checkers/GrafanaLokiChecker.py b/docker/test/integration/cluster/checkers/GrafanaLokiChecker.py deleted file mode 100644 index da78f2a205..0000000000 --- a/docker/test/integration/cluster/checkers/GrafanaLokiChecker.py +++ /dev/null @@ -1,54 +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 requests -from typing import List -from utils import wait_for - - -class GrafanaLokiChecker: - def __init__(self): - self.url = "localhost:3100/loki/api/v1/query_range" - - def veify_log_lines_on_grafana_loki(self, lines: List[str], ssl: bool, tenant_id: str): - labels = '{job="minifi"}' - prefix = "http://" - if ssl: - prefix = "https://" - - query_url = f"{prefix}{self.url}?query={labels}" - - headers = None - if tenant_id: - headers = {'X-Scope-OrgID': tenant_id} - - response = requests.get(query_url, verify=False, timeout=30, headers=headers) - if response.status_code < 200 or response.status_code >= 300: - return False - - json_response = response.json() - if "data" not in json_response or "result" not in json_response["data"] or len(json_response["data"]["result"]) < 1: - return False - - result = json_response["data"]["result"][0] - if "values" not in result: - return False - - for line in lines: - if line not in str(result["values"]): - return False - return True - - def wait_for_lines_on_grafana_loki(self, lines: List[str], timeout_seconds: int, ssl: bool, tenant_id: str): - return wait_for(lambda: self.veify_log_lines_on_grafana_loki(lines, ssl, tenant_id), timeout_seconds) diff --git a/docker/test/integration/cluster/containers/GrafanaLokiContainer.py b/docker/test/integration/cluster/containers/GrafanaLokiContainer.py deleted file mode 100644 index a46cd2dbff..0000000000 --- a/docker/test/integration/cluster/containers/GrafanaLokiContainer.py +++ /dev/null @@ -1,176 +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 -import tempfile -import docker.types -import OpenSSL.crypto - -from .Container import Container -from ssl_utils.SSL_cert_utils import make_server_cert - - -class GrafanaLokiOptions: - def __init__(self): - self.enable_ssl = False - self.enable_multi_tenancy = False - - -class GrafanaLokiContainer(Container): - def __init__(self, feature_context, name, vols, network, image_store, options: GrafanaLokiOptions, command=None): - super().__init__(feature_context, name, "grafana-loki-server", vols, network, image_store, command) - self.ssl = options.enable_ssl - extra_ssl_settings = "" - if self.ssl: - grafana_loki_cert, grafana_loki_key = make_server_cert(f"grafana-loki-server-{feature_context.id}", feature_context.root_ca_cert, feature_context.root_ca_key) - - self.root_ca_file = tempfile.NamedTemporaryFile(delete=False) - self.root_ca_file.write(OpenSSL.crypto.dump_certificate(type=OpenSSL.crypto.FILETYPE_PEM, cert=feature_context.root_ca_cert)) - self.root_ca_file.close() - os.chmod(self.root_ca_file.name, 0o644) - - self.grafana_loki_cert_file = tempfile.NamedTemporaryFile(delete=False) - self.grafana_loki_cert_file.write(OpenSSL.crypto.dump_certificate(type=OpenSSL.crypto.FILETYPE_PEM, cert=grafana_loki_cert)) - self.grafana_loki_cert_file.close() - os.chmod(self.grafana_loki_cert_file.name, 0o644) - - self.grafana_loki_key_file = tempfile.NamedTemporaryFile(delete=False) - self.grafana_loki_key_file.write(OpenSSL.crypto.dump_privatekey(type=OpenSSL.crypto.FILETYPE_PEM, pkey=grafana_loki_key)) - self.grafana_loki_key_file.close() - os.chmod(self.grafana_loki_key_file.name, 0o644) - - extra_ssl_settings = """ - http_tls_config: - cert_file: /etc/loki/cert.pem - key_file: /etc/loki/key.pem - client_ca_file: /etc/loki/root_ca.crt - client_auth_type: VerifyClientCertIfGiven - - grpc_tls_config: - cert_file: /etc/loki/cert.pem - key_file: /etc/loki/key.pem - client_ca_file: /etc/loki/root_ca.crt - client_auth_type: VerifyClientCertIfGiven - -query_scheduler: - grpc_client_config: - grpc_compression: snappy - tls_enabled: true - tls_ca_path: /etc/loki/root_ca.crt - tls_insecure_skip_verify: true - -ingester_client: - grpc_client_config: - grpc_compression: snappy - tls_enabled: true - tls_ca_path: /etc/loki/root_ca.crt - tls_insecure_skip_verify: true - -frontend: - grpc_client_config: - grpc_compression: snappy - tls_enabled: true - tls_ca_path: /etc/loki/root_ca.crt - tls_insecure_skip_verify: true - -frontend_worker: - grpc_client_config: - grpc_compression: snappy - tls_enabled: true - tls_ca_path: /etc/loki/root_ca.crt - tls_insecure_skip_verify: true -""" - - grafana_loki_yml_content = """ -auth_enabled: {enable_multi_tenancy} - -server: - http_listen_port: 3100 - grpc_listen_port: 9095 -{extra_ssl_settings} - -common: - path_prefix: /loki - storage: - filesystem: - chunks_directory: /loki/chunks - rules_directory: /loki/rules - replication_factor: 1 - ring: - kvstore: - store: inmemory - -schema_config: - configs: - - from: 2020-05-15 - store: tsdb - object_store: filesystem - schema: v13 - index: - prefix: index_ - period: 24h - -ruler: - alertmanager_url: http://localhost:9093 - -analytics: - reporting_enabled: false -""".format(extra_ssl_settings=extra_ssl_settings, enable_multi_tenancy=options.enable_multi_tenancy) - - self.yaml_file = tempfile.NamedTemporaryFile(delete=False) - self.yaml_file.write(grafana_loki_yml_content.encode()) - self.yaml_file.close() - os.chmod(self.yaml_file.name, 0o644) - - def get_startup_finished_log_entry(self): - return "Loki started" - - def deploy(self): - if not self.set_deployed(): - return - - logging.info('Creating and running Grafana Loki docker container...') - - mounts = [docker.types.Mount( - type='bind', - source=self.yaml_file.name, - target='/etc/loki/local-config.yaml' - )] - - if self.ssl: - mounts.append(docker.types.Mount( - type='bind', - source=self.root_ca_file.name, - target='/etc/loki/root_ca.crt' - )) - mounts.append(docker.types.Mount( - type='bind', - source=self.grafana_loki_cert_file.name, - target='/etc/loki/cert.pem' - )) - mounts.append(docker.types.Mount( - type='bind', - source=self.grafana_loki_key_file.name, - target='/etc/loki/key.pem' - )) - - self.client.containers.run( - image="grafana/loki:3.2.1", - detach=True, - name=self.name, - network=self.network.name, - ports={'3100/tcp': 3100}, - mounts=mounts, - entrypoint=self.command) diff --git a/docker/test/integration/cluster/containers/ReverseProxyContainer.py b/docker/test/integration/cluster/containers/ReverseProxyContainer.py deleted file mode 100644 index 3cd4863438..0000000000 --- a/docker/test/integration/cluster/containers/ReverseProxyContainer.py +++ /dev/null @@ -1,43 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one or more -# contributor license agreements. See the NOTICE file distributed with -# this work for additional information regarding copyright ownership. -# The ASF licenses this file to You under the Apache License, Version 2.0 -# (the "License"); you may not use this file except in compliance with -# the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. -import logging -from .Container import Container - - -class ReverseProxyContainer(Container): - def __init__(self, feature_context, name, vols, network, image_store, command=None): - super().__init__(feature_context, name, 'reverse-proxy', vols, network, image_store, command) - - def get_startup_finished_log_entry(self): - return "start worker process" - - def deploy(self): - if not self.set_deployed(): - return - - logging.info('Creating and running reverse-proxy docker container...') - self.client.containers.run( - self.image_store.get_image(self.get_engine()), - detach=True, - name=self.name, - network=self.network.name, - environment=[ - "BASIC_USERNAME=admin", - "BASIC_PASSWORD=password", - f"FORWARD_HOST=grafana-loki-server-{self.feature_context.id}", - "FORWARD_PORT=3100", - ], - entrypoint=self.command) - logging.info('Added container \'%s\'', self.name) diff --git a/docker/test/integration/features/MiNiFi_integration_test_driver.py b/docker/test/integration/features/MiNiFi_integration_test_driver.py index 9373c9b99e..30c3d7f721 100644 --- a/docker/test/integration/features/MiNiFi_integration_test_driver.py +++ b/docker/test/integration/features/MiNiFi_integration_test_driver.py @@ -17,7 +17,6 @@ import time import uuid -from typing import List from pydoc import locate from minifi.core.InputPort import InputPort from minifi.core.OutputPort import OutputPort @@ -369,12 +368,6 @@ def enable_prometheus_with_ssl_in_minifi(self): def enable_sql_in_minifi(self): self.cluster.enable_sql_in_minifi() - def enable_ssl_in_grafana_loki(self): - self.cluster.enable_ssl_in_grafana_loki() - - def enable_multi_tenancy_in_grafana_loki(self): - self.cluster.enable_multi_tenancy_in_grafana_loki() - def use_nifi_python_processors_with_system_python_packages_installed_in_minifi(self): self.cluster.use_nifi_python_processors_with_system_python_packages_installed_in_minifi() @@ -447,9 +440,6 @@ def disable_openssl_fips_mode_in_minifi(self): def debug_bundle_can_be_retrieved_through_minifi_controller(self, container_name: str): assert self.cluster.debug_bundle_can_be_retrieved_through_minifi_controller(container_name) or self.cluster.log_app_output() - def check_lines_on_grafana_loki(self, lines: List[str], timeout_seconds: int, ssl: bool, tenant_id=None): - assert self.cluster.wait_for_lines_on_grafana_loki(lines, timeout_seconds, ssl, tenant_id) or self.cluster.log_app_output() - def set_value_on_plc_with_modbus(self, container_name, modbus_cmd): assert self.cluster.set_value_on_plc_with_modbus(container_name, modbus_cmd) diff --git a/docker/test/integration/features/steps/steps.py b/docker/test/integration/features/steps/steps.py index f317294f20..1150a569a9 100644 --- a/docker/test/integration/features/steps/steps.py +++ b/docker/test/integration/features/steps/steps.py @@ -1052,43 +1052,6 @@ def step_impl(context): context.execute_steps(f"then debug bundle can be retrieved through MiNiFi controller in the \"minifi-cpp-flow-{context.feature_id}\" flow") -# Grafana Loki -@given("a Grafana Loki server is set up") -def step_impl(context): - context.test.acquire_container(context=context, name="grafana-loki-server", engine="grafana-loki-server") - - -@given("a Grafana Loki server with SSL is set up") -def step_impl(context): - context.test.enable_ssl_in_grafana_loki() - context.test.acquire_container(context=context, name="grafana-loki-server", engine="grafana-loki-server") - - -@given("a Grafana Loki server is set up with multi-tenancy enabled") -def step_impl(context): - context.test.enable_multi_tenancy_in_grafana_loki() - context.test.acquire_container(context=context, name="grafana-loki-server", engine="grafana-loki-server") - - -@then("\"{lines}\" lines are published to the Grafana Loki server in less than {timeout_seconds:d} seconds") -@then("\"{lines}\" line is published to the Grafana Loki server in less than {timeout_seconds:d} seconds") -def step_impl(context, lines: str, timeout_seconds: int): - context.test.check_lines_on_grafana_loki(lines.split(";"), timeout_seconds, False) - - -@then("\"{lines}\" lines are published to the \"{tenant_id}\" tenant on the Grafana Loki server in less than {timeout_seconds:d} seconds") -@then("\"{lines}\" line is published to the \"{tenant_id}\" tenant on the Grafana Loki server in less than {timeout_seconds:d} seconds") -def step_impl(context, lines: str, tenant_id: str, timeout_seconds: int): - context.test.check_lines_on_grafana_loki(lines.split(";"), timeout_seconds, False, tenant_id) - - -@then("\"{lines}\" lines are published using SSL to the Grafana Loki server in less than {timeout_seconds:d} seconds") -@then("\"{lines}\" line is published using SSL to the Grafana Loki server in less than {timeout_seconds:d} seconds") -def step_impl(context, lines: str, timeout_seconds: int): - context.test.check_lines_on_grafana_loki(lines.split(";"), timeout_seconds, True) - - -@given(u'a SSL context service is set up for Grafana Loki processor \"{processor_name}\"') @given(u'a SSL context service is set up for the following processor: \"{processor_name}\"') def step_impl(context, processor_name: str): setUpSslContextServiceForProcessor(context, processor_name) @@ -1099,12 +1062,6 @@ def step_impl(context, remote_process_group: str): setUpSslContextServiceForRPG(context, remote_process_group) -# Nginx reverse proxy -@given(u'a reverse proxy is set up to forward requests to the Grafana Loki server') -def step_impl(context): - context.test.acquire_container(context=context, name="reverse-proxy", engine="reverse-proxy") - - # Python @given("python with langchain is installed on the MiNiFi agent {install_mode}") def step_impl(context, install_mode): diff --git a/docker/test/integration/minifi/processors/PushGrafanaLokiGrpc.py b/docker/test/integration/minifi/processors/PushGrafanaLokiGrpc.py deleted file mode 100644 index f938d3ef1e..0000000000 --- a/docker/test/integration/minifi/processors/PushGrafanaLokiGrpc.py +++ /dev/null @@ -1,24 +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. -from ..core.Processor import Processor - - -class PushGrafanaLokiGrpc(Processor): - def __init__(self, context, schedule={'scheduling strategy': 'EVENT_DRIVEN'}): - super(PushGrafanaLokiGrpc, self).__init__( - context=context, - clazz='PushGrafanaLokiGrpc', - auto_terminate=['success', 'failure'], - schedule=schedule) diff --git a/docker/test/integration/minifi/processors/PushGrafanaLokiREST.py b/docker/test/integration/minifi/processors/PushGrafanaLokiREST.py deleted file mode 100644 index df4beab28a..0000000000 --- a/docker/test/integration/minifi/processors/PushGrafanaLokiREST.py +++ /dev/null @@ -1,24 +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. -from ..core.Processor import Processor - - -class PushGrafanaLokiREST(Processor): - def __init__(self, context, schedule={'scheduling strategy': 'EVENT_DRIVEN'}): - super(PushGrafanaLokiREST, self).__init__( - context=context, - clazz='PushGrafanaLokiREST', - auto_terminate=['success', 'failure'], - schedule=schedule) diff --git a/docker/test/integration/resources/reverse-proxy/Dockerfile b/docker/test/integration/resources/reverse-proxy/Dockerfile deleted file mode 100644 index a479f117f5..0000000000 --- a/docker/test/integration/resources/reverse-proxy/Dockerfile +++ /dev/null @@ -1,17 +0,0 @@ -# Source: https://gist.github.com/laurentbel/c4c7696890fc71c8061172a932eb52e4 -FROM nginx:1.25.3 - -RUN apt-get update -y && apt-get install -y apache2-utils && rm -rf /var/lib/apt/lists/* - -ENV BASIC_USERNAME=username -ENV BASIC_PASSWORD=password - -ENV FORWARD_HOST=google.com -ENV FORWARD_PORT=80 - -WORKDIR / -COPY nginx-basic-auth.conf nginx-basic-auth.conf - -COPY run.sh ./ -RUN chmod 0755 ./run.sh -CMD [ "./run.sh" ] diff --git a/docker/test/integration/resources/reverse-proxy/nginx-basic-auth.conf b/docker/test/integration/resources/reverse-proxy/nginx-basic-auth.conf deleted file mode 100644 index c64c587c3b..0000000000 --- a/docker/test/integration/resources/reverse-proxy/nginx-basic-auth.conf +++ /dev/null @@ -1,12 +0,0 @@ -# Source: https://gist.github.com/laurentbel/c4c7696890fc71c8061172a932eb52e4 -server { - listen 3030 default_server; - - location / { - auth_basic "Restricted"; - auth_basic_user_file .htpasswd; - - proxy_pass http://${FORWARD_HOST}:${FORWARD_PORT}; - proxy_read_timeout 900; - } -} diff --git a/docker/test/integration/resources/reverse-proxy/run.sh b/docker/test/integration/resources/reverse-proxy/run.sh deleted file mode 100644 index c9c6c436ca..0000000000 --- a/docker/test/integration/resources/reverse-proxy/run.sh +++ /dev/null @@ -1,5 +0,0 @@ -#!/bin/sh -# Source: https://gist.github.com/laurentbel/c4c7696890fc71c8061172a932eb52e4 -envsubst < nginx-basic-auth.conf > /etc/nginx/conf.d/default.conf -htpasswd -c -b /etc/nginx/.htpasswd "${BASIC_USERNAME}" "${BASIC_PASSWORD}" -exec nginx -g "daemon off;" diff --git a/extensions/grafana-loki/tests/features/environment.py b/extensions/grafana-loki/tests/features/environment.py new file mode 100644 index 0000000000..11ab6ba74d --- /dev/null +++ b/extensions/grafana-loki/tests/features/environment.py @@ -0,0 +1,49 @@ +# 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 pathlib import Path +from minifi_test_framework.containers.docker_image_builder import DockerImageBuilder +from minifi_test_framework.core.hooks import common_before_scenario +from minifi_test_framework.core.hooks import common_after_scenario + + +def before_all(context): + check_log_lines_path = Path(__file__).resolve().parent / "resources" / "check_log_lines_on_grafana.py" + check_log_lines_content = None + with open(check_log_lines_path, "rb") as f: + check_log_lines_content = f.read() + dockerfile = """ +FROM python:3.13-slim-bookworm +RUN pip install requests +COPY check_log_lines_on_grafana.py /scripts/check_log_lines_on_grafana.py""" + grafana_helper_builder = DockerImageBuilder( + image_tag="minifi-grafana-loki-helper:latest", + dockerfile_content=dockerfile, + files_on_context={"check_log_lines_on_grafana.py": check_log_lines_content} + ) + grafana_helper_builder.build() + + reverse_proxy_builder = DockerImageBuilder( + image_tag="minifi-reverse-proxy:latest", + build_context_path=str(Path(__file__).resolve().parent / "resources" / "reverse-proxy") + ) + reverse_proxy_builder.build() + + +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/grafana_loki.feature b/extensions/grafana-loki/tests/features/grafana_loki.feature similarity index 82% rename from docker/test/integration/features/grafana_loki.feature rename to extensions/grafana-loki/tests/features/grafana_loki.feature index 478d4c0436..d23bbf212c 100644 --- a/docker/test/integration/features/grafana_loki.feature +++ b/extensions/grafana-loki/tests/features/grafana_loki.feature @@ -16,16 +16,16 @@ @ENABLE_GRAFANA_LOKI Feature: MiNiFi can publish logs to Grafana Loki server - Background: - Given the content of "/tmp/output" is monitored - Scenario: Logs are published to Loki server through REST API Given a Grafana Loki server is set up And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiREST processor with the "Url" property set to "http://grafana-loki-server-${feature_id}:3100/" + And a PushGrafanaLokiREST processor with the "Url" property set to "http://grafana-loki-server-${scenario_id}:3100/" + And PushGrafanaLokiREST is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiREST processor is set to "job=minifi,id=docker-test" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiREST + And PushGrafanaLokiREST's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published to the Grafana Loki server in less than 60 seconds @@ -33,10 +33,13 @@ Feature: MiNiFi can publish logs to Grafana Loki server Given a Grafana Loki server is set up with multi-tenancy enabled And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiREST processor with the "Url" property set to "http://grafana-loki-server-${feature_id}:3100/" + And a PushGrafanaLokiREST processor with the "Url" property set to "http://grafana-loki-server-${scenario_id}:3100/" + And PushGrafanaLokiREST is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiREST processor is set to "job=minifi,id=docker-test" And the "Tenant ID" property of the PushGrafanaLokiREST processor is set to "mytenant" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiREST + And PushGrafanaLokiREST's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published to the "mytenant" tenant on the Grafana Loki server in less than 60 seconds @@ -44,10 +47,14 @@ Feature: MiNiFi can publish logs to Grafana Loki server Given a Grafana Loki server with SSL is set up And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiREST processor with the "Url" property set to "https://grafana-loki-server-${feature_id}:3100/" + And a PushGrafanaLokiREST processor with the "Url" property set to "https://grafana-loki-server-${scenario_id}:3100/" + And PushGrafanaLokiREST is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiREST processor is set to "job=minifi,id=docker-test" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiREST - And a SSL context service is set up for Grafana Loki processor "PushGrafanaLokiREST" + And the "SSL Context Service" property of the PushGrafanaLokiREST processor is set to "SSLContextService" + And an ssl context service is set up + And PushGrafanaLokiREST's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published using SSL to the Grafana Loki server in less than 60 seconds @@ -56,11 +63,14 @@ Feature: MiNiFi can publish logs to Grafana Loki server And a reverse proxy is set up to forward requests to the Grafana Loki server And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiREST processor with the "Url" property set to "http://reverse-proxy-${feature_id}:3030/" + And a PushGrafanaLokiREST processor with the "Url" property set to "http://reverse-proxy-${scenario_id}:3030/" + And PushGrafanaLokiREST is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiREST processor is set to "job=minifi,id=docker-test" And the "Username" property of the PushGrafanaLokiREST processor is set to "admin" And the "Password" property of the PushGrafanaLokiREST processor is set to "password" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiREST + And PushGrafanaLokiREST's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published to the Grafana Loki server in less than 60 seconds @@ -68,9 +78,12 @@ Feature: MiNiFi can publish logs to Grafana Loki server Given a Grafana Loki server is set up And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${feature_id}:9095" + And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${scenario_id}:9095" + And PushGrafanaLokiGrpc is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiGrpc processor is set to "job=minifi,id=docker-test" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiGrpc + And PushGrafanaLokiGrpc's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published to the Grafana Loki server in less than 60 seconds @@ -78,10 +91,13 @@ Feature: MiNiFi can publish logs to Grafana Loki server Given a Grafana Loki server is set up with multi-tenancy enabled And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${feature_id}:9095" + And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${scenario_id}:9095" + And PushGrafanaLokiGrpc is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiGrpc processor is set to "job=minifi,id=docker-test" And the "Tenant ID" property of the PushGrafanaLokiGrpc processor is set to "mytenant" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiGrpc + And PushGrafanaLokiGrpc's success relationship is auto-terminated + When all instances start up Then "log line 1;log line 2;log line 3" lines are published to the "mytenant" tenant on the Grafana Loki server in less than 60 seconds @@ -89,9 +105,13 @@ Feature: MiNiFi can publish logs to Grafana Loki server Given a Grafana Loki server with SSL is set up And a TailFile processor with the "File to Tail" property set to "/tmp/input/test_file.log" And a file with filename "test_file.log" and content "log line 1\nlog line 2\nlog line 3\n" is present in "/tmp/input" - And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${feature_id}:9095" + And a PushGrafanaLokiGrpc processor with the "Url" property set to "grafana-loki-server-${scenario_id}:9095" + And PushGrafanaLokiGrpc is EVENT_DRIVEN And the "Stream Labels" property of the PushGrafanaLokiGrpc processor is set to "job=minifi,id=docker-test" + And the "SSL Context Service" property of the PushGrafanaLokiGrpc processor is set to "SSLContextService" And the "success" relationship of the TailFile processor is connected to the PushGrafanaLokiGrpc - And a SSL context service is set up for Grafana Loki processor "PushGrafanaLokiGrpc" + And PushGrafanaLokiGrpc's success relationship is auto-terminated + And an ssl context service is set up + When all instances start up Then "log line 1;log line 2;log line 3" lines are published using SSL to the Grafana Loki server in less than 60 seconds diff --git a/extensions/grafana-loki/tests/features/resources/check_log_lines_on_grafana.py b/extensions/grafana-loki/tests/features/resources/check_log_lines_on_grafana.py new file mode 100644 index 0000000000..6d961a256f --- /dev/null +++ b/extensions/grafana-loki/tests/features/resources/check_log_lines_on_grafana.py @@ -0,0 +1,79 @@ +# 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 sys +import requests +import time + + +def wait_for(action, timeout_seconds, *args, **kwargs) -> bool: + start_time = time.perf_counter() + while True: + result = action(*args, **kwargs) + if result: + return result + if timeout_seconds < (time.perf_counter() - start_time): + break + time.sleep(1) + return False + + +def verify_log_lines_on_grafana_loki(host: str, lines: list[str], ssl: bool, tenant_id: str) -> bool: + labels = '{job="minifi"}' + prefix = "http://" + if ssl: + prefix = "https://" + + query_url = f"{prefix}{host}:3100/loki/api/v1/query_range?query={labels}" + + headers = None + if tenant_id: + headers = {'X-Scope-OrgID': tenant_id} + + response = requests.get(query_url, verify=False, timeout=30, headers=headers) + if response.status_code < 200 or response.status_code >= 300: + return False + + json_response = response.json() + if "data" not in json_response or "result" not in json_response["data"] or len(json_response["data"]["result"]) < 1: + return False + + result = json_response["data"]["result"][0] + if "values" not in result: + return False + + for line in lines: + if line not in str(result["values"]): + return False + return True + + +def wait_for_lines_on_grafana_loki(host: str, lines: list[str], timeout_seconds: int, ssl: bool, tenant_id: str) -> bool: + return wait_for(lambda: verify_log_lines_on_grafana_loki(host, lines, ssl, tenant_id), timeout_seconds) + + +if __name__ == "__main__": + if len(sys.argv) < 5: + sys.exit(1) + + host = sys.argv[1] + lines = sys.argv[2] + timeout_seconds = int(sys.argv[3]) + ssl = sys.argv[4].lower() == "true" + tenant_id = "" + if len(sys.argv) >= 6: + tenant_id = sys.argv[5] + if not wait_for_lines_on_grafana_loki(host, lines.split(";"), timeout_seconds, ssl, tenant_id): + sys.exit(1) diff --git a/extensions/grafana-loki/tests/features/resources/reverse-proxy/Dockerfile b/extensions/grafana-loki/tests/features/resources/reverse-proxy/Dockerfile new file mode 100644 index 0000000000..28c25c7e0a --- /dev/null +++ b/extensions/grafana-loki/tests/features/resources/reverse-proxy/Dockerfile @@ -0,0 +1,8 @@ +FROM nginx:1.29.4 + +COPY nginx.conf.template /nginx.conf.template +COPY run.sh /run.sh + +RUN apt update -y && apt install -y apache2-utils && rm /etc/nginx/conf.d/default.conf && chmod 0755 /run.sh + +ENTRYPOINT [ "/run.sh" ] diff --git a/extensions/grafana-loki/tests/features/resources/reverse-proxy/nginx.conf.template b/extensions/grafana-loki/tests/features/resources/reverse-proxy/nginx.conf.template new file mode 100644 index 0000000000..05f8251a19 --- /dev/null +++ b/extensions/grafana-loki/tests/features/resources/reverse-proxy/nginx.conf.template @@ -0,0 +1,10 @@ +server { + listen 3030; + server_name default_server; + + location / { + proxy_pass http://${FORWARD_HOST}:${FORWARD_PORT}; + auth_basic "Administrator’s Area"; + auth_basic_user_file /etc/nginx/.htpasswd; + } +} diff --git a/extensions/grafana-loki/tests/features/resources/reverse-proxy/run.sh b/extensions/grafana-loki/tests/features/resources/reverse-proxy/run.sh new file mode 100644 index 0000000000..abbd518856 --- /dev/null +++ b/extensions/grafana-loki/tests/features/resources/reverse-proxy/run.sh @@ -0,0 +1,8 @@ +#!/bin/bash + +htpasswd -bc /etc/nginx/.htpasswd "${BASIC_USERNAME}" "${BASIC_PASSWORD}" + +# shellcheck disable=SC2016 +envsubst '${FORWARD_HOST} ${FORWARD_PORT}' < /nginx.conf.template > /etc/nginx/conf.d/default.conf + +nginx -g "daemon off;" diff --git a/extensions/grafana-loki/tests/features/steps/grafana_loki_container.py b/extensions/grafana-loki/tests/features/steps/grafana_loki_container.py new file mode 100644 index 0000000000..46fc84b5af --- /dev/null +++ b/extensions/grafana-loki/tests/features/steps/grafana_loki_container.py @@ -0,0 +1,151 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +import logging +from 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.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 docker.errors import ContainerError + + +class GrafanaLokiOptions: + def __init__(self, enable_ssl: bool = False, enable_multi_tenancy: bool = False): + self.enable_ssl = enable_ssl + self.enable_multi_tenancy = enable_multi_tenancy + + +class GrafanaLokiContainer(Container): + 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) + 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) + 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) + self.files.append(File("/etc/loki/key.pem", grafana_loki_key_content, permissions=0o644)) + + extra_ssl_settings = """ + http_tls_config: + cert_file: /etc/loki/cert.pem + key_file: /etc/loki/key.pem + client_ca_file: /etc/loki/root_ca.crt + client_auth_type: VerifyClientCertIfGiven + + grpc_tls_config: + cert_file: /etc/loki/cert.pem + key_file: /etc/loki/key.pem + client_ca_file: /etc/loki/root_ca.crt + client_auth_type: VerifyClientCertIfGiven + +query_scheduler: + grpc_client_config: + grpc_compression: snappy + tls_enabled: true + tls_ca_path: /etc/loki/root_ca.crt + tls_insecure_skip_verify: true + +ingester_client: + grpc_client_config: + grpc_compression: snappy + tls_enabled: true + tls_ca_path: /etc/loki/root_ca.crt + tls_insecure_skip_verify: true + +frontend: + grpc_client_config: + grpc_compression: snappy + tls_enabled: true + tls_ca_path: /etc/loki/root_ca.crt + tls_insecure_skip_verify: true + +frontend_worker: + grpc_client_config: + grpc_compression: snappy + tls_enabled: true + tls_ca_path: /etc/loki/root_ca.crt + tls_insecure_skip_verify: true +""" + + grafana_loki_yml_content = """ +auth_enabled: {enable_multi_tenancy} + +server: + http_listen_port: 3100 + grpc_listen_port: 9095 +{extra_ssl_settings} + +common: + path_prefix: /loki + storage: + filesystem: + chunks_directory: /loki/chunks + rules_directory: /loki/rules + replication_factor: 1 + ring: + kvstore: + store: inmemory + +schema_config: + configs: + - from: 2020-05-15 + store: tsdb + object_store: filesystem + schema: v13 + index: + prefix: index_ + period: 24h + +ruler: + alertmanager_url: http://localhost:9093 + +analytics: + reporting_enabled: false +""".format(extra_ssl_settings=extra_ssl_settings, enable_multi_tenancy=options.enable_multi_tenancy) + + self.files.append(File("/etc/loki/local-config.yaml", grafana_loki_yml_content.encode(), permissions=0o644)) + + def deploy(self): + super().deploy() + finished_str = "Loki started" + return wait_for_condition( + condition=lambda: finished_str in self.get_logs(), + timeout_seconds=120, + bail_condition=lambda: self.exited, + context=None) + + @retry_check() + def are_lines_present(self, lines: str, timeout: int, ssl: bool, tenant_id: str = "") -> bool: + try: + self.client.containers.run("minifi-grafana-loki-helper:latest", ["python", "/scripts/check_log_lines_on_grafana.py", self.container_name, lines, str(timeout), str(ssl), tenant_id], + remove=True, stdout=True, stderr=True, network=self.network.name) + return True + except ContainerError as e: + stdout = e.stdout.decode("utf-8", errors="replace") if e.stdout else "" + stderr = e.stderr.decode("utf-8", errors="replace") if e.stderr else "" + logging.error(f"Failed to run python command in grafana loki helper docker with error: '{e}', stdout: '{stdout}', stderr: '{stderr}'") + return False + except Exception as e: + logging.error(f"Unexpected error while running python command in grafana loki helper docker: '{e}'") + return False diff --git a/extensions/grafana-loki/tests/features/steps/reverse_proxy_container.py b/extensions/grafana-loki/tests/features/steps/reverse_proxy_container.py new file mode 100644 index 0000000000..c415f6dba6 --- /dev/null +++ b/extensions/grafana-loki/tests/features/steps/reverse_proxy_container.py @@ -0,0 +1,38 @@ +# 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.minifi_test_context import MinifiTestContext +from minifi_test_framework.containers.container import Container +from minifi_test_framework.core.helpers import wait_for_condition + + +class ReverseProxyContainer(Container): + def __init__(self, test_context: MinifiTestContext): + super().__init__("minifi-reverse-proxy:latest", f"reverse-proxy-{test_context.scenario_id}", test_context.network) + self.environment = [ + "BASIC_USERNAME=admin", + "BASIC_PASSWORD=password", + f"FORWARD_HOST=grafana-loki-server-{test_context.scenario_id}", + "FORWARD_PORT=3100", + ] + + def deploy(self): + super().deploy() + finished_str = "start worker process" + return wait_for_condition( + condition=lambda: finished_str in self.get_logs(), + timeout_seconds=60, + bail_condition=lambda: self.exited, + context=None) diff --git a/extensions/grafana-loki/tests/features/steps/steps.py b/extensions/grafana-loki/tests/features/steps/steps.py new file mode 100644 index 0000000000..bd675e110c --- /dev/null +++ b/extensions/grafana-loki/tests/features/steps/steps.py @@ -0,0 +1,63 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +from behave import step, then + +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 +from minifi_test_framework.core.minifi_test_context import MinifiTestContext +from minifi_test_framework.core.helpers import log_due_to_failure +from grafana_loki_container import GrafanaLokiContainer, GrafanaLokiOptions +from reverse_proxy_container import ReverseProxyContainer + + +@step("a Grafana Loki server is set up") +def step_impl(context: MinifiTestContext): + context.containers["grafana-loki-server"] = GrafanaLokiContainer(context, GrafanaLokiOptions()) + + +@step("a Grafana Loki server is set up with multi-tenancy enabled") +def step_impl(context: MinifiTestContext): + context.containers["grafana-loki-server"] = GrafanaLokiContainer(context, GrafanaLokiOptions(enable_multi_tenancy=True)) + + +@step("a Grafana Loki server with SSL is set up") +def step_impl(context: MinifiTestContext): + context.containers["grafana-loki-server"] = GrafanaLokiContainer(context, GrafanaLokiOptions(enable_ssl=True)) + + +@then("\"{lines}\" lines are published to the Grafana Loki server in less than {timeout_seconds:d} seconds") +@then("\"{lines}\" line is published to the Grafana Loki server in less than {timeout_seconds:d} seconds") +def step_impl(context, lines: str, timeout_seconds: int): + assert context.containers["grafana-loki-server"].are_lines_present(lines, timeout_seconds, ssl=False) or log_due_to_failure(context) + + +@then("\"{lines}\" lines are published to the \"{tenant_id}\" tenant on the Grafana Loki server in less than {timeout_seconds:d} seconds") +@then("\"{lines}\" line is published to the \"{tenant_id}\" tenant on the Grafana Loki server in less than {timeout_seconds:d} seconds") +def step_impl(context, lines: str, timeout_seconds: int, tenant_id: str): + assert context.containers["grafana-loki-server"].are_lines_present(lines, timeout_seconds, ssl=False, tenant_id=tenant_id) or log_due_to_failure(context) + + +@then("\"{lines}\" lines are published using SSL to the Grafana Loki server in less than {timeout_seconds:d} seconds") +@then("\"{lines}\" line is published using SSL to the Grafana Loki server in less than {timeout_seconds:d} seconds") +def step_impl(context, lines: str, timeout_seconds: int): + assert context.containers["grafana-loki-server"].are_lines_present(lines, timeout_seconds, ssl=True) or log_due_to_failure(context) + + +# Nginx reverse proxy +@step('a reverse proxy is set up to forward requests to the Grafana Loki server') +def step_impl(context): + context.containers["reverse-proxy"] = ReverseProxyContainer(context)