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 1aabc615ce NIFI-13755 Improved Controller Service Enabling Process 
(#9273)
1aabc615ce is described below

commit 1aabc615ce923a07281bcb833e247ad3b51bcf42
Author: jrsteinebrey <[email protected]>
AuthorDate: Fri Oct 18 14:05:50 2024 -0400

    NIFI-13755 Improved Controller Service Enabling Process (#9273)
    
    - Updated Standard Controller Service Provider to enable requested 
Controller Services that do not depend on a disabled Controller Service, 
instead of failing to enable the entire set of requested Controller Services
    - Updated enabling process improves behavior when restarting NiFi and 
attempting to enable large numbers of Controller Services, some of which depend 
on disabled Services
    
    Signed-off-by: David Handermann <[email protected]>
---
 .../service/StandardControllerServiceProvider.java | 62 +++++++++++---------
 .../StandardControllerServiceProviderTest.java     | 67 +++++++++++++++++++++-
 .../TestStandardControllerServiceProvider.java     |  8 +--
 3 files changed, 104 insertions(+), 33 deletions(-)

diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
index cf4a53d3e2..0629458332 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceProvider.java
@@ -252,39 +252,49 @@ public class StandardControllerServiceProvider implements 
ControllerServiceProvi
     }
 
     @Override
-    public void enableControllerServices(final 
Collection<ControllerServiceNode> serviceNodes) {
-        boolean shouldStart = true;
-
-        Iterator<ControllerServiceNode> serviceIter = serviceNodes.iterator();
-        while (serviceIter.hasNext() && shouldStart) {
-            ControllerServiceNode controllerServiceNode = serviceIter.next();
-            List<ControllerServiceNode> requiredServices = 
controllerServiceNode.getRequiredControllerServices();
-            for (ControllerServiceNode requiredService : requiredServices) {
-                if (!requiredService.isActive() && 
!serviceNodes.contains(requiredService)) {
-                    shouldStart = false;
-                    logger.debug("Will not start {} because required service 
{} is not active and is not part of the collection of things to start", 
serviceNodes, requiredService);
+    public void enableControllerServices(final 
Collection<ControllerServiceNode> serviceNodesIn) {
+        Collection<ControllerServiceNode> serviceNodes = new 
HashSet<>(serviceNodesIn);
+        for (ControllerServiceNode controllerServiceNode : 
removeControllerServicesWithUnavailableRequirements(serviceNodes)) {
+            try {
+                final Future<Void> future = 
enableControllerServiceAndDependencies(controllerServiceNode);
+
+                future.get(30, TimeUnit.SECONDS);
+                logger.debug("Successfully enabled {}; service state = {}", 
controllerServiceNode, controllerServiceNode.getState());
+            } catch (final ControllerServiceNotValidException csnve) {
+                logger.warn("Failed to enable service {} because it is not 
currently valid", controllerServiceNode);
+            } catch (Exception e) {
+                logger.error("Failed to enable {}", controllerServiceNode, e);
+                if (this.bulletinRepo != null) {
+                    
this.bulletinRepo.addBulletin(BulletinFactory.createBulletin("Controller 
Service",
+                            Severity.ERROR.name(), "Could not start " + 
controllerServiceNode + " due to " + e));
                 }
             }
         }
+    }
 
-        if (shouldStart) {
-            for (ControllerServiceNode controllerServiceNode : serviceNodes) {
-                try {
-                    final Future<Void> future = 
enableControllerServiceAndDependencies(controllerServiceNode);
-
-                    future.get(30, TimeUnit.SECONDS);
-                    logger.debug("Successfully enabled {}; service state = 
{}", controllerServiceNode, controllerServiceNode.getState());
-                } catch (final ControllerServiceNotValidException csnve) {
-                    logger.warn("Failed to enable service {} because it is not 
currently valid", controllerServiceNode);
-                } catch (Exception e) {
-                    logger.error("Failed to enable {}", controllerServiceNode, 
e);
-                    if (this.bulletinRepo != null) {
-                        
this.bulletinRepo.addBulletin(BulletinFactory.createBulletin("Controller 
Service",
-                                Severity.ERROR.name(), "Could not start " + 
controllerServiceNode + " due to " + e));
+    private Collection<ControllerServiceNode> 
removeControllerServicesWithUnavailableRequirements(final 
Collection<ControllerServiceNode> serviceNodes) {
+        boolean recheckNeeded;
+        do {
+            recheckNeeded = false;
+            for (Iterator<ControllerServiceNode> iter = 
serviceNodes.iterator(); iter.hasNext();) {
+                boolean skipStarting = false;
+                final ControllerServiceNode serviceNode = iter.next();
+                final List<ControllerServiceNode> requiredServices = 
serviceNode.getRequiredControllerServices();
+                for (ControllerServiceNode requiredService : requiredServices) 
{
+                    if (!requiredService.isActive() && 
!serviceNodes.contains(requiredService)) {
+                        skipStarting = true;
+                        logger.error("Will not start {} because its required 
service {} is not active and is not part of the collection of things to start", 
serviceNode, requiredService);
                     }
                 }
+                if (skipStarting) {
+                    // If any service was removed, then recheck all remaining 
services because the removed one might be required by another service in the 
list.
+                    recheckNeeded = true;
+                    iter.remove();
+                }
             }
-        }
+        } while (recheckNeeded);
+
+        return serviceNodes;
     }
 
     @Override
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
index b1ed391694..2153c63043 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/StandardControllerServiceProviderTest.java
@@ -37,9 +37,15 @@ import org.junit.jupiter.api.BeforeAll;
 import org.junit.jupiter.api.Test;
 import org.mockito.Mockito;
 
+import java.util.ArrayList;
 import java.util.Collections;
+import java.util.concurrent.CompletableFuture;
 
 import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
 
 public class StandardControllerServiceProviderTest {
 
@@ -69,7 +75,7 @@ public class StandardControllerServiceProviderTest {
     }
 
     private ControllerServiceNode createControllerService(final String type, 
final String id, final BundleCoordinate bundleCoordinate, final 
ControllerServiceProvider serviceProvider) {
-        final ControllerServiceNode serviceNode = new ExtensionBuilder()
+        return new ExtensionBuilder()
             .identifier(id)
             .type(type)
             .bundleCoordinate(bundleCoordinate)
@@ -81,8 +87,6 @@ public class StandardControllerServiceProviderTest {
             .stateManagerProvider(Mockito.mock(StateManagerProvider.class))
             .extensionManager(extensionManager)
             .buildControllerService();
-
-        return serviceNode;
     }
 
     @Test
@@ -106,4 +110,61 @@ public class StandardControllerServiceProviderTest {
     public void testCallImplementationInitialized() throws 
InitializationException {
         implementation.initialize(null);
     }
+
+    private ControllerServiceNode 
populateControllerService(ControllerServiceNode requiredService) { // 
Collection<ControllerServiceNode> serviceNodes) {
+        ControllerServiceNode controllerServiceNode = 
mock(ControllerServiceNode.class);
+        ArrayList<ControllerServiceNode> requiredServices = new ArrayList<>();
+        if (requiredService != null) {
+            requiredServices.add(requiredService);
+        }
+        
when(controllerServiceNode.getRequiredControllerServices()).thenReturn(requiredServices);
+        return controllerServiceNode;
+    }
+
+    @Test
+    public void testEnableControllerServicesAllAreEnabled() {
+        final CompletableFuture<Void> future = new CompletableFuture<>();
+        future.complete(null);
+
+        ProcessScheduler scheduler = Mockito.mock(ProcessScheduler.class);
+        when(scheduler.enableControllerService(any())).thenReturn(future);
+        ControllerServiceProvider provider = new 
StandardControllerServiceProvider(scheduler, null, 
Mockito.mock(FlowManager.class), Mockito.mock(ExtensionManager.class));
+
+        final ArrayList<ControllerServiceNode> serviceNodes = new 
ArrayList<>();
+        serviceNodes.add(populateControllerService(null));
+        serviceNodes.add(populateControllerService(null));
+        provider.enableControllerServices(serviceNodes);
+        verify(scheduler).enableControllerService(serviceNodes.get(0));
+        verify(scheduler).enableControllerService(serviceNodes.get(1));
+    }
+
+    @Test
+    public void testEnableControllerServicesSomeAreEnabled() {
+        final CompletableFuture<Void> future = new CompletableFuture<>();
+        future.complete(null);
+
+        ProcessScheduler scheduler = Mockito.mock(ProcessScheduler.class);
+        when(scheduler.enableControllerService(any())).thenReturn(future);
+        ControllerServiceProvider provider = new 
StandardControllerServiceProvider(scheduler, null, 
Mockito.mock(FlowManager.class), Mockito.mock(ExtensionManager.class));
+
+        final ArrayList<ControllerServiceNode> serviceNodes = new 
ArrayList<>();
+        ControllerServiceNode disabledController = 
populateControllerService(null);
+        // Do not start because disabledController is not in the serviceNodes 
(list of services to start)
+        serviceNodes.add(populateControllerService(disabledController));
+        // Start this service because it has no required services
+        serviceNodes.add(populateControllerService(null));
+        // Do not start because its required service is not started because 
the required service
+        // depends on disabledController which is not in the serviceNodes 
(list of services to start)
+        serviceNodes.add(populateControllerService(serviceNodes.get(0)));
+        // Do not start because its required service depends on 
disabledController through 2 other levels of services
+        serviceNodes.add(populateControllerService(serviceNodes.get(2)));
+        // Start this service because it has a required service which is in 
the list of services to start
+        serviceNodes.add(populateControllerService(serviceNodes.get(1)));
+        provider.enableControllerServices(serviceNodes);
+        verify(scheduler, 
Mockito.times(0)).enableControllerService(serviceNodes.get(0));
+        verify(scheduler, 
Mockito.times(2)).enableControllerService(serviceNodes.get(1));
+        verify(scheduler, 
Mockito.times(0)).enableControllerService(serviceNodes.get(2));
+        verify(scheduler, 
Mockito.times(0)).enableControllerService(serviceNodes.get(3));
+        verify(scheduler, 
Mockito.times(1)).enableControllerService(serviceNodes.get(4));
+    }
 }
diff --git 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
index f0f27e787a..05e54d3a07 100644
--- 
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
+++ 
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/TestStandardControllerServiceProvider.java
@@ -576,10 +576,10 @@ public class TestStandardControllerServiceProvider {
         allBut6.stream().forEach(ControllerServiceNode::performValidation);
 
         provider.enableControllerServices(allBut6);
-        assertFalse(serviceNode1.isActive());
-        assertFalse(serviceNode2.isActive());
-        assertFalse(serviceNode3.isActive());
-        assertFalse(serviceNode4.isActive());
+        assertTrue(serviceNode1.isActive());
+        assertTrue(serviceNode2.isActive());
+        assertTrue(serviceNode3.isActive());
+        assertTrue(serviceNode4.isActive());
         assertFalse(serviceNode5.isActive());
         assertFalse(serviceNode6.isActive());
 

Reply via email to