diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicate.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicate.java index fc5515c0e1..8f2ed91ef1 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicate.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicate.java @@ -42,21 +42,22 @@ class ComponentNodeDefinitionPredicate implements Predicate { } private boolean isComponentFromType(final T componentNode, final ExtensionDefinition extensionDefinition) { - final String componentType = componentNode.getComponentType(); + final String componentClassName = componentNode.getCanonicalClassName(); final BundleCoordinate componentCoordinate = componentNode.getBundleCoordinate(); - final String extensionDefinitionType = extensionDefinition.getImplementationClassName(); + final String extensionDefinitionClassName = extensionDefinition.getImplementationClassName(); final BundleCoordinate extensionDefinitionCoordinate = extensionDefinition.getBundle().getBundleDetails().getCoordinate(); if (PythonBundle.isPythonCoordinate(componentCoordinate)) { + final String componentType = componentNode.getComponentType(); final String pythonComponentType = "python." + componentType; - return pythonComponentType.equals(extensionDefinitionType) && componentCoordinate.equals(extensionDefinitionCoordinate); + return pythonComponentType.equals(extensionDefinitionClassName) && componentCoordinate.equals(extensionDefinitionCoordinate); } else if (componentNode.isExtensionMissing()) { - return componentType.equals(extensionDefinitionType) + return componentClassName.equals(extensionDefinitionClassName) && componentCoordinate.getGroup().equals(extensionDefinitionCoordinate.getGroup()) && componentCoordinate.getId().equals(extensionDefinitionCoordinate.getId()); } else { - return componentType.equals(extensionDefinitionType) && componentCoordinate.equals(extensionDefinitionCoordinate); + return componentClassName.equals(extensionDefinitionClassName) && componentCoordinate.equals(extensionDefinitionCoordinate); } } } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicateTest.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicateTest.java index 0ebbd51c27..8c185b8c4a 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicateTest.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/nar/ComponentNodeDefinitionPredicateTest.java @@ -34,7 +34,8 @@ import static org.mockito.Mockito.when; public class ComponentNodeDefinitionPredicateTest { - private static final String STANDARD_PROCESSOR_TYPE = "org.apache.nifi.MyProcessor"; + private static final String STANDARD_PROCESSOR_COMPONENT_TYPE = "MyProcessor"; + private static final String STANDARD_PROCESSOR_CLASS_NAME = "org.apache.nifi." + STANDARD_PROCESSOR_COMPONENT_TYPE; private static final BundleCoordinate STANDARD_BUNDLE_COORDINATE_V1 = new BundleCoordinate("org.apache.nifi", "my-processors-nar", "1.0.0"); private static final BundleCoordinate STANDARD_BUNDLE_COORDINATE_V2 = new BundleCoordinate("org.apache.nifi", "my-processors-nar", "2.0.0"); @@ -55,7 +56,7 @@ public class ComponentNodeDefinitionPredicateTest { when(standardBundle.getBundleDetails()).thenReturn(standardBundleDetails); standardProcessorDefinition = mock(ExtensionDefinition.class); - when(standardProcessorDefinition.getImplementationClassName()).thenReturn(STANDARD_PROCESSOR_TYPE); + when(standardProcessorDefinition.getImplementationClassName()).thenReturn(STANDARD_PROCESSOR_CLASS_NAME); when(standardProcessorDefinition.getBundle()).thenReturn(standardBundle); final BundleDetails pythonBundleDetails = mock(BundleDetails.class); @@ -72,7 +73,7 @@ public class ComponentNodeDefinitionPredicateTest { @Test public void testWhenComponentNodeMatchesDefinition() { final ComponentNode componentNode = mock(ComponentNode.class); - when(componentNode.getComponentType()).thenReturn(STANDARD_PROCESSOR_TYPE); + when(componentNode.getCanonicalClassName()).thenReturn(STANDARD_PROCESSOR_CLASS_NAME); when(componentNode.getBundleCoordinate()).thenReturn(STANDARD_BUNDLE_COORDINATE_V1); final Predicate predicate = new ComponentNodeDefinitionPredicate(Set.of(standardProcessorDefinition)); @@ -82,7 +83,7 @@ public class ComponentNodeDefinitionPredicateTest { @Test public void testWhenComponentNodeTypeDoesNotMatchDefinition() { final ComponentNode componentNode = mock(ComponentNode.class); - when(componentNode.getComponentType()).thenReturn("com.SomeOtherProcessor"); + when(componentNode.getCanonicalClassName()).thenReturn("com.SomeOtherProcessor"); when(componentNode.getBundleCoordinate()).thenReturn(STANDARD_BUNDLE_COORDINATE_V1); final Predicate predicate = new ComponentNodeDefinitionPredicate(Set.of(standardProcessorDefinition)); @@ -92,7 +93,7 @@ public class ComponentNodeDefinitionPredicateTest { @Test public void testWhenComponentNodeCoordinateDoesMatchDefinition() { final ComponentNode componentNode = mock(ComponentNode.class); - when(componentNode.getComponentType()).thenReturn(STANDARD_PROCESSOR_TYPE); + when(componentNode.getCanonicalClassName()).thenReturn(STANDARD_PROCESSOR_CLASS_NAME); when(componentNode.getBundleCoordinate()).thenReturn(STANDARD_BUNDLE_COORDINATE_V2); final Predicate predicate = new ComponentNodeDefinitionPredicate(Set.of(standardProcessorDefinition)); @@ -102,7 +103,7 @@ public class ComponentNodeDefinitionPredicateTest { @Test public void testWhenComponentNodeIsMissingAndCompatibleBundle() { final ComponentNode componentNode = mock(ComponentNode.class); - when(componentNode.getComponentType()).thenReturn(STANDARD_PROCESSOR_TYPE); + when(componentNode.getCanonicalClassName()).thenReturn(STANDARD_PROCESSOR_CLASS_NAME); when(componentNode.getBundleCoordinate()).thenReturn(STANDARD_BUNDLE_COORDINATE_V2); when(componentNode.isExtensionMissing()).thenReturn(true); diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java index cf620a52d0..53623a5619 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-nar-utils/src/main/java/org/apache/nifi/nar/StandardExtensionDiscoveringManager.java @@ -830,7 +830,7 @@ public class StandardExtensionDiscoveringManager implements ExtensionDiscovering throw new IllegalArgumentException("Class cannot be null"); } final Set extensions = definitionMap.get(definition); - return (extensions == null) ? Collections.emptySet() : extensions; + return (extensions == null) ? Collections.emptySet() : new HashSet<>(extensions); } @Override diff --git a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/nar/NarUploadStandaloneIT.java b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/nar/NarUploadStandaloneIT.java index 2cf2876090..45a03955b8 100644 --- a/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/nar/NarUploadStandaloneIT.java +++ b/nifi-system-tests/nifi-system-test-suite/src/test/java/org/apache/nifi/tests/system/nar/NarUploadStandaloneIT.java @@ -28,8 +28,11 @@ import org.apache.nifi.web.api.entity.ControllerServiceTypesEntity; import org.apache.nifi.web.api.entity.NarDetailsEntity; import org.apache.nifi.web.api.entity.NarSummariesEntity; import org.apache.nifi.web.api.entity.NarSummaryEntity; +import org.apache.nifi.web.api.entity.ProcessorEntity; import org.apache.nifi.web.api.entity.ProcessorTypesEntity; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.io.File; import java.io.IOException; @@ -43,8 +46,11 @@ import static org.junit.jupiter.api.Assertions.assertThrows; public class NarUploadStandaloneIT extends NiFiSystemIT { + private static final Logger logger = LoggerFactory.getLogger(NarUploadStandaloneIT.class); + private static final String NAR_PROVIDER_NARS_LOCATION = "target/nifi-nar-provider-nars"; private static final String PROCESSORS_NAR_ID = "nifi-nar-provider-processors-nar"; + private static final String PROCESSOR_CLASS_NAME = "org.apache.nifi.nar.provider.GetClassLoaderInfo"; private static final String CONTROLLER_SERVICE_API_NAR_ID = "nifi-nar-provider-service-api-nar"; private static final String CONTROLLER_SERVICE_NAR_ID = "nifi-nar-provider-service-nar"; @@ -116,6 +122,14 @@ public class NarUploadStandaloneIT extends NiFiSystemIT { assertNotNull(serviceApiNarDetails.getDependentCoordinates()); assertEquals(2, serviceApiNarDetails.getDependentCoordinates().size()); + // Create instance of the custom processor + final NarCoordinateDTO uploadedProcessorCoordinate = uploadedProcessorsNar.getCoordinate(); + final ProcessorEntity customProcessor = getClientUtil().createProcessor(PROCESSOR_CLASS_NAME, uploadedProcessorCoordinate.getGroup(), + uploadedProcessorCoordinate.getArtifact(), uploadedProcessorCoordinate.getVersion()); + assertNotNull(customProcessor.getComponent()); + assertNotNull(customProcessor.getComponent().getExtensionMissing()); + assertFalse(customProcessor.getComponent().getExtensionMissing()); + // Verify service API NAR can't be replaced while other NARs depend on it assertThrows(NiFiClientException.class, () -> narUploadUtil.uploadNar(narsLocation, CONTROLLER_SERVICE_API_NAR_ID)); @@ -137,6 +151,27 @@ public class NarUploadStandaloneIT extends NiFiSystemIT { // Verify no NARs exist narUploadUtil.verifyNarSummaries(0); + + // Verify custom processor is ghosted + final String customProcessorId = customProcessor.getId(); + waitFor(() -> { + final ProcessorEntity customProcessorAfterDelete = getNifiClient().getProcessorClient().getProcessor(customProcessorId); + logger.info("Waiting for processor {} to be considered missing", customProcessorId); + return customProcessorAfterDelete.getComponent().getExtensionMissing(); + }); + + // Restore NARs + narUploadUtil.uploadNar(narsLocation, CONTROLLER_SERVICE_API_NAR_ID); + narUploadUtil.uploadNar(narsLocation, CONTROLLER_SERVICE_NAR_ID); + final NarSummaryDTO restoredProcessorsNar = narUploadUtil.uploadNar(narsLocation, PROCESSORS_NAR_ID); + waitFor(narUploadUtil.getWaitForNarStateSupplier(restoredProcessorsNar.getIdentifier(), NarState.INSTALLED)); + + // Verify processor is un-ghosted + waitFor(() -> { + final ProcessorEntity customProcessorAfterDelete = getNifiClient().getProcessorClient().getProcessor(customProcessorId); + logger.info("Waiting for processor {} to be considered not missing", customProcessorId); + return !customProcessorAfterDelete.getComponent().getExtensionMissing(); + }); } private boolean matchingBundles(final BundleDTO bundleDTO, final NarCoordinateDTO narCoordinateDTO) {