| /** |
| * 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. |
| */ |
| |
| #define CUSTOM_EXTENSION_INIT |
| |
| #undef NDEBUG |
| #include <fstream> |
| |
| #include "TestBase.h" |
| #include "Catch.h" |
| #include "flow-tests/TestControllerWithFlow.h" |
| #include "EmptyFlow.h" |
| #include "c2/C2MetricsPublisher.h" |
| |
| using minifi::state::response::SerializedResponseNode; |
| |
| template<typename F> |
| const SerializedResponseNode* findNode(const std::vector<SerializedResponseNode>& nodes, F&& filter) { |
| for (auto& node : nodes) { |
| if (filter(node)) return &node; |
| } |
| return nullptr; |
| } |
| |
| const SerializedResponseNode& getNode(const std::vector<SerializedResponseNode>& nodes, std::string_view name) { |
| for (auto& node : nodes) { |
| if (node.name == name) return node; |
| } |
| assert(false); |
| } |
| |
| TEST_CASE("Python processor's description is part of the manifest") { |
| TestControllerWithFlow controller(empty_flow, false /* DEFER FLOW SETUP */); |
| |
| auto python_dir = std::filesystem::path(controller.configuration_->getHome()) / "minifi-python"; |
| utils::file::create_dir(python_dir); |
| std::ofstream{python_dir / "MyPyProc.py"} << |
| "def describe(proc):\n" |
| " proc.setDescription('An amazing processor')\n"; |
| |
| std::ofstream{python_dir / "MyPyProc2.py"} << |
| "def describe(proc):\n" |
| " proc.setDescription('Another amazing processor')\n" |
| " proc.setSupportsDynamicProperties()\n" |
| " proc.addProperty('Prop1', 'A great property', 'banana', True, False)\n"; |
| |
| controller.configuration_->set(minifi::Configuration::nifi_python_processor_dir, python_dir.string()); |
| controller.configuration_->set(minifi::Configuration::nifi_extension_path, "*minifi-python-script*,*minifi-http-curl*"); |
| |
| core::extension::ExtensionManager::get().initialize(controller.configuration_); |
| |
| controller.setupFlow(); |
| |
| auto c2_metrics_publisher = std::static_pointer_cast<minifi::c2::C2MetricsPublisher>(controller.metrics_publisher_store_->getMetricsPublisher(minifi::c2::C2_METRICS_PUBLISHER).lock()); |
| |
| auto agent_info = c2_metrics_publisher->getAgentManifest(); |
| |
| auto& manifest = getNode(agent_info.serialized_nodes, "agentManifest"); |
| |
| auto findPythonProcessor = [&] (const std::string& name) { |
| // each python file gets its own bundle |
| auto* python_bundle = findNode(manifest.children, [&] (auto& child) { |
| return child.name == "bundles" && findNode(child.children, [&] (auto& bundle_child) { |
| return bundle_child.name == "artifact" && bundle_child.value == name + ".py"; |
| }); |
| }); |
| REQUIRE(python_bundle); |
| |
| auto& py_processors = getNode(getNode(python_bundle->children, "componentManifest").children, "processors"); |
| |
| // single processor in each bundle |
| REQUIRE(py_processors.children.size() == 1); |
| |
| return findNode(py_processors.children, [&] (auto& child) {return utils::StringUtils::endsWith(child.name, name);}); |
| }; |
| |
| { |
| auto* MyPyProc = findPythonProcessor("MyPyProc"); |
| REQUIRE(MyPyProc); |
| |
| REQUIRE(getNode(MyPyProc->children, "inputRequirement").value == "INPUT_ALLOWED"); |
| REQUIRE(getNode(MyPyProc->children, "isSingleThreaded").value == true); |
| REQUIRE(getNode(MyPyProc->children, "typeDescription").value == "An amazing processor"); |
| REQUIRE(getNode(MyPyProc->children, "supportsDynamicRelationships").value == false); |
| REQUIRE(getNode(MyPyProc->children, "supportsDynamicProperties").value == false); |
| REQUIRE(getNode(MyPyProc->children, "type").value == "org.apache.nifi.minifi.processors.MyPyProc"); |
| |
| auto& rels = getNode(MyPyProc->children, "supportedRelationships").children; |
| REQUIRE(rels.size() == 2); |
| |
| auto* success = findNode(rels, [] (auto& rel) {return getNode(rel.children, "name").value == "success";}); |
| REQUIRE(success); |
| REQUIRE(getNode(success->children, "description").value == "Script succeeds"); |
| |
| auto* failure = findNode(rels, [] (auto& rel) {return getNode(rel.children, "name").value == "failure";}); |
| REQUIRE(failure); |
| REQUIRE(getNode(failure->children, "description").value == "Script fails"); |
| } |
| |
| |
| { |
| auto* MyPyProc2 = findPythonProcessor("MyPyProc2"); |
| REQUIRE(MyPyProc2); |
| |
| REQUIRE(getNode(MyPyProc2->children, "inputRequirement").value == "INPUT_ALLOWED"); |
| REQUIRE(getNode(MyPyProc2->children, "isSingleThreaded").value == true); |
| REQUIRE(getNode(MyPyProc2->children, "typeDescription").value == "Another amazing processor"); |
| REQUIRE(getNode(MyPyProc2->children, "supportsDynamicRelationships").value == false); |
| REQUIRE(getNode(MyPyProc2->children, "supportsDynamicProperties").value == true); |
| REQUIRE(getNode(MyPyProc2->children, "type").value == "org.apache.nifi.minifi.processors.MyPyProc2"); |
| |
| auto& properties = getNode(MyPyProc2->children, "propertyDescriptors").children; |
| REQUIRE(properties.size() == 1); |
| REQUIRE(properties[0].name == "Prop1"); |
| REQUIRE(getNode(properties[0].children, "name").value == "Prop1"); |
| REQUIRE(getNode(properties[0].children, "required").value == true); |
| REQUIRE(getNode(properties[0].children, "expressionLanguageScope").value == "NONE"); |
| REQUIRE(getNode(properties[0].children, "defaultValue").value == "banana"); |
| |
| auto& rels = getNode(MyPyProc2->children, "supportedRelationships").children; |
| REQUIRE(rels.size() == 2); |
| |
| auto* success = findNode(rels, [] (auto& rel) {return getNode(rel.children, "name").value == "success";}); |
| REQUIRE(success); |
| REQUIRE(getNode(success->children, "description").value == "Script succeeds"); |
| |
| auto* failure = findNode(rels, [] (auto& rel) {return getNode(rel.children, "name").value == "failure";}); |
| REQUIRE(failure); |
| REQUIRE(getNode(failure->children, "description").value == "Script fails"); |
| } |
| } |