pvillard31 commented on code in PR #11403:
URL: https://github.com/apache/nifi/pull/11403#discussion_r3543416096


##########
nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/serialization/VersionedFlowSynchronizerTest.java:
##########
@@ -276,6 +290,70 @@ private VersionedControllerService 
getVersionedControllerService() {
         return controllerService;
     }
 
+    @Test
+    void testSyncReconcilesProviderBackedContextWithNonProvidedParameter() {
+        setRootGroup();
+        setFlowController();
+
+        final String providerId = "provider-1";
+        final String contextName = "openflow-rds-ingest";
+        final String paramName = "db.host";
+
+        // Parameter Provider infrastructure
+        final ParameterProvider parameterProvider = 
mock(ParameterProvider.class);
+        when(parameterProvider.getIdentifier()).thenReturn(providerId);
+        final ParameterProviderNode parameterProviderNode = 
mock(ParameterProviderNode.class);

Review Comment:
   Pushed a commit to address your comment



##########
nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/parameters/ClusteredProviderParamFlowSyncIT.java:
##########
@@ -0,0 +1,191 @@
+/*
+ * 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.parameters;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.apache.nifi.controller.flow.VersionedDataflow;
+import org.apache.nifi.flow.VersionedParameter;
+import org.apache.nifi.flow.VersionedParameterContext;
+import org.apache.nifi.parameter.ParameterSensitivity;
+import org.apache.nifi.parameter.StandardParameterProviderConfiguration;
+import org.apache.nifi.stream.io.StreamUtils;
+import org.apache.nifi.tests.system.NiFiInstance;
+import org.apache.nifi.tests.system.NiFiInstanceFactory;
+import org.apache.nifi.tests.system.NiFiSystemIT;
+import org.apache.nifi.web.api.entity.ParameterContextEntity;
+import org.apache.nifi.web.api.entity.ParameterGroupConfigurationEntity;
+import 
org.apache.nifi.web.api.entity.ParameterProviderApplyParametersRequestEntity;
+import org.apache.nifi.web.api.entity.ParameterProviderEntity;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.Timeout;
+
+import java.io.ByteArrayInputStream;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.nio.file.Files;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.TimeUnit;
+import java.util.zip.GZIPInputStream;
+import java.util.zip.GZIPOutputStream;
+
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Reproduces the flow-synchronization failure that occurs when a 
provider-backed Parameter Context
+ * contains a parameter flagged as user-entered (provided=false) in the 
serialized flow. A provider-backed
+ * context can never hold such a parameter as a live object (it is rejected at 
every write path), but the
+ * provided flag on a serialized VersionedParameter is set independently of 
those guards. When a node loads
+ * or reconciles a flow whose provider-backed context carries a provided=false 
parameter, the flow
+ * synchronizer attempts a manual parameter update, which the Parameter 
Context rejects, and the node fails
+ * to bring its flow up.
+ */
+public class ClusteredProviderParamFlowSyncIT extends NiFiSystemIT {
+
+    private static final String CONTEXT_NAME = "provider-backed-context";
+    private static final String GROUP_NAME = "Parameters";
+    private static final String PARAM_NAME = "db.host";
+    private static final String GUARD_MESSAGE = "cannot be manually updated 
because they are provided by Parameter Provider";
+
+    private final ObjectMapper objectMapper = new ObjectMapper();
+
+    @Override
+    public NiFiInstanceFactory getInstanceFactory() {
+        return createTwoNodeInstanceFactory();
+    }
+
+    @Override
+    protected boolean isDestroyEnvironmentAfterEachTest() {
+        // This test intentionally corrupts the persisted flow, so the 
environment must not be reused.
+        return true;
+    }
+
+    @Test
+    @Timeout(value = 8, unit = TimeUnit.MINUTES)
+    public void 
testProviderBackedContextWithNonProvidedParameterReconcilesOnFlowSync() throws 
Exception {
+        // 1. Create a Parameter Provider and a provider-backed Parameter 
Context, and apply provided parameters
+        //    so that the parameter is provider-supplied (provided=true) on 
both nodes.
+        final ParameterProviderEntity provider = 
getClientUtil().createParameterProvider("PropertiesParameterProvider");
+        final Map<String, String> providerProps = new HashMap<>();
+        providerProps.put("parameters", PARAM_NAME + "=localhost");
+        getClientUtil().updateParameterProviderProperties(provider, 
providerProps);
+
+        final ParameterContextEntity contextEntity = 
getClientUtil().createParameterContextEntity(
+                CONTEXT_NAME, null, Collections.emptySet(), 
Collections.emptyList(),
+                new StandardParameterProviderConfiguration(provider.getId(), 
GROUP_NAME, true));
+        final ParameterContextEntity createdContext = 
getNifiClient().getParamContextClient().createParamContext(contextEntity);
+        getClientUtil().setParameterContext("root", createdContext);
+
+        final ParameterGroupConfigurationEntity groupConfig = new 
ParameterGroupConfigurationEntity();
+        groupConfig.setSynchronized(true);
+        groupConfig.setGroupName(GROUP_NAME);
+        groupConfig.setParameterContextName(CONTEXT_NAME);
+        final Map<String, ParameterSensitivity> sensitivities = new 
HashMap<>();
+        sensitivities.put(PARAM_NAME, ParameterSensitivity.NON_SENSITIVE);
+        groupConfig.setParameterSensitivities(sensitivities);
+
+        getClientUtil().fetchParameters(provider);
+        final ParameterProviderApplyParametersRequestEntity applyRequest =
+                getClientUtil().applyParameters(provider, 
List.of(groupConfig));
+        getClientUtil().waitForParameterProviderApplicationRequestToComplete(
+                applyRequest.getRequest().getParameterProvider().getId(), 
applyRequest.getRequest().getRequestId());
+
+        waitForAllNodesConnected();
+
+        // 2. Stop the whole cluster and corrupt the persisted flow on BOTH 
nodes: flip the provider-backed
+        //    context's parameter to provided=false with a divergent value. 
Corrupting both nodes ensures the
+        //    elected cluster flow carries the contradiction regardless of 
which node wins flow election.
+        final NiFiInstance node1 = getNiFiInstance().getNodeInstance(1);
+        final NiFiInstance node2 = getNiFiInstance().getNodeInstance(2);
+        node1.stop();
+        node2.stop();
+
+        corruptProviderBackedParameter(node1);
+        corruptProviderBackedParameter(node2);
+
+        // 3. Restart both nodes. With the fix, each node recognizes the 
context is provider-backed and reconciles
+        //    it as provider-managed (re-sourcing values from the Parameter 
Provider) rather than attempting a
+        //    manual update, so both nodes rejoin the cluster cleanly.
+        node1.start(true);
+        node2.start(true);
+
+        // 4. The cluster forms: both nodes connect (this would time out if 
reconciliation still failed the guard),
+        //    and neither node logs the provider-backed-context guard failure 
or a flow mismatch.
+        waitForAllNodesConnected();
+
+        final List<NiFiInstance> nodes = List.of(node1, node2);
+        assertNoNodeLogContains(nodes, GUARD_MESSAGE);
+        assertNoNodeLogContains(nodes, "did not Match Cluster Flow");

Review Comment:
   Pushed a commit to address your comment



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to