NIFI-9009: Created VerifiableProcessor, VerifiableControllerService, VerifiableReportingTask components; implemented backend work to call the methods. Added REST APIs and created/updated data models for component configuration verification
Signed-off-by: Joe Gresock <jgresock@gmail.com>
This closes #5288
diff --git a/nifi-api/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java b/nifi-api/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java
new file mode 100644
index 0000000..9f2ab77
--- /dev/null
+++ b/nifi-api/src/main/java/org/apache/nifi/components/ConfigVerificationResult.java
@@ -0,0 +1,83 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.components;
+
+public class ConfigVerificationResult {
+ private final Outcome outcome;
+ private final String verificationStepName;
+ private final String explanation;
+
+ private ConfigVerificationResult(final Builder builder) {
+ outcome = builder.outcome;
+ verificationStepName = builder.verificationStepName;
+ explanation = builder.explanation;
+ }
+
+ public Outcome getOutcome() {
+ return outcome;
+ }
+
+ public String getVerificationStepName() {
+ return verificationStepName;
+ }
+
+ public String getExplanation() {
+ return explanation;
+ }
+
+ @Override
+ public String toString() {
+ return "ConfigVerificationResult[" +
+ "outcome=" + outcome +
+ ", verificationStepName='" + verificationStepName + "'" +
+ ", explanation='" + explanation + "']";
+ }
+
+ public static class Builder {
+ private Outcome outcome = Outcome.SKIPPED;
+ private String verificationStepName = "Unknown Step Name";
+ private String explanation;
+
+ public Builder outcome(final Outcome outcome) {
+ this.outcome = outcome;
+ return this;
+ }
+
+ public Builder verificationStepName(final String verificationStepName) {
+ this.verificationStepName = verificationStepName;
+ return this;
+ }
+
+ public Builder explanation(final String explanation) {
+ this.explanation = explanation;
+ return this;
+ }
+
+ public ConfigVerificationResult build() {
+ return new ConfigVerificationResult(this);
+ }
+ }
+
+ public enum Outcome {
+ SUCCESSFUL,
+
+ FAILED,
+
+ SKIPPED;
+ }
+}
diff --git a/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java b/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
index 81543d7..5b23eb8 100644
--- a/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
+++ b/nifi-api/src/main/java/org/apache/nifi/components/ValidationContext.java
@@ -16,6 +16,7 @@
*/
package org.apache.nifi.components;
+import org.apache.nifi.annotation.behavior.InputRequirement;
import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.ControllerServiceLookup;
@@ -132,4 +133,18 @@
* @return <code>true</code> if all dependencies of the given property descriptor are satisfied, <code>false</code> otherwise
*/
boolean isDependencySatisfied(PropertyDescriptor propertyDescriptor, Function<String, PropertyDescriptor> propertyDescriptorLookup);
+
+ /**
+ * Determines whether or not incoming and outgoing connections should be validated.
+ * If <code>true</code>, then the validation should verify that all Relationships either have one or more connections that include the Relationship,
+ * or that the Relationship is auto-terminated.
+ * Additionally, if <code>true</code>, then any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_REQUIRED}
+ * should be invalid unless it has an incoming (non-looping) connection, and any Processor with an {@link InputRequirement} of {@link InputRequirement.Requirement#INPUT_FORBIDDEN}
+ * should be invalid if it does have any incoming connection.
+ *
+ * @return <code>true</code> if Connections should be validated, <code>false</code> if Connections should be ignored
+ */
+ default boolean isValidateConnections() {
+ return true;
+ }
}
diff --git a/nifi-api/src/main/java/org/apache/nifi/controller/ConfigurationContext.java b/nifi-api/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
index f4a602a..292d75c 100644
--- a/nifi-api/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
+++ b/nifi-api/src/main/java/org/apache/nifi/controller/ConfigurationContext.java
@@ -16,11 +16,12 @@
*/
package org.apache.nifi.controller;
-import java.util.Map;
-import java.util.concurrent.TimeUnit;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.context.PropertyContext;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+
/**
* This context is passed to ControllerServices and Reporting Tasks in order
* to expose their configuration to them.
@@ -34,6 +35,11 @@
Map<PropertyDescriptor, String> getProperties();
/**
+ * @return the annotation data configured for the component
+ */
+ String getAnnotationData();
+
+ /**
* @return a String representation of the scheduling period, or <code>null</code> if
* the component does not have a scheduling period (e.g., for ControllerServices)
*/
diff --git a/nifi-api/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java b/nifi-api/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java
new file mode 100644
index 0000000..c9978f8
--- /dev/null
+++ b/nifi-api/src/main/java/org/apache/nifi/controller/VerifiableControllerService.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.controller;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * Any Controller Service that implements this interface will be provided the opportunity to verify
+ * a given configuration of the Controller Service. This allows the Controller Service to provide meaningful feedback
+ * to users when configuring the dataflow.
+ * </p>
+ *
+ * <p>
+ * Generally speaking, verification differs from validation in that validation is expected to be very
+ * quick and run often. If a Controller Service is not valid, it cannot be started. However, verification may be
+ * more expensive or time-consuming to complete. For example, validation may ensure that a username is
+ * provided for connecting to an external service but should not perform any sort of network connection
+ * in order to verify that the username is accurate. Verification, on the other hand, may create resources
+ * such as network connections, may be more expensive to complete, and may be run only when a user invokes
+ * the action (though verification may later occur at other stages, such as when starting a component).
+ * </p>
+ *
+ * <p>
+ * Verification is allowed to be run only when a Controller Service is fully disabled.
+ * Therefore, any initialization logic that may need to be performed
+ * before the Controller Service is triggered may also be required for verification. However, the framework is not responsible
+ * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such
+ * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog, Map)} itself.
+ * The {@link #verify(ConfigurationContext, ComponentLog, Map)} method will only be called if the configuration is valid according to the
+ * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid).
+ * </p>
+ */
+public interface VerifiableControllerService {
+
+ /**
+ * Verifies that the configuration defined by the given ConfigurationContext is valid.
+ * @param context the ProcessContext that contains the necessary configuration
+ * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result
+ * in producing bulletins, which can be confusing.
+ * @param variables a Map of key/value pairs that can be used to resolve variables referenced in property values via Expression Language
+ *
+ * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed
+ */
+ List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger, Map<String, String> variables);
+
+}
diff --git a/nifi-api/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java b/nifi-api/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java
new file mode 100644
index 0000000..3421879
--- /dev/null
+++ b/nifi-api/src/main/java/org/apache/nifi/processor/VerifiableProcessor.java
@@ -0,0 +1,65 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.processor;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.logging.ComponentLog;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * <p>
+ * Any Processor that implements this interface will be provided the opportunity to verify
+ * a given configuration of the Processor. This allows the Processor to provide meaningful feedback
+ * to users when configuring the dataflow.
+ * </p>
+ *
+ * <p>
+ * Generally speaking, verification differs from validation in that validation is expected to be very
+ * quick and run often. If a Processor is not valid, it cannot be started. However, verification may be
+ * more expensive or time-consuming to complete. For example, validation may ensure that a username is
+ * provided for connecting to an external service but should not perform any sort of network connection
+ * in order to verify that the username is accurate. Verification, on the other hand, may create resources
+ * such as network connections, may be more expensive to complete, and may be run only when a user invokes
+ * the action (though verification may later occur at other stages, such as when starting a component).
+ * </p>
+ *
+ * <p>
+ * Verification is allowed to be run only when a Processor is fully stopped. I.e., it has no active threads
+ * and currently has a state of STOPPED. Therefore, any initialization logic that may need to be performed
+ * before the Processor is triggered may also be required for verification. However, the framework is not responsible
+ * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such
+ * methods should be handled by the {@link #verify(ProcessContext, ComponentLog, Map)} itself.
+ * The {@link #verify(ProcessContext, ComponentLog, Map)} method will only be called if the configuration is valid according to the
+ * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid).
+ * </p>
+ */
+public interface VerifiableProcessor {
+
+ /**
+ * Verifies that the configuration defined by the given ProcessContext is valid.
+ * @param context the ProcessContext that contains the necessary configuration
+ * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result
+ * in producing bulletins, which can be confusing.
+ * @param attributes a mapping of values that can be used as FlowFile attributes for the purpose of evaluating Expression Language for resolving property values
+ * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed
+ */
+ List<ConfigVerificationResult> verify(ProcessContext context, ComponentLog verificationLogger, Map<String, String> attributes);
+
+}
diff --git a/nifi-api/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java b/nifi-api/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java
new file mode 100644
index 0000000..fe674be
--- /dev/null
+++ b/nifi-api/src/main/java/org/apache/nifi/reporting/VerifiableReportingTask.java
@@ -0,0 +1,66 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.reporting;
+
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+
+import java.util.List;
+
+/**
+ * <p>
+ * Any Reporting Task that implements this interface will be provided the opportunity to verify
+ * a given configuration of the Reporting Task. This allows the Reporting Task to provide meaningful feedback
+ * to users when configuring the dataflow.
+ * </p>
+ *
+ * <p>
+ * Generally speaking, verification differs from validation in that validation is expected to be very
+ * quick and run often. If a Reporting Task is not valid, it cannot be started. However, verification may be
+ * more expensive or time-consuming to complete. For example, validation may ensure that a username is
+ * provided for connecting to an external service but should not perform any sort of network connection
+ * in order to verify that the username is accurate. Verification, on the other hand, may create resources
+ * such as network connections, may be more expensive to complete, and may be run only when a user invokes
+ * the action (though verification may later occur at other stages, such as when starting a component).
+ * </p>
+ *
+ * <p>
+ * Verification is allowed to be run only when a Reporting Task is fully stopped. I.e., it has no active threads
+ * and currently has a state of STOPPED. Therefore, any initialization logic that may need to be performed
+ * before the Reporting Task is triggered may also be required for verification. However, the framework is not responsible
+ * for triggering the Lifecycle management stages, such as @OnScheduled before triggering the verification. Such
+ * methods should be handled by the {@link #verify(ConfigurationContext, ComponentLog)} itself.
+ * The {@link #verify(ConfigurationContext, ComponentLog)} method will only be called if the configuration is valid according to the
+ * validation rules (i.e., all Property Descriptors' validators and customValidate methods have indicated that the configuration is valid).
+ * </p>
+ */
+public interface VerifiableReportingTask {
+
+ /**
+ * Verifies that the configuration defined by the given ConfigurationContext is valid.
+ *
+ * @param context the Configuration Context that contains the necessary configuration
+ * @param verificationLogger a logger that can be used during verification. While the typical logger can be used, doing so may result
+ * in producing bulletins, which can be confusing.
+ *
+ * @return a List of ConfigVerificationResults, each illustrating one step of the verification process that was completed
+ */
+ List<ConfigVerificationResult> verify(ConfigurationContext context, ComponentLog verificationLogger);
+
+}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/EmptyPreparedQuery.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/EmptyPreparedQuery.java
index d746e7b..b33fcf4 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/EmptyPreparedQuery.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/EmptyPreparedQuery.java
@@ -16,10 +16,12 @@
*/
package org.apache.nifi.attribute.expression.language;
-
import org.apache.nifi.expression.AttributeValueDecorator;
import org.apache.nifi.processor.exception.ProcessException;
+import java.util.Collections;
+import java.util.Set;
+
public class EmptyPreparedQuery implements PreparedQuery {
private final String value;
@@ -42,4 +44,9 @@
public VariableImpact getVariableImpact() {
return VariableImpact.NEVER_IMPACTED;
}
+
+ @Override
+ public Set<String> getExplicitlyReferencedAttributes() {
+ return Collections.emptySet();
+ }
}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/InvalidPreparedQuery.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/InvalidPreparedQuery.java
index 9a3ec32..953e9ff 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/InvalidPreparedQuery.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/InvalidPreparedQuery.java
@@ -21,6 +21,9 @@
import org.apache.nifi.expression.AttributeValueDecorator;
import org.apache.nifi.processor.exception.ProcessException;
+import java.util.Collections;
+import java.util.Set;
+
/**
* An implementation of PreparedQuery that throws an
* {@link AttributeExpressionLanguageException} when attempting to evaluate the
@@ -51,4 +54,9 @@
public VariableImpact getVariableImpact() {
return VariableImpact.NEVER_IMPACTED;
}
+
+ @Override
+ public Set<String> getExplicitlyReferencedAttributes() {
+ return Collections.emptySet();
+ }
}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/PreparedQuery.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/PreparedQuery.java
index 7bdd287..f7a73e3 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/PreparedQuery.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/PreparedQuery.java
@@ -16,10 +16,11 @@
*/
package org.apache.nifi.attribute.expression.language;
-
import org.apache.nifi.expression.AttributeValueDecorator;
import org.apache.nifi.processor.exception.ProcessException;
+import java.util.Set;
+
public interface PreparedQuery {
String evaluateExpressions(EvaluationContext evaluationContext, AttributeValueDecorator decorator) throws ProcessException;
@@ -34,4 +35,14 @@
* variable impacts this Expression.
*/
VariableImpact getVariableImpact();
+
+ /**
+ * Returns a Set of all attributes that are explicitly referenced by the Prepared Query.
+ * There are some expressions, however, such as <code>${allMatchingAttributes('a.*'):gt(4)}</code>
+ * that reference multiple attributes, but those attributes' names cannot be determined a priori. As a result,
+ * those attributes will not be included in the returned set.
+ *
+ * @return a Set of all attributes that are explicitly referenced by the Prepared Query
+ */
+ Set<String> getExplicitlyReferencedAttributes();
}
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java
index 463ffe4..ea7c6d9 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/Query.java
@@ -19,7 +19,6 @@
import org.antlr.runtime.tree.Tree;
import org.apache.nifi.attribute.expression.language.compile.ExpressionCompiler;
import org.apache.nifi.attribute.expression.language.evaluation.Evaluator;
-import org.apache.nifi.attribute.expression.language.evaluation.EvaluatorState;
import org.apache.nifi.attribute.expression.language.evaluation.QueryResult;
import org.apache.nifi.attribute.expression.language.evaluation.selection.AttributeEvaluator;
import org.apache.nifi.attribute.expression.language.exception.AttributeExpressionLanguageParsingException;
@@ -50,7 +49,6 @@
private final Tree tree;
private final Evaluator<?> evaluator;
private final AtomicBoolean evaluated = new AtomicBoolean(false);
- private final EvaluatorState context = new EvaluatorState();
private Query(final String query, final Tree tree, final Evaluator<?> evaluator) {
this.query = query;
diff --git a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/StandardPreparedQuery.java b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/StandardPreparedQuery.java
index e030175..787ded8 100644
--- a/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/StandardPreparedQuery.java
+++ b/nifi-commons/nifi-expression-language/src/main/java/org/apache/nifi/attribute/expression/language/StandardPreparedQuery.java
@@ -78,6 +78,53 @@
}
@Override
+ public Set<String> getExplicitlyReferencedAttributes() {
+ final Set<String> attributes = new HashSet<>();
+
+ for (final Expression expression : expressions) {
+ if (!(expression instanceof CompiledExpression)) {
+ continue;
+ }
+
+ final CompiledExpression compiled = (CompiledExpression) expression;
+ for (final Evaluator<?> evaluator : compiled.getAllEvaluators()) {
+ if (evaluator instanceof AttributeEvaluator) {
+ final AttributeEvaluator attributeEval = (AttributeEvaluator) evaluator;
+ final Evaluator<String> nameEval = attributeEval.getNameEvaluator();
+
+ if (nameEval instanceof StringLiteralEvaluator) {
+ final String referencedVar = nameEval.evaluate(new StandardEvaluationContext(Collections.emptyMap())).getValue();
+ attributes.add(referencedVar);
+ }
+ } else if (evaluator instanceof AllAttributesEvaluator) {
+ final AllAttributesEvaluator allAttrsEval = (AllAttributesEvaluator) evaluator;
+ final MultiAttributeEvaluator iteratingEval = allAttrsEval.getVariableIteratingEvaluator();
+
+ if (iteratingEval instanceof MultiNamedAttributeEvaluator) {
+ attributes.addAll(((MultiNamedAttributeEvaluator) iteratingEval).getAttributeNames());
+ }
+ } else if (evaluator instanceof AnyAttributeEvaluator) {
+ final AnyAttributeEvaluator allAttrsEval = (AnyAttributeEvaluator) evaluator;
+ final MultiAttributeEvaluator iteratingEval = allAttrsEval.getVariableIteratingEvaluator();
+
+ if (iteratingEval instanceof MultiNamedAttributeEvaluator) {
+ attributes.addAll(((MultiNamedAttributeEvaluator) iteratingEval).getAttributeNames());
+ }
+ } else if (evaluator instanceof MappingEvaluator) {
+ final MappingEvaluator<?> allAttrsEval = (MappingEvaluator<?>) evaluator;
+ final MultiAttributeEvaluator iteratingEval = allAttrsEval.getVariableIteratingEvaluator();
+
+ if (iteratingEval instanceof MultiNamedAttributeEvaluator) {
+ attributes.addAll(((MultiNamedAttributeEvaluator) iteratingEval).getAttributeNames());
+ }
+ }
+ }
+ }
+
+ return attributes;
+ }
+
+ @Override
public VariableImpact getVariableImpact() {
final VariableImpact existing = this.variableImpact;
if (existing != null) {
diff --git a/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java b/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
index 57c5c2e..28f7fa6 100644
--- a/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
+++ b/nifi-mock/src/main/java/org/apache/nifi/util/MockConfigurationContext.java
@@ -79,6 +79,11 @@
}
@Override
+ public String getAnnotationData() {
+ return null;
+ }
+
+ @Override
public Map<String, String> getAllProperties() {
final Map<String,String> propValueMap = new LinkedHashMap<>();
for (final Map.Entry<PropertyDescriptor, String> entry : getProperties().entrySet()) {
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSCredentialsProviderProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSCredentialsProviderProcessor.java
index 5820e37..923878c 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSCredentialsProviderProcessor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSCredentialsProviderProcessor.java
@@ -71,8 +71,7 @@
*/
protected void onScheduledUsingControllerService(ProcessContext context) {
this.client = createClient(context, getCredentialsProvider(context), createConfiguration(context));
- super.initializeRegionAndEndpoint(context);
-
+ super.initializeRegionAndEndpoint(context, this.client);
}
@OnShutdown
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
index 44da978..36e6a59 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/AbstractAWSProcessor.java
@@ -35,6 +35,7 @@
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.processor.AbstractSessionFactoryProcessor;
import org.apache.nifi.processor.ProcessContext;
@@ -154,8 +155,8 @@
protected volatile ClientType client;
protected volatile Region region;
- private static final String VPCE_ENDPOINT_SUFFIX = ".vpce.amazonaws.com";
- private static final Pattern VPCE_ENDPOINT_PATTERN = Pattern.compile("^(?:.+[vpce-][a-z0-9-]+\\.)?([a-z0-9-]+)$");
+ protected static final String VPCE_ENDPOINT_SUFFIX = ".vpce.amazonaws.com";
+ protected static final Pattern VPCE_ENDPOINT_PATTERN = Pattern.compile("^(?:.+[vpce-][a-z0-9-]+\\.)?([a-z0-9-]+)$");
// If protocol is changed to be a property, ensure other uses are also changed
protected static final Protocol DEFAULT_PROTOCOL = Protocol.HTTPS;
@@ -219,8 +220,12 @@
}
protected ClientConfiguration createConfiguration(final ProcessContext context) {
+ return createConfiguration(context, context.getMaxConcurrentTasks());
+ }
+
+ protected ClientConfiguration createConfiguration(final PropertyContext context, final int maxConcurrentTasks) {
final ClientConfiguration config = new ClientConfiguration();
- config.setMaxConnections(context.getMaxConcurrentTasks());
+ config.setMaxConnections(maxConcurrentTasks);
config.setMaxErrorRetry(0);
config.setUserAgent(DEFAULT_USER_AGENT);
// If this is changed to be a property, ensure other uses are also changed
@@ -272,7 +277,7 @@
@OnScheduled
public void onScheduled(final ProcessContext context) {
this.client = createClient(context, getCredentials(context), createConfiguration(context));
- initializeRegionAndEndpoint(context);
+ initializeRegionAndEndpoint(context, this.client);
}
/*
@@ -297,7 +302,7 @@
*/
public abstract void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException;
- protected void initializeRegionAndEndpoint(ProcessContext context) {
+ protected void initializeRegionAndEndpoint(final ProcessContext context, final AmazonWebServiceClient client) {
// if the processor supports REGION, get the configured region.
if (getSupportedPropertyDescriptors().contains(REGION)) {
final String region = context.getProperty(REGION).getValue();
@@ -324,11 +329,11 @@
// falling back to the configured region if the parse fails
// e.g. in case of https://vpce-***-***.sqs.{region}.vpce.amazonaws.com
String region = parseRegionForVPCE(urlstr, this.region.getName());
- this.client.setEndpoint(urlstr, this.client.getServiceName(), region);
+ client.setEndpoint(urlstr, this.client.getServiceName(), region);
} else {
// handling non-vpce custom endpoints where the AWS library can parse the region out
// e.g. https://sqs.{region}.***.***.***.gov
- this.client.setEndpoint(urlstr);
+ client.setEndpoint(urlstr);
}
}
}
@@ -376,7 +381,7 @@
return region;
}
- protected AWSCredentials getCredentials(final ProcessContext context) {
+ protected AWSCredentials getCredentials(final PropertyContext context) {
final String accessKey = context.getProperty(ACCESS_KEY).evaluateAttributeExpressions().getValue();
final String secretKey = context.getProperty(SECRET_KEY).evaluateAttributeExpressions().getValue();
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
index e089785..4bd0769 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-abstract-processors/src/main/java/org/apache/nifi/processors/aws/s3/AbstractS3Processor.java
@@ -16,23 +16,10 @@
*/
package org.apache.nifi.processors.aws.s3;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-import com.amazonaws.auth.AWSStaticCredentialsProvider;
-import org.apache.commons.lang3.StringUtils;
-import org.apache.nifi.components.AllowableValue;
-import org.apache.nifi.components.PropertyDescriptor;
-import org.apache.nifi.expression.ExpressionLanguageScope;
-import org.apache.nifi.flowfile.FlowFile;
-import org.apache.nifi.processor.ProcessContext;
-import org.apache.nifi.processor.util.StandardValidators;
-import org.apache.nifi.processors.aws.AbstractAWSCredentialsProviderProcessor;
-
import com.amazonaws.ClientConfiguration;
import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSCredentialsProvider;
+import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.regions.Region;
import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.S3ClientOptions;
@@ -43,6 +30,18 @@
import com.amazonaws.services.s3.model.Grantee;
import com.amazonaws.services.s3.model.Owner;
import com.amazonaws.services.s3.model.Permission;
+import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.util.StandardValidators;
+import org.apache.nifi.processors.aws.AbstractAWSCredentialsProviderProcessor;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
public abstract class AbstractS3Processor extends AbstractAWSCredentialsProviderProcessor<AmazonS3Client> {
diff --git a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
index cf2f408..8ab51fe 100644
--- a/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
+++ b/nifi-nar-bundles/nifi-aws-bundle/nifi-aws-processors/src/main/java/org/apache/nifi/processors/aws/s3/ListS3.java
@@ -17,6 +17,7 @@
package org.apache.nifi.processors.aws.s3;
import com.amazonaws.services.s3.AmazonS3;
+import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.internal.Constants;
import com.amazonaws.services.s3.model.GetObjectMetadataRequest;
import com.amazonaws.services.s3.model.GetObjectTaggingRequest;
@@ -43,6 +44,8 @@
import org.apache.nifi.annotation.documentation.SeeAlso;
import org.apache.nifi.annotation.documentation.Tags;
import org.apache.nifi.components.AllowableValue;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyDescriptor.Builder;
import org.apache.nifi.components.ValidationContext;
@@ -57,6 +60,7 @@
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.schema.access.SchemaNotFoundException;
import org.apache.nifi.serialization.RecordSetWriter;
@@ -111,7 +115,7 @@
@WritesAttribute(attribute = "s3.user.metadata.___", description = "If 'Write User Metadata' is set to 'True', the user defined metadata associated to the S3 object that is being listed " +
"will be written as part of the flowfile attributes")})
@SeeAlso({FetchS3Object.class, PutS3Object.class, DeleteS3Object.class})
-public class ListS3 extends AbstractS3Processor {
+public class ListS3 extends AbstractS3Processor implements VerifiableProcessor {
public static final PropertyDescriptor DELIMITER = new Builder()
.name("delimiter")
@@ -884,4 +888,49 @@
return keys;
}
}
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog logger, final Map<String, String> attributes) {
+ final AmazonS3Client client = createClient(context, getCredentials(context), createConfiguration(context));
+ initializeRegionAndEndpoint(context, client);
+
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+ final String bucketName = context.getProperty(BUCKET).evaluateAttributeExpressions().getValue();
+
+ if (bucketName == null || bucketName.trim().isEmpty()) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Perform Listing")
+ .outcome(Outcome.FAILED)
+ .explanation("Bucket Name must be specified")
+ .build());
+
+ return results;
+ }
+
+ final String prefix = context.getProperty(PREFIX).getValue();
+
+ // Attempt to perform a listing of objects in the S3 bucket
+ try {
+ final ObjectListing listing = client.listObjects(bucketName, prefix);
+ final int count = listing.getObjectSummaries().size();
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Perform Listing")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Successfully listed contents of bucket '" + bucketName + "', finding " + count + " objects" + (prefix == null ? "" : " with a prefix of '" + prefix + "'"))
+ .build());
+
+ logger.info("Successfully verified configuration");
+ } catch (final Exception e) {
+ logger.warn("Failed to verify configuration. Could not list contents of bucket '{}'", bucketName, e);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Perform Listing")
+ .outcome(Outcome.FAILED)
+ .explanation("Failed to list contents of bucket '" + bucketName + "': " + e.getMessage())
+ .build());
+ }
+
+ return results;
+ }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java
new file mode 100644
index 0000000..b44b40e
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ConfigVerificationResultDTO.java
@@ -0,0 +1,53 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+
+public class ConfigVerificationResultDTO {
+ private String outcome;
+ private String verificationStepName;
+ private String explanation;
+
+ @ApiModelProperty(value = "The outcome of the verification", allowableValues = "SUCCESSFUL, FAILED, SKIPPED")
+ public String getOutcome() {
+ return outcome;
+ }
+
+ public void setOutcome(final String outcome) {
+ this.outcome = outcome;
+ }
+
+ @ApiModelProperty("The name of the verification step")
+ public String getVerificationStepName() {
+ return verificationStepName;
+ }
+
+ public void setVerificationStepName(final String verificationStepName) {
+ this.verificationStepName = verificationStepName;
+ }
+
+ @ApiModelProperty("An explanation of why the step was or was not successful")
+ public String getExplanation() {
+ return explanation;
+ }
+
+ public void setExplanation(final String explanation) {
+ this.explanation = explanation;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
index cb3824f..bf88780 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ControllerServiceDTO.java
@@ -17,6 +17,7 @@
package org.apache.nifi.web.api.dto;
import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModelProperty.AccessMode;
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentEntity;
import javax.xml.bind.annotation.XmlType;
@@ -45,6 +46,7 @@
private Boolean deprecated;
private Boolean isExtensionMissing;
private Boolean multipleVersionsAvailable;
+ private Set<String> referencedAttributes;
private Map<String, String> properties;
private Map<String, PropertyDescriptorDTO> descriptors;
@@ -313,6 +315,15 @@
this.validationStatus = validationStatus;
}
+ @ApiModelProperty(value = "The set of FlowFile Attributes that are referenced via Expression Language by the configured properties", accessMode = AccessMode.READ_ONLY)
+ public Set<String> getReferencedAttributes() {
+ return referencedAttributes;
+ }
+
+ public void setReferencedAttributes(final Set<String> referencedAttributes) {
+ this.referencedAttributes = referencedAttributes;
+ }
+
@Override
public int hashCode() {
final String id = getId();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorConfigDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorConfigDTO.java
index 780e8ac..3d40e60 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorConfigDTO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/ProcessorConfigDTO.java
@@ -17,6 +17,7 @@
package org.apache.nifi.web.api.dto;
import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModelProperty.AccessMode;
import javax.xml.bind.annotation.XmlType;
import java.util.Map;
@@ -50,6 +51,7 @@
private Map<String, String> defaultConcurrentTasks;
private Map<String, String> defaultSchedulingPeriod;
+ private Set<String> referencedAttributes;
public ProcessorConfigDTO() {
@@ -308,4 +310,12 @@
this.defaultSchedulingPeriod = defaultSchedulingPeriod;
}
+ @ApiModelProperty(value = "The set of FlowFile Attributes that are referenced via Expression Language by the configured properties", accessMode = AccessMode.READ_ONLY)
+ public Set<String> getReferencedAttributes() {
+ return referencedAttributes;
+ }
+
+ public void setReferencedAttributes(final Set<String> referencedAttributes) {
+ this.referencedAttributes = referencedAttributes;
+ }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConfigUpdateStepDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConfigUpdateStepDTO.java
new file mode 100644
index 0000000..0779d04
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyConfigUpdateStepDTO.java
@@ -0,0 +1,23 @@
+/*
+ * 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.
+ */
+package org.apache.nifi.web.api.dto;
+
+import javax.xml.bind.annotation.XmlType;
+
+@XmlType(name = "verifyConfigUpdateStep")
+public class VerifyConfigUpdateStepDTO extends UpdateStepDTO {
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyControllerServiceConfigRequestDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyControllerServiceConfigRequestDTO.java
new file mode 100644
index 0000000..e0bb49c
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyControllerServiceConfigRequestDTO.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModelProperty.AccessMode;
+
+import javax.xml.bind.annotation.XmlType;
+import java.util.List;
+import java.util.Map;
+
+@XmlType(name = "verifyControllerServiceConfigRequest")
+public class VerifyControllerServiceConfigRequestDTO extends AsynchronousRequestDTO<VerifyConfigUpdateStepDTO> {
+ private String controllerServiceId;
+ private ControllerServiceDTO controllerService;
+ private Map<String, String> attributes;
+ private List<ConfigVerificationResultDTO> results;
+
+ @ApiModelProperty("The ID of the Controller Service whose configuration was verified")
+ public String getControllerServiceId() {
+ return controllerServiceId;
+ }
+
+ public void setControllerServiceId(final String controllerServiceId) {
+ this.controllerServiceId = controllerServiceId;
+ }
+
+ @ApiModelProperty("The Controller Service")
+ public ControllerServiceDTO getControllerService() {
+ return controllerService;
+ }
+
+ public void setControllerService(final ControllerServiceDTO controllerService) {
+ this.controllerService = controllerService;
+ }
+
+ @ApiModelProperty("FlowFile Attributes that should be used to evaluate Expression Language for resolving property values")
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(final Map<String, String> attributes) {
+ this.attributes = attributes;
+ }
+
+ @ApiModelProperty(value="The Results of the verification", accessMode = AccessMode.READ_ONLY)
+ public List<ConfigVerificationResultDTO> getResults() {
+ return results;
+ }
+
+ public void setResults(final List<ConfigVerificationResultDTO> results) {
+ this.results = results;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyProcessorConfigRequestDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyProcessorConfigRequestDTO.java
new file mode 100644
index 0000000..744cfad
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyProcessorConfigRequestDTO.java
@@ -0,0 +1,69 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModelProperty.AccessMode;
+
+import javax.xml.bind.annotation.XmlType;
+import java.util.List;
+import java.util.Map;
+
+@XmlType(name = "verifyProcessorConfigRequest")
+public class VerifyProcessorConfigRequestDTO extends AsynchronousRequestDTO<VerifyConfigUpdateStepDTO> {
+ private String processorId;
+ private ProcessorConfigDTO processorConfigDTO;
+ private Map<String, String> attributes;
+ private List<ConfigVerificationResultDTO> results;
+
+ @ApiModelProperty("The ID of the Processor whose configuration was verified")
+ public String getProcessorId() {
+ return processorId;
+ }
+
+ public void setProcessorId(final String processorId) {
+ this.processorId = processorId;
+ }
+
+ @ApiModelProperty("The configuration for the Processor")
+ public ProcessorConfigDTO getProcessorConfig() {
+ return processorConfigDTO;
+ }
+
+ public void setProcessorConfig(final ProcessorConfigDTO processorConfigDTO) {
+ this.processorConfigDTO = processorConfigDTO;
+ }
+
+ @ApiModelProperty("FlowFile Attributes that should be used to evaluate Expression Language for resolving property values")
+ public Map<String, String> getAttributes() {
+ return attributes;
+ }
+
+ public void setAttributes(final Map<String, String> attributes) {
+ this.attributes = attributes;
+ }
+
+ @ApiModelProperty(value="The Results of the verification", accessMode = AccessMode.READ_ONLY)
+ public List<ConfigVerificationResultDTO> getResults() {
+ return results;
+ }
+
+ public void setResults(final List<ConfigVerificationResultDTO> results) {
+ this.results = results;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyReportingTaskConfigRequestDTO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyReportingTaskConfigRequestDTO.java
new file mode 100644
index 0000000..07a87c8
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/dto/VerifyReportingTaskConfigRequestDTO.java
@@ -0,0 +1,58 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.dto;
+
+import io.swagger.annotations.ApiModelProperty;
+import io.swagger.annotations.ApiModelProperty.AccessMode;
+
+import javax.xml.bind.annotation.XmlType;
+import java.util.List;
+
+@XmlType(name = "verifyReportingTaskConfigRequest")
+public class VerifyReportingTaskConfigRequestDTO extends AsynchronousRequestDTO<VerifyConfigUpdateStepDTO> {
+ private String reportingTaskId;
+ private ReportingTaskDTO reportingTask;
+ private List<ConfigVerificationResultDTO> results;
+
+ @ApiModelProperty("The ID of the Controller Service whose configuration was verified")
+ public String getReportingTaskId() {
+ return reportingTaskId;
+ }
+
+ public void setReportingTaskId(final String reportingTaskId) {
+ this.reportingTaskId = reportingTaskId;
+ }
+
+ @ApiModelProperty("The Controller Service")
+ public ReportingTaskDTO getReportingTask() {
+ return reportingTask;
+ }
+
+ public void setReportingTask(final ReportingTaskDTO reportingTask) {
+ this.reportingTask = reportingTask;
+ }
+
+ @ApiModelProperty(value="The Results of the verification", accessMode = AccessMode.READ_ONLY)
+ public List<ConfigVerificationResultDTO> getResults() {
+ return results;
+ }
+
+ public void setResults(final List<ConfigVerificationResultDTO> results) {
+ this.results = results;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyControllerServiceConfigRequestEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyControllerServiceConfigRequestEntity.java
new file mode 100644
index 0000000..822a689
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyControllerServiceConfigRequestEntity.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.VerifyControllerServiceConfigRequestDTO;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="verifyControllerServiceConfigRequest")
+public class VerifyControllerServiceConfigRequestEntity extends Entity {
+ private VerifyControllerServiceConfigRequestDTO request;
+
+ @ApiModelProperty("The request")
+ public VerifyControllerServiceConfigRequestDTO getRequest() {
+ return request;
+ }
+
+ public void setRequest(final VerifyControllerServiceConfigRequestDTO request) {
+ this.request = request;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyProcessorConfigRequestEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyProcessorConfigRequestEntity.java
new file mode 100644
index 0000000..8b0dd9a
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyProcessorConfigRequestEntity.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.VerifyProcessorConfigRequestDTO;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="verifyProcessorConfigRequest")
+public class VerifyProcessorConfigRequestEntity extends Entity {
+ private VerifyProcessorConfigRequestDTO request;
+
+ @ApiModelProperty("The request")
+ public VerifyProcessorConfigRequestDTO getRequest() {
+ return request;
+ }
+
+ public void setRequest(final VerifyProcessorConfigRequestDTO request) {
+ this.request = request;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyReportingTaskConfigRequestEntity.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyReportingTaskConfigRequestEntity.java
new file mode 100644
index 0000000..318bf0b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-client-dto/src/main/java/org/apache/nifi/web/api/entity/VerifyReportingTaskConfigRequestEntity.java
@@ -0,0 +1,37 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.web.api.entity;
+
+import io.swagger.annotations.ApiModelProperty;
+import org.apache.nifi.web.api.dto.VerifyReportingTaskConfigRequestDTO;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="verifyReportingTaskConfigRequest")
+public class VerifyReportingTaskConfigRequestEntity extends Entity {
+ private VerifyReportingTaskConfigRequestDTO request;
+
+ @ApiModelProperty("The request")
+ public VerifyReportingTaskConfigRequestDTO getRequest() {
+ return request;
+ }
+
+ public void setRequest(final VerifyReportingTaskConfigRequestDTO request) {
+ this.request = request;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
index c087f9c..76fa4a8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/StandardHttpResponseMapper.java
@@ -77,6 +77,9 @@
import org.apache.nifi.cluster.coordination.http.endpoints.UserGroupsEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.UsersEndpointMerger;
import org.apache.nifi.cluster.coordination.http.endpoints.VariableRegistryEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.VerifyControllerServiceConfigEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.VerifyProcessorConfigEndpointMerger;
+import org.apache.nifi.cluster.coordination.http.endpoints.VerifyReportingTaskConfigEndpointMerger;
import org.apache.nifi.cluster.coordination.http.replication.RequestReplicator;
import org.apache.nifi.cluster.manager.NodeResponse;
import org.apache.nifi.stream.io.NullOutputStream;
@@ -169,6 +172,9 @@
endpointMergers.add(new ParameterContextsEndpointMerger());
endpointMergers.add(new ParameterContextEndpointMerger());
endpointMergers.add(new ParameterContextUpdateEndpointMerger());
+ endpointMergers.add(new VerifyProcessorConfigEndpointMerger());
+ endpointMergers.add(new VerifyControllerServiceConfigEndpointMerger());
+ endpointMergers.add(new VerifyReportingTaskConfigEndpointMerger());
}
@Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConfigVerificationResultMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConfigVerificationResultMerger.java
new file mode 100644
index 0000000..e7a11a4
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/ConfigVerificationResultMerger.java
@@ -0,0 +1,120 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class ConfigVerificationResultMerger {
+ private final Map<String, List<ConfigVerificationResultDTO>> verificationResultDtos = new HashMap<>();
+
+ /**
+ * Adds the config verification results for one of the nodes in the cluster
+ * @param nodeId the ID of the node in the cluster
+ * @param nodeResults the results for the config verification
+ */
+ public void addNodeResults(final NodeIdentifier nodeId, final List<ConfigVerificationResultDTO> nodeResults) {
+ if (nodeResults == null || nodeResults.isEmpty()) {
+ return;
+ }
+
+ verificationResultDtos.put(nodeId.getApiAddress() + ":" + nodeId.getApiPort(), nodeResults);
+ }
+
+ /**
+ * Computes the aggregate list of ConfigVerificationResultDTO based on all of the results added using the {link {@link #addNodeResults(NodeIdentifier, List)}} method
+ * @return the aggregate results of the config verification results from all nodes
+ */
+ public List<ConfigVerificationResultDTO> computeAggregateResults() {
+ // For each node, build up a mapping of Step Name -> Results
+ final Map<String, List<ConfigVerificationResultDTO>> resultsByStepName = new HashMap<>();
+ for (final Map.Entry<String, List<ConfigVerificationResultDTO>> entry : verificationResultDtos.entrySet()) {
+ final String nodeId = entry.getKey();
+ final List<ConfigVerificationResultDTO> nodeResults = entry.getValue();
+
+ // If the result hasn't been set, the task is not yet complete, so we don't have to bother merging the results.
+ if (nodeResults == null) {
+ return null;
+ }
+
+ for (final ConfigVerificationResultDTO result : nodeResults) {
+ final String stepName = result.getVerificationStepName();
+ final List<ConfigVerificationResultDTO> resultList = resultsByStepName.computeIfAbsent(stepName, key -> new ArrayList<>());
+
+ // If skipped or unsuccessful, add the node's address to the explanation
+ if (!Outcome.SUCCESSFUL.name().equals(result.getOutcome())) {
+ result.setExplanation(nodeId + " - " + result.getExplanation());
+ }
+
+ resultList.add(result);
+ }
+ }
+
+ // Merge together all results for each step name
+ final List<ConfigVerificationResultDTO> aggregateResults = new ArrayList<>();
+ for (final Map.Entry<String, List<ConfigVerificationResultDTO>> entry : resultsByStepName.entrySet()) {
+ final String stepName = entry.getKey();
+ final List<ConfigVerificationResultDTO> resultList = entry.getValue();
+
+ final ConfigVerificationResultDTO firstResult = resultList.get(0); // This is safe because the list won't be added to the map unless it has at least 1 element.
+ String outcome = firstResult.getOutcome();
+ String explanation = firstResult.getExplanation();
+
+ for (final ConfigVerificationResultDTO result : resultList) {
+ // If any node indicates failure, the outcome is failure.
+ // Otherwise, if any node indicates that a step was skipped, the outcome is skipped.
+ // Otherwise, all nodes have reported the outcome is successful, so the outcome is successful.
+ if (Outcome.FAILED.name().equals(result.getOutcome())) {
+ outcome = result.getOutcome();
+ explanation = result.getExplanation();
+ } else if (Outcome.SKIPPED.name().equals(result.getOutcome()) && Outcome.SUCCESSFUL.name().equals(outcome)) {
+ outcome = result.getOutcome();
+ explanation = result.getExplanation();
+ }
+ }
+
+ final ConfigVerificationResultDTO resultDto = new ConfigVerificationResultDTO();
+ resultDto.setVerificationStepName(stepName);
+ resultDto.setOutcome(outcome);
+ resultDto.setExplanation(explanation);
+
+ aggregateResults.add(resultDto);
+ }
+
+ // Determine the ordering of the original steps.
+ final Map<String, Integer> stepOrders = new HashMap<>();
+ for (final List<ConfigVerificationResultDTO> resultDtos : verificationResultDtos.values()) {
+ for (final ConfigVerificationResultDTO resultDto : resultDtos) {
+ final String stepName = resultDto.getVerificationStepName();
+ stepOrders.putIfAbsent(stepName, stepOrders.size());
+ }
+ }
+
+ // Sort the results by ordering them based on the order of the original steps. This will retain the original ordering of the steps.
+ aggregateResults.sort(Comparator.comparing(dto -> stepOrders.get(dto.getVerificationStepName())));
+
+ return aggregateResults;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyControllerServiceConfigEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyControllerServiceConfigEndpointMerger.java
new file mode 100644
index 0000000..cee27b2
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyControllerServiceConfigEndpointMerger.java
@@ -0,0 +1,73 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.VerifyControllerServiceConfigRequestDTO;
+import org.apache.nifi.web.api.entity.VerifyControllerServiceConfigRequestEntity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class VerifyControllerServiceConfigEndpointMerger extends AbstractSingleEntityEndpoint<VerifyControllerServiceConfigRequestEntity> {
+ public static final Pattern VERIFY_PROCESSOR_CONFIG_URI_PATTERN = Pattern.compile("/nifi-api/controller-services/[a-f0-9\\-]{36}/config/verification-requests(/[a-f0-9\\-]{36})?");
+
+ @Override
+ protected Class<VerifyControllerServiceConfigRequestEntity> getEntityClass() {
+ return VerifyControllerServiceConfigRequestEntity.class;
+ }
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return VERIFY_PROCESSOR_CONFIG_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected void mergeResponses(final VerifyControllerServiceConfigRequestEntity clientEntity, final Map<NodeIdentifier, VerifyControllerServiceConfigRequestEntity> entityMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+ final VerifyControllerServiceConfigRequestDTO requestDto = clientEntity.getRequest();
+ final List<ConfigVerificationResultDTO> results = requestDto.getResults();
+
+ // If the result hasn't been set, the task is not yet complete, so we don't have to bother merging the results.
+ if (results == null) {
+ return;
+ }
+
+ // Aggregate the Config Verification Results across all nodes into a single List
+ final ConfigVerificationResultMerger resultMerger = new ConfigVerificationResultMerger();
+ for (final Map.Entry<NodeIdentifier, VerifyControllerServiceConfigRequestEntity> entry : entityMap.entrySet()) {
+ final NodeIdentifier nodeId = entry.getKey();
+ final VerifyControllerServiceConfigRequestEntity entity = entry.getValue();
+
+ final List<ConfigVerificationResultDTO> nodeResults = entity.getRequest().getResults();
+ resultMerger.addNodeResults(nodeId, nodeResults);
+ }
+
+ final List<ConfigVerificationResultDTO> aggregateResults = resultMerger.computeAggregateResults();
+
+
+ clientEntity.getRequest().setResults(aggregateResults);
+ }
+
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyProcessorConfigEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyProcessorConfigEndpointMerger.java
new file mode 100644
index 0000000..40db230
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyProcessorConfigEndpointMerger.java
@@ -0,0 +1,72 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.VerifyProcessorConfigRequestDTO;
+import org.apache.nifi.web.api.entity.VerifyProcessorConfigRequestEntity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class VerifyProcessorConfigEndpointMerger extends AbstractSingleEntityEndpoint<VerifyProcessorConfigRequestEntity> {
+ public static final Pattern VERIFY_PROCESSOR_CONFIG_URI_PATTERN = Pattern.compile("/nifi-api/processors/[a-f0-9\\-]{36}/config/verification-requests(/[a-f0-9\\-]{36})?");
+
+ @Override
+ protected Class<VerifyProcessorConfigRequestEntity> getEntityClass() {
+ return VerifyProcessorConfigRequestEntity.class;
+ }
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return VERIFY_PROCESSOR_CONFIG_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected void mergeResponses(final VerifyProcessorConfigRequestEntity clientEntity, final Map<NodeIdentifier, VerifyProcessorConfigRequestEntity> entityMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+ final VerifyProcessorConfigRequestDTO requestDto = clientEntity.getRequest();
+ final List<ConfigVerificationResultDTO> results = requestDto.getResults();
+
+ // If the result hasn't been set, the task is not yet complete, so we don't have to bother merging the results.
+ if (results == null) {
+ return;
+ }
+
+ // Aggregate the Config Verification Results across all nodes into a single List
+ final ConfigVerificationResultMerger resultMerger = new ConfigVerificationResultMerger();
+ for (final Map.Entry<NodeIdentifier, VerifyProcessorConfigRequestEntity> entry : entityMap.entrySet()) {
+ final NodeIdentifier nodeId = entry.getKey();
+ final VerifyProcessorConfigRequestEntity entity = entry.getValue();
+
+ final List<ConfigVerificationResultDTO> nodeResults = entity.getRequest().getResults();
+ resultMerger.addNodeResults(nodeId, nodeResults);
+ }
+
+ final List<ConfigVerificationResultDTO> aggregateResults = resultMerger.computeAggregateResults();
+
+ clientEntity.getRequest().setResults(aggregateResults);
+ }
+
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyReportingTaskConfigEndpointMerger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyReportingTaskConfigEndpointMerger.java
new file mode 100644
index 0000000..eef079b
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-cluster/src/main/java/org/apache/nifi/cluster/coordination/http/endpoints/VerifyReportingTaskConfigEndpointMerger.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.cluster.coordination.http.endpoints;
+
+import org.apache.nifi.cluster.manager.NodeResponse;
+import org.apache.nifi.cluster.protocol.NodeIdentifier;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.VerifyReportingTaskConfigRequestDTO;
+import org.apache.nifi.web.api.entity.VerifyReportingTaskConfigRequestEntity;
+
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+
+public class VerifyReportingTaskConfigEndpointMerger extends AbstractSingleEntityEndpoint<VerifyReportingTaskConfigRequestEntity> {
+ public static final Pattern VERIFY_REPORTING_TASK_CONFIG_URI_PATTERN = Pattern.compile("/nifi-api/reporting-tasks/[a-f0-9\\-]{36}/config/verification-requests(/[a-f0-9\\-]{36})?");
+
+ @Override
+ protected Class<VerifyReportingTaskConfigRequestEntity> getEntityClass() {
+ return VerifyReportingTaskConfigRequestEntity.class;
+ }
+
+ @Override
+ public boolean canHandle(final URI uri, final String method) {
+ return VERIFY_REPORTING_TASK_CONFIG_URI_PATTERN.matcher(uri.getPath()).matches();
+ }
+
+ @Override
+ protected void mergeResponses(final VerifyReportingTaskConfigRequestEntity clientEntity, final Map<NodeIdentifier, VerifyReportingTaskConfigRequestEntity> entityMap,
+ final Set<NodeResponse> successfulResponses, final Set<NodeResponse> problematicResponses) {
+
+ final VerifyReportingTaskConfigRequestDTO requestDto = clientEntity.getRequest();
+ final List<ConfigVerificationResultDTO> results = requestDto.getResults();
+
+ // If the result hasn't been set, the task is not yet complete, so we don't have to bother merging the results.
+ if (results == null) {
+ return;
+ }
+
+ // Aggregate the Config Verification Results across all nodes into a single List
+ final ConfigVerificationResultMerger resultMerger = new ConfigVerificationResultMerger();
+ for (final Map.Entry<NodeIdentifier, VerifyReportingTaskConfigRequestEntity> entry : entityMap.entrySet()) {
+ final NodeIdentifier nodeId = entry.getKey();
+ final VerifyReportingTaskConfigRequestEntity entity = entry.getValue();
+
+ final List<ConfigVerificationResultDTO> nodeResults = entity.getRequest().getResults();
+ resultMerger.addNodeResults(nodeId, nodeResults);
+ }
+
+ final List<ConfigVerificationResultDTO> aggregateResults = resultMerger.computeAggregateResults();
+ clientEntity.getRequest().setResults(aggregateResults);
+ }
+
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
index c3d0049..4248cbf 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/StandardProcessorNode.java
@@ -33,7 +33,10 @@
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
@@ -55,6 +58,7 @@
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.logging.LogRepositoryFactory;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.InstanceClassLoader;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterLookup;
@@ -63,6 +67,7 @@
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.Relationship;
import org.apache.nifi.processor.SimpleProcessLogger;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.scheduling.ExecutionNode;
import org.apache.nifi.scheduling.SchedulingStrategy;
@@ -1062,6 +1067,71 @@
return nonLoopConnections;
}
+ @Override
+ public List<ConfigVerificationResult> verifyConfiguration(final ProcessContext context, final ComponentLog logger, final Map<String, String> attributes, final ExtensionManager extensionManager) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ try {
+ verifyCanPerformVerification();
+
+ final long startNanos = System.nanoTime();
+ // Call super's verifyConfig, which will perform component validation
+ results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), getProcessGroup().getParameterContext()));
+ final long validationComplete = System.nanoTime();
+
+ // If any invalid outcomes from validation, we do not want to perform additional verification, because we only run additional verification when the component is valid.
+ // This is done in order to make it much simpler to develop these verifications, since the developer doesn't have to worry about whether or not the given values are valid.
+ if (!results.isEmpty() && results.stream().anyMatch(result -> result.getOutcome() == Outcome.FAILED)) {
+ return results;
+ }
+
+ final Processor processor = getProcessor();
+ if (processor instanceof VerifiableProcessor) {
+ LOG.debug("{} is a VerifiableProcessor. Will perform full verification of configuration.", this);
+ final VerifiableProcessor verifiable = (VerifiableProcessor) getProcessor();
+
+ // Check if the given configuration requires a different classloader than the current configuration
+ final boolean classpathDifferent = isClasspathDifferent(context.getProperties());
+
+ if (classpathDifferent) {
+ // Create a classloader for the given configuration and use that to verify the component's configuration
+ final Bundle bundle = extensionManager.getBundle(getBundleCoordinate());
+ final Set<URL> classpathUrls = getAdditionalClasspathResources(context.getProperties().keySet(), descriptor -> context.getProperty(descriptor).getValue());
+
+ final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+ try (final InstanceClassLoader detectedClassLoader = extensionManager.createInstanceClassLoader(getComponentType(), getIdentifier(), bundle, classpathUrls, false)) {
+ Thread.currentThread().setContextClassLoader(detectedClassLoader);
+ results.addAll(verifiable.verify(context, logger, attributes));
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ }
+ } else {
+ // Verify the configuration, using the component's classloader
+ try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, processor.getClass(), getIdentifier())) {
+ results.addAll(verifiable.verify(context, logger, attributes));
+ }
+ }
+
+ final long validationNanos = validationComplete - startNanos;
+ final long verificationNanos = System.nanoTime() - validationComplete;
+ LOG.debug("{} completed full configuration validation in {} plus {} for validation",
+ this, FormatUtils.formatNanos(verificationNanos, false), FormatUtils.formatNanos(validationNanos, false));
+ } else {
+ LOG.debug("{} is not a VerifiableProcessor, so will not perform full verification of configuration. Validation took {}", this,
+ FormatUtils.formatNanos(validationComplete - startNanos, false));
+ }
+ } catch (final Throwable t) {
+ LOG.error("Failed to perform verification of processor's configuration for {}", this, t);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(Outcome.FAILED)
+ .verificationStepName("Perform Verification")
+ .explanation("Encountered unexpected failure when attempting to perform verification: " + t)
+ .build());
+ }
+
+ return results;
+ }
@Override
public Collection<ValidationResult> getValidationErrors() {
@@ -1080,36 +1150,38 @@
.forEach(results::add);
// Ensure that any relationships that don't have a connection defined are auto-terminated
- for (final Relationship relationship : getUndefinedRelationships()) {
- if (!isAutoTerminated(relationship)) {
- final ValidationResult error = new ValidationResult.Builder()
- .explanation("Relationship '" + relationship.getName()
- + "' is not connected to any component and is not auto-terminated")
- .subject("Relationship " + relationship.getName()).valid(false).build();
- results.add(error);
+ if (validationContext.isValidateConnections()) {
+ for (final Relationship relationship : getUndefinedRelationships()) {
+ if (!isAutoTerminated(relationship)) {
+ final ValidationResult error = new ValidationResult.Builder()
+ .explanation("Relationship '" + relationship.getName()
+ + "' is not connected to any component and is not auto-terminated")
+ .subject("Relationship " + relationship.getName()).valid(false).build();
+ results.add(error);
+ }
}
- }
- // Ensure that the requirements of the InputRequirement are met.
- switch (getInputRequirement()) {
- case INPUT_ALLOWED:
- break;
- case INPUT_FORBIDDEN: {
- final int incomingConnCount = getIncomingNonLoopConnections().size();
- if (incomingConnCount != 0) {
- results.add(new ValidationResult.Builder().explanation(
- "Processor does not allow upstream connections but currently has " + incomingConnCount)
- .subject("Upstream Connections").valid(false).build());
+ // Ensure that the requirements of the InputRequirement are met.
+ switch (getInputRequirement()) {
+ case INPUT_ALLOWED:
+ break;
+ case INPUT_FORBIDDEN: {
+ final int incomingConnCount = getIncomingNonLoopConnections().size();
+ if (incomingConnCount != 0) {
+ results.add(new ValidationResult.Builder().explanation(
+ "Processor does not allow upstream connections but currently has " + incomingConnCount)
+ .subject("Upstream Connections").valid(false).build());
+ }
+ break;
}
- break;
- }
- case INPUT_REQUIRED: {
- if (getIncomingNonLoopConnections().isEmpty()) {
- results.add(new ValidationResult.Builder()
- .explanation("Processor requires an upstream connection but currently has none")
- .subject("Upstream Connections").valid(false).build());
+ case INPUT_REQUIRED: {
+ if (getIncomingNonLoopConnections().isEmpty()) {
+ results.add(new ValidationResult.Builder()
+ .explanation("Processor requires an upstream connection but currently has none")
+ .subject("Upstream Connections").valid(false).build());
+ }
+ break;
}
- break;
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
index 60a1943..a4477f6 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/reporting/AbstractReportingTaskNode.java
@@ -17,7 +17,10 @@
package org.apache.nifi.controller.reporting;
import org.apache.nifi.annotation.configuration.DefaultSchedule;
+import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.components.validation.ValidationStatus;
@@ -35,10 +38,14 @@
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.controller.service.StandardConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.InstanceClassLoader;
+import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.reporting.ReportingTask;
+import org.apache.nifi.reporting.VerifiableReportingTask;
import org.apache.nifi.scheduling.SchedulingStrategy;
import org.apache.nifi.util.CharacterFilterUtils;
import org.apache.nifi.util.FormatUtils;
@@ -47,7 +54,9 @@
import org.slf4j.LoggerFactory;
import java.net.URL;
+import java.util.ArrayList;
import java.util.Collection;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
@@ -315,4 +324,77 @@
public ParameterLookup getParameterLookup() {
return ParameterLookup.EMPTY;
}
+
+ @Override
+ public void verifyCanPerformVerification() {
+ if (isRunning()) {
+ throw new IllegalStateException("Cannot perform verification because Reporting Task is not fully stopped");
+ }
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfiguration(final ConfigurationContext context, final ComponentLog logger, final ExtensionManager extensionManager) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ try {
+ verifyCanPerformVerification();
+
+ final long startNanos = System.nanoTime();
+ // Call super's verifyConfig, which will perform component validation
+ results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), null));
+ final long validationComplete = System.nanoTime();
+
+ // If any invalid outcomes from validation, we do not want to perform additional verification, because we only run additional verification when the component is valid.
+ // This is done in order to make it much simpler to develop these verifications, since the developer doesn't have to worry about whether or not the given values are valid.
+ if (!results.isEmpty() && results.stream().anyMatch(result -> result.getOutcome() == Outcome.FAILED)) {
+ return results;
+ }
+
+ final ReportingTask reportingTask = getReportingTask();
+ if (reportingTask instanceof VerifiableReportingTask) {
+ logger.debug("{} is a VerifiableReportingTask. Will perform full verification of configuration.", this);
+ final VerifiableReportingTask verifiable = (VerifiableReportingTask) reportingTask;
+
+ // Check if the given configuration requires a different classloader than the current configuration
+ final boolean classpathDifferent = isClasspathDifferent(context.getProperties());
+
+ if (classpathDifferent) {
+ // Create a classloader for the given configuration and use that to verify the component's configuration
+ final Bundle bundle = extensionManager.getBundle(getBundleCoordinate());
+ final Set<URL> classpathUrls = getAdditionalClasspathResources(context.getProperties().keySet(), descriptor -> context.getProperty(descriptor).getValue());
+
+ final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+ try (final InstanceClassLoader detectedClassLoader = extensionManager.createInstanceClassLoader(getComponentType(), getIdentifier(), bundle, classpathUrls, false)) {
+ Thread.currentThread().setContextClassLoader(detectedClassLoader);
+ results.addAll(verifiable.verify(context, logger));
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ }
+ } else {
+ // Verify the configuration, using the component's classloader
+ try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, reportingTask.getClass(), getIdentifier())) {
+ results.addAll(verifiable.verify(context, logger));
+ }
+ }
+
+ final long validationNanos = validationComplete - startNanos;
+ final long verificationNanos = System.nanoTime() - validationComplete;
+ logger.debug("{} completed full configuration validation in {} plus {} for validation",
+ this, FormatUtils.formatNanos(verificationNanos, false), FormatUtils.formatNanos(validationNanos, false));
+ } else {
+ logger.debug("{} is not a VerifiableReportingTask, so will not perform full verification of configuration. Validation took {}", this,
+ FormatUtils.formatNanos(validationComplete - startNanos, false));
+ }
+ } catch (final Throwable t) {
+ logger.error("Failed to perform verification of Reporting Task's configuration for {}", this, t);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(Outcome.FAILED)
+ .verificationStepName("Perform Verification")
+ .explanation("Encountered unexpected failure when attempting to perform verification: " + t)
+ .build());
+ }
+
+ return results;
+ }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
index 028a129..1a2e63e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardConfigurationContext.java
@@ -27,9 +27,13 @@
import org.apache.nifi.controller.ComponentNode;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.ControllerServiceLookup;
+import org.apache.nifi.controller.PropertyConfiguration;
+import org.apache.nifi.controller.PropertyConfigurationMapper;
+import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.registry.VariableRegistry;
import org.apache.nifi.util.FormatUtils;
+import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
@@ -43,13 +47,27 @@
private final VariableRegistry variableRegistry;
private final String schedulingPeriod;
private final Long schedulingNanos;
+ private final Map<PropertyDescriptor, String> properties;
+ private final String annotationData;
public StandardConfigurationContext(final ComponentNode component, final ControllerServiceLookup serviceLookup, final String schedulingPeriod,
final VariableRegistry variableRegistry) {
+ this(component, serviceLookup, schedulingPeriod, variableRegistry, component.getEffectivePropertyValues(), component.getAnnotationData());
+ }
+
+ public StandardConfigurationContext(final ComponentNode component, final Map<String, String> propertyOverrides, final String annotationDataOverride, final ParameterLookup parameterLookup,
+ final ControllerServiceLookup serviceLookup, final String schedulingPeriod, final VariableRegistry variableRegistry) {
+ this(component, serviceLookup, schedulingPeriod, variableRegistry, resolvePropertyValues(component, parameterLookup, propertyOverrides), annotationDataOverride);
+ }
+
+ public StandardConfigurationContext(final ComponentNode component, final ControllerServiceLookup serviceLookup, final String schedulingPeriod,
+ final VariableRegistry variableRegistry, final Map<PropertyDescriptor, String> propertyValues, final String annotationData) {
this.component = component;
this.serviceLookup = serviceLookup;
this.schedulingPeriod = schedulingPeriod;
this.variableRegistry = variableRegistry;
+ this.properties = Collections.unmodifiableMap(propertyValues);
+ this.annotationData = annotationData;
if (schedulingPeriod == null) {
schedulingNanos = null;
@@ -62,7 +80,7 @@
}
preparedQueries = new HashMap<>();
- for (final Map.Entry<PropertyDescriptor, String> entry : component.getEffectivePropertyValues().entrySet()) {
+ for (final Map.Entry<PropertyDescriptor, String> entry : propertyValues.entrySet()) {
final PropertyDescriptor desc = entry.getKey();
String value = entry.getValue();
if (value == null) {
@@ -74,9 +92,29 @@
}
}
+ private static Map<PropertyDescriptor, String> resolvePropertyValues(final ComponentNode component, final ParameterLookup parameterLookup, final Map<String, String> propertyOverrides) {
+ final Map<PropertyDescriptor, String> resolvedProperties = new LinkedHashMap<>(component.getEffectivePropertyValues());
+ final PropertyConfigurationMapper configurationMapper = new PropertyConfigurationMapper();
+
+ for (final Map.Entry<String, String> entry : propertyOverrides.entrySet()) {
+ final String propertyName = entry.getKey();
+ final String propertyValue = entry.getValue();
+ final PropertyDescriptor propertyDescriptor = component.getPropertyDescriptor(propertyName);
+ if (propertyValue == null) {
+ resolvedProperties.remove(propertyDescriptor);
+ } else {
+ final PropertyConfiguration configuration = configurationMapper.mapRawPropertyValuesToPropertyConfiguration(propertyDescriptor, propertyValue);
+ final String effectiveValue = configuration.getEffectiveValue(parameterLookup);
+ resolvedProperties.put(propertyDescriptor, effectiveValue);
+ }
+ }
+
+ return resolvedProperties;
+ }
+
@Override
public PropertyValue getProperty(final PropertyDescriptor property) {
- final String configuredValue = component.getEffectivePropertyValue(property);
+ final String configuredValue = properties.get(property);
// We need to get the 'canonical representation' of the property descriptor from the component itself,
// since the supplied PropertyDescriptor may not have the proper default value.
@@ -89,7 +127,12 @@
@Override
public Map<PropertyDescriptor, String> getProperties() {
- return component.getEffectivePropertyValues();
+ return properties;
+ }
+
+ @Override
+ public String getAnnotationData() {
+ return annotationData;
}
@Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index b2b98a6..0a7e8c5 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -25,7 +25,10 @@
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
import org.apache.nifi.authorization.resource.ResourceType;
+import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.validation.ValidationState;
@@ -39,16 +42,19 @@
import org.apache.nifi.controller.ReloadComponent;
import org.apache.nifi.controller.TerminationAwareLogger;
import org.apache.nifi.controller.ValidationContextFactory;
+import org.apache.nifi.controller.VerifiableControllerService;
import org.apache.nifi.controller.exception.ControllerServiceInstantiationException;
import org.apache.nifi.groups.ProcessGroup;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.nar.InstanceClassLoader;
import org.apache.nifi.nar.NarCloseable;
import org.apache.nifi.parameter.ParameterContext;
import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.processor.SimpleProcessLogger;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.util.CharacterFilterUtils;
+import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.ReflectionUtils;
import org.apache.nifi.util.Tuple;
import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
@@ -61,6 +67,7 @@
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
+import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
@@ -383,6 +390,82 @@
}
@Override
+ public void verifyCanPerformVerification() {
+ if (getState() != ControllerServiceState.DISABLED) {
+ throw new IllegalStateException("Cannot perform verification because the Controller Service is not disabled");
+ }
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verifyConfiguration(final ConfigurationContext context, final ComponentLog logger, final Map<String, String> variables,
+ final ExtensionManager extensionManager) {
+
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ try {
+ verifyCanPerformVerification();
+
+ final long startNanos = System.nanoTime();
+ // Call super's verifyConfig, which will perform component validation
+ results.addAll(super.verifyConfig(context.getProperties(), context.getAnnotationData(), getProcessGroup() == null ? null : getProcessGroup().getParameterContext()));
+ final long validationComplete = System.nanoTime();
+
+ // If any invalid outcomes from validation, we do not want to perform additional verification, because we only run additional verification when the component is valid.
+ // This is done in order to make it much simpler to develop these verifications, since the developer doesn't have to worry about whether or not the given values are valid.
+ if (!results.isEmpty() && results.stream().anyMatch(result -> result.getOutcome() == Outcome.FAILED)) {
+ return results;
+ }
+
+ final ControllerService controllerService = getControllerServiceImplementation();
+ if (controllerService instanceof VerifiableControllerService) {
+ LOG.debug("{} is a VerifiableControllerService. Will perform full verification of configuration.", this);
+
+ final VerifiableControllerService verifiable = (VerifiableControllerService) controllerService;
+
+ // Check if the given configuration requires a different classloader than the current configuration
+ final boolean classpathDifferent = isClasspathDifferent(context.getProperties());
+
+ if (classpathDifferent) {
+ // Create a classloader for the given configuration and use that to verify the component's configuration
+ final Bundle bundle = extensionManager.getBundle(getBundleCoordinate());
+ final Set<URL> classpathUrls = getAdditionalClasspathResources(context.getProperties().keySet(), descriptor -> context.getProperty(descriptor).getValue());
+
+ final ClassLoader currentClassLoader = Thread.currentThread().getContextClassLoader();
+ try (final InstanceClassLoader detectedClassLoader = extensionManager.createInstanceClassLoader(getComponentType(), getIdentifier(), bundle, classpathUrls, false)) {
+ Thread.currentThread().setContextClassLoader(detectedClassLoader);
+ results.addAll(verifiable.verify(context, logger, variables));
+ } finally {
+ Thread.currentThread().setContextClassLoader(currentClassLoader);
+ }
+ } else {
+ // Verify the configuration, using the component's classloader
+ try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, controllerService.getClass(), getIdentifier())) {
+ results.addAll(verifiable.verify(context, logger, variables));
+ }
+ }
+
+ final long validationNanos = validationComplete - startNanos;
+ final long verificationNanos = System.nanoTime() - validationComplete;
+ LOG.debug("{} completed full configuration validation in {} plus {} for validation",
+ this, FormatUtils.formatNanos(verificationNanos, false), FormatUtils.formatNanos(validationNanos, false));
+ } else {
+ LOG.debug("{} is not a VerifiableControllerService, so will not perform full verification of configuration. Validation took {}", this,
+ FormatUtils.formatNanos(validationComplete - startNanos, false));
+ }
+ } catch (final Throwable t) {
+ LOG.error("Failed to perform verification of Controller Service's configuration for {}", this, t);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .outcome(Outcome.FAILED)
+ .verificationStepName("Perform Verification")
+ .explanation("Encountered unexpected failure when attempting to perform verification: " + t)
+ .build());
+ }
+
+ return results;
+ }
+
+ @Override
public boolean isValidationNecessary() {
switch (getState()) {
case DISABLED:
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
index dd16686..99652b8 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/state/manager/StandardStateManagerProvider.java
@@ -256,7 +256,7 @@
provider.initialize(initContext);
}
- final ValidationContext validationContext = new StandardValidationContext(null, propertyStringMap, null, null, null, variableRegistry, null);
+ final ValidationContext validationContext = new StandardValidationContext(null, propertyStringMap, null, null, null, variableRegistry, null, true);
final Collection<ValidationResult> results = provider.validate(validationContext);
final StringBuilder validationFailures = new StringBuilder();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/repository/NopLogRepository.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/repository/NopLogRepository.java
new file mode 100644
index 0000000..9ad7993
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/logging/repository/NopLogRepository.java
@@ -0,0 +1,102 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.logging.repository;
+
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogLevel;
+import org.apache.nifi.logging.LogObserver;
+import org.apache.nifi.logging.LogRepository;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.ConcurrentMap;
+
+public class NopLogRepository implements LogRepository {
+ private final ConcurrentMap<String, LogLevel> observationLevels = new ConcurrentHashMap<>();
+ private volatile ComponentLog logger;
+
+ @Override
+ public void addLogMessage(final LogLevel level, final String message) {
+ }
+
+ @Override
+ public void addLogMessage(final LogLevel level, final String message, final Throwable t) {
+ }
+
+ @Override
+ public void addLogMessage(final LogLevel level, final String messageFormat, final Object[] params) {
+ }
+
+ @Override
+ public void addLogMessage(final LogLevel level, final String messageFormat, final Object[] params, final Throwable t) {
+ }
+
+ @Override
+ public void addObserver(final String observerIdentifier, final LogLevel level, final LogObserver observer) {
+ }
+
+ @Override
+ public void setObservationLevel(final String observerIdentifier, final LogLevel level) {
+ observationLevels.put(observerIdentifier, level);
+ }
+
+ @Override
+ public LogLevel getObservationLevel(final String observerIdentifier) {
+ return observationLevels.get(observerIdentifier);
+ }
+
+ @Override
+ public LogObserver removeObserver(final String observerIdentifier) {
+ observationLevels.remove(observerIdentifier);
+ return null;
+ }
+
+ @Override
+ public void removeAllObservers() {
+ observationLevels.clear();
+ }
+
+ @Override
+ public void setLogger(final ComponentLog logger) {
+ this.logger = logger;
+ }
+
+ @Override
+ public ComponentLog getLogger() {
+ return logger;
+ }
+
+ @Override
+ public boolean isDebugEnabled() {
+ return false;
+ }
+
+ @Override
+ public boolean isInfoEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isWarnEnabled() {
+ return true;
+ }
+
+ @Override
+ public boolean isErrorEnabled() {
+ return true;
+ }
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
index 0c55241..1c9a26b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/SimpleProcessLogger.java
@@ -37,8 +37,12 @@
private final Object component;
public SimpleProcessLogger(final String componentId, final Object component) {
+ this(component, LogRepositoryFactory.getRepository(componentId));
+ }
+
+ public SimpleProcessLogger(final Object component, final LogRepository logRepository) {
this.logger = LoggerFactory.getLogger(component.getClass());
- this.logRepository = LogRepositoryFactory.getRepository(componentId);
+ this.logRepository = logRepository;
this.component = component;
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
index aac9ba3..a08ac26 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardProcessContext.java
@@ -27,13 +27,17 @@
import org.apache.nifi.components.resource.StandardResourceReferenceFactory;
import org.apache.nifi.components.state.StateManager;
import org.apache.nifi.connectable.Connection;
+import org.apache.nifi.controller.ComponentNode;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.ControllerServiceLookup;
import org.apache.nifi.controller.NodeTypeProvider;
import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.controller.PropertyConfiguration;
+import org.apache.nifi.controller.PropertyConfigurationMapper;
import org.apache.nifi.controller.lifecycle.TaskTermination;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.encrypt.PropertyEncryptor;
+import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.processor.exception.TerminatedTaskException;
import org.apache.nifi.scheduling.ExecutionNode;
import org.apache.nifi.util.Connectables;
@@ -57,18 +61,36 @@
private final TaskTermination taskTermination;
private final NodeTypeProvider nodeTypeProvider;
private final Map<PropertyDescriptor, String> properties;
+ private final String annotationData;
public StandardProcessContext(final ProcessorNode processorNode, final ControllerServiceProvider controllerServiceProvider, final PropertyEncryptor propertyEncryptor,
final StateManager stateManager, final TaskTermination taskTermination, final NodeTypeProvider nodeTypeProvider) {
+
+ this(processorNode, controllerServiceProvider, propertyEncryptor, stateManager, taskTermination, nodeTypeProvider,
+ processorNode.getEffectivePropertyValues(), processorNode.getAnnotationData());
+ }
+
+ public StandardProcessContext(final ProcessorNode processorNode, final Map<String, String> propertiesOverride, final String annotationDataOverride, final ParameterLookup parameterLookup,
+ final ControllerServiceProvider controllerServiceProvider, final PropertyEncryptor propertyEncryptor,
+ final StateManager stateManager, final TaskTermination taskTermination, final NodeTypeProvider nodeTypeProvider) {
+
+ this(processorNode, controllerServiceProvider, propertyEncryptor, stateManager, taskTermination, nodeTypeProvider,
+ resolvePropertyValues(processorNode, parameterLookup, propertiesOverride), annotationDataOverride);
+ }
+
+ public StandardProcessContext(final ProcessorNode processorNode, final ControllerServiceProvider controllerServiceProvider, final PropertyEncryptor propertyEncryptor,
+ final StateManager stateManager, final TaskTermination taskTermination, final NodeTypeProvider nodeTypeProvider,
+ final Map<PropertyDescriptor, String> propertyValues, final String annotationData) {
this.procNode = processorNode;
this.controllerServiceProvider = controllerServiceProvider;
this.propertyEncryptor = propertyEncryptor;
this.stateManager = stateManager;
this.taskTermination = taskTermination;
this.nodeTypeProvider = nodeTypeProvider;
+ this.annotationData = annotationData;
- properties = Collections.unmodifiableMap(processorNode.getEffectivePropertyValues());
+ properties = Collections.unmodifiableMap(propertyValues);
preparedQueries = new HashMap<>();
for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
@@ -85,6 +107,27 @@
}
}
+
+ private static Map<PropertyDescriptor, String> resolvePropertyValues(final ComponentNode component, final ParameterLookup parameterLookup, final Map<String, String> propertyValues) {
+ final Map<PropertyDescriptor, String> resolvedProperties = new LinkedHashMap<>(component.getEffectivePropertyValues());
+ final PropertyConfigurationMapper configurationMapper = new PropertyConfigurationMapper();
+
+ for (final Map.Entry<String, String> entry : propertyValues.entrySet()) {
+ final String propertyName = entry.getKey();
+ final String propertyValue = entry.getValue();
+ final PropertyDescriptor propertyDescriptor = component.getPropertyDescriptor(propertyName);
+ if (propertyValue == null) {
+ resolvedProperties.remove(propertyDescriptor);
+ } else {
+ final PropertyConfiguration configuration = configurationMapper.mapRawPropertyValuesToPropertyConfiguration(propertyDescriptor, propertyValue);
+ final String effectiveValue = configuration.getEffectiveValue(parameterLookup);
+ resolvedProperties.put(propertyDescriptor, effectiveValue);
+ }
+ }
+
+ return resolvedProperties;
+ }
+
private void verifyTaskActive() {
if (taskTermination.isTerminated()) {
throw new TerminatedTaskException();
@@ -163,7 +206,7 @@
@Override
public String getAnnotationData() {
verifyTaskActive();
- return procNode.getAnnotationData();
+ return annotationData;
}
@Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
index e2ca1d2..a2ebdf7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContext.java
@@ -64,7 +64,7 @@
private final String componentId;
private final ParameterContext parameterContext;
private final AtomicReference<Map<PropertyDescriptor, String>> effectiveValuesRef = new AtomicReference<>();
-
+ private final boolean validateConnections;
public StandardValidationContext(
final ControllerServiceProvider controllerServiceProvider,
@@ -73,7 +73,8 @@
final String groupId,
final String componentId,
final VariableRegistry variableRegistry,
- final ParameterContext parameterContext) {
+ final ParameterContext parameterContext,
+ final boolean validateConnections) {
super(parameterContext, properties);
this.controllerServiceProvider = controllerServiceProvider;
@@ -83,6 +84,7 @@
this.groupId = groupId;
this.componentId = componentId;
this.parameterContext = parameterContext;
+ this.validateConnections = validateConnections;
preparedQueries = new HashMap<>(properties.size());
for (final Map.Entry<PropertyDescriptor, PropertyConfiguration> entry : properties.entrySet()) {
@@ -121,7 +123,7 @@
final ProcessGroup serviceGroup = serviceNode.getProcessGroup();
final String serviceGroupId = serviceGroup == null ? null : serviceGroup.getIdentifier();
return new StandardValidationContext(controllerServiceProvider, serviceNode.getProperties(), serviceNode.getAnnotationData(), serviceGroupId,
- serviceNode.getIdentifier(), variableRegistry, serviceNode.getProcessGroup().getParameterContext());
+ serviceNode.getIdentifier(), variableRegistry, serviceNode.getProcessGroup().getParameterContext(), validateConnections);
}
@Override
@@ -241,6 +243,10 @@
return value != null;
}
+ @Override
+ public boolean isValidateConnections() {
+ return validateConnections;
+ }
@Override
public String toString() {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
index 3485f9b..2299165 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/processor/StandardValidationContextFactory.java
@@ -38,7 +38,7 @@
@Override
public ValidationContext newValidationContext(final Map<PropertyDescriptor, PropertyConfiguration> properties, final String annotationData, final String groupId, final String componentId,
- final ParameterContext parameterContext) {
- return new StandardValidationContext(serviceProvider, properties, annotationData, groupId, componentId, variableRegistry, parameterContext);
+ final ParameterContext parameterContext, final boolean validateConnections) {
+ return new StandardValidationContext(serviceProvider, properties, annotationData, groupId, componentId, variableRegistry, parameterContext, validateConnections);
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
index 29108c0..4df5d89 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/AbstractComponentNode.java
@@ -17,9 +17,12 @@
package org.apache.nifi.controller;
import org.apache.commons.lang3.StringUtils;
+import org.apache.nifi.attribute.expression.language.Query;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.bundle.Bundle;
import org.apache.nifi.bundle.BundleCoordinate;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.ValidationContext;
@@ -52,6 +55,7 @@
import org.apache.nifi.parameter.ParameterUpdate;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.util.CharacterFilterUtils;
+import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.util.file.classloader.ClassLoaderUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -60,6 +64,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
+import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
@@ -78,6 +83,7 @@
import java.util.stream.Collectors;
public abstract class AbstractComponentNode implements ComponentNode {
+ private static final String PERFORM_VALIDATION_STEP_NAME = "Perform Validation";
private static final Logger logger = LoggerFactory.getLogger(AbstractComponentNode.class);
private final String id;
@@ -163,14 +169,17 @@
return getAdditionalClasspathResources((Collection<PropertyDescriptor>) propertyDescriptors);
}
- private Set<URL> getAdditionalClasspathResources(final Collection<PropertyDescriptor> propertyDescriptors) {
+ protected Set<URL> getAdditionalClasspathResources(final Collection<PropertyDescriptor> propertyDescriptors) {
+ return getAdditionalClasspathResources(propertyDescriptors, this::getEffectivePropertyValue);
+ }
+
+ protected Set<URL> getAdditionalClasspathResources(final Collection<PropertyDescriptor> propertyDescriptors, final Function<PropertyDescriptor, String> effectiveValueLookup) {
final Set<URL> additionalUrls = new LinkedHashSet<>();
final ResourceReferenceFactory resourceReferenceFactory = new StandardResourceReferenceFactory();
for (final PropertyDescriptor descriptor : propertyDescriptors) {
if (descriptor.isDynamicClasspathModifier()) {
- final PropertyConfiguration propertyConfiguration = getProperty(descriptor);
- final String value = propertyConfiguration == null ? null : propertyConfiguration.getEffectiveValue(getParameterContext());
+ final String value = effectiveValueLookup.apply(descriptor);
if (!StringUtils.isEmpty(value)) {
final ResourceContext resourceContext = new StandardResourceContext(resourceReferenceFactory, descriptor);
@@ -184,6 +193,36 @@
return additionalUrls;
}
+ /**
+ * Determines if the given set of properties will result in a different classpath than the currently configured set of properties
+ * @param properties the properties to analyze
+ * @return <code>true</code> if the given set of properties will require a different classpath (and therefore a different classloader) than the currently
+ * configured set of properties
+ */
+ protected boolean isClasspathDifferent(final Map<PropertyDescriptor, String> properties) {
+ // If any property in the given map modifies classpath and is different than the currently configured value,
+ // the given properties will require a different classpath.
+ for (final Map.Entry<PropertyDescriptor, String> entry : properties.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+ final String value = entry.getValue();
+ final String currentlyConfiguredValue = getEffectivePropertyValue(descriptor);
+
+ if (descriptor.isDynamicClasspathModifier() && !Objects.equals(value, currentlyConfiguredValue)) {
+ return true;
+ }
+ }
+
+ // If any property in the currently configured properties modifies classpath and is not in the given set of properties,
+ // the given properties will require a different classpath.
+ for (final Map.Entry<PropertyDescriptor, PropertyConfiguration> entry : getProperties().entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+ if (descriptor.isDynamicClasspathModifier() && !properties.containsKey(descriptor)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
@Override
public void setProperties(final Map<String, String> properties, final boolean allowRemovalOfRequiredProperties) {
@@ -195,10 +234,8 @@
try {
verifyCanUpdateProperties(properties);
- // Keep track of counts of each parameter reference. This way, when we complete the updates to property values, we can
- // update our counts easily.
- final ParameterParser elAgnosticParameterParser = new ExpressionLanguageAgnosticParameterParser();
- final ParameterParser elAwareParameterParser = new ExpressionLanguageAwareParameterParser();
+ final PropertyConfigurationMapper configurationMapper = new PropertyConfigurationMapper();
+ final Map<String, PropertyConfiguration> configurationMap = configurationMapper.mapRawPropertyValuesToPropertyConfiguration(this, properties);
try (final NarCloseable narCloseable = NarCloseable.withComponentNarLoader(extensionManager, getComponent().getClass(), id)) {
boolean classpathChanged = false;
@@ -219,27 +256,17 @@
if (entry.getKey() != null && entry.getValue() == null) {
removeProperty(entry.getKey(), allowRemovalOfRequiredProperties);
} else if (entry.getKey() != null) {
- final String updatedValue = CharacterFilterUtils.filterInvalidXmlCharacters(entry.getValue());
-
// Use the EL-Agnostic Parameter Parser to gather the list of referenced Parameters. We do this because we want to to keep track of which parameters
// are referenced, regardless of whether or not they are referenced from within an EL Expression. However, we also will need to derive a different ParameterTokenList
// that we can provide to the PropertyConfiguration, so that when compiling the Expression Language Expressions, we are able to keep the Parameter Reference within
// the Expression's text.
- final ParameterTokenList updatedValueReferences = elAgnosticParameterParser.parseTokens(updatedValue);
- final List<ParameterReference> parameterReferences = updatedValueReferences.toReferenceList();
+ final PropertyConfiguration propertyConfiguration = configurationMap.get(entry.getKey());
+ final List<ParameterReference> parameterReferences = propertyConfiguration.getParameterReferences();
for (final ParameterReference reference : parameterReferences) {
// increment count in map for this parameter
parameterReferenceCounts.merge(reference.getParameterName(), 1, (a, b) -> a == -1 ? null : a + b);
}
- final PropertyConfiguration propertyConfiguration;
- final boolean supportsEL = getPropertyDescriptor(entry.getKey()).isExpressionLanguageSupported();
- if (supportsEL) {
- propertyConfiguration = new PropertyConfiguration(updatedValue, elAwareParameterParser.parseTokens(updatedValue), parameterReferences);
- } else {
- propertyConfiguration = new PropertyConfiguration(updatedValue, updatedValueReferences, parameterReferences);
- }
-
setProperty(entry.getKey(), propertyConfiguration, this.properties::get);
}
}
@@ -324,6 +351,72 @@
}
}
+ protected List<ConfigVerificationResult> verifyConfig(final Map<PropertyDescriptor, String> propertyValues, final String annotationData, final ParameterContext parameterContext) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ try {
+ final long startNanos = System.nanoTime();
+
+ final Map<PropertyDescriptor, PropertyConfiguration> descriptorToConfigMap = new LinkedHashMap<>();
+ for (final Map.Entry<PropertyDescriptor, String> entry : propertyValues.entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+ final String rawValue = entry.getValue();
+ final String propertyValue = rawValue == null ? descriptor.getDefaultValue() : rawValue;
+
+ final PropertyConfiguration propertyConfiguration = new PropertyConfiguration(propertyValue, null, Collections.emptyList());
+ descriptorToConfigMap.put(descriptor, propertyConfiguration);
+ }
+
+ final ValidationContext validationContext = getValidationContextFactory().newValidationContext(descriptorToConfigMap, annotationData,
+ getProcessGroupIdentifier(), getIdentifier(), parameterContext, false);
+
+ final ValidationState validationState = performValidation(validationContext);
+ final ValidationStatus validationStatus = validationState.getStatus();
+
+ if (validationStatus == ValidationStatus.INVALID) {
+ for (final ValidationResult result : validationState.getValidationErrors()) {
+ if (result.isValid()) {
+ continue;
+ }
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(PERFORM_VALIDATION_STEP_NAME)
+ .outcome(Outcome.FAILED)
+ .explanation("Component is invalid: " + result.toString())
+ .build());
+ }
+
+ if (results.isEmpty()) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(PERFORM_VALIDATION_STEP_NAME)
+ .outcome(Outcome.FAILED)
+ .explanation("Component is invalid but provided no Validation Results to indicate why")
+ .build());
+ }
+
+ logger.debug("{} is not valid with the given configuration. Will not attempt to perform any additional verification of configuration. Validation took {}. Reason not valid: {}",
+ this, results, FormatUtils.formatNanos(System.nanoTime() - startNanos, false));
+ return results;
+ }
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(PERFORM_VALIDATION_STEP_NAME)
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Component Validation passed")
+ .build());
+ } catch (final Throwable t) {
+ logger.error("Failed to perform verification of component's configuration for {}", this, t);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(PERFORM_VALIDATION_STEP_NAME)
+ .outcome(Outcome.FAILED)
+ .explanation("Encountered unexpected failure when attempting to perform verification: " + t)
+ .build());
+ }
+
+ return results;
+ }
+
@Override
public Set<String> getReferencedParameterNames() {
return Collections.unmodifiableSet(parameterReferenceCounts.keySet());
@@ -334,6 +427,19 @@
return !parameterReferenceCounts.isEmpty();
}
+ @Override
+ public Set<String> getReferencedAttributeNames() {
+ final Set<String> referencedAttributes = new HashSet<>();
+
+ for (final PropertyDescriptor descriptor : getPropertyDescriptors()) {
+ final String effectiveValue = getEffectivePropertyValue(descriptor);
+ final Set<String> attributes = Query.prepareWithParametersPreEvaluated(effectiveValue).getExplicitlyReferencedAttributes();
+ referencedAttributes.addAll(attributes);
+ }
+
+ return referencedAttributes;
+ }
+
// Keep setProperty/removeProperty private so that all calls go through setProperties
private void setProperty(final String name, final PropertyConfiguration propertyConfiguration, final Function<PropertyDescriptor, PropertyConfiguration> valueToCompareFunction) {
if (name == null || propertyConfiguration == null || propertyConfiguration.getRawValue() == null) {
@@ -563,7 +669,7 @@
@Override
public ValidationState performValidation(final Map<PropertyDescriptor, PropertyConfiguration> properties, final String annotationData, final ParameterContext parameterContext) {
- final ValidationContext validationContext = validationContextFactory.newValidationContext(properties, annotationData, getProcessGroupIdentifier(), getIdentifier(), parameterContext);
+ final ValidationContext validationContext = validationContextFactory.newValidationContext(properties, annotationData, getProcessGroupIdentifier(), getIdentifier(), parameterContext, true);
return performValidation(validationContext);
}
@@ -1090,7 +1196,7 @@
return context;
}
- context = getValidationContextFactory().newValidationContext(getProperties(), getAnnotationData(), getProcessGroupIdentifier(), getIdentifier(), getParameterContext());
+ context = getValidationContextFactory().newValidationContext(getProperties(), getAnnotationData(), getProcessGroupIdentifier(), getIdentifier(), getParameterContext(), true);
this.validationContext = context;
logger.debug("Updating validation context to {}", context);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ComponentNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ComponentNode.java
index de7612a..9ffd80a 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ComponentNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ComponentNode.java
@@ -79,6 +79,11 @@
void onParametersModified(Map<String, ParameterUpdate> parameterUpdates);
/**
+ * @return the Set of all attributes that are explicitly referenced by Expression Language used in the property values
+ */
+ Set<String> getReferencedAttributeNames();
+
+ /**
* <p>
* Pause triggering asynchronous validation to occur when the component is updated. Often times, it is necessary
* to update several aspects of a component, such as the properties and annotation data, at once. When this occurs,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
index 38a5120..053e97d 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ProcessorNode.java
@@ -24,8 +24,10 @@
import org.apache.nifi.controller.scheduling.SchedulingAgent;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.logging.LogLevel;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.Processor;
import org.apache.nifi.processor.Relationship;
@@ -137,6 +139,14 @@
*/
public abstract void verifyCanStart(Set<ControllerServiceNode> ignoredReferences);
+ public void verifyCanPerformVerification() {
+ if (isRunning()) {
+ throw new IllegalStateException("Cannot perform verification because the Processor is not stopped");
+ }
+ }
+
+ public abstract List<ConfigVerificationResult> verifyConfiguration(ProcessContext processContext, ComponentLog logger, Map<String, String> attributes, ExtensionManager extensionManager);
+
public abstract void verifyCanTerminate();
/**
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/PropertyConfigurationMapper.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/PropertyConfigurationMapper.java
new file mode 100644
index 0000000..9e10ebc
--- /dev/null
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/PropertyConfigurationMapper.java
@@ -0,0 +1,70 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.controller;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.parameter.ExpressionLanguageAgnosticParameterParser;
+import org.apache.nifi.parameter.ExpressionLanguageAwareParameterParser;
+import org.apache.nifi.parameter.ParameterParser;
+import org.apache.nifi.parameter.ParameterReference;
+import org.apache.nifi.parameter.ParameterTokenList;
+import org.apache.nifi.util.CharacterFilterUtils;
+
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+
+public class PropertyConfigurationMapper {
+ private final ParameterParser elAgnosticParameterParser = new ExpressionLanguageAgnosticParameterParser();
+ private final ParameterParser elAwareParameterParser = new ExpressionLanguageAwareParameterParser();
+
+ public Map<String, PropertyConfiguration> mapRawPropertyValuesToPropertyConfiguration(final ComponentNode componentNode, final Map<String, String> rawPropertyValues) {
+ final Map<String, PropertyConfiguration> configurationMap = new LinkedHashMap<>();
+
+ for (final Map.Entry<String, String> entry : rawPropertyValues.entrySet()) {
+ final String propertyName = entry.getKey();
+ final String propertyValue = entry.getValue();
+ final PropertyDescriptor propertyDescriptor = componentNode.getPropertyDescriptor(propertyName);
+ final PropertyConfiguration configuration = mapRawPropertyValuesToPropertyConfiguration(propertyDescriptor, propertyValue);
+ configurationMap.put(propertyName, configuration);
+ }
+
+ return configurationMap;
+ }
+
+ public PropertyConfiguration mapRawPropertyValuesToPropertyConfiguration(final PropertyDescriptor propertyDescriptor, final String propertyValue) {
+ final String updatedValue = CharacterFilterUtils.filterInvalidXmlCharacters(propertyValue);
+
+ // Use the EL-Agnostic Parameter Parser to gather the list of referenced Parameters. We do this because we want to to keep track of which parameters
+ // are referenced, regardless of whether or not they are referenced from within an EL Expression. However, we also will need to derive a different ParameterTokenList
+ // that we can provide to the PropertyConfiguration, so that when compiling the Expression Language Expressions, we are able to keep the Parameter Reference within
+ // the Expression's text.
+ final ParameterTokenList updatedValueReferences = elAgnosticParameterParser.parseTokens(updatedValue);
+ final List<ParameterReference> parameterReferences = updatedValueReferences.toReferenceList();
+
+ final PropertyConfiguration propertyConfiguration;
+ final boolean supportsEL = propertyDescriptor.isExpressionLanguageSupported();
+ if (supportsEL) {
+ return new PropertyConfiguration(updatedValue, elAwareParameterParser.parseTokens(updatedValue), parameterReferences);
+ } else {
+ return new PropertyConfiguration(updatedValue, updatedValueReferences, parameterReferences);
+ }
+
+ }
+
+}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ReportingTaskNode.java
index 09ae5fb..dd9c165 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ReportingTaskNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ReportingTaskNode.java
@@ -17,10 +17,14 @@
package org.apache.nifi.controller;
import org.apache.nifi.controller.service.ControllerServiceNode;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.reporting.ReportingContext;
import org.apache.nifi.reporting.ReportingTask;
import org.apache.nifi.scheduling.SchedulingStrategy;
+import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
@@ -67,7 +71,7 @@
* has no active threads, only that it is not currently scheduled to be
* given any more threads. To determine whether or not the
* <code>ReportingTask</code> has any active threads, see
- * {@link ProcessScheduler#getActiveThreadCount(ReportingTask)}
+ * {@link ProcessScheduler#getActiveThreadCount(Object)}
*/
ScheduledState getScheduledState();
@@ -102,4 +106,23 @@
void verifyCanUpdate();
void verifyCanClearState();
+
+ /**
+ * Verifies that the Reporting Task is in a state in which it can verify a configuration by calling
+ * {@link #verifyConfiguration(ConfigurationContext, ComponentLog, ExtensionManager)}.
+ *
+ * @throws IllegalStateException if not in a state in which configuration can be verified
+ */
+ void verifyCanPerformVerification();
+
+ /**
+ * Verifies that the given configuration is valid for the Reporting Task
+ *
+ * @param context the configuration to verify
+ * @param logger a logger that can be used when performing verification
+ * @param extensionManager extension manager that is used for obtaining appropriate NAR ClassLoaders
+ * @return a list of results indicating whether or not the given configuration is valid
+ */
+ List<ConfigVerificationResult> verifyConfiguration(ConfigurationContext context, ComponentLog logger, ExtensionManager extensionManager);
+
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
index a0a2a86..bf1d060 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/ValidationContextFactory.java
@@ -24,6 +24,7 @@
public interface ValidationContextFactory {
- ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext);
+ ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId, ParameterContext parameterContext,
+ boolean validateConnections);
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
index 5d542df..0a8a7c3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core-api/src/main/java/org/apache/nifi/controller/service/ControllerServiceNode.java
@@ -19,11 +19,16 @@
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.VersionedComponent;
import org.apache.nifi.controller.ComponentNode;
+import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.controller.LoggableComponent;
import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.components.ConfigVerificationResult;
import java.util.List;
+import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ScheduledExecutorService;
@@ -192,6 +197,25 @@
boolean awaitEnabled(long timePeriod, TimeUnit timeUnit) throws InterruptedException;
/**
+ * Verifies that the Controller Service is in a state in which it can verify a configuration by calling
+ * {@link #verifyConfiguration(ConfigurationContext, ComponentLog, Map, ExtensionManager)}.
+ *
+ * @throws IllegalStateException if not in a state in which configuration can be verified
+ */
+ void verifyCanPerformVerification();
+
+ /**
+ * Verifies that the given configuration is valid for the Controller Service
+ *
+ * @param context the configuration to verify
+ * @param logger a logger that can be used when performing verification
+ * @param variables variables that can be used to resolve property values via Expression Language
+ * @param extensionManager extension manager that is used for obtaining appropriate NAR ClassLoaders
+ * @return a list of results indicating whether or not the given configuration is valid
+ */
+ List<ConfigVerificationResult> verifyConfiguration(ConfigurationContext context, ComponentLog logger, Map<String, String> variables, ExtensionManager extensionManager);
+
+ /**
* Sets a new proxy and implementation for this node.
*
* @param implementation the actual implementation controller service
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java
index 26db5c8..f7053d4 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/TemplateUtils.java
@@ -238,6 +238,7 @@
processorConfig.setCustomUiUrl(null);
processorConfig.setDefaultConcurrentTasks(null);
processorConfig.setDefaultSchedulingPeriod(null);
+ processorConfig.setReferencedAttributes(null);
processorConfig.setAutoTerminatedRelationships(null);
}
@@ -306,6 +307,7 @@
serviceDTO.setCustomUiUrl(null);
serviceDTO.setValidationErrors(null);
serviceDTO.setValidationStatus(null);
+ serviceDTO.setReferencedAttributes(null);
}
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java
index c906b22..18f9b98 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/reporting/StandardReportingTaskNode.java
@@ -18,7 +18,6 @@
import org.apache.nifi.annotation.behavior.Restricted;
import org.apache.nifi.annotation.documentation.DeprecationNotice;
-import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.authorization.Resource;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.ResourceFactory;
@@ -32,12 +31,16 @@
import org.apache.nifi.controller.ValidationContextFactory;
import org.apache.nifi.nar.ExtensionManager;
import org.apache.nifi.parameter.ParameterContext;
+import org.apache.nifi.parameter.ParameterLookup;
import org.apache.nifi.registry.ComponentVariableRegistry;
import org.apache.nifi.reporting.ReportingContext;
import org.apache.nifi.reporting.ReportingTask;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
public class StandardReportingTaskNode extends AbstractReportingTaskNode implements ReportingTaskNode {
+ private static final Logger logger = LoggerFactory.getLogger(StandardReportingTaskNode.class);
private final FlowController flowController;
public StandardReportingTaskNode(final LoggableComponent<ReportingTask> reportingTask, final String id, final FlowController controller,
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
index 7f0dda4..bc468ff3 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/StandardProcessorNodeIT.java
@@ -562,7 +562,7 @@
return new ValidationContextFactory() {
@Override
public ValidationContext newValidationContext(Map<PropertyDescriptor, PropertyConfiguration> properties, String annotationData, String groupId, String componentId,
- ParameterContext context) {
+ ParameterContext context, boolean validateConnections) {
return new ValidationContext() {
@Override
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java
index 4459d14..59995f7 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/mock/MockConfigurationContext.java
@@ -42,6 +42,11 @@
}
@Override
+ public String getAnnotationData() {
+ return null;
+ }
+
+ @Override
public String getSchedulingPeriod() {
return "0 secs";
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
index f96aee7..359b587 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/ExtensionManager.java
@@ -45,7 +45,21 @@
* @param additionalUrls additional URLs to add to the instance class loader
* @return the ClassLoader for the given instance of the given type, or null if the type is not a detected extension type
*/
- InstanceClassLoader createInstanceClassLoader(String classType, String instanceIdentifier, Bundle bundle, Set<URL> additionalUrls);
+ default InstanceClassLoader createInstanceClassLoader(String classType, String instanceIdentifier, Bundle bundle, Set<URL> additionalUrls) {
+ return createInstanceClassLoader(classType, instanceIdentifier, bundle, additionalUrls, true);
+ }
+
+ /**
+ * Creates the ClassLoader for the instance of the given type.
+ *
+ * @param classType the type of class to create the ClassLoader for
+ * @param instanceIdentifier the identifier of the specific instance of the classType to look up the ClassLoader for
+ * @param bundle the bundle where the classType exists
+ * @param additionalUrls additional URLs to add to the instance class loader
+ * @param registerClassLoader whether or not to register the class loader as the new classloader for the component with the given ID
+ * @return the ClassLoader for the given instance of the given type, or null if the type is not a detected extension type
+ */
+ InstanceClassLoader createInstanceClassLoader(String classType, String instanceIdentifier, Bundle bundle, Set<URL> additionalUrls, boolean registerClassLoader);
/**
* Retrieves the InstanceClassLoader for the component with the given identifier.
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
index 7e27e32..e2d1826 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java
@@ -356,7 +356,7 @@
}
@Override
- public InstanceClassLoader createInstanceClassLoader(final String classType, final String instanceIdentifier, final Bundle bundle, final Set<URL> additionalUrls) {
+ public InstanceClassLoader createInstanceClassLoader(final String classType, final String instanceIdentifier, final Bundle bundle, final Set<URL> additionalUrls, final boolean register) {
if (StringUtils.isEmpty(classType)) {
throw new IllegalArgumentException("Class-Type is required");
}
@@ -427,7 +427,10 @@
}
}
- instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader);
+ if (register) {
+ instanceClassloaderLookup.put(instanceIdentifier, instanceClassLoader);
+ }
+
return instanceClassLoader;
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
index 55f5d32..84c2230 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/NiFiServiceFacade.java
@@ -41,6 +41,7 @@
import org.apache.nifi.web.api.dto.ClusterDTO;
import org.apache.nifi.web.api.dto.ComponentHistoryDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
import org.apache.nifi.web.api.dto.ControllerConfigurationDTO;
import org.apache.nifi.web.api.dto.ControllerDTO;
@@ -58,6 +59,7 @@
import org.apache.nifi.web.api.dto.ParameterContextDTO;
import org.apache.nifi.web.api.dto.PortDTO;
import org.apache.nifi.web.api.dto.ProcessGroupDTO;
+import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RegistryDTO;
@@ -634,6 +636,15 @@
ProcessorEntity updateProcessor(Revision revision, ProcessorDTO processorDTO);
/**
+ * Performs verification of the given Processor Configuration for the Processor with the given ID
+ * @param processorId the id of the processor
+ * @param processorConfig the configuration to verify
+ * @param attributes a map of values that can be used for resolving FlowFile attributes for Expression Language
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyProcessorConfiguration(String processorId, ProcessorConfigDTO processorConfig, Map<String, String> attributes);
+
+ /**
* Verifies the specified processor can be removed.
*
* @param processorId processor
@@ -1619,6 +1630,25 @@
void verifyCanUpdate(String groupId, VersionedFlowSnapshot proposedFlow, boolean verifyConnectionRemoval, boolean verifyNotDirty);
/**
+ * Verifies that the Processor with the given identifier is in a state where its configuration can be verified
+ * @param processorId the ID of the processor
+ */
+ void verifyCanVerifyProcessorConfig(String processorId);
+
+ /**
+ * Verifies that the Controller Service with the given identifier is in a state where its configuration can be verified
+ * @param controllerServiceId the ID of the service
+ */
+ void verifyCanVerifyControllerServiceConfig(String controllerServiceId);
+
+
+ /**
+ * Verifies that the Reporting Task with the given identifier is in a state where its configuration can be verified
+ * @param reportingTaskId the ID of the service
+ */
+ void verifyCanVerifyReportingTaskConfig(String reportingTaskId);
+
+ /**
* Verifies that the Process Group with the given identifier can be saved to the flow registry
*
* @param groupId the ID of the Process Group
@@ -2014,6 +2044,16 @@
ControllerServiceEntity updateControllerService(Revision revision, ControllerServiceDTO controllerServiceDTO);
/**
+ * Performs verification of the given Configuration for the Controller Service with the given ID
+ * @param controllerServiceId the id of the controller service
+ * @param controllerService the configuration to verify
+ * @param variables a map of values that can be used for resolving FlowFile attributes for Expression Language
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyControllerServiceConfiguration(String controllerServiceId, ControllerServiceDTO controllerService, Map<String, String> variables);
+
+
+ /**
* Deletes the specified label.
*
* @param revision Revision to compare with current base revision
@@ -2099,6 +2139,14 @@
ReportingTaskEntity updateReportingTask(Revision revision, ReportingTaskDTO reportingTaskDTO);
/**
+ * Performs verification of the given Configuration for the Reporting Task with the given ID
+ * @param reportingTaskId the id of the reporting task
+ * @param reportingTask the configuration to verify
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyReportingTaskConfiguration(String reportingTaskId, ReportingTaskDTO reportingTask);
+
+ /**
* Deletes the specified reporting task.
*
* @param revision Revision to compare with current base revision
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
index a35fa2c..cfe7941 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/StandardNiFiServiceFacade.java
@@ -173,6 +173,7 @@
import org.apache.nifi.web.api.dto.ComponentRestrictionPermissionDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
import org.apache.nifi.web.api.dto.ComponentValidationResultDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
import org.apache.nifi.web.api.dto.ControllerConfigurationDTO;
import org.apache.nifi.web.api.dto.ControllerDTO;
@@ -547,6 +548,21 @@
}
@Override
+ public void verifyCanVerifyProcessorConfig(final String processorId) {
+ processorDAO.verifyConfigVerification(processorId);
+ }
+
+ @Override
+ public void verifyCanVerifyControllerServiceConfig(final String controllerServiceId) {
+ controllerServiceDAO.verifyConfigVerification(controllerServiceId);
+ }
+
+ @Override
+ public void verifyCanVerifyReportingTaskConfig(final String reportingTaskId) {
+ reportingTaskDAO.verifyConfigVerification(reportingTaskId);
+ }
+
+ @Override
public void verifyDeleteProcessor(final String processorId) {
processorDAO.verifyDelete(processorId);
}
@@ -748,6 +764,11 @@
return entityFactory.createProcessorEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, status, bulletinEntities);
}
+ @Override
+ public List<ConfigVerificationResultDTO> verifyProcessorConfiguration(final String processorId, final ProcessorConfigDTO processorConfig, final Map<String, String> attributes) {
+ return processorDAO.verifyProcessorConfiguration(processorId, processorConfig, attributes);
+ }
+
private void awaitValidationCompletion(final ComponentNode component) {
component.getValidationStatus(VALIDATION_WAIT_MILLIS, TimeUnit.MILLISECONDS);
}
@@ -2719,6 +2740,10 @@
return entityFactory.createControllerServiceEntity(snapshot.getComponent(), dtoFactory.createRevisionDTO(snapshot.getLastModification()), permissions, operatePermissions, bulletinEntities);
}
+ @Override
+ public List<ConfigVerificationResultDTO> verifyControllerServiceConfiguration(final String controllerServiceId, final ControllerServiceDTO controllerService, final Map<String, String> variables) {
+ return controllerServiceDAO.verifyConfiguration(controllerServiceId, controllerService, variables);
+ }
@Override
public ControllerServiceReferencingComponentsEntity updateControllerServiceReferencingComponents(
@@ -3099,6 +3124,11 @@
}
@Override
+ public List<ConfigVerificationResultDTO> verifyReportingTaskConfiguration(final String reportingTaskId, final ReportingTaskDTO reportingTask) {
+ return reportingTaskDAO.verifyConfiguration(reportingTaskId, reportingTask);
+ }
+
+ @Override
public ReportingTaskEntity deleteReportingTask(final Revision revision, final String reportingTaskId) {
final ReportingTaskNode reportingTask = reportingTaskDAO.getReportingTask(reportingTaskId);
final PermissionsDTO permissions = dtoFactory.createPermissionsDto(reportingTask);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
index c7b5e07..324cffd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ControllerServiceResource.java
@@ -30,25 +30,36 @@
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceState;
import org.apache.nifi.ui.extension.UiExtension;
import org.apache.nifi.ui.extension.UiExtensionMapping;
import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
+import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.RequestManager;
+import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
+import org.apache.nifi.web.api.concurrent.UpdateStep;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
+import org.apache.nifi.web.api.dto.VerifyControllerServiceConfigRequestDTO;
import org.apache.nifi.web.api.entity.ComponentStateEntity;
import org.apache.nifi.web.api.entity.ControllerServiceEntity;
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
import org.apache.nifi.web.api.entity.UpdateControllerServiceReferenceRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyControllerServiceConfigRequestEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
import org.slf4j.Logger;
@@ -70,10 +81,13 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
import java.util.stream.Collectors;
/**
@@ -87,6 +101,9 @@
public class ControllerServiceResource extends ApplicationResource {
private static final Logger logger = LoggerFactory.getLogger(ControllerServiceResource.class);
+ private static final String VERIFICATION_REQUEST_TYPE = "verification-request";
+ private RequestManager<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>> updateRequestManager =
+ new AsyncRequestManager<>(100, TimeUnit.MINUTES.toMillis(1L), "Verify Controller Service Config Thread");
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
@@ -824,6 +841,228 @@
);
}
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/config/verification-requests")
+ @ApiOperation(
+ value = "Performs verification of the Controller Service's configuration",
+ response = VerifyControllerServiceConfigRequestEntity.class,
+ notes = "This will initiate the process of verifying a given Controller Service configuration. This may be a long-running task. As a result, this endpoint will immediately return a " +
+ "ControllerServiceConfigVerificationRequestEntity, and the process of performing the verification will occur asynchronously in the background. " +
+ "The client may then periodically poll the status of the request by " +
+ "issuing a GET request to /controller-services/{serviceId}/verification-requests/{requestId}. Once the request is completed, the client is expected to issue a DELETE request to " +
+ "/controller-services/{serviceId}/verification-requests/{requestId}.",
+ authorizations = {
+ @Authorization(value = "Read - /controller-services/{uuid}")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ }
+ )
+ public Response submitConfigVerificationRequest(
+ @ApiParam(value = "The controller service id.", required = true) @PathParam("id") final String controllerServiceId,
+ @ApiParam(value = "The controller service configuration verification request.", required = true) final VerifyControllerServiceConfigRequestEntity controllerServiceConfigRequest) {
+
+ if (controllerServiceConfigRequest == null) {
+ throw new IllegalArgumentException("Controller Service's configuration must be specified");
+ }
+
+ final VerifyControllerServiceConfigRequestDTO requestDto = controllerServiceConfigRequest.getRequest();
+ if (requestDto == null || requestDto.getControllerService() == null) {
+ throw new IllegalArgumentException("Controller Service must be specified");
+ }
+
+ if (requestDto.getControllerServiceId() == null) {
+ throw new IllegalArgumentException("Controller Service's identifier must be specified in the request");
+ }
+
+ if (!requestDto.getControllerServiceId().equals(controllerServiceId)) {
+ throw new IllegalArgumentException("Controller Service's identifier in the request must match the identifier provided in the URL");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, controllerServiceConfigRequest);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ return withWriteLock(
+ serviceFacade,
+ controllerServiceConfigRequest,
+ lookup -> {
+ final ComponentAuthorizable controllerService = lookup.getControllerService(controllerServiceId);
+ controllerService.getAuthorizable().authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ serviceFacade.verifyCanVerifyControllerServiceConfig(controllerServiceId);
+ },
+ entity -> performAsyncConfigVerification(entity, user)
+ );
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Returns the Verification Request with the given ID",
+ response = VerifyControllerServiceConfigRequestEntity.class,
+ notes = "Returns the Verification Request with the given ID. Once an Verification Request has been created, "
+ + "that request can subsequently be retrieved via this endpoint, and the request that is fetched will contain the updated state, such as percent complete, the "
+ + "current state of the request, and any failures. ",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can get it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response getVerificationRequest(
+ @ApiParam("The ID of the Controller Service") @PathParam("id") final String controllerServiceId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ final VerifyControllerServiceConfigRequestEntity updateRequestEntity = createVerifyControllerServiceConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Deletes the Verification Request with the given ID",
+ response = VerifyControllerServiceConfigRequestEntity.class,
+ notes = "Deletes the Verification Request with the given ID. After a request is created, it is expected "
+ + "that the client will properly clean up the request by DELETE'ing it, once the Verification process has completed. If the request is deleted before the request "
+ + "completes, then the Verification request will finish the step that it is currently performing and then will cancel any subsequent steps.",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can remove it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response deleteValidationRequest(
+ @ApiParam("The ID of the Controller Service") @PathParam("id") final String controllerServiceId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final boolean twoPhaseRequest = isTwoPhaseRequest(httpServletRequest);
+ final boolean executionPhase = isExecutionPhase(httpServletRequest);
+
+ // If this is a standalone node, or if this is the execution phase of the request, perform the actual request.
+ if (!twoPhaseRequest || executionPhase) {
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ updateRequestManager.removeRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ if (asyncRequest == null) {
+ throw new ResourceNotFoundException("Could not find request of type " + VERIFICATION_REQUEST_TYPE + " with ID " + requestId);
+ }
+
+ if (!asyncRequest.isComplete()) {
+ asyncRequest.cancel();
+ }
+
+ final VerifyControllerServiceConfigRequestEntity updateRequestEntity = createVerifyControllerServiceConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+ if (isValidationPhase(httpServletRequest)) {
+ // Perform authorization by attempting to get the request
+ updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ return generateContinueResponse().build();
+ } else if (isCancellationPhase(httpServletRequest)) {
+ return generateOkResponse().build();
+ } else {
+ throw new IllegalStateException("This request does not appear to be part of the two phase commit.");
+ }
+ }
+
+
+ public Response performAsyncConfigVerification(final VerifyControllerServiceConfigRequestEntity configRequest, final NiFiUser user) {
+ // Create an asynchronous request that will occur in the background, because this request may take an indeterminate amount of time.
+ final String requestId = generateUuid();
+
+ final VerifyControllerServiceConfigRequestDTO requestDto = configRequest.getRequest();
+ final String serviceId = requestDto.getControllerServiceId();
+ final List<UpdateStep> updateSteps = Collections.singletonList(new StandardUpdateStep("Verify Controller Service Configuration"));
+
+ final AsynchronousWebRequest<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>> request =
+ new StandardAsynchronousWebRequest<>(requestId, configRequest, serviceId, user, updateSteps);
+
+ // Submit the request to be performed in the background
+ final Consumer<AsynchronousWebRequest<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>>> updateTask = asyncRequest -> {
+ try {
+ final Map<String, String> attributes = requestDto.getAttributes() == null ? Collections.emptyMap() : requestDto.getAttributes();
+ final List<ConfigVerificationResultDTO> results = serviceFacade.verifyControllerServiceConfiguration(serviceId, requestDto.getControllerService(), attributes);
+ asyncRequest.markStepComplete(results);
+ } catch (final Exception e) {
+ logger.error("Failed to verify Controller Service configuration", e);
+ asyncRequest.fail("Failed to verify Controller Service configuration due to " + e);
+ }
+ };
+
+ updateRequestManager.submitRequest(VERIFICATION_REQUEST_TYPE, requestId, request, updateTask);
+
+ // Generate the response
+ final VerifyControllerServiceConfigRequestEntity resultsEntity = createVerifyControllerServiceConfigRequestEntity(request, requestId);
+ return generateOkResponse(resultsEntity).build();
+ }
+
+ private VerifyControllerServiceConfigRequestEntity createVerifyControllerServiceConfigRequestEntity(
+ final AsynchronousWebRequest<VerifyControllerServiceConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest, final String requestId) {
+
+ final VerifyControllerServiceConfigRequestDTO requestDto = asyncRequest.getRequest().getRequest();
+ final List<ConfigVerificationResultDTO> resultsList = asyncRequest.getResults();
+
+ final VerifyControllerServiceConfigRequestDTO dto = new VerifyControllerServiceConfigRequestDTO();
+ dto.setControllerServiceId(requestDto.getControllerServiceId());
+ dto.setControllerService(requestDto.getControllerService());
+ dto.setResults(resultsList);
+
+ dto.setComplete(resultsList != null);
+ dto.setFailureReason(asyncRequest.getFailureReason());
+ dto.setLastUpdated(asyncRequest.getLastUpdated());
+ dto.setPercentCompleted(asyncRequest.getPercentComplete());
+ dto.setRequestId(requestId);
+ dto.setState(asyncRequest.getState());
+ dto.setUri(generateResourceUri("controller-services", requestDto.getControllerServiceId(), "config", "verification-requests", requestId));
+
+ final VerifyControllerServiceConfigRequestEntity entity = new VerifyControllerServiceConfigRequestEntity();
+ entity.setRequest(dto);
+ return entity;
+ }
+
private ControllerServiceDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
final ControllerServiceDTO dto = new ControllerServiceDTO();
dto.setId(id);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
index de758cd..de5d30c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ParameterContextResource.java
@@ -311,7 +311,7 @@
value = "Initiate the Update Request of a Parameter Context",
response = ParameterContextUpdateRequestEntity.class,
notes = "This will initiate the process of updating a Parameter Context. Changing the value of a Parameter may require that one or more components be stopped and " +
- "restarted, so this acttion may take significantly more time than many other REST API actions. As a result, this endpoint will immediately return a ParameterContextUpdateRequestEntity, " +
+ "restarted, so this action may take significantly more time than many other REST API actions. As a result, this endpoint will immediately return a ParameterContextUpdateRequestEntity, " +
"and the process of updating the necessary components will occur asynchronously in the background. The client may then periodically poll the status of the request by " +
"issuing a GET request to /parameter-contexts/update-requests/{requestId}. Once the request is completed, the client is expected to issue a DELETE request to " +
"/parameter-contexts/update-requests/{requestId}.",
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
index 5aed25f..98b924c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ProcessorResource.java
@@ -37,21 +37,32 @@
import org.apache.nifi.web.NiFiServiceFacade;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
+import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.RequestManager;
+import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
+import org.apache.nifi.web.api.concurrent.UpdateStep;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.PositionDTO;
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
+import org.apache.nifi.web.api.dto.VerifyProcessorConfigRequestDTO;
import org.apache.nifi.web.api.entity.ComponentStateEntity;
import org.apache.nifi.web.api.entity.ProcessorDiagnosticsEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorRunStatusEntity;
import org.apache.nifi.web.api.entity.ProcessorsRunStatusDetailsEntity;
-import org.apache.nifi.web.api.entity.RunStatusDetailsRequestEntity;
import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
+import org.apache.nifi.web.api.entity.RunStatusDetailsRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyProcessorConfigRequestEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@@ -69,8 +80,12 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* RESTful endpoint for managing a Processor.
@@ -81,6 +96,12 @@
description = "Endpoint for managing a Processor."
)
public class ProcessorResource extends ApplicationResource {
+ private static final Logger logger = LoggerFactory.getLogger(ProcessorResource.class);
+
+ private static final String VERIFICATION_REQUEST_TYPE = "verification-request";
+ private RequestManager<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>> updateRequestManager =
+ new AsyncRequestManager<>(100, TimeUnit.MINUTES.toMillis(1L), "Verify Processor Config Thread");
+
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
@@ -525,6 +546,168 @@
);
}
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/config/verification-requests")
+ @ApiOperation(
+ value = "Performs verification of the Processor's configuration",
+ response = VerifyProcessorConfigRequestEntity.class,
+ notes = "This will initiate the process of verifying a given Processor configuration. This may be a long-running task. As a result, this endpoint will immediately return a " +
+ "ProcessorConfigVerificationRequestEntity, and the process of performing the verification will occur asynchronously in the background. " +
+ "The client may then periodically poll the status of the request by " +
+ "issuing a GET request to /processors/{processorId}/verification-requests/{requestId}. Once the request is completed, the client is expected to issue a DELETE request to " +
+ "/processors/{processorId}/verification-requests/{requestId}.",
+ authorizations = {
+ @Authorization(value = "Read - /processors/{uuid}")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ }
+ )
+ public Response submitProcessorVerificationRequest(
+ @ApiParam(value = "The processor id.", required = true) @PathParam("id") final String processorId,
+ @ApiParam(value = "The processor configuration verification request.", required = true) final VerifyProcessorConfigRequestEntity processorConfigRequest) {
+
+ if (processorConfigRequest == null) {
+ throw new IllegalArgumentException("Processor's configuration must be specified");
+ }
+
+ final VerifyProcessorConfigRequestDTO requestDto = processorConfigRequest.getRequest();
+ if (requestDto == null || requestDto.getProcessorConfig() == null) {
+ throw new IllegalArgumentException("Processor's configuration must be specified");
+ }
+
+ if (requestDto.getProcessorId() == null) {
+ throw new IllegalArgumentException("Processor's identifier must be specified in the request");
+ }
+
+ if (!requestDto.getProcessorId().equals(processorId)) {
+ throw new IllegalArgumentException("Processor's identifier in the request must match the identifier provided in the URL");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, processorConfigRequest);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ return withWriteLock(
+ serviceFacade,
+ processorConfigRequest,
+ lookup -> {
+ final ComponentAuthorizable processor = lookup.getProcessor(processorId);
+ processor.getAuthorizable().authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ serviceFacade.verifyCanVerifyProcessorConfig(processorId);
+ },
+ entity -> performAsyncConfigVerification(entity, user)
+ );
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Returns the Verification Request with the given ID",
+ response = VerifyProcessorConfigRequestEntity.class,
+ notes = "Returns the Verification Request with the given ID. Once an Verification Request has been created, "
+ + "that request can subsequently be retrieved via this endpoint, and the request that is fetched will contain the updated state, such as percent complete, the "
+ + "current state of the request, and any failures. ",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can get it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response getVerificationRequest(
+ @ApiParam("The ID of the Processor") @PathParam("id") final String processorId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest = updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ final VerifyProcessorConfigRequestEntity updateRequestEntity = createVerifyProcessorConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Deletes the Verification Request with the given ID",
+ response = VerifyProcessorConfigRequestEntity.class,
+ notes = "Deletes the Verification Request with the given ID. After a request is created, it is expected "
+ + "that the client will properly clean up the request by DELETE'ing it, once the Verification process has completed. If the request is deleted before the request "
+ + "completes, then the Verification request will finish the step that it is currently performing and then will cancel any subsequent steps.",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can remove it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response deleteVerificationRequest(
+ @ApiParam("The ID of the Processor") @PathParam("id") final String processorId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final boolean twoPhaseRequest = isTwoPhaseRequest(httpServletRequest);
+ final boolean executionPhase = isExecutionPhase(httpServletRequest);
+
+ // If this is a standalone node, or if this is the execution phase of the request, perform the actual request.
+ if (!twoPhaseRequest || executionPhase) {
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ updateRequestManager.removeRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ if (!asyncRequest.isComplete()) {
+ asyncRequest.cancel();
+ }
+
+ final VerifyProcessorConfigRequestEntity updateRequestEntity = createVerifyProcessorConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+ if (isValidationPhase(httpServletRequest)) {
+ // Perform authorization by attempting to get the request
+ updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ return generateContinueResponse().build();
+ } else if (isCancellationPhase(httpServletRequest)) {
+ return generateOkResponse().build();
+ } else {
+ throw new IllegalStateException("This request does not appear to be part of the two phase commit.");
+ }
+ }
+
+
/**
* Updates the specified processor with the specified values.
*
@@ -532,7 +715,6 @@
* @param id The id of the processor to update.
* @param requestProcessorEntity A processorEntity.
* @return A processorEntity.
- * @throws InterruptedException if interrupted
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@@ -725,7 +907,6 @@
* @param id The id of the processor to update.
* @param requestRunStatus A processorEntity.
* @return A processorEntity.
- * @throws InterruptedException if interrupted
*/
@PUT
@Consumes(MediaType.APPLICATION_JSON)
@@ -805,6 +986,61 @@
return dto;
}
+ public Response performAsyncConfigVerification(final VerifyProcessorConfigRequestEntity processorConfigRequest, final NiFiUser user) {
+ // Create an asynchronous request that will occur in the background, because this request may take an indeterminate amount of time.
+ final String requestId = generateUuid();
+ logger.debug("Generated Config Verification Request with ID {} for Processor {}", requestId, processorConfigRequest.getRequest().getProcessorId());
+
+ final VerifyProcessorConfigRequestDTO requestDto = processorConfigRequest.getRequest();
+ final String processorId = requestDto.getProcessorId();
+ final List<UpdateStep> updateSteps = Collections.singletonList(new StandardUpdateStep("Verify Processor Configuration"));
+
+ final AsynchronousWebRequest<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>> request =
+ new StandardAsynchronousWebRequest<>(requestId, processorConfigRequest, processorId, user, updateSteps);
+
+ // Submit the request to be performed in the background
+ final Consumer<AsynchronousWebRequest<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>>> updateTask = asyncRequest -> {
+ try {
+ final Map<String, String> attributes = requestDto.getAttributes() == null ? Collections.emptyMap() : requestDto.getAttributes();
+ final List<ConfigVerificationResultDTO> results = serviceFacade.verifyProcessorConfiguration(processorId, requestDto.getProcessorConfig(), attributes);
+ asyncRequest.markStepComplete(results);
+ } catch (final Exception e) {
+ logger.error("Failed to verify Processor configuration", e);
+ asyncRequest.fail("Failed to verify Processor configuration due to " + e);
+ }
+ };
+
+ updateRequestManager.submitRequest(VERIFICATION_REQUEST_TYPE, requestId, request, updateTask);
+
+ // Generate the response
+ final VerifyProcessorConfigRequestEntity resultsEntity = createVerifyProcessorConfigRequestEntity(request, requestId);
+ return generateOkResponse(resultsEntity).build();
+ }
+
+ private VerifyProcessorConfigRequestEntity createVerifyProcessorConfigRequestEntity(
+ final AsynchronousWebRequest<VerifyProcessorConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest,
+ final String requestId) {
+ final VerifyProcessorConfigRequestDTO requestDto = asyncRequest.getRequest().getRequest();
+ final List<ConfigVerificationResultDTO> resultsList = asyncRequest.getResults();
+
+ final VerifyProcessorConfigRequestDTO dto = new VerifyProcessorConfigRequestDTO();
+ dto.setProcessorId(requestDto.getProcessorId());
+ dto.setProcessorConfig(requestDto.getProcessorConfig());
+ dto.setResults(resultsList);
+
+ dto.setComplete(resultsList != null);
+ dto.setFailureReason(asyncRequest.getFailureReason());
+ dto.setLastUpdated(asyncRequest.getLastUpdated());
+ dto.setPercentCompleted(asyncRequest.getPercentComplete());
+ dto.setRequestId(requestId);
+ dto.setState(asyncRequest.getState());
+ dto.setUri(generateResourceUri("processors", requestDto.getProcessorId(), "config", "verification-requests", requestId));
+
+ final VerifyProcessorConfigRequestEntity entity = new VerifyProcessorConfigRequestEntity();
+ entity.setRequest(dto);
+ return entity;
+ }
+
// setters
public void setServiceFacade(NiFiServiceFacade serviceFacade) {
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
index 2c516c4..b316f01 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/ReportingTaskResource.java
@@ -29,22 +29,35 @@
import org.apache.nifi.authorization.RequestAction;
import org.apache.nifi.authorization.resource.Authorizable;
import org.apache.nifi.authorization.resource.OperationAuthorizable;
+import org.apache.nifi.authorization.user.NiFiUser;
import org.apache.nifi.authorization.user.NiFiUserUtils;
import org.apache.nifi.ui.extension.UiExtension;
import org.apache.nifi.ui.extension.UiExtensionMapping;
import org.apache.nifi.web.NiFiServiceFacade;
+import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.Revision;
import org.apache.nifi.web.UiExtensionType;
+import org.apache.nifi.web.api.concurrent.AsyncRequestManager;
+import org.apache.nifi.web.api.concurrent.AsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.RequestManager;
+import org.apache.nifi.web.api.concurrent.StandardAsynchronousWebRequest;
+import org.apache.nifi.web.api.concurrent.StandardUpdateStep;
+import org.apache.nifi.web.api.concurrent.UpdateStep;
import org.apache.nifi.web.api.dto.BundleDTO;
import org.apache.nifi.web.api.dto.ComponentStateDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.PropertyDescriptorDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import org.apache.nifi.web.api.dto.VerifyReportingTaskConfigRequestDTO;
import org.apache.nifi.web.api.entity.ComponentStateEntity;
import org.apache.nifi.web.api.entity.PropertyDescriptorEntity;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ReportingTaskRunStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyReportingTaskConfigRequestEntity;
import org.apache.nifi.web.api.request.ClientIdParameter;
import org.apache.nifi.web.api.request.LongParameter;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
@@ -62,8 +75,11 @@
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
+import java.util.Collections;
import java.util.List;
import java.util.Set;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
/**
* RESTful endpoint for managing a Reporting Task.
@@ -75,6 +91,11 @@
)
public class ReportingTaskResource extends ApplicationResource {
+ private static final Logger logger = LoggerFactory.getLogger(ReportingTaskResource.class);
+ private static final String VERIFICATION_REQUEST_TYPE = "verification-request";
+ private RequestManager<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>> updateRequestManager =
+ new AsyncRequestManager<>(100, TimeUnit.MINUTES.toMillis(1L), "Verify Reporting Task Config Thread");
+
private NiFiServiceFacade serviceFacade;
private Authorizer authorizer;
@@ -625,6 +646,228 @@
);
}
+
+ @POST
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("/{id}/config/verification-requests")
+ @ApiOperation(
+ value = "Performs verification of the Reporting Task's configuration",
+ response = VerifyReportingTaskConfigRequestEntity.class,
+ notes = "This will initiate the process of verifying a given Reporting Task configuration. This may be a long-running task. As a result, this endpoint will immediately return a " +
+ "ReportingTaskConfigVerificationRequestEntity, and the process of performing the verification will occur asynchronously in the background. " +
+ "The client may then periodically poll the status of the request by " +
+ "issuing a GET request to /reporting-tasks/{serviceId}/verification-requests/{requestId}. Once the request is completed, the client is expected to issue a DELETE request to " +
+ "/reporting-tasks/{serviceId}/verification-requests/{requestId}.",
+ authorizations = {
+ @Authorization(value = "Read - /reporting-tasks/{uuid}")
+ }
+ )
+ @ApiResponses(
+ value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ }
+ )
+ public Response submitConfigVerificationRequest(
+ @ApiParam(value = "The reporting task id.", required = true) @PathParam("id") final String reportingTaskId,
+ @ApiParam(value = "The reporting task configuration verification request.", required = true) final VerifyReportingTaskConfigRequestEntity reportingTaskConfigRequest) {
+
+ if (reportingTaskConfigRequest == null) {
+ throw new IllegalArgumentException("Reporting Task's configuration must be specified");
+ }
+
+ final VerifyReportingTaskConfigRequestDTO requestDto = reportingTaskConfigRequest.getRequest();
+ if (requestDto == null || requestDto.getReportingTask() == null) {
+ throw new IllegalArgumentException("Reporting Task must be specified");
+ }
+
+ if (requestDto.getReportingTaskId() == null) {
+ throw new IllegalArgumentException("Reporting Task's identifier must be specified in the request");
+ }
+
+ if (!requestDto.getReportingTaskId().equals(reportingTaskId)) {
+ throw new IllegalArgumentException("Reporting Task's identifier in the request must match the identifier provided in the URL");
+ }
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.POST, reportingTaskConfigRequest);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ return withWriteLock(
+ serviceFacade,
+ reportingTaskConfigRequest,
+ lookup -> {
+ final ComponentAuthorizable reportingTask = lookup.getReportingTask(reportingTaskId);
+ reportingTask.getAuthorizable().authorize(authorizer, RequestAction.READ, NiFiUserUtils.getNiFiUser());
+ },
+ () -> {
+ serviceFacade.verifyCanVerifyReportingTaskConfig(reportingTaskId);
+ },
+ entity -> performAsyncConfigVerification(entity, user)
+ );
+ }
+
+ @GET
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Returns the Verification Request with the given ID",
+ response = VerifyReportingTaskConfigRequestEntity.class,
+ notes = "Returns the Verification Request with the given ID. Once an Verification Request has been created, "
+ + "that request can subsequently be retrieved via this endpoint, and the request that is fetched will contain the updated state, such as percent complete, the "
+ + "current state of the request, and any failures. ",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can get it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response getVerificationRequest(
+ @ApiParam("The ID of the Reporting Task") @PathParam("id") final String reportingTaskId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.GET);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ final VerifyReportingTaskConfigRequestEntity updateRequestEntity = createVerifyReportingTaskConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+
+ @DELETE
+ @Consumes(MediaType.WILDCARD)
+ @Produces(MediaType.APPLICATION_JSON)
+ @Path("{id}/config/verification-requests/{requestId}")
+ @ApiOperation(
+ value = "Deletes the Verification Request with the given ID",
+ response = VerifyReportingTaskConfigRequestEntity.class,
+ notes = "Deletes the Verification Request with the given ID. After a request is created, it is expected "
+ + "that the client will properly clean up the request by DELETE'ing it, once the Verification process has completed. If the request is deleted before the request "
+ + "completes, then the Verification request will finish the step that it is currently performing and then will cancel any subsequent steps.",
+ authorizations = {
+ @Authorization(value = "Only the user that submitted the request can remove it")
+ })
+ @ApiResponses(value = {
+ @ApiResponse(code = 400, message = "NiFi was unable to complete the request because it was invalid. The request should not be retried without modification."),
+ @ApiResponse(code = 401, message = "Client could not be authenticated."),
+ @ApiResponse(code = 403, message = "Client is not authorized to make this request."),
+ @ApiResponse(code = 404, message = "The specified resource could not be found."),
+ @ApiResponse(code = 409, message = "The request was valid but NiFi was not in the appropriate state to process it. Retrying the same request later may be successful.")
+ })
+ public Response deleteValidationRequest(
+ @ApiParam("The ID of the Reporting Task") @PathParam("id") final String reportingTaskId,
+ @ApiParam("The ID of the Verification Request") @PathParam("requestId") final String requestId) {
+
+ if (isReplicateRequest()) {
+ return replicate(HttpMethod.DELETE);
+ }
+
+ final NiFiUser user = NiFiUserUtils.getNiFiUser();
+ final boolean twoPhaseRequest = isTwoPhaseRequest(httpServletRequest);
+ final boolean executionPhase = isExecutionPhase(httpServletRequest);
+
+ // If this is a standalone node, or if this is the execution phase of the request, perform the actual request.
+ if (!twoPhaseRequest || executionPhase) {
+ // request manager will ensure that the current is the user that submitted this request
+ final AsynchronousWebRequest<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest =
+ updateRequestManager.removeRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+
+ if (asyncRequest == null) {
+ throw new ResourceNotFoundException("Could not find request of type " + VERIFICATION_REQUEST_TYPE + " with ID " + requestId);
+ }
+
+ if (!asyncRequest.isComplete()) {
+ asyncRequest.cancel();
+ }
+
+ final VerifyReportingTaskConfigRequestEntity updateRequestEntity = createVerifyReportingTaskConfigRequestEntity(asyncRequest, requestId);
+ return generateOkResponse(updateRequestEntity).build();
+ }
+
+ if (isValidationPhase(httpServletRequest)) {
+ // Perform authorization by attempting to get the request
+ updateRequestManager.getRequest(VERIFICATION_REQUEST_TYPE, requestId, user);
+ return generateContinueResponse().build();
+ } else if (isCancellationPhase(httpServletRequest)) {
+ return generateOkResponse().build();
+ } else {
+ throw new IllegalStateException("This request does not appear to be part of the two phase commit.");
+ }
+ }
+
+
+
+ public Response performAsyncConfigVerification(final VerifyReportingTaskConfigRequestEntity configRequest, final NiFiUser user) {
+ // Create an asynchronous request that will occur in the background, because this request may take an indeterminate amount of time.
+ final String requestId = generateUuid();
+
+ final VerifyReportingTaskConfigRequestDTO requestDto = configRequest.getRequest();
+ final String taskId = requestDto.getReportingTaskId();
+ final List<UpdateStep> updateSteps = Collections.singletonList(new StandardUpdateStep("Verify Reporting Task Configuration"));
+
+ final AsynchronousWebRequest<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>> request =
+ new StandardAsynchronousWebRequest<>(requestId, configRequest, taskId, user, updateSteps);
+
+ // Submit the request to be performed in the background
+ final Consumer<AsynchronousWebRequest<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>>> updateTask = asyncRequest -> {
+ try {
+ final List<ConfigVerificationResultDTO> results = serviceFacade.verifyReportingTaskConfiguration(taskId, requestDto.getReportingTask());
+ asyncRequest.markStepComplete(results);
+ } catch (final Exception e) {
+ logger.error("Failed to verify Reporting Task configuration", e);
+ asyncRequest.fail("Failed to verify Reporting Task configuration due to " + e);
+ }
+ };
+
+ updateRequestManager.submitRequest(VERIFICATION_REQUEST_TYPE, requestId, request, updateTask);
+
+ // Generate the response
+ final VerifyReportingTaskConfigRequestEntity resultsEntity = createVerifyReportingTaskConfigRequestEntity(request, requestId);
+ return generateOkResponse(resultsEntity).build();
+ }
+
+ private VerifyReportingTaskConfigRequestEntity createVerifyReportingTaskConfigRequestEntity(
+ final AsynchronousWebRequest<VerifyReportingTaskConfigRequestEntity, List<ConfigVerificationResultDTO>> asyncRequest, final String requestId) {
+
+ final VerifyReportingTaskConfigRequestDTO requestDto = asyncRequest.getRequest().getRequest();
+ final List<ConfigVerificationResultDTO> resultsList = asyncRequest.getResults();
+
+ final VerifyReportingTaskConfigRequestDTO dto = new VerifyReportingTaskConfigRequestDTO();
+ dto.setReportingTaskId(requestDto.getReportingTaskId());
+ dto.setReportingTask(requestDto.getReportingTask());
+ dto.setResults(resultsList);
+
+ dto.setComplete(resultsList != null);
+ dto.setFailureReason(asyncRequest.getFailureReason());
+ dto.setLastUpdated(asyncRequest.getLastUpdated());
+ dto.setPercentCompleted(asyncRequest.getPercentComplete());
+ dto.setRequestId(requestId);
+ dto.setState(asyncRequest.getState());
+ dto.setUri(generateResourceUri("reporting-tasks", requestDto.getReportingTaskId(), "config", "verification-requests", requestId));
+
+ final VerifyReportingTaskConfigRequestEntity entity = new VerifyReportingTaskConfigRequestEntity();
+ entity.setRequest(dto);
+ return entity;
+ }
+
private ReportingTaskDTO createDTOWithDesiredRunStatus(final String id, final String runStatus) {
final ReportingTaskDTO dto = new ReportingTaskDTO();
dto.setId(id);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
index 6408055..abbf126 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/api/dto/DtoFactory.java
@@ -1679,6 +1679,7 @@
dto.getProperties().put(descriptor.getName(), propertyValue);
}
+ dto.setReferencedAttributes(controllerServiceNode.getReferencedAttributeNames());
dto.setValidationStatus(controllerServiceNode.getValidationStatus(1, TimeUnit.MILLISECONDS).name());
// add the validation errors
@@ -4013,6 +4014,7 @@
dto.setSchedulingStrategy(procNode.getSchedulingStrategy().name());
dto.setExecutionNode(procNode.getExecutionNode().name());
dto.setAnnotationData(procNode.getAnnotationData());
+ dto.setReferencedAttributes(procNode.getReferencedAttributeNames());
// set up the default values for concurrent tasks and scheduling period
final Map<String, String> defaultConcurrentTasks = new HashMap<>();
@@ -4143,6 +4145,7 @@
copy.setParentGroupId(original.getParentGroupId());
copy.setName(original.getName());
copy.setProperties(copy(original.getProperties()));
+ copy.setReferencedAttributes(new HashSet<>(original.getReferencedAttributes()));
copy.setReferencingComponents(copy(original.getReferencingComponents()));
copy.setState(original.getState());
copy.setType(original.getType());
@@ -4255,6 +4258,7 @@
copy.setBulletinLevel(original.getBulletinLevel());
copy.setDefaultConcurrentTasks(original.getDefaultConcurrentTasks());
copy.setDefaultSchedulingPeriod(original.getDefaultSchedulingPeriod());
+ copy.setReferencedAttributes(original.getReferencedAttributes());
copy.setLossTolerant(original.isLossTolerant());
return copy;
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
index 2a2ba6b..572ec63 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ControllerServiceDAO.java
@@ -22,8 +22,11 @@
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
public interface ControllerServiceDAO {
@@ -129,6 +132,21 @@
void verifyClearState(String controllerServiceId);
/**
+ * Verifies the controller service is in a state in which its configuration can be verified
+ * @param controllerServiceId the id of the Controller Service
+ */
+ void verifyConfigVerification(String controllerServiceId);
+
+ /**
+ * Performs verification of the Configuration for the Controller Service with the given ID
+ * @param controllerServiceId the id of the controller service
+ * @param controllerService the configuration to verify
+ * @param variables a map of values that can be used for resolving FlowFile attributes for Expression Language
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyConfiguration(String controllerServiceId, ControllerServiceDTO controllerService, Map<String, String> variables);
+
+ /**
* Clears the state of the specified controller service.
*
* @param controllerServiceId controller service id
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessorDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessorDAO.java
index 2b8f78d..ab4ac1c 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessorDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ProcessorDAO.java
@@ -19,8 +19,12 @@
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.controller.ProcessorNode;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
+import java.util.List;
+import java.util.Map;
import java.util.Set;
public interface ProcessorDAO {
@@ -72,6 +76,12 @@
void verifyUpdate(ProcessorDTO processorDTO);
/**
+ * Verifies the specified processor can have its configuration verified
+ * @param processorId the ID of the Processor
+ */
+ void verifyConfigVerification(String processorId);
+
+ /**
* Verifies that the specified processor can be terminated at this time
*
* @param processorId the id of the processor
@@ -94,6 +104,15 @@
ProcessorNode updateProcessor(ProcessorDTO processorDTO);
/**
+ * Performs verification of the given Processor Configuration for the Processor with the given ID
+ * @param processorId the id of the processor
+ * @param processorConfig the configuration to verify
+ * @param attributes a map of values that can be used for resolving FlowFile attributes for Expression Language
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyProcessorConfiguration(String processorId, ProcessorConfigDTO processorConfig, Map<String, String> attributes);
+
+ /**
* Verifies the specified processor can be removed.
*
* @param processorId processor id
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
index 2078268..8af40de 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/ReportingTaskDAO.java
@@ -19,8 +19,10 @@
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.controller.ReportingTaskNode;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
+import java.util.List;
import java.util.Set;
public interface ReportingTaskDAO {
@@ -79,6 +81,20 @@
void verifyUpdate(ReportingTaskDTO reportingTaskDTO);
/**
+ * Verifies the Reporting Task is in a state in which its configuration can be verified
+ * @param reportingTaskId the id of the Reporting Task
+ */
+ void verifyConfigVerification(String reportingTaskId);
+
+ /**
+ * Performs verification of the Configuration for the Reporting Task with the given ID
+ * @param reportingTaskId the id of the Reporting Task
+ * @param reportingTask the configuration to verify
+ * @return verification results
+ */
+ List<ConfigVerificationResultDTO> verifyConfiguration(String reportingTaskId, ReportingTaskDTO reportingTask);
+
+ /**
* Determines whether this reporting task can be removed.
*
* @param reportingTaskId id
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
index ff05f04..6b9457e 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardControllerServiceDAO.java
@@ -21,6 +21,7 @@
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
import org.apache.nifi.controller.ComponentNode;
+import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ScheduledState;
import org.apache.nifi.controller.exception.ControllerServiceInstantiationException;
@@ -29,12 +30,20 @@
import org.apache.nifi.controller.service.ControllerServiceNode;
import org.apache.nifi.controller.service.ControllerServiceProvider;
import org.apache.nifi.controller.service.ControllerServiceState;
+import org.apache.nifi.controller.service.StandardConfigurationContext;
import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogRepository;
+import org.apache.nifi.logging.repository.NopLogRepository;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.parameter.ParameterLookup;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.processor.SimpleProcessLogger;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
import org.apache.nifi.web.dao.ComponentStateDAO;
import org.apache.nifi.web.dao.ControllerServiceDAO;
@@ -45,6 +54,7 @@
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.stream.Collectors;
public class StandardControllerServiceDAO extends ComponentDAO implements ControllerServiceDAO {
@@ -385,6 +395,40 @@
}
@Override
+ public void verifyConfigVerification(final String controllerServiceId) {
+ final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
+ controllerService.verifyCanPerformVerification();
+ }
+
+ @Override
+ public List<ConfigVerificationResultDTO> verifyConfiguration(final String controllerServiceId, final ControllerServiceDTO controllerService, final Map<String, String> variables) {
+ final ControllerServiceNode serviceNode = locateControllerService(controllerServiceId);
+
+ final LogRepository logRepository = new NopLogRepository();
+ final ComponentLog configVerificationLog = new SimpleProcessLogger(serviceNode.getControllerServiceImplementation(), logRepository);
+ final ExtensionManager extensionManager = flowController.getExtensionManager();
+
+ final ParameterLookup parameterLookup = serviceNode.getProcessGroup() == null ? ParameterLookup.EMPTY : serviceNode.getProcessGroup().getParameterContext();
+ final ConfigurationContext configurationContext = new StandardConfigurationContext(serviceNode, controllerService.getProperties(), controllerService.getAnnotationData(),
+ parameterLookup, flowController.getControllerServiceProvider(), null, flowController.getVariableRegistry());
+
+ final List<ConfigVerificationResult> verificationResults = serviceNode.verifyConfiguration(configurationContext, configVerificationLog, variables, extensionManager);
+ final List<ConfigVerificationResultDTO> resultsDtos = verificationResults.stream()
+ .map(this::createConfigVerificationResultDto)
+ .collect(Collectors.toList());
+
+ return resultsDtos;
+ }
+
+ private ConfigVerificationResultDTO createConfigVerificationResultDto(final ConfigVerificationResult result) {
+ final ConfigVerificationResultDTO dto = new ConfigVerificationResultDTO();
+ dto.setExplanation(result.getExplanation());
+ dto.setOutcome(result.getOutcome().name());
+ dto.setVerificationStepName(result.getVerificationStepName());
+ return dto;
+ }
+
+ @Override
public void clearState(final String controllerServiceId) {
final ControllerServiceNode controllerService = locateControllerService(controllerServiceId);
componentStateDAO.clearState(controllerService);
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
index 2f74389..8cfcfc1 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardProcessorDAO.java
@@ -29,10 +29,18 @@
import org.apache.nifi.controller.exception.ComponentLifeCycleException;
import org.apache.nifi.controller.exception.ProcessorInstantiationException;
import org.apache.nifi.controller.exception.ValidationException;
+import org.apache.nifi.documentation.init.NopStateManager;
import org.apache.nifi.groups.ProcessGroup;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.logging.LogLevel;
+import org.apache.nifi.logging.LogRepository;
+import org.apache.nifi.logging.repository.NopLogRepository;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.SimpleProcessLogger;
+import org.apache.nifi.processor.StandardProcessContext;
import org.apache.nifi.scheduling.ExecutionNode;
import org.apache.nifi.scheduling.SchedulingStrategy;
import org.apache.nifi.util.BundleUtils;
@@ -40,6 +48,7 @@
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.dao.ComponentStateDAO;
@@ -58,6 +67,7 @@
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
+import java.util.stream.Collectors;
public class StandardProcessorDAO extends ComponentDAO implements ProcessorDAO {
@@ -341,6 +351,11 @@
processor.getProcessGroup().terminateProcessor(processor);
}
+ @Override
+ public void verifyConfigVerification(final String processorId) {
+ final ProcessorNode processor = locateProcessor(processorId);
+ processor.verifyCanPerformVerification();
+ }
@Override
public void verifyUpdate(final ProcessorDTO processorDTO) {
@@ -427,6 +442,34 @@
}
@Override
+ public List<ConfigVerificationResultDTO> verifyProcessorConfiguration(final String processorId, final ProcessorConfigDTO processorConfig, final Map<String, String> attributes) {
+ final ProcessorNode processor = locateProcessor(processorId);
+
+ final ProcessContext processContext = new StandardProcessContext(processor, processorConfig.getProperties(), processorConfig.getAnnotationData(),
+ processor.getProcessGroup().getParameterContext(), flowController.getControllerServiceProvider(), flowController.getEncryptor(),
+ new NopStateManager(), () -> false, flowController);
+
+ final LogRepository logRepository = new NopLogRepository();
+ final ComponentLog configVerificationLog = new SimpleProcessLogger(processor, logRepository);
+ final ExtensionManager extensionManager = flowController.getExtensionManager();
+ final List<ConfigVerificationResult> verificationResults = processor.verifyConfiguration(processContext, configVerificationLog, attributes, extensionManager);
+
+ final List<ConfigVerificationResultDTO> resultsDtos = verificationResults.stream()
+ .map(this::createConfigVerificationResultDto)
+ .collect(Collectors.toList());
+
+ return resultsDtos;
+ }
+
+ private ConfigVerificationResultDTO createConfigVerificationResultDto(final ConfigVerificationResult result) {
+ final ConfigVerificationResultDTO dto = new ConfigVerificationResultDTO();
+ dto.setExplanation(result.getExplanation());
+ dto.setOutcome(result.getOutcome().name());
+ dto.setVerificationStepName(result.getVerificationStepName());
+ return dto;
+ }
+
+ @Override
public ProcessorNode updateProcessor(ProcessorDTO processorDTO) {
ProcessorNode processor = locateProcessor(processorDTO.getId());
ProcessGroup parentGroup = processor.getProcessGroup();
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
index 939a2d5..b7558fd 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/java/org/apache/nifi/web/dao/impl/StandardReportingTaskDAO.java
@@ -21,6 +21,8 @@
import org.apache.nifi.components.ConfigurableComponent;
import org.apache.nifi.components.state.Scope;
import org.apache.nifi.components.state.StateMap;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.FlowController;
import org.apache.nifi.controller.ReloadComponent;
import org.apache.nifi.controller.ReportingTaskNode;
import org.apache.nifi.controller.ScheduledState;
@@ -28,13 +30,21 @@
import org.apache.nifi.controller.exception.ValidationException;
import org.apache.nifi.controller.reporting.ReportingTaskInstantiationException;
import org.apache.nifi.controller.reporting.ReportingTaskProvider;
+import org.apache.nifi.controller.service.StandardConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.logging.LogRepository;
+import org.apache.nifi.logging.repository.NopLogRepository;
import org.apache.nifi.nar.ExtensionManager;
+import org.apache.nifi.parameter.ParameterLookup;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.processor.SimpleProcessLogger;
import org.apache.nifi.scheduling.SchedulingStrategy;
import org.apache.nifi.util.BundleUtils;
import org.apache.nifi.util.FormatUtils;
import org.apache.nifi.web.NiFiCoreException;
import org.apache.nifi.web.ResourceNotFoundException;
import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ReportingTaskDTO;
import org.apache.nifi.web.dao.ComponentStateDAO;
import org.apache.nifi.web.dao.ReportingTaskDAO;
@@ -48,12 +58,14 @@
import java.util.Set;
import java.util.concurrent.RejectedExecutionException;
import java.util.regex.Matcher;
+import java.util.stream.Collectors;
public class StandardReportingTaskDAO extends ComponentDAO implements ReportingTaskDAO {
private ReportingTaskProvider reportingTaskProvider;
private ComponentStateDAO componentStateDAO;
private ReloadComponent reloadComponent;
+ private FlowController flowController;
private ReportingTaskNode locateReportingTask(final String reportingTaskId) {
// get the reporting task
@@ -242,6 +254,41 @@
verifyUpdate(reportingTask, reportingTaskDTO);
}
+ @Override
+ public void verifyConfigVerification(final String reportingTaskId) {
+ final ReportingTaskNode reportingTask = locateReportingTask(reportingTaskId);
+ reportingTask.verifyCanPerformVerification();
+ }
+
+ @Override
+ public List<ConfigVerificationResultDTO> verifyConfiguration(final String reportingTaskId, final ReportingTaskDTO reportingTask) {
+ final ReportingTaskNode taskNode = locateReportingTask(reportingTaskId);
+
+ final LogRepository logRepository = new NopLogRepository();
+ final ComponentLog configVerificationLog = new SimpleProcessLogger(taskNode.getReportingTask(), logRepository);
+ final ExtensionManager extensionManager = flowController.getExtensionManager();
+
+ final ParameterLookup parameterLookup = ParameterLookup.EMPTY;
+ final ConfigurationContext configurationContext = new StandardConfigurationContext(taskNode, reportingTask.getProperties(), reportingTask.getAnnotationData(),
+ parameterLookup, flowController.getControllerServiceProvider(), null, flowController.getVariableRegistry());
+
+ final List<ConfigVerificationResult> verificationResults = taskNode.verifyConfiguration(configurationContext, configVerificationLog, extensionManager);
+ final List<ConfigVerificationResultDTO> resultsDtos = verificationResults.stream()
+ .map(this::createConfigVerificationResultDto)
+ .collect(Collectors.toList());
+
+ return resultsDtos;
+ }
+
+ private ConfigVerificationResultDTO createConfigVerificationResultDto(final ConfigVerificationResult result) {
+ final ConfigVerificationResultDTO dto = new ConfigVerificationResultDTO();
+ dto.setExplanation(result.getExplanation());
+ dto.setOutcome(result.getOutcome().name());
+ dto.setVerificationStepName(result.getVerificationStepName());
+ return dto;
+ }
+
+
private void verifyUpdate(final ReportingTaskNode reportingTask, final ReportingTaskDTO reportingTaskDTO) {
// ensure the state, if specified, is valid
if (isNotNull(reportingTaskDTO.getState())) {
@@ -380,4 +427,8 @@
public void setReloadComponent(ReloadComponent reloadComponent) {
this.reloadComponent = reloadComponent;
}
+
+ public void setFlowController(FlowController flowController) {
+ this.flowController = flowController;
+ }
}
diff --git a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
index 9bbf044..1b8185b 100644
--- a/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
+++ b/nifi-nar-bundles/nifi-framework-bundle/nifi-framework/nifi-web/nifi-web-api/src/main/resources/nifi-web-api-context.xml
@@ -251,6 +251,7 @@
<property name="reportingTaskProvider" ref="reportingTaskProvider"/>
<property name="componentStateDAO" ref="componentStateDAO"/>
<property name="reloadComponent" ref="reloadComponent" />
+ <property name="flowController" ref="flowController" />
</bean>
<bean id="componentStateDAO" class="org.apache.nifi.web.dao.impl.StandardComponentStateDAO">
<property name="stateManagerProvider" ref="stateManagerProvider"/>
diff --git a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JMSConnectionFactoryProvider.java b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JMSConnectionFactoryProvider.java
index 5da735f..49a95c8 100644
--- a/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JMSConnectionFactoryProvider.java
+++ b/nifi-nar-bundles/nifi-jms-bundle/nifi-jms-processors/src/main/java/org/apache/nifi/jms/cf/JMSConnectionFactoryProvider.java
@@ -26,9 +26,20 @@
import org.apache.nifi.controller.AbstractControllerService;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.expression.ExpressionLanguageScope;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.controller.VerifiableControllerService;
+import javax.jms.Connection;
import javax.jms.ConnectionFactory;
+import javax.jms.ExceptionListener;
+import javax.jms.JMSSecurityException;
+import javax.jms.Session;
+import java.util.ArrayList;
import java.util.List;
+import java.util.Map;
+import java.util.concurrent.atomic.AtomicReference;
/**
* Provides a factory service that creates and initializes
@@ -50,7 +61,9 @@
+ "property and 'com.ibm.mq.jms.MQConnectionFactory.setTransportType(int)' would imply 'transportType' property.",
expressionLanguageScope = ExpressionLanguageScope.VARIABLE_REGISTRY)
@SeeAlso(classNames = {"org.apache.nifi.jms.processors.ConsumeJMS", "org.apache.nifi.jms.processors.PublishJMS"})
-public class JMSConnectionFactoryProvider extends AbstractControllerService implements JMSConnectionFactoryProviderDefinition {
+public class JMSConnectionFactoryProvider extends AbstractControllerService implements JMSConnectionFactoryProviderDefinition, VerifiableControllerService {
+ private static final String ESTABLISH_CONNECTION = "Establish Connection";
+ private static final String VERIFY_JMS_INTERACTION = "Verify JMS Interaction";
protected volatile JMSConnectionFactoryHandler delegate;
@@ -84,4 +97,91 @@
delegate.resetConnectionFactory(cachedFactory);
}
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext context, final ComponentLog verificationLogger, final Map<String, String> variables) {
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+ final JMSConnectionFactoryHandler handler = new JMSConnectionFactoryHandler(context, verificationLogger);
+
+ final AtomicReference<Exception> failureReason = new AtomicReference<>();
+ final ExceptionListener listener = failureReason::set;
+
+ final Connection connection = createConnection(handler.getConnectionFactory(), results, listener, verificationLogger);
+ if (connection != null) {
+ try {
+ createSession(connection, results, failureReason.get(), verificationLogger);
+ } finally {
+ try {
+ connection.close();
+ } catch (final Exception ignored) {
+ }
+ }
+ }
+
+ return results;
+ }
+
+ private Connection createConnection(final ConnectionFactory connectionFactory, final List<ConfigVerificationResult> results, final ExceptionListener exceptionListener, final ComponentLog logger) {
+ try {
+ final Connection connection = connectionFactory.createConnection();
+ connection.setExceptionListener(exceptionListener);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_CONNECTION)
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Successfully established a JMS Connection")
+ .build());
+
+ return connection;
+ } catch (final JMSSecurityException se) {
+ // If we encounter a JMS Security Exception, the documentation states that it is because of an invalid username or password.
+ // There is no username or password configured for the Controller Service itself, however. Those are configured in processors, etc.
+ // As a result, if this is encountered, we will skip verification.
+ logger.debug("Failed to establish a connection to the JMS Server in order to verify configuration because encountered JMS Security Exception", se);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_CONNECTION)
+ .outcome(Outcome.SKIPPED)
+ .explanation("Could not establish a Connection because doing so requires that a username and password be provided")
+ .build());
+ } catch (final Exception e) {
+ logger.warn("Failed to establish a connection to the JMS Server in order to verify configuration", e);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_CONNECTION)
+ .outcome(Outcome.FAILED)
+ .explanation("Was not able to establish a connection to the JMS Server: " + e.toString())
+ .build());
+ }
+
+ return null;
+ }
+
+ private void createSession(final Connection connection, final List<ConfigVerificationResult> results, final Exception capturedException, final ComponentLog logger) {
+ try {
+ final Session session = connection.createSession(false, Session.CLIENT_ACKNOWLEDGE);
+ session.close();
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(VERIFY_JMS_INTERACTION)
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Established a JMS Session with server and successfully terminated it")
+ .build());
+ } catch (final Exception e) {
+ final Exception failure;
+ if (capturedException == null) {
+ failure = e;
+ } else {
+ failure = capturedException;
+ failure.addSuppressed(e);
+ }
+
+ logger.warn("Failed to create a JMS Session in order to verify configuration", failure);
+
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(VERIFY_JMS_INTERACTION)
+ .outcome(Outcome.FAILED)
+ .explanation("Was not able to create a JMS Session: " + failure.toString())
+ .build());
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafkaRecord_2_6.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafkaRecord_2_6.java
index b51c644..13fd811 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafkaRecord_2_6.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafkaRecord_2_6.java
@@ -37,9 +37,11 @@
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
import org.apache.nifi.serialization.RecordReaderFactory;
@@ -84,7 +86,7 @@
+ " For the list of available Kafka properties please refer to: http://kafka.apache.org/documentation.html#configuration.",
expressionLanguageScope = ExpressionLanguageScope.VARIABLE_REGISTRY)
@SeeAlso({ConsumeKafka_2_6.class, PublishKafka_2_6.class, PublishKafkaRecord_2_6.class})
-public class ConsumeKafkaRecord_2_6 extends AbstractProcessor {
+public class ConsumeKafkaRecord_2_6 extends AbstractProcessor implements VerifiableProcessor {
static final AllowableValue OFFSET_EARLIEST = new AllowableValue("earliest", "earliest", "Automatically reset the offset to the earliest offset");
static final AllowableValue OFFSET_LATEST = new AllowableValue("latest", "latest", "Automatically reset the offset to the latest offset");
@@ -488,4 +490,11 @@
}
}
}
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ try (final ConsumerPool consumerPool = createConsumerPool(context, verificationLogger)) {
+ return consumerPool.verifyConfiguration();
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafka_2_6.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafka_2_6.java
index 1f54c70..58f2b24 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafka_2_6.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumeKafka_2_6.java
@@ -36,9 +36,11 @@
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.util.StandardValidators;
@@ -77,7 +79,7 @@
+ " In the event a dynamic property represents a property that was already set, its value will be ignored and WARN message logged."
+ " For the list of available Kafka properties please refer to: http://kafka.apache.org/documentation.html#configuration. ",
expressionLanguageScope = ExpressionLanguageScope.VARIABLE_REGISTRY)
-public class ConsumeKafka_2_6 extends AbstractProcessor {
+public class ConsumeKafka_2_6 extends AbstractProcessor implements VerifiableProcessor {
static final AllowableValue OFFSET_EARLIEST = new AllowableValue("earliest", "earliest", "Automatically reset the offset to the earliest offset");
@@ -462,4 +464,11 @@
}
}
}
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ try (final ConsumerPool consumerPool = createConsumerPool(context, verificationLogger)) {
+ return consumerPool.verifyConfiguration();
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerLease.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerLease.java
index b934f52..bdb9b3d 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerLease.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerLease.java
@@ -500,7 +500,7 @@
session.adjustCounter("Parse Failures", 1, false);
}
- private Map<String, String> getAttributes(final ConsumerRecord<?, ?> consumerRecord) {
+ protected Map<String, String> getAttributes(final ConsumerRecord<?, ?> consumerRecord) {
final Map<String, String> attributes = new HashMap<>();
if (headerNamePattern == null) {
return attributes;
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerPool.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerPool.java
index 0895733..381c7b1 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerPool.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/ConsumerPool.java
@@ -17,28 +17,39 @@
package org.apache.nifi.processors.kafka.pubsub;
import org.apache.kafka.clients.consumer.Consumer;
+import org.apache.kafka.clients.consumer.ConsumerRecord;
+import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer;
+import org.apache.kafka.clients.consumer.OffsetAndMetadata;
import org.apache.kafka.common.KafkaException;
import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.TopicPartition;
import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.serialization.RecordReader;
import org.apache.nifi.serialization.RecordReaderFactory;
import org.apache.nifi.serialization.RecordSetWriterFactory;
+import java.io.ByteArrayInputStream;
import java.io.Closeable;
+import java.io.InputStream;
import java.nio.charset.Charset;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
+import java.util.Set;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.atomic.AtomicLong;
import java.util.regex.Pattern;
+import java.util.stream.Collectors;
/**
* A pool of Kafka Consumers for a given topic. Consumers can be obtained by
@@ -253,6 +264,168 @@
return partitionsEachTopic;
}
+ public List<ConfigVerificationResult> verifyConfiguration() {
+ final List<ConfigVerificationResult> verificationResults = new ArrayList<>();
+
+ // Get a SimpleConsumerLease that we can use to communicate with Kafka
+ SimpleConsumerLease lease = pooledLeases.poll();
+ if (lease == null) {
+ lease = createConsumerLease();
+ if (lease == null) {
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Attempt connection")
+ .outcome(Outcome.FAILED)
+ .explanation("Could not obtain a Lease")
+ .build());
+
+ return verificationResults;
+ }
+ }
+
+ try {
+ final Consumer<byte[], byte[]> consumer = lease.consumer;
+ try {
+ consumer.groupMetadata();
+ } catch (final Exception e) {
+ logger.error("Failed to fetch Consumer Group Metadata in order to verify processor configuration", e);
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Attempt connection")
+ .outcome(Outcome.FAILED)
+ .explanation("Could not fetch Consumer Group Metadata: " + e)
+ .build());
+ }
+
+ try {
+ if (topicPattern == null) {
+ final Map<String, Long> messagesToConsumePerTopic = new HashMap<>();
+
+ for (final String topicName : topics) {
+ final List<PartitionInfo> partitionInfos = consumer.partitionsFor(topicName);
+
+ final Set<TopicPartition> topicPartitions = partitionInfos.stream()
+ .map(partitionInfo -> new TopicPartition(partitionInfo.topic(), partitionInfo.partition()))
+ .collect(Collectors.toSet());
+
+ final Map<TopicPartition, Long> endOffsets = consumer.endOffsets(topicPartitions, Duration.ofSeconds(30));
+ final Map<TopicPartition, Long> beginningOffsets = consumer.beginningOffsets(topicPartitions, Duration.ofSeconds(30));
+ final Map<TopicPartition, OffsetAndMetadata> committedOffsets = consumer.committed(topicPartitions, Duration.ofSeconds(30));
+
+ for (final TopicPartition topicPartition : endOffsets.keySet()) {
+ long endOffset = endOffsets.get(topicPartition);
+ // When no messages have been added to a topic, end offset is 0. However, after the first message is added,
+ // the end offset points to where the next message will be. I.e., it goes from 0 to 2. We want the offset
+ // of the last message, not the offset of where the next one will be. So we subtract one.
+ if (endOffset > 0) {
+ endOffset--;
+ }
+
+ final long beginningOffset = beginningOffsets.getOrDefault(topicPartition, 0L);
+ if (endOffset <= beginningOffset) {
+ messagesToConsumePerTopic.merge(topicPartition.topic(), 0L, Long::sum);
+ continue;
+ }
+
+ final OffsetAndMetadata offsetAndMetadata = committedOffsets.get(topicPartition);
+ final long committedOffset = offsetAndMetadata == null ? 0L : offsetAndMetadata.offset();
+
+ final long currentOffset = Math.max(beginningOffset, committedOffset);
+ final long messagesToConsume = endOffset - currentOffset;
+
+ messagesToConsumePerTopic.merge(topicPartition.topic(), messagesToConsume, Long::sum);
+ }
+ }
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Check Offsets")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Successfully determined offsets for " + messagesToConsumePerTopic.size() + " topics. Number of messages left to consume per topic: " + messagesToConsumePerTopic)
+ .build());
+
+ logger.info("Successfully determined offsets for {} topics. Number of messages left to consume per topic: {}", messagesToConsumePerTopic.size(), messagesToConsumePerTopic);
+ } else {
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Topic Offsets")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Cannot determine Topic Offsets because a Topic Wildcard was used instead of an explicit Topic Name")
+ .build());
+ }
+ } catch (final Exception e) {
+ logger.error("Failed to determine Topic Offsets in order to verify configuration", e);
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Topic Offsets")
+ .outcome(Outcome.FAILED)
+ .explanation("Could not fetch Topic Offsets: " + e)
+ .build());
+ }
+
+ if (readerFactory != null) {
+ final ConfigVerificationResult checkDataResult = checkRecordIsParsable(lease);
+ verificationResults.add(checkDataResult);
+ }
+
+ return verificationResults;
+ } finally {
+ lease.close(true);
+ }
+ }
+
+ private ConfigVerificationResult checkRecordIsParsable(final SimpleConsumerLease consumerLease) {
+ final ConsumerRecords<byte[], byte[]> consumerRecords = consumerLease.consumer.poll(Duration.ofSeconds(30));
+
+ final Map<String, Integer> parseFailuresPerTopic = new HashMap<>();
+ final Map<String, String> latestParseFailureDescription = new HashMap<>();
+ final Map<String, Integer> recordsPerTopic = new HashMap<>();
+
+ for (final ConsumerRecord<byte[], byte[]> consumerRecord : consumerRecords) {
+ recordsPerTopic.merge(consumerRecord.topic(), 1, Integer::sum);
+ final Map<String, String> attributes = consumerLease.getAttributes(consumerRecord);
+
+ final byte[] recordBytes = consumerRecord.value() == null ? new byte[0] : consumerRecord.value();
+ try (final InputStream in = new ByteArrayInputStream(recordBytes)) {
+ final RecordReader reader = readerFactory.createRecordReader(attributes, in, recordBytes.length, logger);
+ while (reader.nextRecord() != null) {
+ }
+ } catch (final Exception e) {
+ parseFailuresPerTopic.merge(consumerRecord.topic(), 1, Integer::sum);
+ latestParseFailureDescription.put(consumerRecord.topic(), e.toString());
+ }
+ }
+
+ // Note here that we do not commit the offsets. We will just let the consumer close without committing the offsets, which
+ // will roll back the consumption of the messages.
+ if (recordsPerTopic.isEmpty()) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Received no messages to attempt parsing within the 30 second timeout")
+ .build();
+ }
+
+ if (parseFailuresPerTopic.isEmpty()) {
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Was able to parse all Records consumed from topics. Number of Records consumed from each topic: " + recordsPerTopic)
+ .build();
+ } else {
+ final Map<String, String> failureDescriptions = new HashMap<>();
+ for (final String topic : recordsPerTopic.keySet()) {
+ final int records = recordsPerTopic.get(topic);
+ final Integer failures = parseFailuresPerTopic.get(topic);
+ final String failureReason = latestParseFailureDescription.get(topic);
+ final String description = "Failed to parse " + failures + " out of " + records + " records. Sample failure reason: " + failureReason;
+ failureDescriptions.put(topic, description);
+ }
+
+ return new ConfigVerificationResult.Builder()
+ .verificationStepName("Parse Records")
+ .outcome(Outcome.FAILED)
+ .explanation("With the configured Record Reader, failed to parse at least one Record. Failures per topic: " + failureDescriptions)
+ .build();
+ }
+ }
+
/**
* Obtains a consumer from the pool if one is available or lazily
* initializes a new one if deemed necessary.
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafkaRecord_2_6.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafkaRecord_2_6.java
index 863440e..6cb3e4c 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafkaRecord_2_6.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafkaRecord_2_6.java
@@ -35,11 +35,14 @@
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.InputStreamCallback;
import org.apache.nifi.processor.util.FlowFileFilters;
@@ -95,7 +98,7 @@
@WritesAttribute(attribute = "msg.count", description = "The number of messages that were sent to Kafka for this FlowFile. This attribute is added only to "
+ "FlowFiles that are routed to success.")
@SeeAlso({PublishKafka_2_6.class, ConsumeKafka_2_6.class, ConsumeKafkaRecord_2_6.class})
-public class PublishKafkaRecord_2_6 extends AbstractProcessor {
+public class PublishKafkaRecord_2_6 extends AbstractProcessor implements VerifiableProcessor {
protected static final String MSG_COUNT = "msg.count";
static final AllowableValue DELIVERY_REPLICATED = new AllowableValue("all", "Guarantee Replicated Delivery",
@@ -570,4 +573,12 @@
return (session, flowFiles) -> session.transfer(flowFiles, REL_FAILURE);
}
}
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ final String topic = context.getProperty(TOPIC).evaluateAttributeExpressions(attributes).getValue();
+ try (final PublisherPool pool = createPublisherPool(context)) {
+ return pool.verifyConfiguration(topic);
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafka_2_6.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafka_2_6.java
index 66c1dcb..9f6bdc7 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafka_2_6.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublishKafka_2_6.java
@@ -34,11 +34,14 @@
import org.apache.nifi.components.ValidationResult;
import org.apache.nifi.expression.ExpressionLanguageScope;
import org.apache.nifi.flowfile.FlowFile;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.components.ConfigVerificationResult;
import org.apache.nifi.processor.DataUnit;
import org.apache.nifi.processor.ProcessContext;
import org.apache.nifi.processor.ProcessSession;
import org.apache.nifi.processor.Relationship;
+import org.apache.nifi.processor.VerifiableProcessor;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.processor.io.InputStreamCallback;
import org.apache.nifi.processor.util.FlowFileFilters;
@@ -81,7 +84,7 @@
@WritesAttribute(attribute = "msg.count", description = "The number of messages that were sent to Kafka for this FlowFile. This attribute is added only to "
+ "FlowFiles that are routed to success. If the <Message Demarcator> Property is not set, this will always be 1, but if the Property is set, it may "
+ "be greater than 1.")
-public class PublishKafka_2_6 extends AbstractProcessor {
+public class PublishKafka_2_6 extends AbstractProcessor implements VerifiableProcessor {
protected static final String MSG_COUNT = "msg.count";
static final AllowableValue DELIVERY_REPLICATED = new AllowableValue("all", "Guarantee Replicated Delivery",
@@ -531,4 +534,11 @@
return null;
}
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ final String topic = context.getProperty(TOPIC).evaluateAttributeExpressions(attributes).getValue();
+ try (final PublisherPool pool = createPublisherPool(context)) {
+ return pool.verifyConfiguration(topic);
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherLease.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherLease.java
index 4911409..f66b8b1 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherLease.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherLease.java
@@ -21,9 +21,12 @@
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.ProducerRecord;
import org.apache.kafka.clients.producer.RecordMetadata;
+import org.apache.kafka.common.PartitionInfo;
import org.apache.kafka.common.header.Headers;
import org.apache.nifi.flowfile.FlowFile;
import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.schema.access.SchemaNotFoundException;
import org.apache.nifi.serialization.RecordSetWriter;
import org.apache.nifi.serialization.RecordSetWriterFactory;
@@ -41,7 +44,9 @@
import java.io.InputStream;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
import java.util.Collections;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
@@ -308,4 +313,28 @@
return tracker;
}
+
+ public List<ConfigVerificationResult> verifyConfiguration(final String topic) {
+ final List<ConfigVerificationResult> verificationResults = new ArrayList<>();
+
+ try {
+ final List<PartitionInfo> partitionInfos = producer.partitionsFor(topic);
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Topic Partitions")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Determined that there are " + partitionInfos.size() + " partitions for topic " + topic)
+ .build());
+ } catch (final Exception e) {
+ logger.error("Failed to determine Partition Information for Topic {} in order to verify configuration", topic, e);
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Topic Partitions")
+ .outcome(Outcome.FAILED)
+ .explanation("Could not fetch Partition Information: " + e)
+ .build());
+ }
+
+ return verificationResults;
+ }
}
diff --git a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherPool.java b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherPool.java
index 0de5111..9e2c30f 100644
--- a/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherPool.java
+++ b/nifi-nar-bundles/nifi-kafka-bundle/nifi-kafka-2-6-processors/src/main/java/org/apache/nifi/processors/kafka/pubsub/PublisherPool.java
@@ -20,10 +20,12 @@
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.Producer;
import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.components.ConfigVerificationResult;
import java.io.Closeable;
import java.nio.charset.Charset;
import java.util.HashMap;
+import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@@ -113,4 +115,10 @@
protected int available() {
return publisherQueue.size();
}
+
+ public List<ConfigVerificationResult> verifyConfiguration(final String topic) {
+ try (final PublisherLease lease = obtainPublisher()) {
+ return lease.verifyConfiguration(topic);
+ }
+ }
}
diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/AbstractSiteToSiteReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/AbstractSiteToSiteReportingTask.java
index cf63a70..12b9bff 100644
--- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/AbstractSiteToSiteReportingTask.java
+++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/main/java/org/apache/nifi/reporting/AbstractSiteToSiteReportingTask.java
@@ -16,27 +16,17 @@
*/
package org.apache.nifi.reporting;
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.charset.StandardCharsets;
-import java.text.DateFormat;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Supplier;
-import javax.json.JsonArray;
-import javax.json.JsonObjectBuilder;
-import javax.json.JsonValue;
import org.apache.nifi.annotation.lifecycle.OnStopped;
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.context.PropertyContext;
+import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.flowfile.attributes.CoreAttributes;
+import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.processor.exception.ProcessException;
import org.apache.nifi.remote.Transaction;
+import org.apache.nifi.remote.TransferDirection;
import org.apache.nifi.remote.client.SiteToSiteClient;
import org.apache.nifi.reporting.s2s.SiteToSiteUtils;
import org.apache.nifi.schema.access.SchemaNotFoundException;
@@ -65,10 +55,28 @@
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.node.ArrayNode;
+import javax.json.JsonArray;
+import javax.json.JsonObjectBuilder;
+import javax.json.JsonValue;
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.text.DateFormat;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Supplier;
+
/**
* Base class for ReportingTasks that send data over site-to-site.
*/
-public abstract class AbstractSiteToSiteReportingTask extends AbstractReportingTask {
+public abstract class AbstractSiteToSiteReportingTask extends AbstractReportingTask implements VerifiableReportingTask {
+ private static final String ESTABLISH_COMMUNICATION = "Establish Site-to-Site Connection";
protected static final String LAST_EVENT_ID_KEY = "last_event_id";
protected static final String DESTINATION_URL_PATH = "/nifi";
@@ -114,7 +122,7 @@
return properties;
}
- public void setup(final ReportingContext reportContext) throws IOException {
+ public void setup(final PropertyContext reportContext) throws IOException {
if (siteToSiteClient == null) {
siteToSiteClient = SiteToSiteUtils.getClient(reportContext, getLogger(), null);
}
@@ -507,4 +515,41 @@
}
}
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext context, final ComponentLog verificationLogger) {
+ final List<ConfigVerificationResult> verificationResults = new ArrayList<>();
+
+ try (final SiteToSiteClient client = SiteToSiteUtils.getClient(context, verificationLogger, null)) {
+ final Transaction transaction = client.createTransaction(TransferDirection.SEND);
+
+ // If transaction is null, indicates that all nodes are penalized
+ if (transaction == null) {
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_COMMUNICATION)
+ .outcome(Outcome.SKIPPED)
+ .explanation("All nodes in destination NiFi are currently 'penalized', meaning that there have been recent failures communicating with the destination NiFi, or that" +
+ " the NiFi instance is applying backpressure")
+ .build());
+ } else {
+ transaction.cancel("Just verifying configuration");
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_COMMUNICATION)
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("Established connection to destination NiFi instance and Received indication that it is ready to ready to receive data")
+ .build());
+ }
+ } catch (final Exception e) {
+ verificationLogger.error("Failed to establish site-to-site connection", e);
+
+ verificationResults.add(new ConfigVerificationResult.Builder()
+ .verificationStepName(ESTABLISH_COMMUNICATION)
+ .outcome(Outcome.FAILED)
+ .explanation("Failed to establish Site-to-Site Connection: " + e)
+ .build());
+ }
+
+ return verificationResults;
+ }
}
diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteBulletinReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteBulletinReportingTask.java
index 00cb4b9..6acf3e8 100644
--- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteBulletinReportingTask.java
+++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteBulletinReportingTask.java
@@ -17,29 +17,11 @@
package org.apache.nifi.reporting;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.json.Json;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonValue;
-
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext;
+import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.logging.ComponentLog;
import org.apache.nifi.remote.Transaction;
import org.apache.nifi.remote.TransferDirection;
@@ -53,6 +35,24 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
public class TestSiteToSiteBulletinReportingTask {
@Test
@@ -183,7 +183,7 @@
final List<byte[]> dataSent = new ArrayList<>();
@Override
- public void setup(ReportingContext reportContext) throws IOException {
+ public void setup(PropertyContext reportContext) {
if(siteToSiteClient == null) {
final SiteToSiteClient client = Mockito.mock(SiteToSiteClient.class);
final Transaction transaction = Mockito.mock(Transaction.class);
diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteMetricsReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteMetricsReportingTask.java
index 6db84ef..e2134c4 100644
--- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteMetricsReportingTask.java
+++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteMetricsReportingTask.java
@@ -17,33 +17,13 @@
package org.apache.nifi.reporting;
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.fail;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Map.Entry;
-import java.util.UUID;
-
-import javax.json.Json;
-import javax.json.JsonArray;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonValue;
-
import org.apache.commons.lang3.SystemUtils;
import org.apache.nifi.attribute.expression.language.StandardPropertyValue;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.ValidationContext;
import org.apache.nifi.components.ValidationResult;
+import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.controller.status.ProcessGroupStatus;
import org.apache.nifi.controller.status.ProcessorStatus;
import org.apache.nifi.logging.ComponentLog;
@@ -64,6 +44,26 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import javax.json.Json;
+import javax.json.JsonArray;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static org.mockito.Mockito.when;
+
public class TestSiteToSiteMetricsReportingTask {
private ReportingContext context;
@@ -304,7 +304,7 @@
final List<byte[]> dataSent = new ArrayList<>();
@Override
- public void setup(ReportingContext reportContext) throws IOException {
+ public void setup(PropertyContext reportContext) {
if(siteToSiteClient == null) {
final SiteToSiteClient client = Mockito.mock(SiteToSiteClient.class);
final Transaction transaction = Mockito.mock(Transaction.class);
diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteProvenanceReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteProvenanceReportingTask.java
index 5202e03..d3d88ee 100644
--- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteProvenanceReportingTask.java
+++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteProvenanceReportingTask.java
@@ -17,30 +17,12 @@
package org.apache.nifi.reporting;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertNull;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-import java.util.concurrent.atomic.AtomicInteger;
-
-import javax.json.Json;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonValue;
-
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
import org.apache.nifi.components.state.Scope;
+import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.controller.ConfigurationContext;
import org.apache.nifi.controller.status.ConnectionStatus;
import org.apache.nifi.controller.status.ProcessGroupStatus;
@@ -65,8 +47,24 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
-import com.fasterxml.jackson.databind.JsonNode;
-import com.fasterxml.jackson.databind.ObjectMapper;
+import javax.json.Json;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonValue;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.atomic.AtomicInteger;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.when;
public class TestSiteToSiteProvenanceReportingTask {
@@ -677,7 +675,7 @@
final List<byte[]> dataSent = new ArrayList<>();
@Override
- public void setup(ReportingContext reportContext) throws IOException {
+ public void setup(PropertyContext reportContext) {
if(siteToSiteClient == null) {
final SiteToSiteClient client = Mockito.mock(SiteToSiteClient.class);
final Transaction transaction = Mockito.mock(Transaction.class);
diff --git a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
index 3dfab49..82a16e0 100644
--- a/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
+++ b/nifi-nar-bundles/nifi-site-to-site-reporting-bundle/nifi-site-to-site-reporting-task/src/test/java/org/apache/nifi/reporting/TestSiteToSiteStatusReportingTask.java
@@ -17,33 +17,9 @@
package org.apache.nifi.reporting;
-
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
-import static org.junit.Assert.assertNull;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.nio.charset.StandardCharsets;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.UUID;
-
-import javax.json.Json;
-import javax.json.JsonNumber;
-import javax.json.JsonObject;
-import javax.json.JsonReader;
-import javax.json.JsonString;
-import javax.json.JsonValue;
-
import org.apache.nifi.components.PropertyDescriptor;
import org.apache.nifi.components.PropertyValue;
+import org.apache.nifi.context.PropertyContext;
import org.apache.nifi.controller.status.ConnectionStatus;
import org.apache.nifi.controller.status.PortStatus;
import org.apache.nifi.controller.status.ProcessGroupStatus;
@@ -67,6 +43,29 @@
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
+import javax.json.Json;
+import javax.json.JsonNumber;
+import javax.json.JsonObject;
+import javax.json.JsonReader;
+import javax.json.JsonString;
+import javax.json.JsonValue;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.when;
+
public class TestSiteToSiteStatusReportingTask {
private ReportingContext context;
@@ -538,7 +537,7 @@
final List<byte[]> dataSent = new ArrayList<>();
@Override
- public void setup(ReportingContext reportContext) throws IOException {
+ public void setup(PropertyContext reportContext) {
if(siteToSiteClient == null) {
final SiteToSiteClient client = Mockito.mock(SiteToSiteClient.class);
final Transaction transaction = Mockito.mock(Transaction.class);
diff --git a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
index ba6e9dd..d9d1510 100644
--- a/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
+++ b/nifi-stateless/nifi-stateless-bundle/nifi-stateless-engine/src/main/java/org/apache/nifi/stateless/engine/StandardStatelessEngine.java
@@ -262,7 +262,7 @@
final Map<PropertyDescriptor, PropertyConfiguration> fullPropertyMap = buildConfiguredAndDefaultPropertyMap(component, explicitlyConfiguredPropertyMap);
final ValidationContext validationContext = new StandardValidationContext(controllerServiceProvider, fullPropertyMap,
- null, null, componentId, VariableRegistry.EMPTY_REGISTRY, null);
+ null, null, componentId, VariableRegistry.EMPTY_REGISTRY, null, true);
final Collection<ValidationResult> validationResults = component.validate(validationContext);
return validationResults.stream()
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/cs/tests/system/EnsureControllerServiceConfigurationCorrect.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/cs/tests/system/EnsureControllerServiceConfigurationCorrect.java
new file mode 100644
index 0000000..1ae7631
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/cs/tests/system/EnsureControllerServiceConfigurationCorrect.java
@@ -0,0 +1,141 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.cs.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.controller.VerifiableControllerService;
+import org.apache.nifi.logging.ComponentLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.nifi.expression.ExpressionLanguageScope.FLOWFILE_ATTRIBUTES;
+import static org.apache.nifi.processor.util.StandardValidators.POSITIVE_INTEGER_VALIDATOR;
+
+public class EnsureControllerServiceConfigurationCorrect extends AbstractControllerService implements VerifiableControllerService {
+ static final PropertyDescriptor SUCCESSFUL_VERIFICATION = new PropertyDescriptor.Builder()
+ .name("Successful Verification")
+ .displayName("Successful Verification")
+ .description("Whether or not Verification should succeed")
+ .required(true)
+ .allowableValues("true", "false")
+ .build();
+
+ static final PropertyDescriptor VERIFICATION_STEPS = new PropertyDescriptor.Builder()
+ .name("Verification Steps")
+ .displayName("Verification Steps")
+ .description("The number of steps to use in the Verification")
+ .required(true)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .expressionLanguageSupported(FLOWFILE_ATTRIBUTES)
+ .defaultValue("1")
+ .build();
+
+ static final PropertyDescriptor EXCEPTION_ON_VERIFICATION = new PropertyDescriptor.Builder()
+ .name("Exception on Verification")
+ .displayName("Exception on Verification")
+ .description("If true, attempting to perform verification will throw a RuntimeException")
+ .required(true)
+ .allowableValues("true", "false")
+ .defaultValue("false")
+ .build();
+
+ static final PropertyDescriptor FAILURE_NODE_NUMBER = new PropertyDescriptor.Builder()
+ .name("Failure Node Number")
+ .displayName("Failure Node Number")
+ .description("The Node Number to Fail On")
+ .required(false)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .build();
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return Arrays.asList(SUCCESSFUL_VERIFICATION, VERIFICATION_STEPS, EXCEPTION_ON_VERIFICATION, FAILURE_NODE_NUMBER);
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ final boolean exception = context.getProperty(EXCEPTION_ON_VERIFICATION).asBoolean();
+ if (exception) {
+ throw new RuntimeException("Intentional Exception - Processor was configured to throw an Exception when performing config verification");
+ }
+
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ final int iterations;
+ try {
+ iterations = context.getProperty(VERIFICATION_STEPS).evaluateAttributeExpressions(attributes).asInteger();
+ } catch (final NumberFormatException nfe) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Number of Verification Steps")
+ .outcome(Outcome.FAILED)
+ .explanation("Invalid value for the number of Verification Steps")
+ .build());
+
+ return results;
+ }
+
+ final boolean success = context.getProperty(SUCCESSFUL_VERIFICATION).asBoolean();
+ final Outcome outcome = success ? Outcome.SUCCESSFUL : Outcome.FAILED;
+
+ for (int i=0; i < iterations; i++) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verification Step #" + i)
+ .outcome(outcome)
+ .explanation("Verification Step #" + i)
+ .build());
+ }
+
+ // Consider the 'Failure Node Number' Property. This makes it easy to get different results from different nodes for testing purposes
+ final Integer failureNodeNum = context.getProperty(FAILURE_NODE_NUMBER).asInteger();
+ if (failureNodeNum == null) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Not configured to Fail based on node number")
+ .build());
+ } else {
+ final String currentNodeNumberString = System.getProperty("nodeNumber");
+ final Integer currentNodeNumber = currentNodeNumberString == null ? null : Integer.parseInt(currentNodeNumberString);
+ final boolean shouldFail = Objects.equals(failureNodeNum, currentNodeNumber);
+
+ if (shouldFail) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.FAILED)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured to fail on this Node Number")
+ .build());
+ } else {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured not to fail on this Node Number")
+ .build());
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EnsureProcessorConfigurationCorrect.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EnsureProcessorConfigurationCorrect.java
new file mode 100644
index 0000000..c527d0c
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/processors/tests/system/EnsureProcessorConfigurationCorrect.java
@@ -0,0 +1,149 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.processors.tests.system;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.components.PropertyDescriptor.Builder;
+import org.apache.nifi.logging.ComponentLog;
+import org.apache.nifi.processor.AbstractProcessor;
+import org.apache.nifi.processor.ProcessContext;
+import org.apache.nifi.processor.ProcessSession;
+import org.apache.nifi.processor.VerifiableProcessor;
+import org.apache.nifi.processor.exception.ProcessException;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+import static org.apache.nifi.expression.ExpressionLanguageScope.FLOWFILE_ATTRIBUTES;
+import static org.apache.nifi.processor.util.StandardValidators.POSITIVE_INTEGER_VALIDATOR;
+
+public class EnsureProcessorConfigurationCorrect extends AbstractProcessor implements VerifiableProcessor {
+
+ static final PropertyDescriptor SUCCESSFUL_VERIFICATION = new Builder()
+ .name("Successful Verification")
+ .displayName("Successful Verification")
+ .description("Whether or not Verification should succeed")
+ .required(true)
+ .allowableValues("true", "false")
+ .build();
+
+ static final PropertyDescriptor VERIFICATION_STEPS = new Builder()
+ .name("Verification Steps")
+ .displayName("Verification Steps")
+ .description("The number of steps to use in the Verification")
+ .required(true)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .expressionLanguageSupported(FLOWFILE_ATTRIBUTES)
+ .defaultValue("1")
+ .build();
+
+ static final PropertyDescriptor EXCEPTION_ON_VERIFICATION = new Builder()
+ .name("Exception on Verification")
+ .displayName("Exception on Verification")
+ .description("If true, attempting to perform verification will throw a RuntimeException")
+ .required(true)
+ .allowableValues("true", "false")
+ .defaultValue("false")
+ .build();
+
+ static final PropertyDescriptor FAILURE_NODE_NUMBER = new Builder()
+ .name("Failure Node Number")
+ .displayName("Failure Node Number")
+ .description("The Node Number to Fail On")
+ .required(false)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .build();
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return Arrays.asList(SUCCESSFUL_VERIFICATION, VERIFICATION_STEPS, EXCEPTION_ON_VERIFICATION, FAILURE_NODE_NUMBER);
+ }
+
+ @Override
+ public void onTrigger(final ProcessContext context, final ProcessSession session) throws ProcessException {
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ProcessContext context, final ComponentLog verificationLogger, final Map<String, String> attributes) {
+ final boolean exception = context.getProperty(EXCEPTION_ON_VERIFICATION).asBoolean();
+ if (exception) {
+ throw new RuntimeException("Intentional Exception - Processor was configured to throw an Exception when performing config verification");
+ }
+
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ final int iterations;
+ try {
+ iterations = context.getProperty(VERIFICATION_STEPS).evaluateAttributeExpressions(attributes).asInteger();
+ } catch (final NumberFormatException nfe) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Number of Verification Steps")
+ .outcome(Outcome.FAILED)
+ .explanation("Invalid value for the number of Verification Steps")
+ .build());
+
+ return results;
+ }
+
+ final boolean success = context.getProperty(SUCCESSFUL_VERIFICATION).asBoolean();
+ final Outcome outcome = success ? Outcome.SUCCESSFUL : Outcome.FAILED;
+
+ for (int i=0; i < iterations; i++) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verification Step #" + i)
+ .outcome(outcome)
+ .explanation("Verification Step #" + i)
+ .build());
+ }
+
+ // Consider the 'Failure Node Number' Property. This makes it easy to get different results from different nodes for testing purposes
+ final Integer failureNodeNum = context.getProperty(FAILURE_NODE_NUMBER).asInteger();
+ if (failureNodeNum == null) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Not configured to Fail based on node number")
+ .build());
+ } else {
+ final String currentNodeNumberString = System.getProperty("nodeNumber");
+ final Integer currentNodeNumber = currentNodeNumberString == null ? null : Integer.parseInt(currentNodeNumberString);
+ final boolean shouldFail = Objects.equals(failureNodeNum, currentNodeNumber);
+
+ if (shouldFail) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.FAILED)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured to fail on this Node Number")
+ .build());
+ } else {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured not to fail on this Node Number")
+ .build());
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/reporting/EnsureReportingTaskConfigurationCorrect.java b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/reporting/EnsureReportingTaskConfigurationCorrect.java
new file mode 100644
index 0000000..26125d2
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/java/org/apache/nifi/reporting/EnsureReportingTaskConfigurationCorrect.java
@@ -0,0 +1,143 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.reporting;
+
+import org.apache.nifi.components.ConfigVerificationResult;
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.ConfigurationContext;
+import org.apache.nifi.logging.ComponentLog;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Objects;
+
+import static org.apache.nifi.expression.ExpressionLanguageScope.FLOWFILE_ATTRIBUTES;
+import static org.apache.nifi.processor.util.StandardValidators.POSITIVE_INTEGER_VALIDATOR;
+
+public class EnsureReportingTaskConfigurationCorrect extends AbstractReportingTask implements VerifiableReportingTask {
+ static final PropertyDescriptor SUCCESSFUL_VERIFICATION = new PropertyDescriptor.Builder()
+ .name("Successful Verification")
+ .displayName("Successful Verification")
+ .description("Whether or not Verification should succeed")
+ .required(true)
+ .allowableValues("true", "false")
+ .build();
+
+ static final PropertyDescriptor VERIFICATION_STEPS = new PropertyDescriptor.Builder()
+ .name("Verification Steps")
+ .displayName("Verification Steps")
+ .description("The number of steps to use in the Verification")
+ .required(true)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .expressionLanguageSupported(FLOWFILE_ATTRIBUTES)
+ .defaultValue("1")
+ .build();
+
+ static final PropertyDescriptor EXCEPTION_ON_VERIFICATION = new PropertyDescriptor.Builder()
+ .name("Exception on Verification")
+ .displayName("Exception on Verification")
+ .description("If true, attempting to perform verification will throw a RuntimeException")
+ .required(true)
+ .allowableValues("true", "false")
+ .defaultValue("false")
+ .build();
+
+ static final PropertyDescriptor FAILURE_NODE_NUMBER = new PropertyDescriptor.Builder()
+ .name("Failure Node Number")
+ .displayName("Failure Node Number")
+ .description("The Node Number to Fail On")
+ .required(false)
+ .addValidator(POSITIVE_INTEGER_VALIDATOR)
+ .build();
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return Arrays.asList(SUCCESSFUL_VERIFICATION, VERIFICATION_STEPS, EXCEPTION_ON_VERIFICATION, FAILURE_NODE_NUMBER);
+ }
+
+
+ @Override
+ public void onTrigger(final ReportingContext context) {
+ }
+
+ @Override
+ public List<ConfigVerificationResult> verify(final ConfigurationContext context, final ComponentLog verificationLogger) {
+ final boolean exception = context.getProperty(EXCEPTION_ON_VERIFICATION).asBoolean();
+ if (exception) {
+ throw new RuntimeException("Intentional Exception - Processor was configured to throw an Exception when performing config verification");
+ }
+
+ final List<ConfigVerificationResult> results = new ArrayList<>();
+
+ final int iterations;
+ try {
+ iterations = context.getProperty(VERIFICATION_STEPS).asInteger();
+ } catch (final NumberFormatException nfe) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Determine Number of Verification Steps")
+ .outcome(Outcome.FAILED)
+ .explanation("Invalid value for the number of Verification Steps")
+ .build());
+
+ return results;
+ }
+
+ final boolean success = context.getProperty(SUCCESSFUL_VERIFICATION).asBoolean();
+ final Outcome outcome = success ? Outcome.SUCCESSFUL : Outcome.FAILED;
+
+ for (int i=0; i < iterations; i++) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Verification Step #" + i)
+ .outcome(outcome)
+ .explanation("Verification Step #" + i)
+ .build());
+ }
+
+ // Consider the 'Failure Node Number' Property. This makes it easy to get different results from different nodes for testing purposes
+ final Integer failureNodeNum = context.getProperty(FAILURE_NODE_NUMBER).asInteger();
+ if (failureNodeNum == null) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SKIPPED)
+ .explanation("Not configured to Fail based on node number")
+ .build());
+ } else {
+ final String currentNodeNumberString = System.getProperty("nodeNumber");
+ final Integer currentNodeNumber = currentNodeNumberString == null ? null : Integer.parseInt(currentNodeNumberString);
+ final boolean shouldFail = Objects.equals(failureNodeNum, currentNodeNumber);
+
+ if (shouldFail) {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.FAILED)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured to fail on this Node Number")
+ .build());
+ } else {
+ results.add(new ConfigVerificationResult.Builder()
+ .verificationStepName("Fail Based on Node Number")
+ .outcome(Outcome.SUCCESSFUL)
+ .explanation("This node is Node Number " + currentNodeNumberString + " and configured not to fail on this Node Number")
+ .build());
+ }
+ }
+
+ return results;
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
index 74a1f12..40c1ee9 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.controller.ControllerService
@@ -13,6 +13,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+org.apache.nifi.cs.tests.system.EnsureControllerServiceConfigurationCorrect
org.apache.nifi.cs.tests.system.FakeControllerService1
org.apache.nifi.cs.tests.system.StandardCountService
org.apache.nifi.cs.tests.system.StandardSleepService
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
index 9e70823..cb653cd 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.processor.Processor
@@ -19,6 +19,7 @@
org.apache.nifi.processors.tests.system.DependOnProperties
org.apache.nifi.processors.tests.system.DoNotTransferFlowFile
org.apache.nifi.processors.tests.system.Duplicate
+org.apache.nifi.processors.tests.system.EnsureProcessorConfigurationCorrect
org.apache.nifi.processors.tests.system.EvaluatePropertiesWithDifferentELScopes
org.apache.nifi.processors.tests.system.FakeProcessor
org.apache.nifi.processors.tests.system.FakeDynamicPropertiesProcessor
diff --git a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.reporting.ReportingTask b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.reporting.ReportingTask
index 64970e2..607006f 100644
--- a/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.reporting.ReportingTask
+++ b/nifi-system-tests/nifi-system-test-extensions-bundle/nifi-system-test-extensions/src/main/resources/META-INF/services/org.apache.nifi.reporting.ReportingTask
@@ -13,4 +13,5 @@
# See the License for the specific language governing permissions and
# limitations under the License.
+org.apache.nifi.reporting.EnsureReportingTaskConfigurationCorrect
org.apache.nifi.reporting.WriteToFileReportingTask
\ No newline at end of file
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
index af27178..e551e54 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiClientUtil.java
@@ -29,6 +29,7 @@
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClient;
import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
import org.apache.nifi.web.api.dto.BundleDTO;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
import org.apache.nifi.web.api.dto.ConnectableDTO;
import org.apache.nifi.web.api.dto.ConnectionDTO;
import org.apache.nifi.web.api.dto.ControllerServiceDTO;
@@ -45,9 +46,13 @@
import org.apache.nifi.web.api.dto.ProcessorConfigDTO;
import org.apache.nifi.web.api.dto.ProcessorDTO;
import org.apache.nifi.web.api.dto.RemoteProcessGroupDTO;
+import org.apache.nifi.web.api.dto.ReportingTaskDTO;
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.dto.VariableDTO;
import org.apache.nifi.web.api.dto.VariableRegistryDTO;
+import org.apache.nifi.web.api.dto.VerifyControllerServiceConfigRequestDTO;
+import org.apache.nifi.web.api.dto.VerifyProcessorConfigRequestDTO;
+import org.apache.nifi.web.api.dto.VerifyReportingTaskConfigRequestDTO;
import org.apache.nifi.web.api.dto.flow.FlowDTO;
import org.apache.nifi.web.api.dto.flow.ProcessGroupFlowDTO;
import org.apache.nifi.web.api.dto.provenance.ProvenanceDTO;
@@ -75,10 +80,14 @@
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProvenanceEntity;
import org.apache.nifi.web.api.entity.RemoteProcessGroupEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ScheduleComponentsEntity;
import org.apache.nifi.web.api.entity.VariableEntity;
import org.apache.nifi.web.api.entity.VariableRegistryEntity;
import org.apache.nifi.web.api.entity.VariableRegistryUpdateRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyControllerServiceConfigRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyProcessorConfigRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyReportingTaskConfigRequestEntity;
import org.junit.Assert;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -345,7 +354,11 @@
while (true) {
final ProcessorEntity entity = nifiClient.getProcessorClient().getProcessor(processorId);
final String state = entity.getComponent().getState();
- if (!expectedState.equals(state)) {
+
+ // We've reached the desired state if the state equal the expected state, OR if we expect stopped and the state is disabled (because disabled implies stopped)
+ final boolean desiredStateReached = expectedState.equals(state) || ("STOPPED".equalsIgnoreCase(expectedState) && "DISABLED".equalsIgnoreCase(state));
+
+ if (!desiredStateReached) {
Thread.sleep(10L);
continue;
}
@@ -362,6 +375,19 @@
}
}
+ public ControllerServiceEntity updateControllerService(final ControllerServiceEntity currentEntity, final Map<String, String> properties) throws NiFiClientException, IOException {
+ final ControllerServiceDTO dto = new ControllerServiceDTO();
+ dto.setProperties(properties);
+ dto.setId(currentEntity.getId());
+
+ final ControllerServiceEntity entity = new ControllerServiceEntity();
+ entity.setId(currentEntity.getId());
+ entity.setComponent(dto);
+ entity.setRevision(currentEntity.getRevision());
+
+ return nifiClient.getControllerServicesClient().updateControllerService(entity);
+ }
+
public ControllerServiceEntity enableControllerService(final ControllerServiceEntity entity) throws NiFiClientException, IOException {
final ControllerServiceRunStatusEntity runStatusEntity = new ControllerServiceRunStatusEntity();
runStatusEntity.setState("ENABLED");
@@ -953,4 +979,123 @@
return current;
}
+ public List<ConfigVerificationResultDTO> verifyProcessorConfig(final String processorId, final Map<String, String> properties) throws InterruptedException, IOException, NiFiClientException {
+ return verifyProcessorConfig(processorId, properties, Collections.emptyMap());
+ }
+
+ public List<ConfigVerificationResultDTO> verifyProcessorConfig(final String processorId, final Map<String, String> properties, final Map<String, String> attributes)
+ throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorConfigDTO processorConfig = new ProcessorConfigDTO();
+ processorConfig.setProperties(properties);
+
+ final VerifyProcessorConfigRequestDTO requestDto = new VerifyProcessorConfigRequestDTO();
+ requestDto.setProcessorId(processorId);
+ requestDto.setProcessorConfig(processorConfig);
+ requestDto.setAttributes(attributes);
+
+ final VerifyProcessorConfigRequestEntity verificationRequest = new VerifyProcessorConfigRequestEntity();
+ verificationRequest.setRequest(requestDto);
+
+ VerifyProcessorConfigRequestEntity results = nifiClient.getProcessorClient().submitConfigVerificationRequest(verificationRequest);
+ while (!results.getRequest().isComplete()) {
+ Thread.sleep(50L);
+ results = nifiClient.getProcessorClient().getConfigVerificationRequest(processorId, results.getRequest().getRequestId());
+ }
+
+ nifiClient.getProcessorClient().deleteConfigVerificationRequest(processorId, results.getRequest().getRequestId());
+
+ return results.getRequest().getResults();
+ }
+
+ public List<ConfigVerificationResultDTO> verifyControllerServiceConfig(final String serviceId, final Map<String, String> properties)
+ throws InterruptedException, IOException,NiFiClientException {
+ return verifyControllerServiceConfig(serviceId, properties, Collections.emptyMap());
+ }
+
+ public List<ConfigVerificationResultDTO> verifyControllerServiceConfig(final String serviceId, final Map<String, String> properties, final Map<String, String> attributes)
+ throws NiFiClientException, IOException, InterruptedException {
+
+ final ControllerServiceDTO serviceDto = new ControllerServiceDTO();
+ serviceDto.setProperties(properties);
+ serviceDto.setId(serviceId);
+
+ final VerifyControllerServiceConfigRequestDTO requestDto = new VerifyControllerServiceConfigRequestDTO();
+ requestDto.setControllerService(serviceDto);
+ requestDto.setAttributes(attributes);
+ requestDto.setControllerServiceId(serviceId);
+
+ final VerifyControllerServiceConfigRequestEntity verificationRequest = new VerifyControllerServiceConfigRequestEntity();
+ verificationRequest.setRequest(requestDto);
+
+ VerifyControllerServiceConfigRequestEntity results = nifiClient.getControllerServicesClient().submitConfigVerificationRequest(verificationRequest);
+ while (!results.getRequest().isComplete()) {
+ Thread.sleep(50L);
+ results = nifiClient.getControllerServicesClient().getConfigVerificationRequest(serviceId, results.getRequest().getRequestId());
+ }
+
+ nifiClient.getControllerServicesClient().deleteConfigVerificationRequest(serviceId, results.getRequest().getRequestId());
+
+ return results.getRequest().getResults();
+ }
+
+ public List<ConfigVerificationResultDTO> verifyReportingTaskConfig(final String taskId, final Map<String, String> properties)
+ throws InterruptedException, IOException,NiFiClientException {
+
+ final ReportingTaskDTO taskDto = new ReportingTaskDTO();
+ taskDto.setProperties(properties);
+ taskDto.setId(taskId);
+
+ final VerifyReportingTaskConfigRequestDTO requestDto = new VerifyReportingTaskConfigRequestDTO();
+ requestDto.setReportingTaskId(taskId);
+ requestDto.setReportingTask(taskDto);
+
+ final VerifyReportingTaskConfigRequestEntity verificationRequest = new VerifyReportingTaskConfigRequestEntity();
+ verificationRequest.setRequest(requestDto);
+
+ VerifyReportingTaskConfigRequestEntity results = nifiClient.getReportingTasksClient().submitConfigVerificationRequest(verificationRequest);
+ while (!results.getRequest().isComplete()) {
+ Thread.sleep(50L);
+ results = nifiClient.getReportingTasksClient().getConfigVerificationRequest(taskId, results.getRequest().getRequestId());
+ }
+
+ nifiClient.getReportingTasksClient().deleteConfigVerificationRequest(taskId, results.getRequest().getRequestId());
+
+ return results.getRequest().getResults();
+ }
+
+ public ReportingTaskEntity createReportingTask(final String simpleTypeName) throws NiFiClientException, IOException {
+ return createReportingTask(NiFiSystemIT.TEST_REPORTING_TASK_PACKAGE + "." + simpleTypeName, NiFiSystemIT.NIFI_GROUP_ID, NiFiSystemIT.TEST_EXTENSIONS_ARTIFACT_ID, nifiVersion);
+ }
+
+ public ReportingTaskEntity createReportingTask(final String type, final String bundleGroupId, final String artifactId, final String version)
+ throws NiFiClientException, IOException {
+ final ReportingTaskDTO dto = new ReportingTaskDTO();
+ dto.setType(type);
+
+ final BundleDTO bundle = new BundleDTO();
+ bundle.setGroup(bundleGroupId);
+ bundle.setArtifact(artifactId);
+ bundle.setVersion(version);
+ dto.setBundle(bundle);
+
+ final ReportingTaskEntity entity = new ReportingTaskEntity();
+ entity.setComponent(dto);
+ entity.setRevision(createNewRevision());
+
+ return nifiClient.getControllerClient().createReportingTask(entity);
+ }
+
+ public ReportingTaskEntity updateReportingTask(final ReportingTaskEntity currentEntity, final Map<String, String> properties) throws NiFiClientException, IOException {
+ final ReportingTaskDTO dto = new ReportingTaskDTO();
+ dto.setProperties(properties);
+ dto.setId(currentEntity.getId());
+
+ final ReportingTaskEntity entity = new ReportingTaskEntity();
+ entity.setId(currentEntity.getId());
+ entity.setComponent(dto);
+ entity.setRevision(currentEntity.getRevision());
+
+ return nifiClient.getReportingTasksClient().updateReportingTask(entity);
+ }
+
}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
index 5be74fb..23e3a6f 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/NiFiSystemIT.java
@@ -54,6 +54,7 @@
public static final String TEST_EXTENSIONS_ARTIFACT_ID = "nifi-system-test-extensions-nar";
public static final String TEST_PROCESSORS_PACKAGE = "org.apache.nifi.processors.tests.system";
public static final String TEST_CS_PACKAGE = "org.apache.nifi.cs.tests.system";
+ public static final String TEST_REPORTING_TASK_PACKAGE = "org.apache.nifi.reporting";
private static final Pattern FRAMEWORK_NAR_PATTERN = Pattern.compile("nifi-framework-nar-(.*?)\\.nar");
private static final File LIB_DIR = new File("target/nifi-lib-assembly/lib");
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableControllerServiceSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableControllerServiceSystemIT.java
new file mode 100644
index 0000000..ee54fed
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableControllerServiceSystemIT.java
@@ -0,0 +1,62 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.tests.system.SpawnedClusterNiFiInstanceFactory;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class ClusteredVerifiableControllerServiceSystemIT extends VerifiableControllerServiceSystemIT {
+ @Override
+ protected NiFiInstanceFactory getInstanceFactory() {
+ return new SpawnedClusterNiFiInstanceFactory(
+ "src/test/resources/conf/clustered/node1/bootstrap.conf",
+ "src/test/resources/conf/clustered/node2/bootstrap.conf");
+ }
+
+ @Test
+ public void testDifferentResultsFromDifferentNodes() throws InterruptedException, IOException, NiFiClientException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Failure Node Number", "2");
+ getClientUtil().updateControllerService(service, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.FAILED.name(), resultList.get(2).getOutcome());
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableProcessorSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableProcessorSystemIT.java
new file mode 100644
index 0000000..2f0e49d
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableProcessorSystemIT.java
@@ -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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.tests.system.SpawnedClusterNiFiInstanceFactory;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class ClusteredVerifiableProcessorSystemIT extends VerifiableProcessorSystemIT {
+
+ @Override
+ protected NiFiInstanceFactory getInstanceFactory() {
+ return new SpawnedClusterNiFiInstanceFactory(
+ "src/test/resources/conf/clustered/node1/bootstrap.conf",
+ "src/test/resources/conf/clustered/node2/bootstrap.conf");
+ }
+
+ @Test
+ public void testDifferentResultsFromDifferentNodes() throws InterruptedException, IOException, NiFiClientException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Failure Node Number", "2");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.FAILED.name(), resultList.get(2).getOutcome());
+ }
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableReportingTaskSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableReportingTaskSystemIT.java
new file mode 100644
index 0000000..128cd0c
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/ClusteredVerifiableReportingTaskSystemIT.java
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.tests.system.SpawnedClusterNiFiInstanceFactory;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+
+public class ClusteredVerifiableReportingTaskSystemIT extends VerifiableReportingTaskSystemIT {
+
+ @Override
+ protected NiFiInstanceFactory getInstanceFactory() {
+ return new SpawnedClusterNiFiInstanceFactory(
+ "src/test/resources/conf/clustered/node1/bootstrap.conf",
+ "src/test/resources/conf/clustered/node2/bootstrap.conf");
+ }
+
+ @Test
+ public void testDifferentResultsFromDifferentNodes() throws InterruptedException, IOException, NiFiClientException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Failure Node Number", "2");
+ getClientUtil().updateReportingTask(reportingTask, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.FAILED.name(), resultList.get(2).getOutcome());
+ }
+
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableControllerServiceSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableControllerServiceSystemIT.java
new file mode 100644
index 0000000..6109f38
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableControllerServiceSystemIT.java
@@ -0,0 +1,195 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ControllerServiceEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class VerifiableControllerServiceSystemIT extends NiFiSystemIT {
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateControllerService(service, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateControllerService(service, properties);
+
+ // Verify with properties that will give us failed verification
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "false");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), invalidProperties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be FAILED because the 'Successful Verification' property is set to false
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWithAttributes() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Verification Steps", "${steps}");
+ getClientUtil().updateControllerService(service, properties);
+
+ final Map<String, String> goodAttributes = Collections.singletonMap("steps", "5");
+ final Map<String, String> badAttributes = Collections.singletonMap("steps", "foo");
+
+ // Verify using attributes that should give us a successful verification
+ List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), properties, goodAttributes);
+ assertEquals("Got unexpected results: " + resultList, 7, resultList.size());
+
+ // Should have SUCCESS for validation, then 5 successes for the steps. Then 1 skipped for the Fail on Primary Node
+ for (int i=0; i < resultList.size() - 1; i++) {
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(i).getOutcome());
+ }
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(resultList.size() - 1).getOutcome());
+
+ // Verify using attributes that should give us a failed verification
+ resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), properties, badAttributes);
+ assertEquals(2, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be FAILED because the number of Verification Steps are invalid
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentInvalid() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateControllerService(service, invalidProperties);
+
+ final Map<String, String> validProperties = Collections.singletonMap("Successful Verification", "true");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), validProperties);
+ assertEquals(3, resultList.size());
+
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentInvalid() throws InterruptedException, IOException, NiFiClientException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateControllerService(service, invalidProperties);
+
+ final Map<String, String> otherInvalidProperties = Collections.singletonMap("Successful Verification", "bar");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), otherInvalidProperties);
+ assertEquals(1, resultList.size());
+
+ for (final ConfigVerificationResultDTO resultDto : resultList) {
+ assertEquals(Outcome.FAILED.name(), resultDto.getOutcome());
+ }
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentRunning() throws InterruptedException, IOException, NiFiClientException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateControllerService(service, properties);
+
+ getClientUtil().enableControllerService(service);
+
+ assertThrows(NiFiClientException.class, () -> {
+ getClientUtil().verifyControllerServiceConfig(service.getId(), properties);
+ });
+ }
+
+
+ @Test
+ public void testVerifyWhenExceptionThrown() throws InterruptedException, IOException, NiFiClientException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("EnsureControllerServiceConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Exception on Verification", "true");
+ getClientUtil().updateControllerService(service, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), properties);
+ assertEquals(2, resultList.size());
+
+ // Results should show that validation is successful but that there was a failure in performing verification
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ }
+
+ @Test
+ public void testValidProcessorWithoutVerifiableControllerServiceAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("StandardSleepService");
+
+ // Even though processor does not implement VerifiableProcessor, validation should still be run
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), Collections.emptyMap());
+ assertEquals(1, resultList.size());
+
+ // Even though GenerateFlowFile is not connected, it should be valid because connections are not considered when verifying the processor
+ assertEquals("Unexpected results: " + resultList, Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ }
+
+ @Test
+ public void testInvalidConfigForProcessorWithoutVerifiableControllerServiceAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ControllerServiceEntity service = getClientUtil().createControllerService("StandardSleepService");
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyControllerServiceConfig(service.getId(), Collections.singletonMap("Validate Sleep Time", "foo"));
+ assertEquals(1, resultList.size());
+
+ assertEquals("Unexpected results: " + resultList, Outcome.FAILED.name(), resultList.get(0).getOutcome());
+ }
+
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableProcessorSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableProcessorSystemIT.java
new file mode 100644
index 0000000..dbf1153
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableProcessorSystemIT.java
@@ -0,0 +1,226 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class VerifiableProcessorSystemIT extends NiFiSystemIT {
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ // Make processor valid
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ // Verify with properties that will give us failed verification
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "false");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), invalidProperties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be FAILED because the 'Successful Verification' property is set to false
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWithAttributes() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Verification Steps", "${steps}");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ final Map<String, String> goodAttributes = Collections.singletonMap("steps", "5");
+ final Map<String, String> badAttributes = Collections.singletonMap("steps", "foo");
+
+ // Verify using attributes that should give us a successful verification
+ List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), properties, goodAttributes);
+ assertEquals("Got unexpected results: " + resultList, 7, resultList.size());
+
+ // Should have SUCCESS for validation, then 5 successes for the steps. Then 1 skipped for the Fail on Primary Node
+ for (int i=0; i < resultList.size() - 1; i++) {
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(i).getOutcome());
+ }
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(resultList.size() - 1).getOutcome());
+
+ // Verify using attributes that should give us a failed verification
+ resultList = getClientUtil().verifyProcessorConfig(processor.getId(), properties, badAttributes);
+ assertEquals(2, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be FAILED because the number of Verification Steps are invalid
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentInvalid() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateProcessorProperties(processor, invalidProperties);
+
+ // Wait until the processor has become invalid
+ getClientUtil().waitForInvalidProcessor(processor.getId());
+
+ final Map<String, String> validProperties = Collections.singletonMap("Successful Verification", "true");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), validProperties);
+ assertEquals(3, resultList.size());
+
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentInvalid() throws InterruptedException, IOException, NiFiClientException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateProcessorProperties(processor, invalidProperties);
+
+ // Wait until the processor has become invalid
+ getClientUtil().waitForInvalidProcessor(processor.getId());
+
+ final Map<String, String> otherInvalidProperties = Collections.singletonMap("Successful Verification", "bar");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), otherInvalidProperties);
+ assertEquals(1, resultList.size());
+
+ for (final ConfigVerificationResultDTO resultDto : resultList) {
+ assertEquals(Outcome.FAILED.name(), resultDto.getOutcome());
+ }
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentRunning() throws InterruptedException, IOException, NiFiClientException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ getClientUtil().startProcessGroupComponents("root");
+ getClientUtil().waitForProcessorState(processor.getId(), "RUNNING");
+
+ assertThrows(NiFiClientException.class, () -> {
+ getClientUtil().verifyProcessorConfig(processor.getId(), properties);
+ });
+ }
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentDisabled() throws InterruptedException, IOException, NiFiClientException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+ getNifiClient().getProcessorClient().disableProcessor(processor);
+
+ final Map<String, String> validProperties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateProcessorProperties(processor, validProperties);
+
+ List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), validProperties);
+ assertEquals(3, resultList.size());
+
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+
+ final Map<String, String> failureProperties = Collections.singletonMap("Successful Verification", "false");
+ resultList = getClientUtil().verifyProcessorConfig(processor.getId(), failureProperties);
+ assertEquals(3, resultList.size());
+
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWhenExceptionThrown() throws InterruptedException, IOException, NiFiClientException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("EnsureProcessorConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Exception on Verification", "true");
+ getClientUtil().updateProcessorProperties(processor, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), properties);
+ assertEquals(2, resultList.size());
+
+ // Results should show that validation is successful but that there was a failure in performing verification
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ }
+
+ @Test
+ public void testValidProcessorWithoutVerifiableProcessorAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("GenerateFlowFile");
+
+ // Even though processor does not implement VerifiableProcessor, validation should still be run
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), Collections.emptyMap());
+ assertEquals(1, resultList.size());
+
+ // Even though GenerateFlowFile is not connected, it should be valid because connections are not considered when verifying the processor
+ assertEquals("Unexpected results: " + resultList, Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ }
+
+ @Test
+ public void testInvalidConfigForProcessorWithoutVerifiableProcessorAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ProcessorEntity processor = getClientUtil().createProcessor("GenerateFlowFile");
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyProcessorConfig(processor.getId(), Collections.singletonMap("File Size", "foo"));
+ assertEquals(1, resultList.size());
+
+ assertEquals("Unexpected results: " + resultList, Outcome.FAILED.name(), resultList.get(0).getOutcome());
+ }
+
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableReportingTaskSystemIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableReportingTaskSystemIT.java
new file mode 100644
index 0000000..8eaaa81
--- /dev/null
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/verification/VerifiableReportingTaskSystemIT.java
@@ -0,0 +1,174 @@
+/*
+ * 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.
+ */
+
+package org.apache.nifi.tests.system.verification;
+
+import org.apache.nifi.components.ConfigVerificationResult.Outcome;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.toolkit.cli.impl.client.nifi.NiFiClientException;
+import org.apache.nifi.web.api.dto.ConfigVerificationResultDTO;
+import org.apache.nifi.web.api.entity.ReportingTaskEntity;
+import org.apache.nifi.web.api.entity.ReportingTaskRunStatusEntity;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThrows;
+
+public class VerifiableReportingTaskSystemIT extends NiFiSystemIT {
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateReportingTask(reportingTask, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be verification results
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentValid() throws NiFiClientException, IOException, InterruptedException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateReportingTask(reportingTask, properties);
+
+ // Verify with properties that will give us failed verification
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "false");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), invalidProperties);
+ assertEquals(3, resultList.size());
+
+ // First verification result will be component validation.
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ // Second verification result will be FAILED because the 'Successful Verification' property is set to false
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ // Third verification result is for Fail On Primary Node
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentInvalid() throws NiFiClientException, IOException, InterruptedException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateReportingTask(reportingTask, invalidProperties);
+
+ final Map<String, String> validProperties = Collections.singletonMap("Successful Verification", "true");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), validProperties);
+ assertEquals(3, resultList.size());
+
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(1).getOutcome());
+ assertEquals(Outcome.SKIPPED.name(), resultList.get(2).getOutcome());
+ }
+
+ @Test
+ public void testVerifyWithInvalidConfigWhenComponentInvalid() throws InterruptedException, IOException, NiFiClientException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> invalidProperties = Collections.singletonMap("Successful Verification", "foo");
+ getClientUtil().updateReportingTask(reportingTask, invalidProperties);
+
+ final Map<String, String> otherInvalidProperties = Collections.singletonMap("Successful Verification", "bar");
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), otherInvalidProperties);
+ assertEquals(1, resultList.size());
+
+ for (final ConfigVerificationResultDTO resultDto : resultList) {
+ assertEquals(Outcome.FAILED.name(), resultDto.getOutcome());
+ }
+ }
+
+ @Test
+ public void testVerificationWithValidConfigWhenComponentRunning() throws IOException, NiFiClientException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> properties = Collections.singletonMap("Successful Verification", "true");
+ getClientUtil().updateReportingTask(reportingTask, properties);
+
+ // Start reporting task
+ final ReportingTaskRunStatusEntity runStatusEntity = new ReportingTaskRunStatusEntity();
+ runStatusEntity.setRevision(reportingTask.getRevision());
+ runStatusEntity.setState("RUNNING");
+ getNifiClient().getReportingTasksClient().activateReportingTask(reportingTask.getId(), runStatusEntity);
+
+ assertThrows(NiFiClientException.class, () -> getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties));
+ }
+
+
+ @Test
+ public void testVerifyWhenExceptionThrown() throws InterruptedException, IOException, NiFiClientException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("EnsureReportingTaskConfigurationCorrect");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Successful Verification", "true");
+ properties.put("Exception on Verification", "true");
+ getClientUtil().updateReportingTask(reportingTask, properties);
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties);
+ assertEquals(2, resultList.size());
+
+ // Results should show that validation is successful but that there was a failure in performing verification
+ assertEquals(Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ assertEquals(Outcome.FAILED.name(), resultList.get(1).getOutcome());
+ }
+
+ @Test
+ public void testValidProcessorWithoutVerifiableReportingTaskAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("WriteToFileReportingTask");
+
+ // Even though processor does not implement VerifiableProcessor, validation should still be run
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Filename", "./logs");
+ properties.put("Text", "Hello World");
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties);
+ assertEquals(1, resultList.size());
+
+ // Even though GenerateFlowFile is not connected, it should be valid because connections are not considered when verifying the processor
+ assertEquals("Unexpected results: " + resultList, Outcome.SUCCESSFUL.name(), resultList.get(0).getOutcome());
+ }
+
+ @Test
+ public void testInvalidConfigForProcessorWithoutVerifiableReportingTaskAnnotation() throws NiFiClientException, IOException, InterruptedException {
+ final ReportingTaskEntity reportingTask = getClientUtil().createReportingTask("WriteToFileReportingTask");
+
+ final Map<String, String> properties = new HashMap<>();
+ properties.put("Filename", "/foo-i-do-not-exist");
+ properties.put("Text", "Hello World");
+
+ final List<ConfigVerificationResultDTO> resultList = getClientUtil().verifyReportingTaskConfig(reportingTask.getId(), properties);
+ assertEquals(1, resultList.size());
+
+ assertEquals("Unexpected results: " + resultList, Outcome.FAILED.name(), resultList.get(0).getOutcome());
+ }
+
+}
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/bootstrap.conf b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/bootstrap.conf
index 18fd125..343e2e8 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/bootstrap.conf
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/bootstrap.conf
@@ -22,7 +22,11 @@
graceful.shutdown.seconds=20
# JVM memory settings
-java.arg.2= -Xms512m
+java.arg.2=-Xms512m
java.arg.3=-Xmx512m
java.arg.14=-Djava.awt.headless=true
+
+#java.arg.debug=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8002
+
+java.arg.nodeNum=-DnodeNumber=1
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/logback.xml b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/logback.xml
index 89ca35e..ecda00a 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/logback.xml
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node1/logback.xml
@@ -151,6 +151,9 @@
<logger name="org.apache.nifi.web.api.AccessResource" level="INFO" additivity="false">
<appender-ref ref="USER_FILE"/>
</logger>
+ <logger name="org.apache.nifi.web.api" level="DEBUG" additivity="false">
+ <appender-ref ref="USER_FILE"/>
+ </logger>
<!--
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/bootstrap.conf b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/bootstrap.conf
index 4b4f5c3..b4e3b82 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/bootstrap.conf
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/bootstrap.conf
@@ -22,7 +22,11 @@
graceful.shutdown.seconds=20
# JVM memory settings
-java.arg.2= -Xms512m
+java.arg.2=-Xms512m
java.arg.3=-Xmx512m
java.arg.14=-Djava.awt.headless=true
+
+#java.arg.debug=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8003
+
+java.arg.nodeNum=-DnodeNumber=2
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/logback.xml b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/logback.xml
index 05ed5d5..4de6225 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/logback.xml
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/clustered/node2/logback.xml
@@ -153,6 +153,9 @@
<logger name="org.apache.nifi.web.api.AccessResource" level="INFO" additivity="false">
<appender-ref ref="USER_FILE"/>
</logger>
+ <logger name="org.apache.nifi.web.api" level="DEBUG" additivity="false">
+ <appender-ref ref="USER_FILE"/>
+ </logger>
<!--
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/bootstrap.conf b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/bootstrap.conf
index 0af207a..da7940a 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/bootstrap.conf
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/resources/conf/default/bootstrap.conf
@@ -22,7 +22,9 @@
graceful.shutdown.seconds=20
# JVM memory settings
-java.arg.2= -Xms512m
+java.arg.2=-Xms512m
java.arg.3=-Xmx512m
java.arg.14=-Djava.awt.headless=true
+
+#java.arg.debug=-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=8002
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerServicesClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerServicesClient.java
index 71e6e71..1befb2a 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerServicesClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ControllerServicesClient.java
@@ -20,6 +20,7 @@
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
import org.apache.nifi.web.api.entity.UpdateControllerServiceReferenceRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyControllerServiceConfigRequestEntity;
import java.io.IOException;
@@ -41,4 +42,11 @@
ControllerServiceReferencingComponentsEntity getControllerServiceReferences(String id) throws NiFiClientException, IOException;
ControllerServiceReferencingComponentsEntity updateControllerServiceReferences(UpdateControllerServiceReferenceRequestEntity referencesEntity) throws NiFiClientException, IOException;
+
+ VerifyControllerServiceConfigRequestEntity submitConfigVerificationRequest(VerifyControllerServiceConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException;
+
+ VerifyControllerServiceConfigRequestEntity getConfigVerificationRequest(String serviceId, String verificationRequestId) throws NiFiClientException, IOException;
+
+ VerifyControllerServiceConfigRequestEntity deleteConfigVerificationRequest(String serviceId, String verificationRequestId) throws NiFiClientException, IOException;
+
}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ProcessorClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ProcessorClient.java
index ea89d06..634aad5 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ProcessorClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ProcessorClient.java
@@ -17,6 +17,7 @@
package org.apache.nifi.toolkit.cli.impl.client.nifi;
import org.apache.nifi.web.api.entity.ProcessorEntity;
+import org.apache.nifi.web.api.entity.VerifyProcessorConfigRequestEntity;
import java.io.IOException;
@@ -46,4 +47,11 @@
ProcessorEntity deleteProcessor(String processorId, String clientId, long version) throws NiFiClientException, IOException;
ProcessorEntity deleteProcessor(ProcessorEntity processorEntity) throws NiFiClientException, IOException;
+
+ VerifyProcessorConfigRequestEntity submitConfigVerificationRequest(VerifyProcessorConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException;
+
+ VerifyProcessorConfigRequestEntity getConfigVerificationRequest(String processorId, String verificationRequestId) throws NiFiClientException, IOException;
+
+ VerifyProcessorConfigRequestEntity deleteConfigVerificationRequest(String processorId, String verificationRequestId) throws NiFiClientException, IOException;
+
}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ReportingTasksClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ReportingTasksClient.java
index 2b08b56..57dc2aa 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ReportingTasksClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/ReportingTasksClient.java
@@ -19,6 +19,7 @@
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ReportingTaskRunStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyReportingTaskConfigRequestEntity;
import java.io.IOException;
@@ -29,6 +30,14 @@
ReportingTaskEntity getReportingTask(String id) throws NiFiClientException, IOException;
+ ReportingTaskEntity updateReportingTask(ReportingTaskEntity reportingTaskEntity) throws NiFiClientException, IOException;
+
ReportingTaskEntity activateReportingTask(String id, ReportingTaskRunStatusEntity runStatusEntity) throws NiFiClientException, IOException;
+ VerifyReportingTaskConfigRequestEntity submitConfigVerificationRequest(VerifyReportingTaskConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException;
+
+ VerifyReportingTaskConfigRequestEntity getConfigVerificationRequest(String taskId, String verificationRequestId) throws NiFiClientException, IOException;
+
+ VerifyReportingTaskConfigRequestEntity deleteConfigVerificationRequest(String taskId, String verificationRequestId) throws NiFiClientException, IOException;
+
}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerServicesClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerServicesClient.java
index ec5676e..2b164c1 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerServicesClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyControllerServicesClient.java
@@ -25,6 +25,7 @@
import org.apache.nifi.web.api.entity.ControllerServiceReferencingComponentsEntity;
import org.apache.nifi.web.api.entity.ControllerServiceRunStatusEntity;
import org.apache.nifi.web.api.entity.UpdateControllerServiceReferenceRequestEntity;
+import org.apache.nifi.web.api.entity.VerifyControllerServiceConfigRequestEntity;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@@ -175,4 +176,64 @@
});
}
+
+ @Override
+ public VerifyControllerServiceConfigRequestEntity submitConfigVerificationRequest(final VerifyControllerServiceConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException {
+ if (configRequestEntity == null) {
+ throw new IllegalArgumentException("Config Request Entity cannot be null");
+ }
+ if (configRequestEntity.getRequest() == null) {
+ throw new IllegalArgumentException("Config Request DTO cannot be null");
+ }
+ if (configRequestEntity.getRequest().getControllerServiceId() == null) {
+ throw new IllegalArgumentException("Controller Service ID cannot be null");
+ }
+ if (configRequestEntity.getRequest().getControllerService() == null) {
+ throw new IllegalArgumentException("Controller Service cannot be null");
+ }
+
+ return executeAction("Error submitting Config Verification Request", () -> {
+ final WebTarget target = controllerServicesTarget
+ .path("{id}/config/verification-requests")
+ .resolveTemplate("id", configRequestEntity.getRequest().getControllerServiceId());
+
+ return getRequestBuilder(target).post(
+ Entity.entity(configRequestEntity, MediaType.APPLICATION_JSON_TYPE),
+ VerifyControllerServiceConfigRequestEntity.class
+ );
+ });
+
+ }
+
+ @Override
+ public VerifyControllerServiceConfigRequestEntity getConfigVerificationRequest(final String serviceId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error retrieving Config Verification Request", () -> {
+ final WebTarget target = controllerServicesTarget
+ .path("{id}/config/verification-requests/{requestId}")
+ .resolveTemplate("id", serviceId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).get(VerifyControllerServiceConfigRequestEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyControllerServiceConfigRequestEntity deleteConfigVerificationRequest(final String serviceId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error deleting Config Verification Request", () -> {
+ final WebTarget target = controllerServicesTarget
+ .path("{id}/config/verification-requests/{requestId}")
+ .resolveTemplate("id", serviceId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).delete(VerifyControllerServiceConfigRequestEntity.class);
+ });
+ }
}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyProcessorClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyProcessorClient.java
index 467ede1..bf0442c 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyProcessorClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyProcessorClient.java
@@ -23,6 +23,7 @@
import org.apache.nifi.web.api.dto.RevisionDTO;
import org.apache.nifi.web.api.entity.ProcessorEntity;
import org.apache.nifi.web.api.entity.ProcessorRunStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyProcessorConfigRequestEntity;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@@ -178,4 +179,63 @@
);
});
}
+
+ @Override
+ public VerifyProcessorConfigRequestEntity submitConfigVerificationRequest(final VerifyProcessorConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException {
+ if (configRequestEntity == null) {
+ throw new IllegalArgumentException("Config Request Entity cannot be null");
+ }
+ if (configRequestEntity.getRequest() == null) {
+ throw new IllegalArgumentException("Config Request DTO cannot be null");
+ }
+ if (configRequestEntity.getRequest().getProcessorId() == null) {
+ throw new IllegalArgumentException("Processor ID cannot be null");
+ }
+ if (configRequestEntity.getRequest().getProcessorConfig() == null) {
+ throw new IllegalArgumentException("Processor Config cannot be null");
+ }
+
+ return executeAction("Error submitting Config Verification Request", () -> {
+ final WebTarget target = processorTarget
+ .path("/config/verification-requests")
+ .resolveTemplate("id", configRequestEntity.getRequest().getProcessorId());
+
+ return getRequestBuilder(target).post(
+ Entity.entity(configRequestEntity, MediaType.APPLICATION_JSON_TYPE),
+ VerifyProcessorConfigRequestEntity.class
+ );
+ });
+ }
+
+ @Override
+ public VerifyProcessorConfigRequestEntity getConfigVerificationRequest(final String processorId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error retrieving Config Verification Request", () -> {
+ final WebTarget target = processorTarget
+ .path("/config/verification-requests/{requestId}")
+ .resolveTemplate("id", processorId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).get(VerifyProcessorConfigRequestEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyProcessorConfigRequestEntity deleteConfigVerificationRequest(final String processorId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error deleting Config Verification Request", () -> {
+ final WebTarget target = processorTarget
+ .path("/config/verification-requests/{requestId}")
+ .resolveTemplate("id", processorId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).delete(VerifyProcessorConfigRequestEntity.class);
+ });
+ }
}
diff --git a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyReportingTasksClient.java b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyReportingTasksClient.java
index bf4f26e..4fc0582 100644
--- a/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyReportingTasksClient.java
+++ b/nifi-toolkit/nifi-toolkit-cli/src/main/java/org/apache/nifi/toolkit/cli/impl/client/nifi/impl/JerseyReportingTasksClient.java
@@ -23,6 +23,7 @@
import org.apache.nifi.toolkit.cli.impl.client.nifi.RequestConfig;
import org.apache.nifi.web.api.entity.ReportingTaskEntity;
import org.apache.nifi.web.api.entity.ReportingTaskRunStatusEntity;
+import org.apache.nifi.web.api.entity.VerifyReportingTaskConfigRequestEntity;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.WebTarget;
@@ -58,6 +59,23 @@
}
@Override
+ public ReportingTaskEntity updateReportingTask(final ReportingTaskEntity reportingTaskEntity) throws NiFiClientException, IOException {
+ if (reportingTaskEntity == null) {
+ throw new IllegalArgumentException("Reporting Task cannot be null");
+ }
+ if (reportingTaskEntity.getComponent() == null) {
+ throw new IllegalArgumentException("Component cannot be null");
+ }
+
+ return executeAction("Error updating Reporting Task", () -> {
+ final WebTarget target = reportingTasksTarget.path(reportingTaskEntity.getId());
+ return getRequestBuilder(target).put(
+ Entity.entity(reportingTaskEntity, MediaType.APPLICATION_JSON_TYPE),
+ ReportingTaskEntity.class);
+ });
+ }
+
+ @Override
public ReportingTaskEntity activateReportingTask(final String id,
final ReportingTaskRunStatusEntity runStatusEntity) throws NiFiClientException, IOException {
if (StringUtils.isBlank(id)) {
@@ -76,4 +94,63 @@
ReportingTaskEntity.class);
});
}
+
+ @Override
+ public VerifyReportingTaskConfigRequestEntity submitConfigVerificationRequest(final VerifyReportingTaskConfigRequestEntity configRequestEntity) throws NiFiClientException, IOException {
+ if (configRequestEntity == null) {
+ throw new IllegalArgumentException("Config Request Entity cannot be null");
+ }
+ if (configRequestEntity.getRequest() == null) {
+ throw new IllegalArgumentException("Config Request DTO cannot be null");
+ }
+ if (configRequestEntity.getRequest().getReportingTaskId() == null) {
+ throw new IllegalArgumentException("Reporting Task ID cannot be null");
+ }
+ if (configRequestEntity.getRequest().getReportingTask() == null) {
+ throw new IllegalArgumentException("Reporting Task cannot be null");
+ }
+
+ return executeAction("Error submitting Config Verification Request", () -> {
+ final WebTarget target = reportingTasksTarget
+ .path("{id}/config/verification-requests")
+ .resolveTemplate("id", configRequestEntity.getRequest().getReportingTaskId());
+
+ return getRequestBuilder(target).post(
+ Entity.entity(configRequestEntity, MediaType.APPLICATION_JSON_TYPE),
+ VerifyReportingTaskConfigRequestEntity.class
+ );
+ });
+ }
+
+ @Override
+ public VerifyReportingTaskConfigRequestEntity getConfigVerificationRequest(final String taskId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error retrieving Config Verification Request", () -> {
+ final WebTarget target = reportingTasksTarget
+ .path("{id}/config/verification-requests/{requestId}")
+ .resolveTemplate("id", taskId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).get(VerifyReportingTaskConfigRequestEntity.class);
+ });
+ }
+
+ @Override
+ public VerifyReportingTaskConfigRequestEntity deleteConfigVerificationRequest(final String taskId, final String verificationRequestId) throws NiFiClientException, IOException {
+ if (verificationRequestId == null) {
+ throw new IllegalArgumentException("Verification Request ID cannot be null");
+ }
+
+ return executeAction("Error deleting Config Verification Request", () -> {
+ final WebTarget target = reportingTasksTarget
+ .path("{id}/config/verification-requests/{requestId}")
+ .resolveTemplate("id", taskId)
+ .resolveTemplate("requestId", verificationRequestId);
+
+ return getRequestBuilder(target).delete(VerifyReportingTaskConfigRequestEntity.class);
+ });
+ }
}