From 63a37a9269c50be1a8eebb83c4d93057dfa98147 Mon Sep 17 00:00:00 2001 From: Bob Paulin Date: Thu, 30 Apr 2026 07:15:28 -0500 Subject: [PATCH] NIFI-15891: Connector Asset Sync on Create and Restore --- .../StandardConnectorRepository.java | 14 +++++- .../TestStandardConnectorRepository.java | 47 +++++++++++++++++-- 2 files changed, 57 insertions(+), 4 deletions(-) diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java index 0b0382ccfc22..330cf1ee957b 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/main/java/org/apache/nifi/components/connector/StandardConnectorRepository.java @@ -96,13 +96,25 @@ public void verifyCreate(final String connectorId) { @Override public void addConnector(final ConnectorNode connector) { - syncFromProvider(connector); + syncAssetsFromProvider(connector); connectors.put(connector.getIdentifier(), connector); } @Override public void restoreConnector(final ConnectorNode connector) { connectors.put(connector.getIdentifier(), connector); + + // Reconcile asset binaries and working configuration with the provider so that connectors + // restored from the persisted flow do not require an explicit applyUpdate to download + // assets that are missing locally or whose digests are unknown. Provider failures during + // restore must not prevent the connector from being added to the repository, since the + // provider may be temporarily unavailable at startup. + try { + syncAssetsFromProvider(connector); + } catch (final Exception e) { + logger.warn("Failed to synchronize assets from provider for restored {}; continuing without sync", connector, e); + } + logger.debug("Successfully restored {}", connector); } diff --git a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java index f6e502008523..e4534add357e 100644 --- a/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java +++ b/nifi-framework-bundle/nifi-framework/nifi-framework-core/src/test/java/org/apache/nifi/components/connector/TestStandardConnectorRepository.java @@ -51,6 +51,8 @@ import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; @@ -696,10 +698,49 @@ public void testSyncAssetsFromProviderCallsSyncAssetsThenReloads() { repository.syncAssetsFromProvider(connector); - // Step 1: provider.syncAssets() called - verify(provider).syncAssets("connector-1"); + // Step 1: provider.syncAssets() called (also invoked by addConnector, so happens at least twice) + verify(provider, atLeast(2)).syncAssets("connector-1"); // Step 2: provider.load() called to reload updated config (may also have been called during addConnector) - verify(provider, org.mockito.Mockito.atLeastOnce()).load("connector-1"); + verify(provider, atLeastOnce()).load("connector-1"); + } + + @Test + public void testAddConnectorSyncsAssetsFromProvider() { + final ConnectorConfigurationProvider provider = mock(ConnectorConfigurationProvider.class); + when(provider.load("connector-1")).thenReturn(Optional.empty()); + final StandardConnectorRepository repository = createRepositoryWithProvider(provider); + + final ConnectorNode connector = createSimpleConnectorNode("connector-1", "Test"); + repository.addConnector(connector); + + verify(provider).syncAssets("connector-1"); + verify(provider).load("connector-1"); + } + + @Test + public void testRestoreConnectorSyncsAssetsFromProvider() { + final ConnectorConfigurationProvider provider = mock(ConnectorConfigurationProvider.class); + when(provider.load("connector-1")).thenReturn(Optional.empty()); + final StandardConnectorRepository repository = createRepositoryWithProvider(provider); + + final ConnectorNode connector = createSimpleConnectorNode("connector-1", "Test"); + repository.restoreConnector(connector); + + assertEquals(1, repository.getConnectors().size()); + verify(provider).syncAssets("connector-1"); + } + + @Test + public void testRestoreConnectorTolerantOfProviderFailure() { + final ConnectorConfigurationProvider provider = mock(ConnectorConfigurationProvider.class); + doThrow(new ConnectorConfigurationProviderException("Provider unavailable")) + .when(provider).syncAssets("connector-1"); + final StandardConnectorRepository repository = createRepositoryWithProvider(provider); + + final ConnectorNode connector = createSimpleConnectorNode("connector-1", "Test"); + repository.restoreConnector(connector); + + assertEquals(1, repository.getConnectors().size(), "Restore must succeed even if provider sync fails"); } @Test