From 3c63b064aa4b2fd93bb2bc065203a2fbc6d16d40 Mon Sep 17 00:00:00 2001 From: Eric Secules Date: Fri, 4 Sep 2026 17:03:35 -0700 Subject: [PATCH] NIFI-16276 fix flow synchronizer so that it doesn't clear parameter context when updating to new version of flow. --- ...tandardVersionedComponentSynchronizer.java | 6 +-- ...ardVersionedComponentSynchronizerTest.java | 33 +++++++++++++++++ .../nifi/tests/system/NiFiClientUtil.java | 5 ++- .../ParameterContextPreservationIT.java | 37 +++++++++++++++++++ 4 files changed, 77 insertions(+), 4 deletions(-) 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 0b233d489bbd..1a84ddec7ee0 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 @@ -2193,9 +2193,9 @@ private void updateParameterContext(final ProcessGroup group, final VersionedPro // Update the Parameter Context final ParameterContext currentParamContext = group.getParameterContext(); final String proposedParameterContextName = proposed.getParameterContextName(); - if (proposedParameterContextName == null && currentParamContext != null) { - group.setParameterContext(null); - } else if (proposedParameterContextName != null) { + // A group's own versioned definition may never carry a Parameter Context if it's meant to be bound locally by + // whatever parent embeds it, so a null name here must not clear an existing binding. + if (proposedParameterContextName != null) { final VersionedParameterContext versionedParameterContext = versionedParameterContexts.get(proposedParameterContextName); if (versionedParameterContext != null) { createMissingParameterProvider(versionedParameterContext, versionedParameterContext.getParameterProvider(), parameterProviderReferences, componentIdGenerator); 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 9c0953c1f455..68986823f96b 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 @@ -1792,6 +1792,39 @@ public void testUpdateParameterContextWhenContextDoesExist() { verify(processGroup, times(1)).setParameterContext(any(ParameterContext.class)); } + @Test + public void testLocallyAssignedParameterContextPreservedWhenChildGroupUpdatedFromOwnStandaloneVersion() { + // Updating a child group directly to its own standalone version, which never declared a Parameter Context, must not clear a Parameter Context locally assigned to it by a parent. + final ProcessGroup processGroup = mock(ProcessGroup.class); + when(processGroup.getIdentifier()).thenReturn("pg1"); + when(processGroup.getPosition()).thenReturn(new org.apache.nifi.connectable.Position(0, 0)); + when(processGroup.getFlowFileConcurrency()).thenReturn(FlowFileConcurrency.UNBOUNDED); + when(processGroup.getFlowFileOutboundPolicy()).thenReturn(FlowFileOutboundPolicy.BATCH_OUTPUT); + when(processGroup.getExecutionEngine()).thenReturn(ExecutionEngine.STANDARD); + + // A Parameter Context already bound to the child group, simulating a parent's local assignment. + final ParameterContext existingParameterContext = new StandardParameterContext.Builder() + .id("existing-pc") + .name("HTTP ReceiverB") + .parameterReferenceManager(parameterReferenceManager) + .build(); + when(processGroup.getParameterContext()).thenReturn(existingParameterContext); + + // The child group's own standalone versioned flow definition never had a Parameter Context of its own. + final VersionedProcessGroup rootGroup = new VersionedProcessGroup(); + rootGroup.setIdentifier("pg1"); + rootGroup.setParameterContextName(null); + + final VersionedExternalFlow externalFlow = new VersionedExternalFlow(); + externalFlow.setFlowContents(rootGroup); + externalFlow.setParameterContexts(Collections.emptyMap()); + + synchronizer.synchronize(processGroup, externalFlow, synchronizationOptions); + + // The locally-assigned Parameter Context must survive the update. + verify(processGroup, never()).setParameterContext(null); + } + @Test public void testNewParameterInInheritedContextAddedDuringSync() throws FlowSynchronizationException, InterruptedException, TimeoutException { // Create P2 with paramA 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 faef5b9a5245..00776437c144 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 @@ -1154,7 +1154,10 @@ private ParameterContextReferenceEntity createReferenceEntity(final String id, f public ProcessGroupEntity setParameterContext(final String groupId, final ParameterContextEntity parameterContext) throws NiFiClientException, IOException { final ProcessGroupEntity processGroup = nifiClient.getProcessGroupClient().getProcessGroup(groupId); - processGroup.getComponent().setParameterContext(createReferenceEntity(parameterContext.getId())); + ProcessGroupDTO component = new ProcessGroupDTO(); + component.setId(processGroup.getId()); + component.setParameterContext(createReferenceEntity(parameterContext.getId())); + processGroup.setComponent(component); return nifiClient.getProcessGroupClient().updateProcessGroup(processGroup); } 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 ee66dc130ad0..6f7653badbb7 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 @@ -601,4 +601,41 @@ void testApplyParameterContextRecursivelyAcrossVersionedDescendant() throws NiFi assertNotNull(childAfterUpdate.getComponent().getVersionControlInformation(), "Versioned child Process Group should remain under version control after a recursive Parameter Context change"); } + + /** + * Verifies that a Parameter Context locally assigned to a shared, independently version-controlled child Process Group survives a version upgrade of that child. + */ + @Test + void testLocallyAssignedParameterContextPreservedWhenVersionedChildUpgraded() throws NiFiClientException, IOException, InterruptedException { + final FlowRegistryClientEntity clientEntity = registerClient(); + final NiFiClientUtil util = getClientUtil(); + + final ParameterContextEntity locallyAssignedContext = util.createParameterContext("locally-assigned-context", Map.of(PARAMETER_NAME, PARAMETER_VALUE)); + + // Shared child Process Group, version-controlled on its own, deliberately without a Parameter Context of its own. + final ProcessGroupEntity versionedGroup = util.createProcessGroup("shared-utility", "root"); + //util.setParameterContext(versionedGroup.getId(), emptyContext); + final VersionControlInformationEntity childVciV1 = util.startVersionControl(versionedGroup, clientEntity, TEST_FLOWS_BUCKET, "SharedUtility"); + + final ProcessGroupEntity instance = util.importFlowFromRegistry("root", childVciV1.getVersionControlInformation()); + + // Assign a Parameter Context to the child locally, outside its own versioned definition. + util.setParameterContext(instance.getId(), locallyAssignedContext); + + // Modify the child's own definition and commit version 2 so there's a newer version to upgrade to. + final ProcessorEntity processor = util.createProcessor(PROCESSOR_TYPE, versionedGroup.getId()); + util.setAutoTerminatedRelationships(processor, RELATIONSHIP_SUCCESS); + final ProcessGroupEntity refreshedChildGroup = getNifiClient().getProcessGroupClient().getProcessGroup(versionedGroup.getId()); + util.saveFlowVersion(refreshedChildGroup, clientEntity, childVciV1); + + // Update the child directly to version 2, fetching its own standalone snapshot which still declares no Parameter Context. + util.changeFlowVersion(instance.getId(), VERSION_2); + + final ProcessGroupEntity instanceAfterUpgrade = getNifiClient().getProcessGroupClient().getProcessGroup(instance.getId()); + assertEquals(VERSION_2, instanceAfterUpgrade.getComponent().getVersionControlInformation().getVersion()); + assertNotNull(instanceAfterUpgrade.getComponent().getParameterContext(), + "Parameter Context locally assigned to the child should survive the version upgrade"); + assertEquals(locallyAssignedContext.getId(), instanceAfterUpgrade.getComponent().getParameterContext().getId(), + "Child Process Group should remain bound to the locally-assigned Parameter Context after upgrading to a new version"); + } }