This is an automated email from the ASF dual-hosted git repository.
exceptionfactory pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/nifi.git
The following commit(s) were added to refs/heads/main by this push:
new 8a5aebefab8 NIFI-16333 Invalidated Secrets Manager caching when
starting a Connector (#11663)
8a5aebefab8 is described below
commit 8a5aebefab8e7f834aa5cfead4f4ce83b1ed114b
Author: Pierre Villard <[email protected]>
AuthorDate: Mon Sep 14 17:13:12 2026 +0200
NIFI-16333 Invalidated Secrets Manager caching when starting a Connector
(#11663)
Signed-off-by: David Handermann <[email protected]>
---
.../connector/StandardConnectorNode.java | 4 +-
.../connector/TestStandardConnectorNode.java | 81 ++++++++++++++++++++--
2 files changed, 78 insertions(+), 7 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
index d5d836dfc64..5daa9ae0fdd 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorNode.java
@@ -790,7 +790,6 @@ public class StandardConnectorNode implements
ConnectorNode, GroupedComponent {
try {
stateTransition.setDesiredState(ConnectorState.RUNNING);
activeFlowContext.getConfigurationContext().resolvePropertyValues();
-
verifyCanStart();
boolean startScheduled = false;
@@ -1061,6 +1060,9 @@ public class StandardConnectorNode implements
ConnectorNode, GroupedComponent {
}
try (final NarCloseable ignored =
NarCloseable.withComponentNarLoader(extensionManager,
connectorDetails.getConnector().getClass(), getIdentifier())) {
+ initializationContext.getSecretsManager().invalidateCache();
+
activeFlowContext.getConfigurationContext().resolvePropertyValues();
+ verifyCanStart();
connectorDetails.getConnector().start(activeFlowContext);
} catch (final Exception e) {
if (getCurrentState() == ConnectorState.STOPPING) {
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
index 05682b556a6..e68ca70c4a8 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorNode.java
@@ -54,6 +54,7 @@ import org.mockito.MockitoAnnotations;
import java.io.IOException;
import java.time.Duration;
+import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
@@ -84,12 +85,17 @@ import static org.mockito.ArgumentMatchers.anySet;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
public class TestStandardConnectorNode {
private static final long STOP_NOT_EXPECTED_MILLIS = 250L;
+ private static final String REQUIRED_STEP = "requiredStep";
+ private static final String REQUIRED_SECRET = "RequiredSecret";
+ private static final String FIRST_SECRET_VALUE = "first";
+ private static final String SECOND_SECRET_VALUE = "second";
private FlowEngine scheduler;
@@ -183,6 +189,52 @@ public class TestStandardConnectorNode {
assertFalse(stopFuture.isCancelled());
}
+ @Test
+ public void testRestartRefreshesSecretBeforeStartingConnector() throws
Exception {
+ final SecretReference secretReference = new
SecretReference("provider-id", "Provider", "password",
"Provider.group.password");
+ final Secret firstSecret = mock(Secret.class);
+ when(firstSecret.getValue()).thenReturn(FIRST_SECRET_VALUE);
+ final Secret secondSecret = mock(Secret.class);
+ when(secondSecret.getValue()).thenReturn(SECOND_SECRET_VALUE);
+ final AtomicReference<Secret> currentSecret = new
AtomicReference<>(firstSecret);
+
+ when(secretsManager.getSecrets(anySet())).thenAnswer(invocation ->
Map.of(secretReference, currentSecret.get()));
+
+ final StartRecordingSecretConnector connector = new
StartRecordingSecretConnector();
+ final StandardConnectorNode connectorNode =
createConnectorNode(connector, secretsManager);
+ seedActiveConfiguration(connectorNode, REQUIRED_STEP,
Map.of(REQUIRED_SECRET, secretReference));
+
+ connectorNode.start(scheduler).get(5, TimeUnit.SECONDS);
+ connectorNode.stop(scheduler).get(5, TimeUnit.SECONDS);
+ currentSecret.set(secondSecret);
+ connectorNode.start(scheduler).get(5, TimeUnit.SECONDS);
+ connectorNode.start(scheduler).get(5, TimeUnit.SECONDS);
+
+ assertEquals(List.of(FIRST_SECRET_VALUE, SECOND_SECRET_VALUE),
connector.getStartedSecrets());
+ verify(secretsManager, times(2)).invalidateCache();
+ }
+
+ @Test
+ public void testStartResolvesPropertyBeforeValidation() throws Exception {
+ final SecretReference secretReference = new
SecretReference("provider-id", "Provider", "password",
"Provider.group.password");
+ final AtomicReference<Secret> currentSecret = new AtomicReference<>();
+ when(secretsManager.getSecrets(anySet())).thenAnswer(invocation -> {
+ final Secret secret = currentSecret.get();
+ return secret == null ? Map.of() : Map.of(secretReference, secret);
+ });
+
+ final Secret secret = mock(Secret.class);
+ when(secret.getValue()).thenReturn(FIRST_SECRET_VALUE);
+
+ final StandardConnectorNode connectorNode = createConnectorNode(new
StartRecordingSecretConnector(), secretsManager);
+ seedActiveConfiguration(connectorNode, REQUIRED_STEP,
Map.of(REQUIRED_SECRET, secretReference));
+ currentSecret.set(secret);
+
+ connectorNode.start(scheduler).get(5, TimeUnit.SECONDS);
+
+ assertEquals(ConnectorState.RUNNING, connectorNode.getCurrentState());
+ }
+
@Test
public void testStartFutureCompletedOnlyWhenRunning() throws Exception {
final StandardConnectorNode connectorNode = createConnectorNode();
@@ -923,9 +975,9 @@ public class TestStandardConnectorNode {
// Without the empty-stub filter this would surface as "[null] could
not be found"; with it, the connector's
// required-property validation should produce "<name> is required"
instead.
final Map<String, ConnectorValueReference> propertyValues = new
HashMap<>();
- propertyValues.put("RequiredSecret", new SecretReference(null, "My
Provider", null, null));
+ propertyValues.put(REQUIRED_SECRET, new SecretReference(null, "My
Provider", null, null));
- final List<ConfigVerificationResult> results =
connectorNode.verifyConfigurationStep("requiredStep", new
StepConfiguration(propertyValues));
+ final List<ConfigVerificationResult> results =
connectorNode.verifyConfigurationStep(REQUIRED_STEP, new
StepConfiguration(propertyValues));
final List<ConfigVerificationResult> failures = results.stream()
.filter(result -> result.getOutcome() ==
ConfigVerificationResult.Outcome.FAILED)
@@ -952,8 +1004,8 @@ public class TestStandardConnectorNode {
connectorNode.transitionStateForUpdating();
connectorNode.prepareForUpdate();
final Map<String, ConnectorValueReference> propertyValues = new
HashMap<>();
- propertyValues.put("RequiredSecret", new SecretReference(null, "My
Provider", null, null));
- connectorNode.setConfiguration("requiredStep", new
StepConfiguration(propertyValues));
+ propertyValues.put(REQUIRED_SECRET, new SecretReference(null, "My
Provider", null, null));
+ connectorNode.setConfiguration(REQUIRED_STEP, new
StepConfiguration(propertyValues));
connectorNode.applyUpdate();
final ValidationState state = connectorNode.performValidation();
@@ -1949,7 +2001,7 @@ public class TestStandardConnectorNode {
@Override
public List<ConfigurationStep> getConfigurationSteps() {
final ConnectorPropertyDescriptor secretProperty = new
ConnectorPropertyDescriptor.Builder()
- .name("RequiredSecret")
+ .name(REQUIRED_SECRET)
.description("A required secret with no dependencies")
.type(PropertyType.SECRET)
.required(true)
@@ -1962,7 +2014,7 @@ public class TestStandardConnectorNode {
.build();
final ConfigurationStep step = new ConfigurationStep.Builder()
- .name("requiredStep")
+ .name(REQUIRED_STEP)
.propertyGroups(List.of(propertyGroup))
.build();
@@ -1983,6 +2035,23 @@ public class TestStandardConnectorNode {
}
}
+ private static class StartRecordingSecretConnector extends
RequiredSecretConnector {
+ private final List<String> startedSecrets = new ArrayList<>();
+
+ @Override
+ public void start(final FlowContext activeContext) {
+
startedSecrets.add(activeContext.getConfigurationContext().getProperty(REQUIRED_STEP,
REQUIRED_SECRET).getValue());
+ }
+
+ @Override
+ public void stop(final FlowContext activeContext) {
+ }
+
+ private List<String> getStartedSecrets() {
+ return startedSecrets;
+ }
+ }
+
private static class DefaultValueConnector extends AbstractConnector {
@Override
public VersionedExternalFlow getInitialFlow() {