You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@nifi.apache.org by ex...@apache.org on 2026/03/31 17:40:51 UTC
(nifi) branch main updated: NIFI-15772 Fixed inherited Parameter Context synchronization on versioned Process Group upgrades (#11082)
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 a08dcd708bd NIFI-15772 Fixed inherited Parameter Context synchronization on versioned Process Group upgrades (#11082)
a08dcd708bd is described below
commit a08dcd708bd7f9eb03a55cbf24cfd3f39b49f2b7
Author: Pierre Villard <pi...@gmail.com>
AuthorDate: Tue Mar 31 19:40:43 2026 +0200
NIFI-15772 Fixed inherited Parameter Context synchronization on versioned Process Group upgrades (#11082)
Signed-off-by: David Handermann <ex...@apache.org>
---
.../StandardVersionedComponentSynchronizer.java | 11 +++
...StandardVersionedComponentSynchronizerTest.java | 48 ++++++++++++
.../registry/ParameterContextPreservationIT.java | 86 ++++++++++++++++++++++
3 files changed, 145 insertions(+)
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
index ae823db265d..424b4782c2d 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizer.java
@@ -449,6 +449,17 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
// parameter contexts that inherit from one another and neither the inheriting nor inherited parameter context exists.
if (versionedParameterContexts != null) {
versionedParameterContexts.values().forEach(this::createParameterContextWithoutReferences);
+
+ // After ensuring all contexts exist, add any missing parameters to all existing contexts from the proposed definitions.
+ // This is necessary because createParameterContextWithoutReferences skips contexts that already exist, so new parameters
+ // added to inherited contexts (e.g., a parent P2 inherited by the group's bound context P1) would otherwise be missed.
+ final ComponentIdGenerator componentIdGenerator = context.getComponentIdGenerator();
+ for (final Map.Entry<String, VersionedParameterContext> entry : versionedParameterContexts.entrySet()) {
+ final ParameterContext existingContext = getParameterContextByName(entry.getKey());
+ if (existingContext != null) {
+ addMissingConfiguration(entry.getValue(), existingContext, versionedParameterContexts, parameterProviderReferences, componentIdGenerator);
+ }
+ }
}
updateParameterContext(group, proposed, versionedParameterContexts, parameterProviderReferences, context.getComponentIdGenerator());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizerTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizerTest.java
index fe1ae5b4f41..1dd5bd17f7f 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizerTest.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/test/java/org/apache/nifi/flow/synchronization/StandardVersionedComponentSynchronizerTest.java
@@ -1305,6 +1305,54 @@ public class StandardVersionedComponentSynchronizerTest {
verify(processGroup, times(1)).setParameterContext(any(ParameterContext.class));
}
+ @Test
+ public void testNewParameterInInheritedContextAddedDuringSync() throws FlowSynchronizationException, InterruptedException, TimeoutException {
+ // Create P2 with paramA
+ final VersionedParameterContext versionedP2 = createVersionedParameterContext("P2", Map.of("paramA", "valueA"), Collections.emptySet());
+ synchronizer.synchronize(null, versionedP2, synchronizationOptions);
+
+ final ParameterContext p2 = parameterContextManager.getParameterContextNameMapping().get("P2");
+
+ // Create P1 with its own parameter, then set up inheritance from P2
+ final VersionedParameterContext versionedP1 = createVersionedParameterContext("P1", Map.of("paramOwn", "ownValue"), Collections.emptySet());
+ synchronizer.synchronize(null, versionedP1, synchronizationOptions);
+
+ final ParameterContext p1 = parameterContextManager.getParameterContextNameMapping().get("P1");
+ p1.setInheritedParameterContexts(List.of(p2));
+ assertEquals(1, p1.getInheritedParameterContexts().size());
+ assertEquals("P2", p1.getInheritedParameterContexts().get(0).getName());
+
+ // Prepare the process group mock bound to P1
+ final ProcessGroup processGroup = createMockProcessGroup();
+ when(processGroup.getParameterContext()).thenReturn(p1);
+
+ // Build proposed flow for version 2: P2 now has paramA and paramX
+ final VersionedParameterContext proposedP2 = createVersionedParameterContext("P2", Map.of("paramA", "valueA", "paramX", "valueX"), Collections.emptySet());
+ proposedP2.setInheritedParameterContexts(Collections.emptyList());
+
+ final VersionedParameterContext proposedP1 = createVersionedParameterContext("P1", Map.of("paramOwn", "ownValue"), Collections.emptySet());
+ proposedP1.setInheritedParameterContexts(List.of("P2"));
+
+ final Map<String, VersionedParameterContext> parameterContextMap = new HashMap<>();
+ parameterContextMap.put("P1", proposedP1);
+ parameterContextMap.put("P2", proposedP2);
+
+ final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
+ rootGroup.setIdentifier(processGroup.getIdentifier());
+ rootGroup.setParameterContextName("P1");
+
+ final VersionedExternalFlow externalFlow = new VersionedExternalFlow();
+ externalFlow.setFlowContents(rootGroup);
+ externalFlow.setParameterContexts(parameterContextMap);
+
+ synchronizer.synchronize(processGroup, externalFlow, synchronizationOptions);
+
+ // Verify P2 now contains paramX
+ assertTrue(p2.getParameter("paramX").isPresent(), "paramX should have been added to inherited context P2");
+ assertEquals("valueX", p2.getParameter("paramX").get().getValue());
+ assertTrue(p2.getParameter("paramA").isPresent(), "paramA should still exist on P2");
+ }
+
private VersionedParameterContext createVersionedParameterContext(final String name, final Map<String, String> parameters, final Set<String> sensitiveParamNames) {
final Set<VersionedParameter> versionedParameters = new HashSet<>();
for (final Map.Entry<String, String> entry : parameters.entrySet()) {
diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
index d1937b06271..592bd29958b 100644
--- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
+++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/registry/ParameterContextPreservationIT.java
@@ -37,11 +37,14 @@ import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertTrue;
/**
* System tests to verify that parameter context bindings and inheritance chains are preserved
@@ -148,6 +151,89 @@ class ParameterContextPreservationIT extends NiFiSystemIT {
"NEW Process Group B should use the same parameter context as its parent A2, not be bound to the first parameter context P");
}
+ /**
+ * Verifies that new parameters added to an inherited parameter context are properly synchronized
+ * when upgrading a versioned process group.
+ *
+ * Scenario: P1 inherits from P2. PG is bound to P1. In version 2, a new parameter is added to P2.
+ * After importing version 1 fresh and upgrading to version 2, P2 should contain the new parameter.
+ */
+ @Test
+ void testNewParameterInInheritedContextSynchronizedDuringUpgrade() throws NiFiClientException, IOException, InterruptedException {
+ final FlowRegistryClientEntity clientEntity = registerClient();
+ final NiFiClientUtil util = getClientUtil();
+
+ // Create P2 with parameter "paramA"
+ final ParameterContextEntity paramContextP2 = util.createParameterContext("P2", Map.of("paramA", "valueA"));
+
+ // Create P1 inheriting from P2 with its own parameter
+ final ParameterContextEntity paramContextP1 = util.createParameterContext("P1", Map.of("paramOwn", "ownValue"), List.of(paramContextP2.getId()), null);
+
+ // Create PG bound to P1 with a processor that references paramA (inherited from P2)
+ final ProcessGroupEntity groupPG = util.createProcessGroup("PG", "root");
+ util.setParameterContext(groupPG.getId(), paramContextP1);
+
+ final ProcessorEntity processor = util.createProcessor(PROCESSOR_TYPE, groupPG.getId());
+ util.updateProcessorProperties(processor, Collections.singletonMap(PROCESSOR_PROPERTY_TEXT, "#{paramA}"));
+ util.setAutoTerminatedRelationships(processor, RELATIONSHIP_SUCCESS);
+
+ // Save as version 1 (P2 has only paramA)
+ final VersionControlInformationEntity vciV1 = util.startVersionControl(groupPG, clientEntity, TEST_FLOWS_BUCKET, "InheritedParamFlow");
+ final String flowId = vciV1.getVersionControlInformation().getFlowId();
+
+ // Add new parameter "paramX" to P2 and save as version 2
+ final ParameterContextEntity currentP2 = getNifiClient().getParamContextClient().getParamContext(paramContextP2.getId(), false);
+ final ParameterContextUpdateRequestEntity updateRequest = util.updateParameterContext(currentP2, Map.of("paramA", "valueA", "paramX", "valueX"));
+ util.waitForParameterContextRequestToComplete(paramContextP2.getId(), updateRequest.getRequest().getRequestId());
+
+ final ProcessGroupEntity refreshedPG = getNifiClient().getProcessGroupClient().getProcessGroup(groupPG.getId());
+ util.saveFlowVersion(refreshedPG, clientEntity, vciV1);
+
+ // Clean up the original flow and parameter contexts
+ final ProcessGroupEntity pgForStopVc = getNifiClient().getProcessGroupClient().getProcessGroup(groupPG.getId());
+ getNifiClient().getVersionsClient().stopVersionControl(pgForStopVc);
+ util.deleteAll(groupPG.getId());
+ final ProcessGroupEntity pgToDelete = getNifiClient().getProcessGroupClient().getProcessGroup(groupPG.getId());
+ getNifiClient().getProcessGroupClient().deleteProcessGroup(pgToDelete);
+
+ final ParameterContextEntity p1ToDelete = getNifiClient().getParamContextClient().getParamContext(paramContextP1.getId(), false);
+ getNifiClient().getParamContextClient().deleteParamContext(paramContextP1.getId(), String.valueOf(p1ToDelete.getRevision().getVersion()));
+
+ final ParameterContextEntity p2ToDelete = getNifiClient().getParamContextClient().getParamContext(paramContextP2.getId(), false);
+ getNifiClient().getParamContextClient().deleteParamContext(paramContextP2.getId(), String.valueOf(p2ToDelete.getRevision().getVersion()));
+
+ // Import version 1 fresh -- creates new P1 and P2 with only paramA on P2
+ final ProcessGroupEntity importedPG = importFlowWithReplaceParameterContext(clientEntity.getId(), flowId, VERSION_1);
+
+ // Locate the imported P2 by finding P1's inherited context
+ final ProcessGroupEntity fetchedPG = getNifiClient().getProcessGroupClient().getProcessGroup(importedPG.getId());
+ final String importedP1Id = fetchedPG.getComponent().getParameterContext().getId();
+ final ParameterContextEntity importedP1 = getNifiClient().getParamContextClient().getParamContext(importedP1Id, false);
+ assertEquals(1, importedP1.getComponent().getInheritedParameterContexts().size());
+ final String importedP2Id = importedP1.getComponent().getInheritedParameterContexts().get(0).getId();
+
+ // Verify P2 has only paramA after importing version 1
+ final ParameterContextEntity importedP2 = getNifiClient().getParamContextClient().getParamContext(importedP2Id, false);
+ final Set<String> p2NamesAfterImport = getParameterNames(importedP2);
+ assertTrue(p2NamesAfterImport.contains("paramA"), "paramA should exist on P2 after importing version 1");
+ assertFalse(p2NamesAfterImport.contains("paramX"), "paramX should not exist on P2 after importing version 1");
+
+ // Upgrade from version 1 to version 2
+ util.changeFlowVersion(importedPG.getId(), VERSION_2);
+
+ // Verify P2 now contains paramX after the upgrade
+ final ParameterContextEntity p2AfterUpgrade = getNifiClient().getParamContextClient().getParamContext(importedP2Id, false);
+ final Set<String> p2NamesAfterUpgrade = getParameterNames(p2AfterUpgrade);
+ assertTrue(p2NamesAfterUpgrade.contains("paramA"), "paramA should still exist on P2 after upgrading to version 2");
+ assertTrue(p2NamesAfterUpgrade.contains("paramX"), "paramX should exist on P2 after upgrading to version 2");
+ }
+
+ private Set<String> getParameterNames(final ParameterContextEntity context) {
+ return context.getComponent().getParameters().stream()
+ .map(entity -> entity.getParameter().getName())
+ .collect(Collectors.toSet());
+ }
+
private ProcessGroupEntity importFlowWithReplaceParameterContext(
final String registryClientId,
final String flowId,