From 0f1266fd5647d075f68e0d170cdba1db0104ea4f Mon Sep 17 00:00:00 2001 From: Antonio Santos Date: Wed, 26 Aug 2026 10:44:19 +0200 Subject: [PATCH 1/4] reload: preserve configuration path during hot reload Relative service file references resolve against the directory containing the main configuration. Reload created a new context without preserving that directory, so relative files could no longer be resolved. Copy the canonical path before loading the new configuration. Parsers, external plugins, upstream HA files, and stream processor files all use this path when resolving relative references. Fixes #12333 Signed-off-by: Antonio Santos --- src/flb_reload.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/flb_reload.c b/src/flb_reload.c index 9f44034e2c4..36c476ce1ff 100644 --- a/src/flb_reload.c +++ b/src/flb_reload.c @@ -533,6 +533,20 @@ int flb_reload(flb_ctx_t *ctx, struct flb_cf *cf_opts) new_config = new_ctx->config; + if (old_config->conf_path) { + new_config->conf_path = flb_strdup(old_config->conf_path); + if (!new_config->conf_path) { + if (file != NULL) { + flb_sds_destroy(file); + } + flb_cf_destroy(new_cf); + flb_destroy(new_ctx); + flb_error("[reload] copying configuration path failed. Reloading is halted"); + flb_reload_watchdog_cleanup(watchdog_ctx); + return FLB_RELOAD_HALTED; + } + } + /* Inherit verbose from the old ctx instance */ verbose = ctx->config->verbose; new_config->verbose = verbose; From 64f5816ad5987adc89f366199e84f5d34e0e9a7f Mon Sep 17 00:00:00 2001 From: Antonio Santos Date: Wed, 26 Aug 2026 10:44:28 +0200 Subject: [PATCH 2/4] tests: cover relative parser path reload Signed-off-by: Antonio Santos --- tests/internal/data/reload/fluent-bit.conf | 1 + tests/internal/data/reload/parsers.conf | 3 +++ tests/internal/reload.c | 18 +++++++++++++++++- 3 files changed, 21 insertions(+), 1 deletion(-) create mode 100644 tests/internal/data/reload/parsers.conf diff --git a/tests/internal/data/reload/fluent-bit.conf b/tests/internal/data/reload/fluent-bit.conf index b46ec97230e..9ea4a0fc54b 100644 --- a/tests/internal/data/reload/fluent-bit.conf +++ b/tests/internal/data/reload/fluent-bit.conf @@ -2,6 +2,7 @@ Flush 1 Daemon Off Log_Level error + Parsers_File parsers.conf HTTP_Server On HTTP_Listen 0.0.0.0 HTTP_Port 2022 diff --git a/tests/internal/data/reload/parsers.conf b/tests/internal/data/reload/parsers.conf new file mode 100644 index 00000000000..f7c3f9b3ed3 --- /dev/null +++ b/tests/internal/data/reload/parsers.conf @@ -0,0 +1,3 @@ +[PARSER] + Name reload_test + Format json diff --git a/tests/internal/reload.c b/tests/internal/reload.c index c56759f9eb9..0b0de797cc2 100644 --- a/tests/internal/reload.c +++ b/tests/internal/reload.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -22,6 +23,7 @@ #define FLB_YAML FLB_TESTS_DATA_PATH "/data/reload/yaml/processor.yaml" #define FLB_YAML_MISSING_INCLUDE FLB_TESTS_DATA_PATH "/data/reload/yaml/missing_include.yaml" #define FLB_CLASSIC FLB_TESTS_DATA_PATH "/data/reload/fluent-bit.conf" +#define FLB_CLASSIC_PATH FLB_TESTS_DATA_PATH "/data/reload/" void test_reconstruct_cf() { @@ -147,11 +149,14 @@ void test_reload() cf = flb_cf_create_from_file(cf, FLB_CLASSIC); TEST_CHECK(cf != NULL); + ctx->config->conf_path = flb_strdup(FLB_CLASSIC_PATH); + TEST_CHECK(ctx->config->conf_path != NULL); ctx->config->conf_path_file = flb_sds_create(FLB_CLASSIC); ctx->config->enable_hot_reload = FLB_TRUE; status = flb_config_load_config_format(ctx->config, cf); TEST_CHECK(status == 0); + TEST_CHECK(flb_parser_get("reload_test", ctx->config) != NULL); /* Start the engine */ status = flb_start(ctx); @@ -161,13 +166,24 @@ void test_reload() sleep(2); status = flb_reload(ctx, cf_opts); - TEST_CHECK(status == 0); + if (!TEST_CHECK(status == 0)) { + flb_cf_destroy(cf_opts); + if (status == FLB_RELOAD_HALTED) { + flb_stop(ctx); + flb_destroy(ctx); + } + return; + } sleep(2); /* flb context should be replaced with flb_reload() */ ctx = flb_context_get(); + if (TEST_CHECK(ctx->config->conf_path != NULL)) { + TEST_CHECK(strcmp(ctx->config->conf_path, FLB_CLASSIC_PATH) == 0); + } + TEST_CHECK(flb_parser_get("reload_test", ctx->config) != NULL); TEST_CHECK(mk_list_size(&ctx->config->cf_opts->inputs) == 1); TEST_CHECK(mk_list_size(&ctx->config->inputs) == 2); From 6a1ed7d9de95e438d99191894bb4e3f455cb7887 Mon Sep 17 00:00:00 2001 From: Antonio Santos Date: Wed, 26 Aug 2026 10:44:38 +0200 Subject: [PATCH 3/4] tests: integration: cover relative parser path reload Signed-off-by: Antonio Santos --- .../fluent-bit-relative-parser-after.conf | 29 ++++++++++++ .../fluent-bit-relative-parser-after.yaml | 32 +++++++++++++ .../fluent-bit-relative-parser-before.conf | 29 ++++++++++++ .../fluent-bit-relative-parser-before.yaml | 32 +++++++++++++ .../config/relative-parser.conf | 4 ++ .../tests/test_hot_reload_watch_001.py | 47 +++++++++++++++++-- 6 files changed, 170 insertions(+), 3 deletions(-) create mode 100644 tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.conf create mode 100644 tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.yaml create mode 100644 tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.conf create mode 100644 tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.yaml create mode 100644 tests/integration/scenarios/hot_reload_watch/config/relative-parser.conf diff --git a/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.conf b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.conf new file mode 100644 index 00000000000..15065a66e60 --- /dev/null +++ b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.conf @@ -0,0 +1,29 @@ +[SERVICE] + Flush 1 + Log_Level info + Parsers_File relative-parser.conf + HTTP_Server On + HTTP_Listen 127.0.0.1 + HTTP_Port ${FLUENT_BIT_HTTP_MONITORING_PORT} + Hot_Reload On + +[INPUT] + Name dummy + Tag relative_parser + Samples 1 + Dummy {"message":"after"} + +[FILTER] + Name parser + Match relative_parser + Key_Name message + Parser reload_test + Reserve_Data On + +[OUTPUT] + Name opentelemetry + Match relative_parser + Host 127.0.0.1 + Port ${TEST_SUITE_HTTP_PORT} + Logs_URI /v1/logs + Logs_Body_Key $parsed diff --git a/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.yaml b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.yaml new file mode 100644 index 00000000000..a738850530f --- /dev/null +++ b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-after.yaml @@ -0,0 +1,32 @@ +service: + flush: 1 + log_level: info + parsers_file: relative-parser.conf + http_server: on + http_port: ${FLUENT_BIT_HTTP_MONITORING_PORT} + hot_reload: on + +pipeline: + inputs: + - name: dummy + tag: relative_parser + samples: 1 + dummy: | + { + "message": "after" + } + + filters: + - name: parser + match: relative_parser + key_name: message + parser: reload_test + reserve_data: on + + outputs: + - name: opentelemetry + match: relative_parser + host: 127.0.0.1 + port: ${TEST_SUITE_HTTP_PORT} + logs_uri: /v1/logs + logs_body_key: $parsed diff --git a/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.conf b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.conf new file mode 100644 index 00000000000..db462214bc0 --- /dev/null +++ b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.conf @@ -0,0 +1,29 @@ +[SERVICE] + Flush 1 + Log_Level info + Parsers_File relative-parser.conf + HTTP_Server On + HTTP_Listen 127.0.0.1 + HTTP_Port ${FLUENT_BIT_HTTP_MONITORING_PORT} + Hot_Reload On + +[INPUT] + Name dummy + Tag relative_parser + Samples 1 + Dummy {"message":"before"} + +[FILTER] + Name parser + Match relative_parser + Key_Name message + Parser reload_test + Reserve_Data On + +[OUTPUT] + Name opentelemetry + Match relative_parser + Host 127.0.0.1 + Port ${TEST_SUITE_HTTP_PORT} + Logs_URI /v1/logs + Logs_Body_Key $parsed diff --git a/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.yaml b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.yaml new file mode 100644 index 00000000000..c6f00d67d00 --- /dev/null +++ b/tests/integration/scenarios/hot_reload_watch/config/fluent-bit-relative-parser-before.yaml @@ -0,0 +1,32 @@ +service: + flush: 1 + log_level: info + parsers_file: relative-parser.conf + http_server: on + http_port: ${FLUENT_BIT_HTTP_MONITORING_PORT} + hot_reload: on + +pipeline: + inputs: + - name: dummy + tag: relative_parser + samples: 1 + dummy: | + { + "message": "before" + } + + filters: + - name: parser + match: relative_parser + key_name: message + parser: reload_test + reserve_data: on + + outputs: + - name: opentelemetry + match: relative_parser + host: 127.0.0.1 + port: ${TEST_SUITE_HTTP_PORT} + logs_uri: /v1/logs + logs_body_key: $parsed diff --git a/tests/integration/scenarios/hot_reload_watch/config/relative-parser.conf b/tests/integration/scenarios/hot_reload_watch/config/relative-parser.conf new file mode 100644 index 00000000000..b208ee1e055 --- /dev/null +++ b/tests/integration/scenarios/hot_reload_watch/config/relative-parser.conf @@ -0,0 +1,4 @@ +[PARSER] + Name reload_test + Format regex + Regex ^(?.*)$ diff --git a/tests/integration/scenarios/hot_reload_watch/tests/test_hot_reload_watch_001.py b/tests/integration/scenarios/hot_reload_watch/tests/test_hot_reload_watch_001.py index fe195f87bca..1e801470017 100644 --- a/tests/integration/scenarios/hot_reload_watch/tests/test_hot_reload_watch_001.py +++ b/tests/integration/scenarios/hot_reload_watch/tests/test_hot_reload_watch_001.py @@ -37,17 +37,24 @@ class Service: - def __init__(self, before_name, after_name): + def __init__(self, before_name, after_name, support_files=None): self.test_path = os.path.dirname(os.path.abspath(__file__)) self.config_dir = os.path.abspath(os.path.join(self.test_path, "../config")) self.before_config = os.path.join(self.config_dir, before_name) self.after_config = os.path.join(self.config_dir, after_name) + self.support_files = support_files or [] self.runtime_dir = tempfile.mkdtemp(prefix="flb-hot-reload-watch-") - self.runtime_config = os.path.join(self.runtime_dir, "fluent-bit.yaml") + extension = os.path.splitext(before_name)[1] + self.runtime_config = os.path.join(self.runtime_dir, f"fluent-bit{extension}") data_storage["logs"] = [] def start(self): shutil.copyfile(self.before_config, self.runtime_config) + for support_file in self.support_files: + shutil.copyfile( + os.path.join(self.config_dir, support_file), + os.path.join(self.runtime_dir, support_file), + ) self.flb = FluentBitManager(self.runtime_config) self.test_suite_http_port = find_available_port(starting_port=50000) @@ -96,7 +103,7 @@ def read_message(self, index): return payload["resourceLogs"][0]["scopeLogs"][0]["logRecords"][0]["body"]["stringValue"] def replace_config(self): - pending_path = os.path.join(self.runtime_dir, "fluent-bit.yaml.tmp") + pending_path = f"{self.runtime_config}.tmp" shutil.copyfile(self.after_config, pending_path) os.replace(pending_path, self.runtime_config) @@ -178,3 +185,37 @@ def test_hot_reload_http_yaml_config_change(): assert_reload_result(service) finally: service.stop() + + +@pytest.mark.parametrize( + ("before_name", "after_name"), + [ + ("fluent-bit-relative-parser-before.conf", "fluent-bit-relative-parser-after.conf"), + ("fluent-bit-relative-parser-before.yaml", "fluent-bit-relative-parser-after.yaml"), + ], + ids=["classic", "yaml"], +) +def test_hot_reload_http_relative_parser(before_name, after_name): + parser_file = "relative-parser.conf" + + # Ensure the parser cannot be loaded relative to the process working directory. + assert not os.path.exists(os.path.join(os.getcwd(), parser_file)) + + service = Service(before_name, after_name, support_files=[parser_file]) + + try: + service.start() + service.wait_for_log_count(1) + assert service.read_message(0) == "before" + + service.replace_config() + + with pytest.raises(TimeoutError): + service.flb.wait_for_hot_reload_count(1, timeout=2) + + payload = service.flb.trigger_http_reload() + assert payload["reload"] == "done" + service.flb.wait_for_hot_reload_count(1) + assert_reload_result(service) + finally: + service.stop() From 39aa8152a6660a48551644244f71ddaad277ce57 Mon Sep 17 00:00:00 2001 From: Antonio Santos Date: Thu, 27 Aug 2026 11:22:27 +0200 Subject: [PATCH 4/4] tests: simplify reload assertions Signed-off-by: Antonio Santos --- tests/internal/reload.c | 15 ++++----------- 1 file changed, 4 insertions(+), 11 deletions(-) diff --git a/tests/internal/reload.c b/tests/internal/reload.c index 0b0de797cc2..f5018ed282a 100644 --- a/tests/internal/reload.c +++ b/tests/internal/reload.c @@ -166,23 +166,16 @@ void test_reload() sleep(2); status = flb_reload(ctx, cf_opts); - if (!TEST_CHECK(status == 0)) { - flb_cf_destroy(cf_opts); - if (status == FLB_RELOAD_HALTED) { - flb_stop(ctx); - flb_destroy(ctx); - } - return; - } + TEST_CHECK(status == 0); + TEST_MSG("Expected reload status 0, got %d", status); sleep(2); /* flb context should be replaced with flb_reload() */ ctx = flb_context_get(); - if (TEST_CHECK(ctx->config->conf_path != NULL)) { - TEST_CHECK(strcmp(ctx->config->conf_path, FLB_CLASSIC_PATH) == 0); - } + TEST_CHECK(ctx->config->conf_path != NULL && + strcmp(ctx->config->conf_path, FLB_CLASSIC_PATH) == 0); TEST_CHECK(flb_parser_get("reload_test", ctx->config) != NULL); TEST_CHECK(mk_list_size(&ctx->config->cf_opts->inputs) == 1); TEST_CHECK(mk_list_size(&ctx->config->inputs) == 2);