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 b95e584263bf5117afa9b1bebe0f2948c030c3d0 Author: Ferenc Gerlits <[email protected]> AuthorDate: Thu Jan 22 15:30:25 2026 +0100 MINIFICPP-2342 Do not overwrite config files during an upgrade Currently, MiNiFi modifies the installed config files (minifi.properties and config.yml) while it runs. This confuses the Windows installer, so changes to these config files get lost when MiNiFi is upgraded. After this change, the minifi.properties, minifi-log.properties and minifi-uid.properties files are no longer modified by MiNiFi at runtime, so they can be safely replaced by new versions during upgrade. All changes to the settings should be put into new files in the minifi.properties.d (minifi-log.properties.d, minifi-uid.properties.d) directory; these new files will not be touched by the upgrade. The config.yml file will no longer be installed as part of MiNiFi. If C2 is enabled, config.yml will be fetched from the C2 server; otherwise, MiNiFi will create a new file with an empty flow, and the user can edit this. Either way, config.yml will not be touched by an upgrade. Closes #2069 Signed-off-by: Marton Szasz <[email protected]> --- README.md | 16 +- conf/CMakeLists.txt | 8 - conf/config.yml.in | 21 --- conf/minifi-log.properties.in | 5 + conf/minifi-uid.properties.in | 5 + conf/minifi.properties.in | 5 + core-framework/include/utils/file/FileUtils.h | 6 + .../cluster/containers/MinifiContainer.py | 3 - .../tests/unit/ConfigurationTests.cpp | 203 +++++++++++++++------ libminifi/include/core/logging/LoggerProperties.h | 2 +- libminifi/include/properties/Configuration.h | 2 +- libminifi/include/properties/Properties.h | 20 +- libminifi/include/utils/ChecksumCalculator.h | 11 +- libminifi/src/core/FlowConfiguration.cpp | 19 +- .../core/state/nodes/ConfigurationChecksums.cpp | 2 +- libminifi/src/properties/Properties.cpp | 139 ++++++++++---- libminifi/src/utils/ChecksumCalculator.cpp | 63 ++++--- .../test/integration/C2PropertiesUpdateTests.cpp | 32 ++-- libminifi/test/unit/ChecksumCalculatorTests.cpp | 34 +++- .../test/unit/ConfigurationChecksumsTests.cpp | 6 +- libminifi/test/unit/IdTests.cpp | 28 +-- minifi_main/MiNiFiMain.cpp | 2 +- packaging/msi/WixWin.wsi.in | 60 +++--- packaging/rpm/expected-rpm-contents.in | 1 - 24 files changed, 440 insertions(+), 253 deletions(-) diff --git a/README.md b/README.md index 31d067ffb..1d0798b69 100644 --- a/README.md +++ b/README.md @@ -457,8 +457,22 @@ The performance tests can similarly be enabled. To execute them and see their ou $ ctest --verbose -L performance ``` + ### Configuring -The 'conf' directory in the installation root contains a template config.yml document, minifi.properties, and minifi-log.properties. Please see our [Configuration document](CONFIGURE.md) for details on how to configure agents. +The 'conf' directory in the installation root contains all configuration files. + +The files conf/minifi.properties, conf/minifi-log.properties and conf/minifi-uid.properties contain key-value pair configuration settings; +these are the default settings supplied by the latest MiNiFi version. If you would like to modify these, you should create a corresponding +.d directory (e.g. conf/minifi.properties.d) and put your settings in a new file inside this directory. These files are read and applied +in lexicographic order, after the default settings file. +The Windows installer creates a conf/minifi.properties.d/10_installer.properties file, which contains C2 connection settings. +If C2 is enabled and settings are added/modified from the C2 server, these will be saved in conf/minifi.properties.d/90_c2.properties. + +The conf/config.yml file contains the flow definition (i.e. the layout of processors, controller services etc). When you start MiNiFi for +the first time, the flow will be fetched from the C2 server (if available), or a file containing an empty flow will be created by MiNiFi. + +Please see our [Configuration document](CONFIGURE.md) for details on how to configure agents. + ### Installing as a service diff --git a/conf/CMakeLists.txt b/conf/CMakeLists.txt index 309b5453b..dcdcb80a1 100644 --- a/conf/CMakeLists.txt +++ b/conf/CMakeLists.txt @@ -41,12 +41,6 @@ else() message(FATAL_ERROR "Invalid MINIFI_PACKAGING_TYPE") endif() -configure_file( - config.yml.in - ${CMAKE_BINARY_DIR}/conf/config.yml - @ONLY -) - configure_file( minifi.properties.in ${CMAKE_BINARY_DIR}/conf/minifi.properties @@ -75,7 +69,6 @@ if (MINIFI_PACKAGING_TYPE STREQUAL "RPM") ${CMAKE_BINARY_DIR}/conf/minifi.properties ${CMAKE_BINARY_DIR}/conf/minifi-log.properties ${CMAKE_BINARY_DIR}/conf/minifi-uid.properties - ${CMAKE_BINARY_DIR}/conf/config.yml DESTINATION /${CMAKE_INSTALL_SYSCONFDIR}/${PROJECT_NAME} COMPONENT bin) elseif (MINIFI_PACKAGING_TYPE STREQUAL "TGZ") @@ -83,7 +76,6 @@ elseif (MINIFI_PACKAGING_TYPE STREQUAL "TGZ") ${CMAKE_BINARY_DIR}/conf/minifi.properties ${CMAKE_BINARY_DIR}/conf/minifi-log.properties ${CMAKE_BINARY_DIR}/conf/minifi-uid.properties - ${CMAKE_BINARY_DIR}/conf/config.yml DESTINATION conf COMPONENT bin) else() diff --git a/conf/config.yml.in b/conf/config.yml.in deleted file mode 100644 index 0bf85fc3d..000000000 --- a/conf/config.yml.in +++ /dev/null @@ -1,21 +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. - -Flow Controller: - name: MiNiFi Flow -Processors: [] -Connections: [] -Remote Processing Groups: [] -Provenance Reporting: diff --git a/conf/minifi-log.properties.in b/conf/minifi-log.properties.in index 9d9946715..3e9930fb4 100644 --- a/conf/minifi-log.properties.in +++ b/conf/minifi-log.properties.in @@ -13,6 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +### NOTE: Changes to this file are overwritten on upgrade. Add your custom settings to new files ### +### in the conf/minifi-log.properties.d directory instead. These custom settings files should have ### +### a name ending with ".properties", and they are applied in lexicographic order, after this file. ### +### Example filename: conf/minifi-log.properties.d/90-log-to-stderr.properties ### + #More verbose pattern by default #Format details at https://github.com/gabime/spdlog/wiki/3.-Custom-formatting spdlog.pattern=[%Y-%m-%d %H:%M:%S.%e] [%n] [%l] %v diff --git a/conf/minifi-uid.properties.in b/conf/minifi-uid.properties.in index fb49014d2..e07ea6837 100644 --- a/conf/minifi-uid.properties.in +++ b/conf/minifi-uid.properties.in @@ -13,6 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +### NOTE: Changes to this file are overwritten on upgrade. Add your custom settings to new files ### +### in the conf/minifi-uid.properties.d directory instead. These custom settings files should have ### +### a name ending with ".properties", and they are applied in lexicographic order, after this file. ### +### Example filename: conf/minifi-uid.properties.d/10-use-random.properties ### + # Implementation for uid generation. # Valid values: # time - use uuid_generate_time diff --git a/conf/minifi.properties.in b/conf/minifi.properties.in index 7eceafc60..7ae002fbc 100644 --- a/conf/minifi.properties.in +++ b/conf/minifi.properties.in @@ -13,6 +13,11 @@ # See the License for the specific language governing permissions and # limitations under the License. +### NOTE: Changes to this file are overwritten on upgrade. Add your custom settings to new files ### +### in the conf/minifi.properties.d directory instead. These custom settings files should have ### +### a name ending with ".properties", and they are applied in lexicographic order, after this file. ### +### Example filenames: conf/minifi.properties.d/10-c2.properties, conf/minifi.properties.d/90-repository-tweaks.properties ### + # Core Properties # nifi.flow.configuration.file=@MINIFI_PATH_FLOW_CONFIG@ nifi.administrative.yield.duration=30 sec diff --git a/core-framework/include/utils/file/FileUtils.h b/core-framework/include/utils/file/FileUtils.h index 06b1cc2bd..07d5d610f 100644 --- a/core-framework/include/utils/file/FileUtils.h +++ b/core-framework/include/utils/file/FileUtils.h @@ -242,6 +242,12 @@ inline int copy_file(const std::filesystem::path& path_from, const std::filesyst return 0; } +inline bool move_file(const std::filesystem::path& source_path, const std::filesystem::path& dest_path) { + std::error_code ec; + std::filesystem::rename(source_path, dest_path, ec); + return !ec; +} + inline void addFilesMatchingExtension(const std::shared_ptr<core::logging::Logger> &logger, const std::filesystem::path& originalPath, const std::filesystem::path& extension, diff --git a/docker/test/integration/cluster/containers/MinifiContainer.py b/docker/test/integration/cluster/containers/MinifiContainer.py index 387a3bd0d..62c5564de 100644 --- a/docker/test/integration/cluster/containers/MinifiContainer.py +++ b/docker/test/integration/cluster/containers/MinifiContainer.py @@ -238,9 +238,6 @@ class MinifiContainer(FlowContainer): else: image = 'apacheminificpp:' + MinifiContainer.MINIFI_TAG_PREFIX + MinifiContainer.MINIFI_VERSION - if self.options.use_flow_config_from_url: - self.command = ["/bin/sh", "-c", "rm " + MinifiContainer.MINIFI_LOCATIONS.config_path + " && " + MinifiContainer.MINIFI_LOCATIONS.run_minifi_cmd] - ports = {} if self.options.enable_prometheus or self.options.enable_prometheus_with_ssl: ports = {'9936/tcp': 9936} diff --git a/extensions/standard-processors/tests/unit/ConfigurationTests.cpp b/extensions/standard-processors/tests/unit/ConfigurationTests.cpp index bc4db378a..f8b078e42 100644 --- a/extensions/standard-processors/tests/unit/ConfigurationTests.cpp +++ b/extensions/standard-processors/tests/unit/ConfigurationTests.cpp @@ -15,12 +15,26 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include <unordered_set> + #include "unit/TestBase.h" #include "unit/Catch.h" - #include "properties/Configuration.h" #include "utils/Environment.h" +namespace { +bool settingsInFileAreAsExpected(const std::filesystem::path& file_name, const std::unordered_set<std::string>& expected_contents) { + std::unordered_set<std::string> actual_contents; + std::ifstream file{file_name}; + if (file.is_open()) { + for (std::string line; std::getline(file, line); ) { + actual_contents.insert(line); + } + } + return expected_contents == actual_contents; +} +} // namespace + namespace org::apache::nifi::minifi::test { TEST_CASE("Configuration can merge lists of property names", "[mergeProperties]") { @@ -59,41 +73,37 @@ TEST_CASE("Configuration can fix misconfigured timeperiod<->integer validated pr LogTestController::getInstance().setInfo<minifi::Properties>(); TestController test_controller; - auto properties_path = test_controller.createTempDirectory() / "test.properties"; + const auto conf_directory = test_controller.createTempDirectory(); + const auto original_properties_path = conf_directory / "test.properties"; + const auto updated_properties_path = conf_directory / "test.properties.d" / PropertiesImpl::C2PropertiesFileName; - std::ofstream{properties_path} + std::ofstream{original_properties_path} << "nifi.c2.agent.heartbeat.period=1min\n" << "nifi.administrative.yield.duration=30000\n"; - auto properties_file_time_after_creation = std::filesystem::last_write_time(properties_path); + auto properties_file_time_after_creation = std::filesystem::last_write_time(original_properties_path); const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::ConfigureImpl>(); - configure->loadConfigureFile(properties_path); + configure->loadConfigureFile(original_properties_path); CHECK(configure->get("nifi.c2.agent.heartbeat.period") == "60000"); CHECK(configure->get("nifi.administrative.yield.duration") == "30000 ms"); { - CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - std::string second_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(std::getline(properties_file, second_line)); - CHECK(first_line == "nifi.c2.agent.heartbeat.period=1min"); - CHECK(second_line == "nifi.administrative.yield.duration=30000"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(settingsInFileAreAsExpected(original_properties_path, {"nifi.c2.agent.heartbeat.period=1min", "nifi.administrative.yield.duration=30000"})); } CHECK(configure->commitChanges()); { - CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - std::string second_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(std::getline(properties_file, second_line)); - CHECK(first_line == "nifi.c2.agent.heartbeat.period=60000"); - CHECK(second_line == "nifi.administrative.yield.duration=30000 ms"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(updated_properties_path)); + CHECK(settingsInFileAreAsExpected(updated_properties_path, {"nifi.c2.agent.heartbeat.period=60000", "nifi.administrative.yield.duration=30000 ms"})); } + + const std::shared_ptr<minifi::Configure> configure_reread = std::make_shared<minifi::ConfigureImpl>(); + configure_reread->loadConfigureFile(original_properties_path); + CHECK(configure_reread->get("nifi.c2.agent.heartbeat.period") == "60000"); + CHECK(configure_reread->get("nifi.administrative.yield.duration") == "30000 ms"); } TEST_CASE("Configuration can fix misconfigured datasize<->integer validated properties") { @@ -101,80 +111,159 @@ TEST_CASE("Configuration can fix misconfigured datasize<->integer validated prop LogTestController::getInstance().setInfo<minifi::Properties>(); TestController test_controller; - auto properties_path = test_controller.createTempDirectory() / "test.properties"; + const auto conf_directory = test_controller.createTempDirectory(); + const auto original_properties_path = conf_directory / "test.properties"; + const auto updated_properties_path = conf_directory / "test.properties.d" / PropertiesImpl::C2PropertiesFileName; { - std::ofstream properties_file(properties_path); + std::ofstream properties_file(original_properties_path); properties_file << "appender.rolling.max_file_size=6000" << std::endl; properties_file.close(); } - auto properties_file_time_after_creation = std::filesystem::last_write_time(properties_path); + auto properties_file_time_after_creation = std::filesystem::last_write_time(original_properties_path); const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::ConfigureImpl>(); - - configure->loadConfigureFile(properties_path, "nifi.log."); + configure->loadConfigureFile(original_properties_path, "nifi.log."); CHECK(configure->get("appender.rolling.max_file_size") == "6000 B"); { - CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(first_line == "appender.rolling.max_file_size=6000"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(settingsInFileAreAsExpected(original_properties_path, {"appender.rolling.max_file_size=6000"})); } CHECK(configure->commitChanges()); { - CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(first_line == "appender.rolling.max_file_size=6000 B"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(updated_properties_path)); + CHECK(settingsInFileAreAsExpected(updated_properties_path, {"appender.rolling.max_file_size=6000 B"})); } -} + const std::shared_ptr<minifi::Configure> configure_reread = std::make_shared<minifi::ConfigureImpl>(); + configure_reread->loadConfigureFile(original_properties_path, "nifi.log."); + CHECK(configure_reread->get("appender.rolling.max_file_size") == "6000 B"); +} TEST_CASE("Configuration can fix misconfigured validated properties within environmental variables") { LogTestController::getInstance().setInfo<minifi::Configure>(); LogTestController::getInstance().setInfo<minifi::Properties>(); + TestController test_controller; - auto properties_path = test_controller.createTempDirectory() / "test.properties"; + const auto conf_directory = test_controller.createTempDirectory(); + const auto original_properties_path = conf_directory / "test.properties"; + const auto updated_properties_path = conf_directory / "test.properties.d" / PropertiesImpl::C2PropertiesFileName; CHECK(minifi::utils::Environment::setEnvironmentVariable("SOME_VARIABLE", "4000")); - std::ofstream{properties_path} + std::ofstream{original_properties_path} << "compression.cached.log.max.size=${SOME_VARIABLE}\n" << "compression.compressed.log.max.size=3000\n"; - auto properties_file_time_after_creation = std::filesystem::last_write_time(properties_path); + auto properties_file_time_after_creation = std::filesystem::last_write_time(original_properties_path); const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::ConfigureImpl>(); - configure->loadConfigureFile(properties_path, "nifi.log."); + configure->loadConfigureFile(original_properties_path, "nifi.log."); CHECK(configure->get("compression.cached.log.max.size") == "4000 B"); CHECK(configure->get("compression.compressed.log.max.size") == "3000 B"); { - CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - std::string second_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(std::getline(properties_file, second_line)); - CHECK(first_line == "compression.cached.log.max.size=${SOME_VARIABLE}"); - CHECK(second_line == "compression.compressed.log.max.size=3000"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(settingsInFileAreAsExpected(original_properties_path, {"compression.cached.log.max.size=${SOME_VARIABLE}", "compression.compressed.log.max.size=3000"})); } CHECK(configure->commitChanges()); { - CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(properties_path)); - std::ifstream properties_file(properties_path); - std::string first_line; - std::string second_line; - CHECK(std::getline(properties_file, first_line)); - CHECK(std::getline(properties_file, second_line)); - CHECK(first_line == "compression.cached.log.max.size=${SOME_VARIABLE}"); - CHECK(second_line == "compression.compressed.log.max.size=3000 B"); + CHECK(properties_file_time_after_creation == std::filesystem::last_write_time(original_properties_path)); + CHECK(properties_file_time_after_creation <= std::filesystem::last_write_time(updated_properties_path)); + CHECK(settingsInFileAreAsExpected(updated_properties_path, {"compression.compressed.log.max.size=3000 B"})); } + + const std::shared_ptr<minifi::Configure> configure_reread = std::make_shared<minifi::ConfigureImpl>(); + configure_reread->loadConfigureFile(original_properties_path, "nifi.log."); + CHECK(configure_reread->get("compression.cached.log.max.size") == "4000 B"); + CHECK(configure_reread->get("compression.compressed.log.max.size") == "3000 B"); +} + +TEST_CASE("Committing changes to a configuration creates a backup file") { + LogTestController::getInstance().setInfo<minifi::Configure>(); + LogTestController::getInstance().setInfo<minifi::Properties>(); + + TestController test_controller; + const auto conf_directory = test_controller.createTempDirectory(); + const auto original_properties_path = conf_directory / "test.properties"; + const auto updated_properties_path = conf_directory / "test.properties.d" / PropertiesImpl::C2PropertiesFileName; + const auto backup_properties_path = [&]() { + auto path = updated_properties_path; + path += ".bak"; + return path; + }(); + + std::ofstream{original_properties_path} + << "number.of.lions=7\n" + << "number.of.elephants=12\n" + << "number.of.giraffes=30\n"; + + const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::ConfigureImpl>(); + configure->loadConfigureFile(original_properties_path); + + CHECK(configure->get("number.of.lions") == "7"); + CHECK(configure->get("number.of.elephants") == "12"); + CHECK(configure->get("number.of.giraffes") == "30"); + + configure->set("number.of.lions", "8"); + CHECK(configure->commitChanges()); + CHECK(settingsInFileAreAsExpected(updated_properties_path, {"number.of.lions=8"})); + CHECK_FALSE(std::filesystem::exists(backup_properties_path)); + + const std::shared_ptr<minifi::Configure> configure_2 = std::make_shared<minifi::ConfigureImpl>(); + configure_2->loadConfigureFile(original_properties_path); + CHECK(configure_2->get("number.of.lions") == "8"); + CHECK(configure_2->get("number.of.elephants") == "12"); + CHECK(configure_2->get("number.of.giraffes") == "30"); + + configure->set("number.of.giraffes", "29"); + CHECK(configure->commitChanges()); + CHECK(settingsInFileAreAsExpected(updated_properties_path, {"number.of.lions=8", "number.of.giraffes=29"})); + CHECK(settingsInFileAreAsExpected(backup_properties_path, {"number.of.lions=8"})); + + const std::shared_ptr<minifi::Configure> configure_3 = std::make_shared<minifi::ConfigureImpl>(); + configure_3->loadConfigureFile(original_properties_path); + CHECK(configure_3->get("number.of.lions") == "8"); + CHECK(configure_3->get("number.of.elephants") == "12"); + CHECK(configure_3->get("number.of.giraffes") == "29"); +} + +TEST_CASE("Backup file are skipped when reading config files") { + LogTestController::getInstance().setInfo<minifi::Configure>(); + LogTestController::getInstance().setInfo<minifi::Properties>(); + + TestController test_controller; + const auto conf_directory = test_controller.createTempDirectory(); + const auto original_properties_path = conf_directory / "test.properties"; + const auto updated_properties_path = conf_directory / "test.properties.d" / PropertiesImpl::C2PropertiesFileName; + const auto backup_properties_path = [&]() { + auto path = updated_properties_path; + path += ".bak"; + return path; + }(); + + std::ofstream{original_properties_path} + << "number.of.lions=7\n" + << "number.of.elephants=12\n"; + + utils::file::create_dir(updated_properties_path.parent_path()); + std::ofstream{updated_properties_path} + << "number.of.lions=8\n"; + + std::ofstream{backup_properties_path} + << "number.of.elephants=20\n" + << "number.of.giraffes=30\n"; + + const std::shared_ptr<minifi::Configure> configure = std::make_shared<minifi::ConfigureImpl>(); + configure->loadConfigureFile(original_properties_path); + + CHECK(configure->get("number.of.lions") == "8"); + CHECK(configure->get("number.of.elephants") == "12"); + CHECK_FALSE(configure->get("number.of.giraffes")); } } // namespace org::apache::nifi::minifi::test diff --git a/libminifi/include/core/logging/LoggerProperties.h b/libminifi/include/core/logging/LoggerProperties.h index 90098d447..c776216cb 100644 --- a/libminifi/include/core/logging/LoggerProperties.h +++ b/libminifi/include/core/logging/LoggerProperties.h @@ -32,7 +32,7 @@ namespace org::apache::nifi::minifi::core::logging { class LoggerProperties : public PropertiesImpl { public: explicit LoggerProperties(std::filesystem::path default_log_dir) - : PropertiesImpl("Logger properties"), + : PropertiesImpl(PersistTo::MultipleFiles, "Logger properties"), default_log_dir_(std::move(default_log_dir)) { } /** diff --git a/libminifi/include/properties/Configuration.h b/libminifi/include/properties/Configuration.h index 547aeb35d..0dbdb434f 100644 --- a/libminifi/include/properties/Configuration.h +++ b/libminifi/include/properties/Configuration.h @@ -34,7 +34,7 @@ class PropertyValidator; class ConfigurationImpl : public PropertiesImpl, public virtual Configuration { public: - ConfigurationImpl() : PropertiesImpl("MiNiFi configuration") {} + ConfigurationImpl() : PropertiesImpl(PersistTo::MultipleFiles, "MiNiFi configuration") {} }; } // namespace org::apache::nifi::minifi diff --git a/libminifi/include/properties/Properties.h b/libminifi/include/properties/Properties.h index f103d0565..4f94f6401 100644 --- a/libminifi/include/properties/Properties.h +++ b/libminifi/include/properties/Properties.h @@ -41,10 +41,14 @@ class PropertiesImpl : public virtual Properties { }; public: - explicit PropertiesImpl(std::string name = ""); + enum class PersistTo { SingleFile, MultipleFiles }; + + explicit PropertiesImpl(PersistTo persist_to, std::string name = ""); ~PropertiesImpl() override = default; + static constexpr std::string_view C2PropertiesFileName = "90_c2.properties"; + const std::string& getName() const override { return name_; } @@ -104,7 +108,6 @@ class PropertiesImpl : public virtual Properties { void loadConfigureFile(const std::filesystem::path& configuration_file, std::string_view prefix = "") override; - std::vector<std::string> getConfiguredKeys() const override { std::vector<std::string> keys; for (auto &property : properties_) { @@ -122,18 +125,17 @@ class PropertiesImpl : public virtual Properties { std::map<std::string, std::string> getProperties() const override; private: - std::map<std::string, PropertyValue> properties_; + std::filesystem::path extraPropertiesFilesDirName() const; + void setPropertiesFromFile(const std::filesystem::path& properties_file, std::string_view prefix); + std::map<std::string, PropertyValue> properties_; bool dirty_{false}; - - std::filesystem::path properties_file_; - + std::filesystem::path base_properties_file_; + std::vector<std::filesystem::path> properties_files_; utils::ChecksumCalculator checksum_calculator_; - - // Mutex for protection mutable std::mutex mutex_; std::shared_ptr<core::logging::Logger> logger_; - + PersistTo persist_to_; std::string name_; }; diff --git a/libminifi/include/utils/ChecksumCalculator.h b/libminifi/include/utils/ChecksumCalculator.h index 8cc1a4ed4..c11931d58 100644 --- a/libminifi/include/utils/ChecksumCalculator.h +++ b/libminifi/include/utils/ChecksumCalculator.h @@ -20,7 +20,7 @@ #include <filesystem> #include <optional> #include <string> -#include <utility> +#include <vector> namespace org::apache::nifi::minifi::utils { @@ -29,16 +29,15 @@ class ChecksumCalculator { static constexpr const char* CHECKSUM_TYPE = "SHA256"; static constexpr size_t LENGTH_OF_HASH_IN_BYTES = 32; - void setFileLocation(const std::filesystem::path& file_location); - [[nodiscard]] std::filesystem::path getFileName() const; + void setFileLocations(std::vector<std::filesystem::path> file_locations); + [[nodiscard]] std::filesystem::path getFileNameOfFirstFileLocation() const; std::string getChecksum(); void invalidateChecksum(); private: - static std::string computeChecksum(const std::filesystem::path& file_location); + static std::string computeChecksum(const std::vector<std::filesystem::path>& file_locations); - std::optional<std::filesystem::path> file_location_; - std::optional<std::filesystem::path> file_name_; + std::vector<std::filesystem::path> file_locations_; std::optional<std::string> checksum_; }; diff --git a/libminifi/src/core/FlowConfiguration.cpp b/libminifi/src/core/FlowConfiguration.cpp index 2e7b56484..11f58bd10 100644 --- a/libminifi/src/core/FlowConfiguration.cpp +++ b/libminifi/src/core/FlowConfiguration.cpp @@ -29,6 +29,19 @@ #include "minifi-cpp/SwapManager.h" #include "Connection.h" +namespace { +void createDefaultFlowConfigFile(const std::filesystem::path& path) { + std::ofstream ostream(path); + ostream.exceptions(std::ofstream::failbit | std::ofstream::badbit); + ostream << "Flow Controller:\n" + " name: MiNiFi Flow\n" + "Processors: []\n" + "Connections: []\n" + "Remote Processing Groups: []\n" + "Provenance Reporting:\n"; +} +} // namespace + namespace org::apache::nifi::minifi::core { FlowConfiguration::FlowConfiguration(ConfigurationContext ctx) @@ -52,12 +65,16 @@ FlowConfiguration::FlowConfiguration(ConfigurationContext ctx) if (!ctx.path) { logger_->log_error("Configuration path is not specified."); } else { + const bool c2_enabled = configuration_->get(Configure::nifi_c2_enable).and_then(&utils::string::toBool).value_or(false); + if (!c2_enabled && !ctx.path->empty() && !std::filesystem::exists(*ctx.path)) { + createDefaultFlowConfigFile(*ctx.path); + } config_path_ = utils::file::canonicalize(*ctx.path); if (!config_path_) { logger_->log_error("Couldn't find config file \"{}\".", ctx.path->string()); config_path_ = ctx.path; } - checksum_calculator_.setFileLocation(*config_path_); + checksum_calculator_.setFileLocations(std::vector{*config_path_}); } } diff --git a/libminifi/src/core/state/nodes/ConfigurationChecksums.cpp b/libminifi/src/core/state/nodes/ConfigurationChecksums.cpp index c2181e7fc..f9b965c10 100644 --- a/libminifi/src/core/state/nodes/ConfigurationChecksums.cpp +++ b/libminifi/src/core/state/nodes/ConfigurationChecksums.cpp @@ -32,7 +32,7 @@ std::vector<SerializedResponseNode> ConfigurationChecksums::serialize() { for (auto checksum_calculator : checksum_calculators_) { SerializedResponseNode file_checksum_node; - file_checksum_node.name = checksum_calculator->getFileName().string(); + file_checksum_node.name = checksum_calculator->getFileNameOfFirstFileLocation().string(); file_checksum_node.value = checksum_calculator->getChecksum(); checksums_node.children.push_back(file_checksum_node); } diff --git a/libminifi/src/properties/Properties.cpp b/libminifi/src/properties/Properties.cpp index b60e1499d..1624a8557 100644 --- a/libminifi/src/properties/Properties.cpp +++ b/libminifi/src/properties/Properties.cpp @@ -18,19 +18,20 @@ #include "properties/Properties.h" #include <fstream> +#include <ranges> #include <string> #include "core/logging/LoggerConfiguration.h" #include "properties/Configuration.h" #include "properties/PropertiesFile.h" -#include "range/v3/algorithm/all_of.hpp" #include "utils/StringUtils.h" #include "utils/file/FileUtils.h" namespace org::apache::nifi::minifi { -PropertiesImpl::PropertiesImpl(std::string name) +PropertiesImpl::PropertiesImpl(PersistTo persist_to, std::string name) : logger_(core::logging::LoggerFactory<Properties>::getLogger()), + persist_to_(persist_to), name_(std::move(name)) { } @@ -72,12 +73,12 @@ const core::PropertyValidator* getValidator(const std::string& lookup_value) { // isdigit requires unsigned chars as input bool allDigits(const std::string& value) { - return ranges::all_of(value, [](const unsigned char c){ return ::isdigit(c); }); + return std::ranges::all_of(value, [](const unsigned char c){ return ::isdigit(c); }); } // isdigit requires unsigned chars as input bool allDigitsOrSpaces(const std::string& value) { - return ranges::all_of(value, [](const unsigned char c) { return std::isdigit(c) || std::isspace(c);}); + return std::ranges::all_of(value, [](const unsigned char c) { return std::isdigit(c) || std::isspace(c);}); } std::optional<std::string> ensureTimePeriodValidatedPropertyHasExplicitUnit(const core::PropertyValidator* const validator, const std::string& value) { @@ -157,8 +158,41 @@ void fixValidatedProperty(const std::string& property_name, needs_to_persist_new_value = false; } } + +auto getExtraPropertiesFileNames(const std::filesystem::path& extra_properties_files_dir, const std::shared_ptr<core::logging::Logger>& logger) { + std::vector<std::filesystem::path> extra_properties_file_names; + if (utils::file::exists(extra_properties_files_dir) && utils::file::is_directory(extra_properties_files_dir)) { + utils::file::list_dir(extra_properties_files_dir, [&](const std::filesystem::path&, const std::filesystem::path& file_name) { + if (file_name.string().ends_with(".properties")) { + extra_properties_file_names.push_back(file_name); + } + return true; + }, logger, /* recursive = */ false); + } + std::ranges::sort(extra_properties_file_names); + return extra_properties_file_names; +} + +void updateChangedPropertiesInPropertiesFile(minifi::PropertiesFile& current_content, const auto& properties) { + for (const auto& prop : properties) { + if (!prop.second.need_to_persist_new_value) { + continue; + } + if (current_content.hasValue(prop.first)) { + current_content.update(prop.first, prop.second.persisted_value); + } else { + current_content.append(prop.first, prop.second.persisted_value); + } + } +} } // namespace +std::filesystem::path PropertiesImpl::extraPropertiesFilesDirName() const { + auto extra_properties_files_dir = base_properties_file_; + extra_properties_files_dir += ".d"; + return extra_properties_files_dir; +} + void PropertiesImpl::loadConfigureFile(const std::filesystem::path& configuration_file, std::string_view prefix) { std::lock_guard<std::mutex> lock(mutex_); if (configuration_file.empty()) { @@ -173,23 +207,42 @@ void PropertiesImpl::loadConfigureFile(const std::filesystem::path& configuratio } std::error_code ec; - properties_file_ = std::filesystem::canonical(configuration_file, ec); + base_properties_file_ = std::filesystem::canonical(configuration_file, ec); if (ec.value() != 0) { logger_->log_warn("Configuration file '{}' does not exist, and it could not be created", configuration_file); return; } + properties_files_ = { base_properties_file_ }; + const auto extra_properties_files_dir = extraPropertiesFilesDirName(); + const auto extra_properties_file_names = getExtraPropertiesFileNames(extra_properties_files_dir, logger_); + for (const auto& file_name : extra_properties_file_names) { + properties_files_.push_back(extra_properties_files_dir / file_name); + } + logger_->log_info("Using configuration file to load configuration for {} from {} (located at {})", - getName().c_str(), configuration_file.string(), properties_file_.string()); + getName().c_str(), configuration_file.string(), base_properties_file_.string()); + if (!extra_properties_file_names.empty()) { + auto list_of_files = utils::string::join(", ", extra_properties_file_names, [](const auto& path) { return path.string(); }); + logger_->log_info("Also reading configuration from files {} in {}", list_of_files, extra_properties_files_dir.string()); + } + + properties_.clear(); + dirty_ = false; + for (const auto& properties_file : properties_files_) { + setPropertiesFromFile(properties_file, prefix); + } + + checksum_calculator_.setFileLocations(properties_files_); +} - std::ifstream file(properties_file_, std::ifstream::in); +void PropertiesImpl::setPropertiesFromFile(const std::filesystem::path& properties_file, std::string_view prefix) { + std::ifstream file(properties_file, std::ifstream::in); if (!file.good()) { - logger_->log_error("load configure file failed {}", properties_file_); + logger_->log_error("load configure file failed {}", properties_file); return; } - properties_.clear(); - dirty_ = false; for (const auto& line : PropertiesFile{file}) { auto key = line.getKey(); auto persisted_value = line.getValue(); @@ -199,59 +252,65 @@ void PropertiesImpl::loadConfigureFile(const std::filesystem::path& configuratio dirty_ = dirty_ || need_to_persist_new_value; properties_[key] = {persisted_value, value, need_to_persist_new_value}; } - checksum_calculator_.setFileLocation(properties_file_); } std::filesystem::path PropertiesImpl::getFilePath() const { std::lock_guard<std::mutex> lock(mutex_); - return properties_file_; + return base_properties_file_; } bool PropertiesImpl::commitChanges() { std::lock_guard<std::mutex> lock(mutex_); if (!dirty_) { - logger_->log_info("Attempt to persist, but properties are not updated"); + logger_->log_debug("commitChanges() called, but properties have not changed, nothing to do"); return true; } - std::ifstream file(properties_file_, std::ifstream::in); + const auto output_file = (persist_to_ == PersistTo::SingleFile ? base_properties_file_ : extraPropertiesFilesDirName() / C2PropertiesFileName); + if (!std::filesystem::exists(output_file)) { + logger_->log_debug("Configuration file {} does not exist yet, creating it", output_file); + utils::file::create_dir(output_file.parent_path(), /* recursive = */ true); + std::ofstream file{output_file}; + } + + std::ifstream file(output_file, std::ifstream::in); if (!file) { - logger_->log_error("load configure file failed {}", properties_file_); + logger_->log_error("Failed to load configuration file {}", output_file); return false; } - - auto new_file = properties_file_; - new_file += ".new"; - PropertiesFile current_content{file}; - for (const auto& prop : properties_) { - if (!prop.second.need_to_persist_new_value) { - continue; - } - if (current_content.hasValue(prop.first)) { - current_content.update(prop.first, prop.second.persisted_value); - } else { - current_content.append(prop.first, prop.second.persisted_value); - } - } + file.close(); + + updateChangedPropertiesInPropertiesFile(current_content, properties_); + auto new_file = output_file; + new_file += ".new"; try { current_content.writeTo(new_file); } catch (const std::exception&) { - logger_->log_error("Could not update {}", properties_file_); + logger_->log_error("Could not write to {}", new_file); return false; } - auto backup = properties_file_; - backup += ".bak"; - if (utils::file::FileUtils::copy_file(properties_file_, backup) == 0 && utils::file::FileUtils::copy_file(new_file, properties_file_) == 0) { - logger_->log_info("Persisted {}", properties_file_); - checksum_calculator_.invalidateChecksum(); - dirty_ = false; - return true; + std::error_code ec; + const auto existing_file_size = std::filesystem::file_size(output_file, ec); + if (ec || existing_file_size == 0) { + if (!utils::file::move_file(new_file, output_file)) { + logger_->log_error("Could not create minifi properties file {}", output_file); + return false; + } + } else { + auto backup = output_file; + backup += ".bak"; + if (!utils::file::move_file(output_file, backup) || !utils::file::move_file(new_file, output_file)) { + logger_->log_error("Could not update minifi properties file {}", output_file); + return false; + } } - logger_->log_error("Could not update {}", properties_file_); - return false; + logger_->log_info("Persisted {}", output_file); + checksum_calculator_.invalidateChecksum(); + dirty_ = false; + return true; } std::map<std::string, std::string> PropertiesImpl::getProperties() const { @@ -264,7 +323,7 @@ std::map<std::string, std::string> PropertiesImpl::getProperties() const { } std::shared_ptr<Properties> Properties::create() { - return std::make_shared<PropertiesImpl>(); + return std::make_shared<PropertiesImpl>(PropertiesImpl::PersistTo::SingleFile); } } // namespace org::apache::nifi::minifi diff --git a/libminifi/src/utils/ChecksumCalculator.cpp b/libminifi/src/utils/ChecksumCalculator.cpp index 14e1f831d..ae32e8ee4 100644 --- a/libminifi/src/utils/ChecksumCalculator.cpp +++ b/libminifi/src/utils/ChecksumCalculator.cpp @@ -21,7 +21,6 @@ #include <fstream> #include "sodium/crypto_hash_sha256.h" -#include "utils/file/FileUtils.h" #include "utils/StringUtils.h" #include "properties/Configuration.h" @@ -29,51 +28,59 @@ namespace { const std::string AGENT_IDENTIFIER_KEY = std::string(org::apache::nifi::minifi::Configuration::nifi_c2_agent_identifier) + "="; +namespace utils = org::apache::nifi::minifi::utils; + +void addFileToChecksum(const std::filesystem::path& file_path, crypto_hash_sha256_state& state) { + std::ifstream input_file{file_path, std::ios::in | std::ios::binary}; + if (!input_file.is_open()) { + throw std::runtime_error(utils::string::join_pack("Could not open config file '", file_path.string(), "' to compute the checksum: ", std::strerror(errno))); + } + + std::string line; + while (std::getline(input_file, line)) { + // skip lines containing the agent identifier, so agents in the same class will have the same checksum + if (line.starts_with(AGENT_IDENTIFIER_KEY)) { + continue; + } + if (!input_file.eof()) { // eof() means we have just read the last line, which was not terminated by a newline + line.append("\n"); + } + crypto_hash_sha256_update(&state, reinterpret_cast<const unsigned char*>(line.data()), line.size()); + } + if (input_file.bad()) { + throw std::runtime_error(utils::string::join_pack("Error reading config file '", file_path.string(), "' while computing the checksum: ", std::strerror(errno))); + } +} + } // namespace namespace org::apache::nifi::minifi::utils { -void ChecksumCalculator::setFileLocation(const std::filesystem::path& file_location) { - file_location_ = file_location; - file_name_ = file_location.filename(); +void ChecksumCalculator::setFileLocations(std::vector<std::filesystem::path> file_locations) { + gsl_Expects(!file_locations.empty()); + file_locations_ = std::move(file_locations); invalidateChecksum(); } -std::filesystem::path ChecksumCalculator::getFileName() const { - gsl_Expects(file_name_); - return *file_name_; +std::filesystem::path ChecksumCalculator::getFileNameOfFirstFileLocation() const { + gsl_Expects(!file_locations_.empty()); + return file_locations_.front().filename(); } std::string ChecksumCalculator::getChecksum() { - gsl_Expects(file_location_); + gsl_Expects(!file_locations_.empty()); if (!checksum_) { - checksum_ = computeChecksum(*file_location_); + checksum_ = computeChecksum(file_locations_); } return *checksum_; } -std::string ChecksumCalculator::computeChecksum(const std::filesystem::path& file_location) { - std::ifstream input_file{file_location, std::ios::in | std::ios::binary}; - if (!input_file.is_open()) { - throw std::runtime_error(string::join_pack("Could not open config file '", file_location.string(), "' to compute the checksum: ", std::strerror(errno))); - } - +std::string ChecksumCalculator::computeChecksum(const std::vector<std::filesystem::path>& file_locations) { crypto_hash_sha256_state state; crypto_hash_sha256_init(&state); - std::string line; - while (std::getline(input_file, line)) { - // skip lines containing the agent identifier, so agents in the same class will have the same checksum - if (string::startsWith(line, AGENT_IDENTIFIER_KEY)) { - continue; - } - if (!input_file.eof()) { // eof() means we have just read the last line, which was not terminated by a newline - line.append("\n"); - } - crypto_hash_sha256_update(&state, reinterpret_cast<const unsigned char*>(line.data()), line.size()); - } - if (input_file.bad()) { - throw std::runtime_error(string::join_pack("Error reading config file '", file_location.string(), "' while computing the checksum: ", std::strerror(errno))); + for (const auto& file_location : file_locations) { + addFileToChecksum(file_location, state); } std::array<unsigned char, LENGTH_OF_HASH_IN_BYTES> hash{}; diff --git a/libminifi/test/integration/C2PropertiesUpdateTests.cpp b/libminifi/test/integration/C2PropertiesUpdateTests.cpp index b762a2c86..e6cf2389d 100644 --- a/libminifi/test/integration/C2PropertiesUpdateTests.cpp +++ b/libminifi/test/integration/C2PropertiesUpdateTests.cpp @@ -155,9 +155,9 @@ TEST_CASE("C2PropertiesUpdateTests", "[c2test]") { logger2->log_debug("DummyClass2::before"); logger3->log_debug("DummyClass3::before"); - REQUIRE(!log_test_controller->contains("DummyClass1::before", 0s)); - REQUIRE(!log_test_controller->contains("DummyClass2::before", 0s)); - REQUIRE(!log_test_controller->contains("DummyClass3::before", 0s)); + CHECK(!log_test_controller->contains("DummyClass1::before", 0s)); + CHECK(!log_test_controller->contains("DummyClass2::before", 0s)); + CHECK(!log_test_controller->contains("DummyClass3::before", 0s)); } // On msvc, the passed lambda can't capture a reference to the object under construction, so we need to late-init harness. @@ -194,23 +194,25 @@ TEST_CASE("C2PropertiesUpdateTests", "[c2test]") { logger2->log_debug("DummyClass2::after"); // this should still not log logger3->log_debug("DummyClass3::after"); } - REQUIRE(log_test_controller->contains("DummyClass1::after", 0s)); - REQUIRE(!log_test_controller->contains("DummyClass2::after", 0s)); - REQUIRE(log_test_controller->contains("DummyClass3::after", 0s)); + CHECK(log_test_controller->contains("DummyClass1::after", 0s)); + CHECK_FALSE(log_test_controller->contains("DummyClass2::after", 0s)); + CHECK(log_test_controller->contains("DummyClass3::after", 0s)); { - minifi::PropertiesFile minifi_properties(std::ifstream{home_dir / "conf/minifi.properties"}); - REQUIRE(!minifi_properties.hasValue("nifi.dummy.property")); - REQUIRE(minifi_properties.getValue("nifi.property.one") == "bush"); - REQUIRE(minifi_properties.getValue("nifi.property.two") == "ring"); - REQUIRE(!minifi_properties.hasValue(minifi::Configuration::nifi_c2_rest_heartbeat_minimize_updates)); - REQUIRE(minifi_properties.getValue(minifi::Configuration::minifi_disk_space_watchdog_enable) == "true"); + const std::shared_ptr<minifi::Configure> minifi_properties = std::make_shared<minifi::ConfigureImpl>(); + minifi_properties->loadConfigureFile(home_dir / "conf" / "minifi.properties"); + CHECK_FALSE(minifi_properties->get("nifi.dummy.property")); + CHECK(minifi_properties->get("nifi.property.one") == "bush"); + CHECK(minifi_properties->get("nifi.property.two") == "ring"); + CHECK_FALSE(minifi_properties->get(minifi::Configuration::nifi_c2_rest_heartbeat_minimize_updates)); + CHECK(minifi_properties->get(minifi::Configuration::minifi_disk_space_watchdog_enable) == "true"); } { - minifi::PropertiesFile minifi_log_properties(std::ifstream{home_dir / "conf/minifi-log.properties"}); - REQUIRE(!minifi_log_properties.hasValue("logger.org::apache::nifi::minifi::test::dummy")); - REQUIRE(minifi_log_properties.getValue("logger.org::apache::nifi::minifi::test::DummyClass1") == "DEBUG,ostream"); + const std::shared_ptr<minifi::Configure> minifi_log_properties = std::make_shared<minifi::ConfigureImpl>(); + minifi_log_properties->loadConfigureFile(home_dir / "conf" / "minifi-log.properties"); + CHECK_FALSE(minifi_log_properties->get("logger.org::apache::nifi::minifi::test::dummy")); + CHECK(minifi_log_properties->get("logger.org::apache::nifi::minifi::test::DummyClass1") == "DEBUG,ostream"); } }); diff --git a/libminifi/test/unit/ChecksumCalculatorTests.cpp b/libminifi/test/unit/ChecksumCalculatorTests.cpp index fabecddfe..82623814c 100644 --- a/libminifi/test/unit/ChecksumCalculatorTests.cpp +++ b/libminifi/test/unit/ChecksumCalculatorTests.cpp @@ -37,17 +37,33 @@ TEST_CASE("ChecksumCalculator can calculate the checksum, which is equal to sha2 REQUIRE(size_t{utils::ChecksumCalculator::LENGTH_OF_HASH_IN_BYTES} == size_t{32}); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); REQUIRE(checksum_calculator.getChecksum() == CHECKSUM_FOR_ONE_LINE_OF_TEXT); } +TEST_CASE("The input of ChecksumCalculator can be in multiple files", "[ChecksumCalculator]") { + TestController test_controller; + const auto test_dir = test_controller.createTempDirectory(); + const auto single_file_location = minifi::test::utils::putFileToDir(test_dir, "single.txt", "one line of text\nsecond line of text\n"); + const auto multiple_1_file_location = minifi::test::utils::putFileToDir(test_dir, "multiple_1.txt", "one line of text\n"); + const auto multiple_2_file_location = minifi::test::utils::putFileToDir(test_dir, "multiple_2.txt", "second line of text\n"); + + utils::ChecksumCalculator checksum_calculator_single; + checksum_calculator_single.setFileLocations(std::vector{single_file_location}); + + utils::ChecksumCalculator checksum_calculator_multiple; + checksum_calculator_multiple.setFileLocations(std::vector{multiple_1_file_location, multiple_2_file_location}); + + CHECK(checksum_calculator_single.getChecksum() == checksum_calculator_multiple.getChecksum()); +} + TEST_CASE("On Windows text files, the checksum calculated is also the same as sha256sum", "[ChecksumCalculator]") { TestController test_controller; auto test_dir = test_controller.createTempDirectory(); auto file_location = minifi::test::utils::putFileToDir(test_dir, "simple.txt", "one line of text\r\n"); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); REQUIRE(checksum_calculator.getChecksum() == "94fc46c62ef6cc5b45cbad9fd53116cfb15a80960a9b311c1c27e5b5265ad4b4"); } @@ -57,7 +73,7 @@ TEST_CASE("The checksum can be reset and recomputed", "[ChecksumCalculator]") { auto file_location = minifi::test::utils::putFileToDir(test_dir, "simple.txt", "one line of text\n"); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); REQUIRE(checksum_calculator.getChecksum() == CHECKSUM_FOR_ONE_LINE_OF_TEXT); std::ofstream append_to_file(file_location, std::ios::binary | std::ios::app); @@ -76,11 +92,11 @@ TEST_CASE("If the file location is updated, the checksum will be recomputed", "[ auto file_location = minifi::test::utils::putFileToDir(test_dir, "simple.txt", "one line of text\n"); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); REQUIRE(checksum_calculator.getChecksum() == CHECKSUM_FOR_ONE_LINE_OF_TEXT); auto other_file_location = minifi::test::utils::putFileToDir(test_dir, "long.txt", "one line of text\nanother line of text\n"); - checksum_calculator.setFileLocation(other_file_location); + checksum_calculator.setFileLocations(std::vector{other_file_location}); REQUIRE(checksum_calculator.getChecksum() == CHECKSUM_FOR_TWO_LINES_OF_TEXT); } @@ -92,7 +108,7 @@ TEST_CASE("Checksums can be computed for binary (eg. encrypted) files, too", "[C auto file_location = minifi::test::utils::putFileToDir(test_dir, "simple.txt", binary_data); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); REQUIRE(checksum_calculator.getChecksum() == "bdec77160c394c067419735de757e4daa1c4679ea45e82a33fa8f706eed87709"); } @@ -109,16 +125,16 @@ TEST_CASE("The agent identifier is excluded from the checksum", "[ChecksumCalcul "nifi.c2.agent.heartbeat.period=10 sec\n"); utils::ChecksumCalculator checksum_calculator_1; - checksum_calculator_1.setFileLocation(file_location_1); + checksum_calculator_1.setFileLocations(std::vector{file_location_1}); utils::ChecksumCalculator checksum_calculator_2; - checksum_calculator_2.setFileLocation(file_location_2); + checksum_calculator_2.setFileLocations(std::vector{file_location_2}); REQUIRE(checksum_calculator_1.getChecksum() == checksum_calculator_2.getChecksum()); } TEST_CASE("ChecksumCalculator::getChecksum will throw if the file does not exist", "[ChecksumCalculator]") { utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation("/this/file/does/not/exist/84a77fd9-16b3-49d2-aead-a1f9e58e530d"); + checksum_calculator.setFileLocations(std::vector{std::filesystem::path{"/this/file/does/not/exist/84a77fd9-16b3-49d2-aead-a1f9e58e530d"}}); REQUIRE_THROWS(checksum_calculator.getChecksum()); } diff --git a/libminifi/test/unit/ConfigurationChecksumsTests.cpp b/libminifi/test/unit/ConfigurationChecksumsTests.cpp index 6da67a66e..956907ff2 100644 --- a/libminifi/test/unit/ConfigurationChecksumsTests.cpp +++ b/libminifi/test/unit/ConfigurationChecksumsTests.cpp @@ -39,7 +39,7 @@ TEST_CASE("If one checksum calculator is added, we get a node with one child", " auto file_location = minifi::test::utils::putFileToDir(test_dir, "simple.txt", "one line of text\n"); utils::ChecksumCalculator checksum_calculator; - checksum_calculator.setFileLocation(file_location); + checksum_calculator.setFileLocations(std::vector{file_location}); ConfigurationChecksums configuration_checksums; configuration_checksums.addChecksumCalculator(checksum_calculator); @@ -62,9 +62,9 @@ TEST_CASE("If two checksum calculators are added, we get a node with two childre auto file_location_2 = minifi::test::utils::putFileToDir(test_dir, "second.txt", "this is the second file\n"); utils::ChecksumCalculator checksum_calculator_1; - checksum_calculator_1.setFileLocation(file_location_1); + checksum_calculator_1.setFileLocations(std::vector{file_location_1}); utils::ChecksumCalculator checksum_calculator_2; - checksum_calculator_2.setFileLocation(file_location_2); + checksum_calculator_2.setFileLocations(std::vector{file_location_2}); ConfigurationChecksums configuration_checksums; configuration_checksums.addChecksumCalculator(checksum_calculator_1); diff --git a/libminifi/test/unit/IdTests.cpp b/libminifi/test/unit/IdTests.cpp index 048072b5d..aac8ef0fb 100644 --- a/libminifi/test/unit/IdTests.cpp +++ b/libminifi/test/unit/IdTests.cpp @@ -39,7 +39,7 @@ TEST_CASE("Test default is time", "[id]") { LogTestController::getInstance().setDebug<utils::IdGenerator>(); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); - generator->initialize(std::make_shared<minifi::PropertiesImpl>()); + generator->initialize(std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties")); REQUIRE(true == LogTestController::getInstance().contains("Using uuid_generate_time implementation for uids.")); LogTestController::getInstance().reset(); @@ -49,7 +49,7 @@ TEST_CASE("Test time", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "TiMe"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -69,7 +69,7 @@ TEST_CASE("Test Generate Move", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "TiMe"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -86,7 +86,7 @@ TEST_CASE("Test random", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "RaNDoM"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -106,7 +106,7 @@ TEST_CASE("Test uuid_default", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "UUID_default"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -120,7 +120,7 @@ TEST_CASE("Test invalid", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "InVaLiD"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -134,7 +134,7 @@ TEST_CASE("Test parse", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "time"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -160,7 +160,7 @@ TEST_CASE("Test parse invalid", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "time"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -187,7 +187,7 @@ TEST_CASE("Test to_string", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "time"); std::shared_ptr<utils::IdGenerator> generator = utils::IdGenerator::getIdGenerator(); @@ -229,7 +229,7 @@ TEST_CASE("Test Hex Device Segment 16 bits correct digits", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "minifi_uid"); id_props->set("uid.minifi.device.segment", "09aF"); @@ -255,7 +255,7 @@ TEST_CASE("Test Hex Device Segment 16 bits too many digits", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "minifi_uid"); id_props->set("uid.minifi.device.segment", "09aFee"); @@ -283,7 +283,7 @@ TEST_CASE("Test Hex Device Segment 18 bits", "[id]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); id_props->set("uid.implementation", "minifi_uid"); id_props->set("uid.minifi.device.segment.bits", "18"); id_props->set("uid.minifi.device.segment", "09aF8"); @@ -317,7 +317,7 @@ TEST_CASE("Collision", "[collision]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); SECTION("random") { id_props->set("uid.implementation", "random"); } @@ -357,7 +357,7 @@ TEST_CASE("Speed", "[speed]") { TestController test_controller; LogTestController::getInstance().setDebug<utils::IdGenerator>(); - std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(); + std::shared_ptr<minifi::Properties> id_props = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); std::string implementation; SECTION("random") { implementation = "random"; diff --git a/minifi_main/MiNiFiMain.cpp b/minifi_main/MiNiFiMain.cpp index d6d32a50c..f5c852cf1 100644 --- a/minifi_main/MiNiFiMain.cpp +++ b/minifi_main/MiNiFiMain.cpp @@ -306,7 +306,7 @@ int main(int argc, char **argv) { logger_configuration.initialize(log_properties); - std::shared_ptr<minifi::Properties> uid_properties = std::make_shared<minifi::PropertiesImpl>("UID properties"); + std::shared_ptr<minifi::Properties> uid_properties = std::make_shared<minifi::PropertiesImpl>(minifi::PropertiesImpl::PersistTo::MultipleFiles, "UID properties"); uid_properties->loadConfigureFile(locations->uid_properties_path_); utils::IdGenerator::getIdGenerator()->initialize(uid_properties); diff --git a/packaging/msi/WixWin.wsi.in b/packaging/msi/WixWin.wsi.in index e294b547d..9c912f604 100644 --- a/packaging/msi/WixWin.wsi.in +++ b/packaging/msi/WixWin.wsi.in @@ -58,10 +58,9 @@ ${WIX_EXTRA_FEATURES} <Feature Id="InstallConf" Title="Apache NiFi MiNiFi C++ Configuration" AllowAdvertise="yes" Level="1"> <ComponentRef Id="LOGPROP"/> <ComponentRef Id="UIDPROP"/> - <ComponentRef Id="CONFIGFILE"/> - <ComponentRef Id="OPENSSLCONFCOMP"/> <ComponentRef Id="UpdateConfig"/> - <ComponentRef Id="UpdateConfigNotExist"/> + <ComponentRef Id="MINIFIPROP"/> + <ComponentRef Id="OPENSSLCONFCOMP"/> </Feature> <?ifdef INCLUDE_PYTHON_PROCESSORS?> @@ -263,16 +262,16 @@ ${WIX_EXTRA_FEATURES} </UI> - <Property Id="AGENT_CLASS" Value="Your Agent Class" /> - <Property Id="AGENT_IDENTIFIER" /> - <Property Id="AGENT_HEARTBEAT" Value="30 sec" /> - <Property Id="SERVER_PATH_BASE" Value="http://localhost:8181/api" /> - <Property Id="SERVER_HEARTBEAT" Value="/c2-protocol/heartbeat" /> - <Property Id="SERVER_ACK" Value="/c2-protocol/acknowledge" /> - <Property Id="ENABLEC2" /> - <Property Id="AUTOSTART" Value="1" /> - <Property Id="SERVICEACCOUNT" Value="LocalSystem" /> - <Property Id="SERVICEACCOUNTPASSWORD" /> + <Property Id="AGENT_CLASS" Secure="yes" Value="Your Agent Class" /> + <Property Id="AGENT_IDENTIFIER" Secure="yes" /> + <Property Id="AGENT_HEARTBEAT" Secure="yes" Value="30 sec" /> + <Property Id="SERVER_PATH_BASE" Secure="yes" Value="http://localhost:8181/api" /> + <Property Id="SERVER_HEARTBEAT" Secure="yes" Value="/c2-protocol/heartbeat" /> + <Property Id="SERVER_ACK" Secure="yes" Value="/c2-protocol/acknowledge" /> + <Property Id="ENABLEC2" Secure="yes" /> + <Property Id="AUTOSTART" Secure="yes" Value="1" /> + <Property Id="SERVICEACCOUNT" Secure="yes" Value="LocalSystem" /> + <Property Id="SERVICEACCOUNTPASSWORD" Secure="yes" /> <SetProperty Id="AGENT_IDENTIFIER" After="AppSearch" Value="[ComputerName]" Sequence="first" /> <SetProperty Id="ENABLEC2" After="AppSearch" Value="0" Sequence="first" > @@ -303,27 +302,22 @@ ${WIX_EXTRA_FEATURES} <File Id="UID" Source="conf/minifi-uid.properties" KeyPath="yes"/> </Component> - <Component Id="CONFIGFILE" Guid="87658309-0339-425c-8633-f54ffaaa4944"> - <File Id="CONFIG" Source="conf/config.yml" KeyPath="yes"/> - </Component> - - <Component Id="UpdateConfig" Guid="87658309-0339-425c-8633-f54ffaaa4945"> - - <File Source="conf/minifi.properties" Id="minifiprops" KeyPath="yes" /> - - <IniFile Id="ConfigFileA" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.agent.class" Value="[AGENT_CLASS]" /> - <IniFile Id="ConfigFileI" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.agent.identifier" Value="[AGENT_IDENTIFIER]" /> - <IniFile Id="ConfigFileE" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.enable" Value="true" /> - <IniFile Id="ConfigFileT" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.agent.heartbeat.period" Value="[AGENT_HEARTBEAT]" /> - <IniFile Id="ConfigFileS" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.rest.path.base" Value="[SERVER_PATH_BASE]" /> - <IniFile Id="ConfigFileH" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.rest.url" Value="[SERVER_HEARTBEAT]" /> - <IniFile Id="ConfigFileAck" Action="addLine" Name="minifi.properties" Directory="CONFIGDIR" Section="c2props" Key="nifi.c2.rest.url.ack" Value="[SERVER_ACK]" /> - <Condition><![CDATA[ENABLEC2="1"]]></Condition> - </Component> - <Component Id="UpdateConfigNotExist" Guid="87658309-0339-425c-8633-f54ffaaa4946"> + <Directory Id="MINIFIPROP.D" Name="minifi.properties.d"> + <Component Id="UpdateConfig" Guid="87658309-0339-425c-8633-f54ffaaa4945" Permanent="yes"> + <CreateFolder /> + <IniFile Id="ConfigFileA" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.agent.class" Value="[AGENT_CLASS]" /> + <IniFile Id="ConfigFileI" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.agent.identifier" Value="[AGENT_IDENTIFIER]" /> + <IniFile Id="ConfigFileE" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.enable" Value="true" /> + <IniFile Id="ConfigFileT" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.agent.heartbeat.period" Value="[AGENT_HEARTBEAT]" /> + <IniFile Id="ConfigFileS" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.rest.path.base" Value="[SERVER_PATH_BASE]" /> + <IniFile Id="ConfigFileH" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.rest.url" Value="[SERVER_HEARTBEAT]" /> + <IniFile Id="ConfigFileAck" Action="addLine" Name="10_installer.properties" Directory="MINIFIPROP.D" Section="c2props" Key="nifi.c2.rest.url.ack" Value="[SERVER_ACK]" /> + <Condition><![CDATA[ENABLEC2="1"]]></Condition> + </Component> + </Directory> - <File Source="conf/minifi.properties" Id="minifiprops2" KeyPath="yes" /> - <Condition><![CDATA[ENABLEC2<>"1"]]></Condition> + <Component Id="MINIFIPROP" Guid="87658309-0339-425c-8633-f54ffaaa4946"> + <File Id="PROPERTIES" Source="conf/minifi.properties" KeyPath="yes" /> </Component> </Directory> diff --git a/packaging/rpm/expected-rpm-contents.in b/packaging/rpm/expected-rpm-contents.in index cfdd8fa0b..b4eb8a37f 100644 --- a/packaging/rpm/expected-rpm-contents.in +++ b/packaging/rpm/expected-rpm-contents.in @@ -1,5 +1,4 @@ /etc/nifi-minifi-cpp -/etc/nifi-minifi-cpp/config.yml /etc/nifi-minifi-cpp/fips /etc/nifi-minifi-cpp/fips/openssl.cnf /etc/nifi-minifi-cpp/minifi-log.properties
