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/04/09 15:06:24 UTC
(nifi) branch main updated: NIFI-15785 Update Parameter and Parameter Context descriptions on versioned flow upgrades (#11096)
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 87e65dbbe81 NIFI-15785 Update Parameter and Parameter Context descriptions on versioned flow upgrades (#11096)
87e65dbbe81 is described below
commit 87e65dbbe81b1ba6a1921bd542dbd5db5f56ab19
Author: Pierre Villard <pi...@gmail.com>
AuthorDate: Thu Apr 9 17:06:17 2026 +0200
NIFI-15785 Update Parameter and Parameter Context descriptions on versioned flow upgrades (#11096)
Signed-off-by: David Handermann <ex...@apache.org>
---
.../StandardVersionedComponentSynchronizer.java | 13 +-
...StandardVersionedComponentSynchronizerTest.java | 315 ++++++++++++++-------
.../serialization/VersionedFlowSynchronizer.java | 6 +-
.../registry/ParameterContextPreservationIT.java | 94 ++++++
4 files changed, 322 insertions(+), 106 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 092d2f7e7bb..3334380aaa7 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
@@ -2375,7 +2375,14 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
for (final VersionedParameter versionedParameter : versionedParameterContext.getParameters()) {
final Optional<Parameter> parameterOption = currentParameterContext.getParameter(versionedParameter.getName());
if (parameterOption.isPresent()) {
- // Skip this parameter, since it is already defined. We only want to add missing parameters
+ final Parameter existingParameter = parameterOption.get();
+ if (!Objects.equals(existingParameter.getDescriptor().getDescription(), versionedParameter.getDescription())) {
+ final Parameter updatedParameter = new Parameter.Builder()
+ .fromParameter(existingParameter)
+ .description(versionedParameter.getDescription())
+ .build();
+ parameters.put(versionedParameter.getName(), updatedParameter);
+ }
continue;
}
@@ -2385,6 +2392,10 @@ public class StandardVersionedComponentSynchronizer implements VersionedComponen
currentParameterContext.setParameters(parameters);
+ if (!Objects.equals(currentParameterContext.getDescription(), versionedParameterContext.getDescription())) {
+ currentParameterContext.setDescription(versionedParameterContext.getDescription());
+ }
+
// If the current parameter context doesn't have any inherited param contexts but the versioned one does,
// add the versioned ones.
if (currentParameterContext.getInheritedParameterContexts().isEmpty()
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 1dd5bd17f7f..6793d09216f 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
@@ -144,6 +144,44 @@ public class StandardVersionedComponentSynchronizerTest {
private static final String SENSITIVE_PROPERTY_NAME = "Access Token";
+ private static final String PARAM_ABC = "abc";
+
+ private static final String PARAM_SECRET = "secret";
+
+ private static final String VALUE_XYZ = "xyz";
+
+ private static final String VALUE_YES = "yes";
+
+ private static final String VALUE_123 = "123";
+
+ private static final String VALUE_MAYBE = "maybe";
+
+ private static final String CONTEXT_NAME_1 = "Context 1";
+
+ private static final String CONTEXT_NAME_2 = "Context 2";
+
+ private static final String CONTEXT_NAME_PARAMS = "Params";
+
+ private static final Set<String> SENSITIVE_PARAM_NAMES = Set.of(PARAM_SECRET);
+
+ private static final Map<String, String> SINGLE_PARAMETER = Map.of(PARAM_ABC, VALUE_XYZ);
+
+ private static final Map<String, String> INITIAL_PARAMETERS = Map.of(PARAM_ABC, VALUE_XYZ, PARAM_SECRET, VALUE_YES);
+
+ private static final Map<String, String> UPDATED_PARAMETERS = Map.of(PARAM_ABC, VALUE_123, PARAM_SECRET, VALUE_MAYBE);
+
+ private static final String ORIGINAL_PARAMETER_DESCRIPTION = "Original description";
+
+ private static final String UPDATED_PARAMETER_DESCRIPTION = "Updated description";
+
+ private static final Map<String, String> ORIGINAL_DESCRIPTION_MAP = Map.of(PARAM_ABC, ORIGINAL_PARAMETER_DESCRIPTION);
+
+ private static final Map<String, String> UPDATED_DESCRIPTION_MAP = Map.of(PARAM_ABC, UPDATED_PARAMETER_DESCRIPTION);
+
+ private static final String ORIGINAL_CONTEXT_DESCRIPTION = "Generated for unit test";
+
+ private static final String UPDATED_CONTEXT_DESCRIPTION = "Updated context description";
+
private ProcessorNode processorA;
private ProcessorNode processorB;
private Connection connectionAB;
@@ -1010,10 +1048,7 @@ public class StandardVersionedComponentSynchronizerTest {
@Test
public void testCreatingParameterContext() throws FlowSynchronizationException, InterruptedException, TimeoutException {
- final Map<String, String> parameterMap = new HashMap<>();
- parameterMap.put("abc", "xyz");
- parameterMap.put("secret", "yes");
- final VersionedParameterContext proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
+ final VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_1, INITIAL_PARAMETERS, SENSITIVE_PARAM_NAMES);
synchronizer.synchronize(null, proposed, synchronizationOptions);
@@ -1026,75 +1061,56 @@ public class StandardVersionedComponentSynchronizerTest {
final Map<ParameterDescriptor, Parameter> createdParameters = created.getParameters();
assertEquals(2, createdParameters.size());
- final Parameter abc = created.getParameter("abc").get();
- assertEquals("abc", abc.getDescriptor().getName());
+ final Parameter abc = created.getParameter(PARAM_ABC).get();
+ assertEquals(PARAM_ABC, abc.getDescriptor().getName());
assertFalse(abc.getDescriptor().isSensitive());
- assertEquals("xyz", abc.getValue());
+ assertEquals(VALUE_XYZ, abc.getValue());
- final Parameter secret = created.getParameter("secret").get();
- assertEquals("secret", secret.getDescriptor().getName());
+ final Parameter secret = created.getParameter(PARAM_SECRET).get();
+ assertEquals(PARAM_SECRET, secret.getDescriptor().getName());
assertTrue(secret.getDescriptor().isSensitive());
- assertEquals("yes", secret.getValue());
+ assertEquals(VALUE_YES, secret.getValue());
}
@Test
public void testUpdateParametersNoReferences() throws FlowSynchronizationException, InterruptedException, TimeoutException {
- // Create the initial context
testCreatingParameterContext();
final ParameterContext existing = parameterContextManager.getParameterContexts().iterator().next();
-
- final Map<String, String> parameterMap = new HashMap<>();
- parameterMap.put("abc", "123");
- parameterMap.put("secret", "maybe");
-
- final VersionedParameterContext proposed = createVersionedParameterContext("Context 2", parameterMap, Collections.singleton("secret"));
+ final VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_2, UPDATED_PARAMETERS, SENSITIVE_PARAM_NAMES);
synchronizer.synchronize(existing, proposed, synchronizationOptions);
- assertEquals("123", existing.getParameter("abc").get().getValue());
- assertEquals("maybe", existing.getParameter("secret").get().getValue());
- assertEquals("Context 2", existing.getName());
+ assertEquals(VALUE_123, existing.getParameter(PARAM_ABC).get().getValue());
+ assertEquals(VALUE_MAYBE, existing.getParameter(PARAM_SECRET).get().getValue());
+ assertEquals(CONTEXT_NAME_2, existing.getName());
}
@Test
public void testUpdateParametersReferenceProcessorNotStopping() throws FlowSynchronizationException, InterruptedException, TimeoutException {
- // Create the initial context
testCreatingParameterContext();
final ParameterContext existing = parameterContextManager.getParameterContexts().iterator().next();
-
- final Map<String, String> parameterMap = new HashMap<>();
- parameterMap.put("abc", "123");
- parameterMap.put("secret", "maybe");
-
- final VersionedParameterContext proposed = createVersionedParameterContext("Context 2", parameterMap, Collections.singleton("secret"));
+ final VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_2, UPDATED_PARAMETERS, SENSITIVE_PARAM_NAMES);
final ProcessorNode processorA = createMockProcessor();
startProcessor(processorA, false);
synchronizationOptions = createQuickFailSynchronizationOptions(FlowSynchronizationOptions.ComponentStopTimeoutAction.THROW_TIMEOUT_EXCEPTION);
- when(parameterReferenceManager.getProcessorsReferencing(existing, "abc")).thenReturn(Collections.singleton(processorA));
+ when(parameterReferenceManager.getProcessorsReferencing(existing, PARAM_ABC)).thenReturn(Collections.singleton(processorA));
assertThrows(TimeoutException.class, () -> synchronizer.synchronize(existing, proposed, synchronizationOptions));
- // Updates should not occur.
- assertEquals("xyz", existing.getParameter("abc").get().getValue());
- assertEquals("yes", existing.getParameter("secret").get().getValue());
- assertEquals("Context 1", existing.getName());
+ assertEquals(VALUE_XYZ, existing.getParameter(PARAM_ABC).get().getValue());
+ assertEquals(VALUE_YES, existing.getParameter(PARAM_SECRET).get().getValue());
+ assertEquals(CONTEXT_NAME_1, existing.getName());
}
@Test
public void testUpdateParametersReferenceStopping() throws FlowSynchronizationException, InterruptedException, TimeoutException {
- // Create the initial context
testCreatingParameterContext();
final ParameterContext existing = parameterContextManager.getParameterContexts().iterator().next();
-
- final Map<String, String> parameterMap = new HashMap<>();
- parameterMap.put("abc", "123");
- parameterMap.put("secret", "maybe");
-
- final VersionedParameterContext proposed = createVersionedParameterContext("Context 2", parameterMap, Collections.singleton("secret"));
+ final VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_2, UPDATED_PARAMETERS, SENSITIVE_PARAM_NAMES);
final ProcessorNode processorA = createMockProcessor();
startProcessor(processorA, true);
@@ -1107,7 +1123,6 @@ public class StandardVersionedComponentSynchronizerTest {
when(service.isActive()).thenAnswer(invocation -> serviceActive.get());
when(service.getState()).thenAnswer(invocation -> serviceActive.get() ? ControllerServiceState.ENABLED : ControllerServiceState.DISABLED);
- // Make Processors A and B reference the controller service and start them
setReferences(service, processorA, processorB);
startProcessor(processorB);
@@ -1117,17 +1132,15 @@ public class StandardVersionedComponentSynchronizerTest {
return CompletableFuture.completedFuture(null);
});
- when(parameterReferenceManager.getProcessorsReferencing(existing, "abc")).thenReturn(Collections.emptySet());
- when(parameterReferenceManager.getControllerServicesReferencing(existing, "abc")).thenReturn(Collections.singleton(service));
+ when(parameterReferenceManager.getProcessorsReferencing(existing, PARAM_ABC)).thenReturn(Collections.emptySet());
+ when(parameterReferenceManager.getControllerServicesReferencing(existing, PARAM_ABC)).thenReturn(Collections.singleton(service));
synchronizer.synchronize(existing, proposed, synchronizationOptions);
- // Updates should occur.
- assertEquals("123", existing.getParameter("abc").get().getValue());
- assertEquals("maybe", existing.getParameter("secret").get().getValue());
- assertEquals("Context 2", existing.getName());
+ assertEquals(VALUE_123, existing.getParameter(PARAM_ABC).get().getValue());
+ assertEquals(VALUE_MAYBE, existing.getParameter(PARAM_SECRET).get().getValue());
+ assertEquals(CONTEXT_NAME_2, existing.getName());
- // Verify controller service/reference lifecycles
verify(controllerServiceProvider).unscheduleReferencingComponents(service);
verify(controllerServiceProvider).disableControllerServicesAsync(Collections.singleton(service));
verify(controllerServiceProvider).enableControllerServicesAsync(Collections.singleton(service));
@@ -1136,16 +1149,10 @@ public class StandardVersionedComponentSynchronizerTest {
@Test
public void testUpdateParametersControllerServiceNotDisabling() throws FlowSynchronizationException, InterruptedException, TimeoutException {
- // Create the initial context
testCreatingParameterContext();
final ParameterContext existing = parameterContextManager.getParameterContexts().iterator().next();
-
- final Map<String, String> parameterMap = new HashMap<>();
- parameterMap.put("abc", "123");
- parameterMap.put("secret", "maybe");
-
- final VersionedParameterContext proposed = createVersionedParameterContext("Context 2", parameterMap, Collections.singleton("secret"));
+ final VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_2, UPDATED_PARAMETERS, SENSITIVE_PARAM_NAMES);
final ProcessorNode processorA = createMockProcessor();
final ProcessorNode processorB = createMockProcessor();
@@ -1154,7 +1161,6 @@ public class StandardVersionedComponentSynchronizerTest {
when(service.isActive()).thenReturn(true);
when(service.getState()).thenReturn(ControllerServiceState.ENABLED);
- // Make Processors A and B reference the controller service and start them
setReferences(service, processorA, processorB);
startProcessor(processorA, true);
startProcessor(processorB);
@@ -1164,21 +1170,19 @@ public class StandardVersionedComponentSynchronizerTest {
completedFutureMap.put(processorB, CompletableFuture.completedFuture(null));
when(controllerServiceProvider.unscheduleReferencingComponents(service)).thenReturn(completedFutureMap);
- when(controllerServiceProvider.disableControllerServicesAsync(anyCollection())).thenReturn(new CompletableFuture<>()); // Never complete future = never disable service
+ when(controllerServiceProvider.disableControllerServicesAsync(anyCollection())).thenReturn(new CompletableFuture<>());
synchronizationOptions = createQuickFailSynchronizationOptions(FlowSynchronizationOptions.ComponentStopTimeoutAction.TERMINATE);
- when(parameterReferenceManager.getProcessorsReferencing(existing, "abc")).thenReturn(Collections.emptySet());
- when(parameterReferenceManager.getControllerServicesReferencing(existing, "abc")).thenReturn(Collections.singleton(service));
+ when(parameterReferenceManager.getProcessorsReferencing(existing, PARAM_ABC)).thenReturn(Collections.emptySet());
+ when(parameterReferenceManager.getControllerServicesReferencing(existing, PARAM_ABC)).thenReturn(Collections.singleton(service));
assertThrows(TimeoutException.class, () -> synchronizer.synchronize(existing, proposed, synchronizationOptions));
- // Updates should not occur.
- assertEquals("xyz", existing.getParameter("abc").get().getValue());
- assertEquals("yes", existing.getParameter("secret").get().getValue());
- assertEquals("Context 1", existing.getName());
+ assertEquals(VALUE_XYZ, existing.getParameter(PARAM_ABC).get().getValue());
+ assertEquals(VALUE_YES, existing.getParameter(PARAM_SECRET).get().getValue());
+ assertEquals(CONTEXT_NAME_1, existing.getName());
- // Verify controller service/reference lifecycles
verify(controllerServiceProvider).unscheduleReferencingComponents(service);
verify(controllerServiceProvider).disableControllerServicesAsync(Collections.singleton(service));
verify(controllerServiceProvider, times(0)).enableControllerServicesAsync(Collections.singleton(service));
@@ -1191,68 +1195,54 @@ public class StandardVersionedComponentSynchronizerTest {
testCreatingParameterContext();
final ParameterContext existing = parameterContextManager.getParameterContexts().iterator().next();
- final Map<String, String> originalParams = new HashMap<>();
- originalParams.put("abc", "xyz");
- originalParams.put("secret", "yes");
-
- // Test no changes
- Map<String, String> parameterMap = new HashMap<>(originalParams);
- VersionedParameterContext proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
+ Map<String, String> parameterMap = new HashMap<>(INITIAL_PARAMETERS);
+ VersionedParameterContext proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
assertEquals(Collections.emptySet(), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test non-sensitive param change
- parameterMap = new HashMap<>(originalParams);
- parameterMap.put("abc", "hello");
- proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
- assertEquals(Collections.singleton("abc"), synchronizer.getUpdatedParameterNames(existing, proposed));
+ parameterMap = new HashMap<>(INITIAL_PARAMETERS);
+ parameterMap.put(PARAM_ABC, "hello");
+ proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
+ assertEquals(Collections.singleton(PARAM_ABC), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test sensitive param change
- parameterMap = new HashMap<>(originalParams);
- parameterMap.put("secret", "secret");
- proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
- assertEquals(Collections.singleton("secret"), synchronizer.getUpdatedParameterNames(existing, proposed));
+ parameterMap = new HashMap<>(INITIAL_PARAMETERS);
+ parameterMap.put(PARAM_SECRET, PARAM_SECRET);
+ proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
+ assertEquals(Collections.singleton(PARAM_SECRET), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test removed parameters
parameterMap.clear();
- proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
- assertEquals(new HashSet<>(Arrays.asList("abc", "secret")), synchronizer.getUpdatedParameterNames(existing, proposed));
+ proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
+ assertEquals(new HashSet<>(Arrays.asList(PARAM_ABC, PARAM_SECRET)), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test added parameter
- parameterMap = new HashMap<>(originalParams);
+ parameterMap = new HashMap<>(INITIAL_PARAMETERS);
parameterMap.put("Added", "Added");
- proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
+ proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
assertEquals(Collections.singleton("Added"), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test added, removed, and updated parameters
- parameterMap = new HashMap<>(originalParams);
+ parameterMap = new HashMap<>(INITIAL_PARAMETERS);
parameterMap.put("Added", "Added");
parameterMap.put("Added 2", "Added");
- parameterMap.remove("secret");
- parameterMap.put("abc", "hello");
- proposed = createVersionedParameterContext("Context 1", parameterMap, Collections.singleton("secret"));
- assertEquals(new HashSet<>(Arrays.asList("abc", "secret", "Added", "Added 2")), synchronizer.getUpdatedParameterNames(existing, proposed));
+ parameterMap.remove(PARAM_SECRET);
+ parameterMap.put(PARAM_ABC, "hello");
+ proposed = createVersionedParameterContext(CONTEXT_NAME_1, parameterMap, SENSITIVE_PARAM_NAMES);
+ assertEquals(new HashSet<>(Arrays.asList(PARAM_ABC, PARAM_SECRET, "Added", "Added 2")), synchronizer.getUpdatedParameterNames(existing, proposed));
- // Test change value due to inherited parameter context reordering
final Map<String, String> inheritedParameters = new HashMap<>();
- // Context 1: abc = xyz
- // Context 3: abc = def
- inheritedParameters.put("abc", "def");
+ inheritedParameters.put(PARAM_ABC, "def");
final VersionedParameterContext context3 = createVersionedParameterContext("Context 3", inheritedParameters, Collections.emptySet());
synchronizer.synchronize(null, context3, synchronizationOptions);
parameterMap = new HashMap<>();
- proposed = createVersionedParameterContext("Context 2", parameterMap, Collections.emptySet());
+ proposed = createVersionedParameterContext(CONTEXT_NAME_2, parameterMap, Collections.emptySet());
synchronizer.synchronize(null, proposed, synchronizationOptions);
- ParameterContext context2 = parameterContextManager.getParameterContextNameMapping().get("Context 2");
- proposed.setInheritedParameterContexts(List.of("Context 1", "Context 3"));
+ ParameterContext context2 = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_2);
+ proposed.setInheritedParameterContexts(List.of(CONTEXT_NAME_1, "Context 3"));
synchronizer.synchronize(context2, proposed, synchronizationOptions);
- proposed.setInheritedParameterContexts(List.of("Context 3", "Context 1"));
- context2 = parameterContextManager.getParameterContextNameMapping().get("Context 2");
- // The effective value of abc should change here due to the reordering
- assertEquals(Collections.singleton("abc"), synchronizer.getUpdatedParameterNames(context2, proposed));
+ proposed.setInheritedParameterContexts(List.of("Context 3", CONTEXT_NAME_1));
+ context2 = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_2);
+ assertEquals(Collections.singleton(PARAM_ABC), synchronizer.getUpdatedParameterNames(context2, proposed));
}
@Test
@@ -1353,6 +1343,123 @@ public class StandardVersionedComponentSynchronizerTest {
assertTrue(p2.getParameter("paramA").isPresent(), "paramA should still exist on P2");
}
+ @Test
+ public void testParameterDescriptionUpdatedDuringSync() throws FlowSynchronizationException, InterruptedException, TimeoutException {
+ final VersionedParameterContext versionedContext = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_1,
+ SINGLE_PARAMETER, ORIGINAL_DESCRIPTION_MAP, Collections.emptySet());
+ synchronizer.synchronize(null, versionedContext, synchronizationOptions);
+
+ final ParameterContext context = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_1);
+ assertEquals(ORIGINAL_PARAMETER_DESCRIPTION, context.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+
+ final VersionedParameterContext proposed = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_1,
+ SINGLE_PARAMETER, UPDATED_DESCRIPTION_MAP, Collections.emptySet());
+
+ synchronizer.synchronize(context, proposed, synchronizationOptions);
+
+ assertEquals(UPDATED_PARAMETER_DESCRIPTION, context.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+ assertEquals(VALUE_XYZ, context.getParameter(PARAM_ABC).get().getValue());
+ }
+
+ @Test
+ public void testParameterDescriptionUpdatedDuringProcessGroupSync() throws FlowSynchronizationException, InterruptedException, TimeoutException {
+ final VersionedParameterContext versionedContext = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_PARAMS,
+ SINGLE_PARAMETER, ORIGINAL_DESCRIPTION_MAP, Collections.emptySet());
+ synchronizer.synchronize(null, versionedContext, synchronizationOptions);
+
+ final ParameterContext paramContext = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_PARAMS);
+ assertEquals(ORIGINAL_PARAMETER_DESCRIPTION, paramContext.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+
+ final ProcessGroup processGroup = createMockProcessGroup();
+ when(processGroup.getParameterContext()).thenReturn(paramContext);
+
+ final VersionedParameterContext proposedParams = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_PARAMS,
+ SINGLE_PARAMETER, UPDATED_DESCRIPTION_MAP, Collections.emptySet());
+
+ final Map<String, VersionedParameterContext> parameterContextMap = Map.of(CONTEXT_NAME_PARAMS, proposedParams);
+
+ final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
+ rootGroup.setIdentifier(processGroup.getIdentifier());
+ rootGroup.setParameterContextName(CONTEXT_NAME_PARAMS);
+
+ final VersionedExternalFlow externalFlow = new VersionedExternalFlow();
+ externalFlow.setFlowContents(rootGroup);
+ externalFlow.setParameterContexts(parameterContextMap);
+
+ synchronizer.synchronize(processGroup, externalFlow, synchronizationOptions);
+
+ assertEquals(UPDATED_PARAMETER_DESCRIPTION, paramContext.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+ assertEquals(VALUE_XYZ, paramContext.getParameter(PARAM_ABC).get().getValue());
+ }
+
+ @Test
+ public void testParameterContextDescriptionUpdatedDuringProcessGroupSync() throws FlowSynchronizationException, InterruptedException, TimeoutException {
+ final VersionedParameterContext versionedContext = createVersionedParameterContext(CONTEXT_NAME_PARAMS, SINGLE_PARAMETER, Collections.emptySet());
+ synchronizer.synchronize(null, versionedContext, synchronizationOptions);
+
+ final ParameterContext paramContext = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_PARAMS);
+ assertEquals(ORIGINAL_CONTEXT_DESCRIPTION, paramContext.getDescription());
+
+ final ProcessGroup processGroup = createMockProcessGroup();
+ when(processGroup.getParameterContext()).thenReturn(paramContext);
+
+ final VersionedParameterContext proposedParams = createVersionedParameterContext(CONTEXT_NAME_PARAMS, SINGLE_PARAMETER, Collections.emptySet());
+ proposedParams.setDescription(UPDATED_CONTEXT_DESCRIPTION);
+
+ final Map<String, VersionedParameterContext> parameterContextMap = Map.of(CONTEXT_NAME_PARAMS, proposedParams);
+
+ final VersionedProcessGroup rootGroup = new VersionedProcessGroup();
+ rootGroup.setIdentifier(processGroup.getIdentifier());
+ rootGroup.setParameterContextName(CONTEXT_NAME_PARAMS);
+
+ final VersionedExternalFlow externalFlow = new VersionedExternalFlow();
+ externalFlow.setFlowContents(rootGroup);
+ externalFlow.setParameterContexts(parameterContextMap);
+
+ synchronizer.synchronize(processGroup, externalFlow, synchronizationOptions);
+
+ assertEquals(UPDATED_CONTEXT_DESCRIPTION, paramContext.getDescription());
+ }
+
+ @Test
+ public void testParameterDescriptionUnchangedWhenValueSame() throws FlowSynchronizationException, InterruptedException, TimeoutException {
+ final VersionedParameterContext versionedContext = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_1,
+ SINGLE_PARAMETER, ORIGINAL_DESCRIPTION_MAP, Collections.emptySet());
+ synchronizer.synchronize(null, versionedContext, synchronizationOptions);
+
+ final ParameterContext context = parameterContextManager.getParameterContextNameMapping().get(CONTEXT_NAME_1);
+ assertEquals(ORIGINAL_PARAMETER_DESCRIPTION, context.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+
+ final VersionedParameterContext proposed = createVersionedParameterContextWithDescriptions(CONTEXT_NAME_1,
+ SINGLE_PARAMETER, ORIGINAL_DESCRIPTION_MAP, Collections.emptySet());
+
+ synchronizer.synchronize(context, proposed, synchronizationOptions);
+
+ assertEquals(ORIGINAL_PARAMETER_DESCRIPTION, context.getParameter(PARAM_ABC).get().getDescriptor().getDescription());
+ assertEquals(VALUE_XYZ, context.getParameter(PARAM_ABC).get().getValue());
+ }
+
+ private VersionedParameterContext createVersionedParameterContextWithDescriptions(final String name, final Map<String, String> parameters,
+ final Map<String, String> descriptions, final Set<String> sensitiveParamNames) {
+ final Set<VersionedParameter> versionedParameters = new HashSet<>();
+ for (final Map.Entry<String, String> entry : parameters.entrySet()) {
+ final VersionedParameter param = new VersionedParameter();
+ param.setName(entry.getKey());
+ param.setValue(entry.getValue());
+ param.setSensitive(sensitiveParamNames.contains(entry.getKey()));
+ param.setDescription(descriptions.get(entry.getKey()));
+ versionedParameters.add(param);
+ }
+
+ final VersionedParameterContext context = new VersionedParameterContext();
+ context.setName(name);
+ context.setDescription(ORIGINAL_CONTEXT_DESCRIPTION);
+ context.setParameters(versionedParameters);
+ context.setIdentifier(UUID.randomUUID().toString());
+
+ return context;
+ }
+
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()) {
@@ -1365,7 +1472,7 @@ public class StandardVersionedComponentSynchronizerTest {
final VersionedParameterContext context = new VersionedParameterContext();
context.setName(name);
- context.setDescription("Generated for unit test");
+ context.setDescription(ORIGINAL_CONTEXT_DESCRIPTION);
context.setParameters(versionedParameters);
context.setIdentifier(UUID.randomUUID().toString());
diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
index 2a07ac621fe..f2893fca8b1 100644
--- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
+++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizer.java
@@ -982,9 +982,11 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
final Map<String, String> currentValues = new HashMap<>();
final Map<String, Set<String>> currentAssetReferences = new HashMap<>();
+ final Map<String, String> currentDescriptions = new HashMap<>();
parameterContext.getParameters().values().forEach(param -> {
currentValues.put(param.getDescriptor().getName(), param.getValue());
currentAssetReferences.put(param.getDescriptor().getName(), getAssetIds(param));
+ currentDescriptions.put(param.getDescriptor().getName(), param.getDescriptor().getDescription());
});
final Map<String, Parameter> updatedParameters = new HashMap<>();
@@ -993,11 +995,13 @@ public class VersionedFlowSynchronizer implements FlowSynchronizer {
final String parameterName = parameter.getName();
final String currentValue = currentValues.get(parameterName);
final Set<String> currentAssetIds = currentAssetReferences.getOrDefault(parameterName, Collections.emptySet());
+ final String currentDescription = currentDescriptions.get(parameterName);
final Parameter updatedParameterObject = parameters.get(parameterName);
final String updatedValue = updatedParameterObject.getValue();
final Set<String> updatedAssetIds = getAssetIds(updatedParameterObject);
- if (!Objects.equals(currentValue, updatedValue) || !currentAssetIds.equals(updatedAssetIds)) {
+ final String updatedDescription = updatedParameterObject.getDescriptor().getDescription();
+ if (!Objects.equals(currentValue, updatedValue) || !currentAssetIds.equals(updatedAssetIds) || !Objects.equals(currentDescription, updatedDescription)) {
updatedParameters.put(parameterName, updatedParameterObject);
}
proposedParameterNames.add(parameterName);
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 592bd29958b..931763f3ebe 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
@@ -27,6 +27,7 @@ import org.apache.nifi.web.api.entity.FlowRegistryClientEntity;
import org.apache.nifi.web.api.entity.ParameterContextEntity;
import org.apache.nifi.web.api.entity.ParameterContextReferenceEntity;
import org.apache.nifi.web.api.entity.ParameterContextUpdateRequestEntity;
+import org.apache.nifi.web.api.entity.ParameterEntity;
import org.apache.nifi.web.api.entity.ProcessGroupEntity;
import org.apache.nifi.web.api.entity.ProcessGroupFlowEntity;
import org.apache.nifi.web.api.entity.ProcessorEntity;
@@ -64,6 +65,11 @@ class ParameterContextPreservationIT extends NiFiSystemIT {
private static final String RELATIONSHIP_SUCCESS = "success";
private static final String VERSION_1 = "1";
private static final String VERSION_2 = "2";
+ private static final String DESCRIPTION_UPDATE_FLOW_NAME = "DescriptionUpdateFlow";
+ private static final String PARAMETER_DESCRIPTION_V1 = "Description for version 1";
+ private static final String PARAMETER_DESCRIPTION_V2 = "Description for version 2";
+ private static final String CONTEXT_DESCRIPTION_V1 = "Context description v1";
+ private static final String CONTEXT_DESCRIPTION_V2 = "Context description v2";
@Test
void testNewProcessGroupUsesCorrectParameterContextDuringUpgrade() throws NiFiClientException, IOException, InterruptedException {
@@ -228,6 +234,94 @@ class ParameterContextPreservationIT extends NiFiSystemIT {
assertTrue(p2NamesAfterUpgrade.contains("paramX"), "paramX should exist on P2 after upgrading to version 2");
}
+ /**
+ * Verifies that parameter descriptions are updated when upgrading a versioned process group from one version
+ * to the next, even when the parameter value itself remains unchanged.
+ */
+ @Test
+ void testParameterDescriptionUpdatedDuringVersionUpgrade() throws NiFiClientException, IOException, InterruptedException {
+ final FlowRegistryClientEntity clientEntity = registerClient();
+ final NiFiClientUtil util = getClientUtil();
+
+ final Set<ParameterEntity> parameterEntitiesV1 = Set.of(util.createParameterEntity(PARAMETER_NAME, PARAMETER_DESCRIPTION_V1, false, PARAMETER_VALUE));
+ final ParameterContextEntity paramContext = getNifiClient().getParamContextClient().createParamContext(
+ util.createParameterContextEntity(PARAMETER_CONTEXT_NAME, CONTEXT_DESCRIPTION_V1, parameterEntitiesV1));
+
+ final ProcessGroupEntity groupA = util.createProcessGroup(GROUP_A_NAME, "root");
+ util.setParameterContext(groupA.getId(), paramContext);
+
+ final ProcessorEntity processor = util.createProcessor(PROCESSOR_TYPE, groupA.getId());
+ util.updateProcessorProperties(processor, Collections.singletonMap(PROCESSOR_PROPERTY_TEXT, PARAMETER_REFERENCE));
+ util.setAutoTerminatedRelationships(processor, RELATIONSHIP_SUCCESS);
+
+ final VersionControlInformationEntity vciV1 = util.startVersionControl(groupA, clientEntity, TEST_FLOWS_BUCKET, DESCRIPTION_UPDATE_FLOW_NAME);
+ final String flowId = vciV1.getVersionControlInformation().getFlowId();
+
+ // Update the parameter description (keeping the same value) and context description, then save as version 2
+ final ParameterContextEntity currentContext = getNifiClient().getParamContextClient().getParamContext(paramContext.getId(), false);
+ final Set<ParameterEntity> parameterEntitiesV2 = Set.of(util.createParameterEntity(PARAMETER_NAME, PARAMETER_DESCRIPTION_V2, false, PARAMETER_VALUE));
+ final ParameterContextEntity entityUpdate = util.createParameterContextEntity(PARAMETER_CONTEXT_NAME, CONTEXT_DESCRIPTION_V2, parameterEntitiesV2);
+ entityUpdate.setId(currentContext.getId());
+ entityUpdate.setRevision(currentContext.getRevision());
+ entityUpdate.getComponent().setId(currentContext.getComponent().getId());
+ final ParameterContextUpdateRequestEntity updateRequest = getNifiClient().getParamContextClient().updateParamContext(entityUpdate);
+ util.waitForParameterContextRequestToComplete(paramContext.getId(), updateRequest.getRequest().getRequestId());
+
+ final ProcessGroupEntity refreshedGroupA = getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+ util.saveFlowVersion(refreshedGroupA, clientEntity, vciV1);
+
+ // Clean up the original flow
+ final ProcessGroupEntity groupAForStopVc = getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+ getNifiClient().getVersionsClient().stopVersionControl(groupAForStopVc);
+ util.deleteAll(groupA.getId());
+ final ProcessGroupEntity groupAToDelete = getNifiClient().getProcessGroupClient().getProcessGroup(groupA.getId());
+ getNifiClient().getProcessGroupClient().deleteProcessGroup(groupAToDelete);
+
+ final ParameterContextEntity contextToDelete = getNifiClient().getParamContextClient().getParamContext(paramContext.getId(), false);
+ getNifiClient().getParamContextClient().deleteParamContext(paramContext.getId(), String.valueOf(contextToDelete.getRevision().getVersion()));
+
+ // Import version 1 fresh
+ final ProcessGroupEntity importedGroup = importFlowWithReplaceParameterContext(clientEntity.getId(), flowId, VERSION_1);
+
+ final ProcessGroupEntity fetchedGroup = getNifiClient().getProcessGroupClient().getProcessGroup(importedGroup.getId());
+ final String importedContextId = fetchedGroup.getComponent().getParameterContext().getId();
+
+ // Verify version 1 descriptions
+ final ParameterContextEntity importedContext = getNifiClient().getParamContextClient().getParamContext(importedContextId, false);
+ final String descriptionAfterV1 = getParameterDescription(importedContext, PARAMETER_NAME);
+ assertEquals(PARAMETER_DESCRIPTION_V1, descriptionAfterV1);
+ assertEquals(CONTEXT_DESCRIPTION_V1, importedContext.getComponent().getDescription());
+
+ // Upgrade from version 1 to version 2
+ util.changeFlowVersion(importedGroup.getId(), VERSION_2);
+
+ // Verify descriptions were updated to version 2
+ final ParameterContextEntity contextAfterUpgrade = getNifiClient().getParamContextClient().getParamContext(importedContextId, false);
+ final String descriptionAfterV2 = getParameterDescription(contextAfterUpgrade, PARAMETER_NAME);
+ assertEquals(PARAMETER_DESCRIPTION_V2, descriptionAfterV2);
+ assertEquals(CONTEXT_DESCRIPTION_V2, contextAfterUpgrade.getComponent().getDescription());
+
+ // Verify the value was not changed
+ final String valueAfterUpgrade = getParameterValue(contextAfterUpgrade, PARAMETER_NAME);
+ assertEquals(PARAMETER_VALUE, valueAfterUpgrade);
+ }
+
+ private String getParameterDescription(final ParameterContextEntity context, final String parameterName) {
+ return context.getComponent().getParameters().stream()
+ .filter(entity -> parameterName.equals(entity.getParameter().getName()))
+ .map(entity -> entity.getParameter().getDescription())
+ .findFirst()
+ .orElse(null);
+ }
+
+ private String getParameterValue(final ParameterContextEntity context, final String parameterName) {
+ return context.getComponent().getParameters().stream()
+ .filter(entity -> parameterName.equals(entity.getParameter().getName()))
+ .map(entity -> entity.getParameter().getValue())
+ .findFirst()
+ .orElse(null);
+ }
+
private Set<String> getParameterNames(final ParameterContextEntity context) {
return context.getComponent().getParameters().stream()
.map(entity -> entity.getParameter().getName())