This is an automated email from the ASF dual-hosted git repository.
pvillard 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 3c0c64619b NIFI-15296 Fixed enabling Controller Services with
dependencies
3c0c64619b is described below
commit 3c0c64619bb29e06516308491331ba4e9c20caae
Author: exceptionfactory <[email protected]>
AuthorDate: Thu Dec 4 21:35:37 2025 -0600
NIFI-15296 Fixed enabling Controller Services with dependencies
- Filtered list of required Controller Services based on whether the
property descriptor has satisfied dependencies to avoid enabling unnecessary
referenced Controller Services
- Added unit test with Primary Controller Service referencing Secondary
Controller Service depending on specified properties
Signed-off-by: Pierre Villard <[email protected]>
This closes #10603.
---
.../service/StandardControllerServiceNode.java | 37 +++--
.../service/StandardControllerServiceProvider.java | 16 +-
.../StandardControllerServiceProviderTest.java | 183 ++++++++++++++-------
.../controller/service/mock/PrimaryService.java | 49 ++++++
.../controller/service/mock/SecondaryService.java | 23 +++
5 files changed, 236 insertions(+), 72 deletions(-)
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
index ac0e91fada..18202bc10d 100644
---
a/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-components/src/main/java/org/apache/nifi/controller/service/StandardControllerServiceNode.java
@@ -319,21 +319,36 @@ public class StandardControllerServiceNode extends
AbstractComponentNode impleme
return processGroup == null ? null :
processGroup.getParameterContext();
}
-
+ /**
+ * Get Controller Services required to be enabled with filtering based on
satisfied dependencies
+ *
+ * @return Controller Service Nodes required to be enabled
+ */
@Override
public List<ControllerServiceNode> getRequiredControllerServices() {
- Set<ControllerServiceNode> requiredServices = new HashSet<>();
- for (Entry<PropertyDescriptor, String> entry :
getEffectivePropertyValues().entrySet()) {
- PropertyDescriptor descriptor = entry.getKey();
- if (descriptor.getControllerServiceDefinition() != null &&
entry.getValue() != null) {
- // CS property could point to a non-existent CS, so protect
against requiredNode being null
- final String referenceId = entry.getValue();
- final ControllerServiceNode requiredNode =
serviceProvider.getControllerServiceNode(referenceId);
- if (requiredNode != null) {
- requiredServices.add(requiredNode);
+ final Set<ControllerServiceNode> requiredServices = new HashSet<>();
+
+ final ValidationContext validationContext = getValidationContext();
+ for (final Entry<PropertyDescriptor, String> entry :
getEffectivePropertyValues().entrySet()) {
+ final PropertyDescriptor descriptor = entry.getKey();
+ final Class<? extends ControllerService>
controllerServiceDefinition = descriptor.getControllerServiceDefinition();
+ final String referenceId = entry.getValue();
+ // Skip Property Descriptors not referencing Controller Services
+ if (controllerServiceDefinition == null || referenceId == null) {
+ continue;
+ }
+
+ // Controller Services with unsatisfied dependencies are not
required
+ final boolean dependencySatisfied =
validationContext.isDependencySatisfied(descriptor,
this::getPropertyDescriptor);
+ if (dependencySatisfied) {
+ final ControllerServiceNode requiredServiceNode =
serviceProvider.getControllerServiceNode(referenceId);
+ if (requiredServiceNode == null) {
+ LOG.warn("Referenced Controller Service [{}] not found for
Service [{}] Property [{}]", referenceId, getIdentifier(),
descriptor.getName());
} else {
- LOG.warn("Unable to locate referenced controller service
with id {}", referenceId);
+ requiredServices.add(requiredServiceNode);
}
+ } else {
+ LOG.debug("Referenced Controller Service [{}] not required for
Service [{}] Property [{}]", referenceId, getIdentifier(),
descriptor.getName());
}
}
return new ArrayList<>(requiredServices);
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 ee9c2400c3..714a5e0bd2 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
@@ -243,8 +243,7 @@ public class StandardControllerServiceProvider implements
ControllerServiceProvi
@Override
public CompletableFuture<Void> enableControllerService(final
ControllerServiceNode serviceNode) {
if (serviceNode.isActive()) {
- final CompletableFuture<Void> future =
CompletableFuture.completedFuture(null);
- return future;
+ return CompletableFuture.completedFuture(null);
}
serviceNode.verifyCanEnable();
@@ -260,8 +259,8 @@ public class StandardControllerServiceProvider implements
ControllerServiceProvi
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.debug("{} enabled with state [{}]",
controllerServiceNode, controllerServiceNode.getState());
+ } catch (final ControllerServiceNotValidException e) {
logger.warn("Failed to enable service {} because it is not
currently valid", controllerServiceNode);
} catch (Exception e) {
logger.error("Failed to enable {}", controllerServiceNode, e);
@@ -284,7 +283,7 @@ public class StandardControllerServiceProvider implements
ControllerServiceProvi
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);
+ logger.error("{} not started: Required Service {} not
active and not requested for enabling", serviceNode, requiredService);
}
}
if (skipStarting) {
@@ -657,6 +656,7 @@ public class StandardControllerServiceProvider implements
ControllerServiceProvi
return null;
}
+ @SuppressWarnings("unchecked")
private Class<? extends ControllerService> getServiceInterfaceByName(final
Class<?> serviceClass, final String type) {
for (final Class<?> serviceInterface : serviceClass.getInterfaces()) {
if (!ControllerService.class.isAssignableFrom(serviceInterface)) {
@@ -679,6 +679,12 @@ public class StandardControllerServiceProvider implements
ControllerServiceProvi
return node == null ? null : node.getName();
}
+ /**
+ * Remove Controller Service and suppress warnings related to
InstanceClassLoader that may not be defined
+ *
+ * @param serviceNode Controller Service Node to be removed
+ */
+ @SuppressWarnings("resource")
@Override
public void removeControllerService(final ControllerServiceNode
serviceNode) {
requireNonNull(serviceNode);
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 366e3f7e24..d18d489649 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
@@ -35,121 +35,123 @@ import org.apache.nifi.util.NiFiProperties;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
-import org.mockito.Mockito;
+import org.junit.jupiter.api.Timeout;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.mockito.Mock;
+import org.mockito.junit.jupiter.MockitoExtension;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.Map;
import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.Executors;
+import java.util.concurrent.ScheduledExecutorService;
+import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-public class StandardControllerServiceProviderTest {
+@ExtendWith(MockitoExtension.class)
+class StandardControllerServiceProviderTest {
+
+ private static final String NAR_LIBRARY_DIRECTORY = "./target/lib";
+
+ private static final String TEST_SERVICE_CLASS =
"org.apache.nifi.controller.service.util.TestControllerService";
+ private static final String PRIMARY_SERVICE_CLASS =
"org.apache.nifi.controller.service.mock.PrimaryService";
+ private static final String SECONDARY_SERVICE_CLASS =
"org.apache.nifi.controller.service.mock.SecondaryService";
+
+ private static final String SECONDARY_SERVICE_ENABLED_PROPERTY =
"Secondary Service Enabled";
+ private static final String SECONDARY_SERVICE_PROPERTY = "Secondary
Service";
+
+ private static final String SERVICE_ID = "service-id";
+ private static final String SECONDARY_SERVICE_ID = "secondary-service-id";
- private ControllerService proxied;
- private ControllerService implementation;
private static ExtensionDiscoveringManager extensionManager;
private static Bundle systemBundle;
+ @Mock
+ private ProcessScheduler scheduler;
+
+ @Mock
+ private FlowManager flowManager;
+
+ private ControllerServiceProvider serviceProvider;
+
+ private ControllerService proxied;
+ private ControllerService implementation;
+
@BeforeAll
- public static void setupSuite() {
- final NiFiProperties nifiProperties =
NiFiProperties.createBasicNiFiProperties(StandardControllerServiceProviderTest.class.getResource("/conf/nifi.properties").getFile());
+ static void setupSuite() {
+ final Map<String, String> additionalProperties = Map.of(
+ NiFiProperties.NAR_LIBRARY_DIRECTORY, NAR_LIBRARY_DIRECTORY
+ );
+ final NiFiProperties nifiProperties =
NiFiProperties.createBasicNiFiProperties(null, additionalProperties);
- // load the system bundle
systemBundle = SystemBundle.create(nifiProperties);
extensionManager = new StandardExtensionDiscoveringManager();
extensionManager.discoverExtensions(systemBundle,
Collections.emptySet());
}
@BeforeEach
- public void setup() throws Exception {
- String id = "id";
- String clazz =
"org.apache.nifi.controller.service.util.TestControllerService";
- ControllerServiceProvider provider = new
StandardControllerServiceProvider(null, null, Mockito.mock(FlowManager.class),
Mockito.mock(ExtensionManager.class));
- ControllerServiceNode node = createControllerService(clazz, id,
systemBundle.getBundleDetails().getCoordinate(), provider);
+ void setup() {
+ serviceProvider = new StandardControllerServiceProvider(scheduler,
null, flowManager, mock(ExtensionManager.class));
+ final ControllerServiceNode node = createControllerService(SERVICE_ID,
TEST_SERVICE_CLASS, systemBundle.getBundleDetails().getCoordinate(),
serviceProvider);
proxied = node.getProxiedControllerService();
implementation = node.getControllerServiceImplementation();
}
- private ControllerServiceNode createControllerService(final String type,
final String id, final BundleCoordinate bundleCoordinate, final
ControllerServiceProvider serviceProvider) {
- return new ExtensionBuilder()
- .identifier(id)
- .type(type)
- .bundleCoordinate(bundleCoordinate)
- .controllerServiceProvider(serviceProvider)
- .processScheduler(Mockito.mock(ProcessScheduler.class))
- .nodeTypeProvider(Mockito.mock(NodeTypeProvider.class))
- .validationTrigger(Mockito.mock(ValidationTrigger.class))
- .reloadComponent(Mockito.mock(ReloadComponent.class))
- .stateManagerProvider(Mockito.mock(StateManagerProvider.class))
- .extensionManager(extensionManager)
- .buildControllerService();
- }
-
@Test
- public void testCallProxiedOnPropertyModified() {
+ void testCallProxiedOnPropertyModified() {
assertThrows(UnsupportedOperationException.class,
() -> proxied.onPropertyModified(null, "oldValue",
"newValue"));
}
@Test
- public void testCallImplementationOnPropertyModified() {
+ void testCallImplementationOnPropertyModified() {
implementation.onPropertyModified(null, "oldValue", "newValue");
}
@Test
- public void testCallProxiedInitialized() throws InitializationException {
+ void testCallProxiedInitialized() {
assertThrows(UnsupportedOperationException.class,
() -> proxied.initialize(null));
}
@Test
- public void testCallImplementationInitialized() throws
InitializationException {
+ void testCallImplementationInitialized() throws InitializationException {
implementation.initialize(null);
}
- private ControllerServiceNode
populateControllerService(ControllerServiceNode requiredService) { //
Collection<ControllerServiceNode> serviceNodes) {
- ControllerServiceNode controllerServiceNode =
mock(ControllerServiceNode.class);
- List<ControllerServiceNode> requiredServices = new ArrayList<>();
- if (requiredService != null) {
- requiredServices.add(requiredService);
- }
-
when(controllerServiceNode.getRequiredControllerServices()).thenReturn(requiredServices);
- return controllerServiceNode;
- }
-
@Test
- public void testEnableControllerServicesAllAreEnabled() {
+ 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 List<ControllerServiceNode> serviceNodes = new ArrayList<>();
serviceNodes.add(populateControllerService(null));
serviceNodes.add(populateControllerService(null));
- provider.enableControllerServices(serviceNodes);
+ serviceProvider.enableControllerServices(serviceNodes);
verify(scheduler).enableControllerService(serviceNodes.get(0));
verify(scheduler).enableControllerService(serviceNodes.get(1));
}
@Test
- public void testEnableControllerServicesSomeAreEnabled() {
+ 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 List<ControllerServiceNode> serviceNodes = new ArrayList<>();
- ControllerServiceNode disabledController =
populateControllerService(null);
+ final 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
@@ -161,11 +163,80 @@ public class StandardControllerServiceProviderTest {
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));
+
+ serviceProvider.enableControllerServices(serviceNodes);
+
+ verify(scheduler,
times(0)).enableControllerService(serviceNodes.get(0));
+ verify(scheduler,
times(2)).enableControllerService(serviceNodes.get(1));
+ verify(scheduler,
times(0)).enableControllerService(serviceNodes.get(2));
+ verify(scheduler,
times(0)).enableControllerService(serviceNodes.get(3));
+ verify(scheduler,
times(1)).enableControllerService(serviceNodes.get(4));
+ }
+
+ @Timeout(10)
+ @Test
+ void testEnableControllerServicesDependencyNotEnabled() throws
InterruptedException {
+ final BundleCoordinate systemCoordinate =
systemBundle.getBundleDetails().getCoordinate();
+ final ControllerServiceNode primaryServiceNode =
createControllerService(SERVICE_ID, PRIMARY_SERVICE_CLASS, systemCoordinate,
serviceProvider);
+ serviceProvider.onControllerServiceAdded(primaryServiceNode);
+
+ final ControllerServiceNode secondaryServiceNode =
createControllerService(SECONDARY_SERVICE_ID, SECONDARY_SERVICE_CLASS,
systemCoordinate, serviceProvider);
+ serviceProvider.onControllerServiceAdded(secondaryServiceNode);
+
+ final Map<String, String> primaryProperties = Map.of(
+ SECONDARY_SERVICE_ENABLED_PROPERTY, Boolean.FALSE.toString(),
+ SECONDARY_SERVICE_PROPERTY, SECONDARY_SERVICE_ID
+ );
+ primaryServiceNode.setProperties(primaryProperties);
+ final List<ControllerServiceNode> serviceNodes =
List.of(primaryServiceNode);
+
+ // Enable Primary Service using Count Down Latch to signal completion
+ final CountDownLatch enabledLatch = new CountDownLatch(1);
+
when(scheduler.enableControllerService(eq(primaryServiceNode))).then(invocationOnMock
-> {
+ try (ScheduledExecutorService executorService =
Executors.newSingleThreadScheduledExecutor()) {
+ final CompletableFuture<Void> nodeEnabledFuture =
primaryServiceNode.enable(executorService, 5000, true);
+ nodeEnabledFuture.join();
+ enabledLatch.countDown();
+ }
+ final CompletableFuture<Void> enableFuture = new
CompletableFuture<>();
+ enableFuture.complete(null);
+ return enableFuture;
+ });
+
+ serviceProvider.enableControllerServices(serviceNodes);
+
+ enabledLatch.await();
+ assertEquals(ControllerServiceState.ENABLED,
primaryServiceNode.getState());
+
+ assertEquals(ControllerServiceState.DISABLED,
secondaryServiceNode.getState(), "Secondary Service should remain disabled");
+ }
+
+ private ControllerServiceNode createControllerService(
+ final String identifier,
+ final String type,
+ final BundleCoordinate bundleCoordinate,
+ final ControllerServiceProvider serviceProvider
+ ) {
+ return new ExtensionBuilder()
+ .identifier(identifier)
+ .type(type)
+ .bundleCoordinate(bundleCoordinate)
+ .controllerServiceProvider(serviceProvider)
+ .processScheduler(mock(ProcessScheduler.class))
+ .nodeTypeProvider(mock(NodeTypeProvider.class))
+ .validationTrigger(mock(ValidationTrigger.class))
+ .reloadComponent(mock(ReloadComponent.class))
+ .stateManagerProvider(mock(StateManagerProvider.class))
+ .extensionManager(extensionManager)
+ .buildControllerService();
+ }
+
+ private ControllerServiceNode populateControllerService(final
ControllerServiceNode requiredService) {
+ final ControllerServiceNode controllerServiceNode =
mock(ControllerServiceNode.class);
+ if (requiredService != null) {
+ final List<ControllerServiceNode> requiredServices =
List.of(requiredService);
+
when(controllerServiceNode.getRequiredControllerServices()).thenReturn(requiredServices);
+ }
+ return controllerServiceNode;
}
}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/PrimaryService.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/PrimaryService.java
new file mode 100644
index 0000000000..4a67d140a4
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/PrimaryService.java
@@ -0,0 +1,49 @@
+/*
+ * 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.controller.service.mock;
+
+import org.apache.nifi.components.PropertyDescriptor;
+import org.apache.nifi.controller.AbstractControllerService;
+import org.apache.nifi.controller.ControllerService;
+
+import java.util.List;
+
+public class PrimaryService extends AbstractControllerService {
+
+ public static final PropertyDescriptor SECONDARY_SERVICE_ENABLED = new
PropertyDescriptor.Builder()
+ .name("Secondary Service Enabled")
+ .required(true)
+ .allowableValues(Boolean.TRUE.toString(), Boolean.FALSE.toString())
+ .build();
+
+ public static final PropertyDescriptor SECONDARY_SERVICE = new
PropertyDescriptor.Builder()
+ .name("Secondary Service")
+ .identifiesControllerService(ControllerService.class)
+ .required(true)
+ .dependsOn(SECONDARY_SERVICE_ENABLED, Boolean.TRUE.toString())
+ .build();
+
+ private static final List<PropertyDescriptor> PROPERTY_DESCRIPTORS =
List.of(
+ SECONDARY_SERVICE_ENABLED,
+ SECONDARY_SERVICE
+ );
+
+ @Override
+ protected List<PropertyDescriptor> getSupportedPropertyDescriptors() {
+ return PROPERTY_DESCRIPTORS;
+ }
+}
diff --git
a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/SecondaryService.java
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/SecondaryService.java
new file mode 100644
index 0000000000..2a25b89ad1
--- /dev/null
+++
b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/controller/service/mock/SecondaryService.java
@@ -0,0 +1,23 @@
+/*
+ * 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.controller.service.mock;
+
+import org.apache.nifi.controller.AbstractControllerService;
+
+public class SecondaryService extends AbstractControllerService {
+
+}