[ 
https://issues.apache.org/jira/browse/NIP-49?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel
 ]

Mark Payne resolved NIP-49.
---------------------------
    Resolution: Fixed

> Connector Upgrade Drain and Flow Definition Persistence
> -------------------------------------------------------
>
>                 Key: NIP-49
>                 URL: https://issues.apache.org/jira/browse/NIP-49
>             Project: NiFi Improvement Proposal
>          Issue Type: Improvement
>            Reporter: Bob Paulin
>            Priority: Major
>         Attachments: image-2026-10-12-08-39-24-494.png
>
>
> This proposal complements NIP-47. NIP-47 relocates FlowFiles after a flow has 
> been replaced. This proposal covers the step before that: persisting the flow 
> a connector last applied, restoring it when different Connector code is 
> loaded, and giving the connector a chance to empty connections the upgrade 
> will remove before the new flow is installed.
> h2. Motivation
> A Connector version change can load different code and rebuild the managed 
> flow from configuration. Whenever the loaded version differs from the version 
> that built the authoritative flow, the framework must detect the change and 
> perform flow evolution before starting the Connector.
> Changing a Connector version at runtime is not currently supported, but may 
> be supported in the near future. This proposal does not define how or when a 
> version is selected and does not introduce runtime version changes. It 
> defines one persistence and evolution contract that applies whenever the 
> loaded version changes.
> Without that contract, connections from the previous flow can disappear and 
> any FlowFiles still queued in them become orphaned. A Connector that needs to 
> finish processing that data, or discard it deliberately, has no hook that 
> runs while the previous flow is still installed.
> The previous flow also is not written to {{flow.json}} except while the 
> Connector is in troubleshooting. When different code is loaded, the framework 
> rebuilds the flow by calling that code, so the queues have nothing to 
> reattach to.
> h2. *Proposed nifi-api changes*
> All additions are backward compatible. Existing connectors compile and run 
> unchanged.
> h3. *FlowEvolutionContext*
> {code:java}
> public interface FlowEvolutionContext {
>     /**
>      * The pre-upgrade Active flow. Its flow, parameters, and bundle are from 
> the authoritative snapshot.
>      * Its configuration has already been migrated and filtered for the 
> loaded Connector version.
>      */
>     FlowContext getPreUpgradeActiveFlowContext();
>     /** The bundle of the loaded Connector code, whether loaded at startup or 
> changed at runtime. */
>     Bundle getPostUpgradeBundle();
> }
> {code}
> The context provides the pre-upgrade flow and the loaded Connector bundle. If 
> flow evolution fails, the framework replaces the supplied context with a 
> wrapper that rejects every method declared by {{{}FlowEvolutionContext{}}}, 
> including {{getPreUpgradeActiveFlowContext()}} and 
> {{{}getPostUpgradeBundle(){}}}. This covers timeout, cancellation, and any 
> other evolution failure, so a connector that retained the context cannot 
> continue using it.
> h3. *Connector.prepareForFlowEvolution*
> {code:java}
> default CompletableFuture<Void> prepareForFlowEvolution(FlowEvolutionContext 
> context) throws FlowUpdateException {
>     return CompletableFuture.completedFuture(null);
> } {code}
> The framework invokes this hook while the pre-upgrade flow is still installed 
> and before the upgraded flow removes connections. The method returns promptly 
> with a non-null future that owns all preparation work. The hook empties 
> pre-upgrade connections the upgraded flow no longer contains before that 
> future completes. Synchronous setup can throw {{{}FlowUpdateException{}}}; 
> asynchronous failures complete the future exceptionally. The hook is not 
> called when the Connector leaves Troubleshooting; the managed flow must 
> already be empty then.
> The framework cancels the future on evolution timeout, user drain 
> cancellation or stop, coordinator interruption, and cluster monitoring 
> failure. {{CompletableFuture.cancel(true)}} does not interrupt arbitrary 
> executor tasks, so connector implementations must make delegated work 
> cooperatively cancellable and must not complete the future while 
> fire-and-forget work can still mutate the pre-upgrade flow. Connector-created 
> asynchronous threads must preserve the Connector NAR classloader context.
> Only normal completion advances phase one. The framework then predicts the 
> upgraded flow with {{getActiveFlow}} and the Working context, and fails 
> evolution if a removed connection still holds FlowFiles.
> h3. *Connector.getActiveFlow*
> The method must remain idempotent and build the flow only from the supplied 
> context's configuration. When called with the Working context, its result 
> predicts what {{applyUpdate}} will install.
> h3. *VersionedConnector*
>  * managedProcessGroup, managedParameterContext, and managedFlowBundle 
> persist the authoritative flow, its parameters, and the Connector version 
> that built it.
>  * troubleshootingProcessGroup separately persists user edits made in 
> troubleshooting.
>  * bundle identifies the Connector code to load and remains independent from 
> managedFlowBundle.
> ConnectorInitializationContext is unchanged. It does not gain a bundle getter.
> h2. *Framework behavior*
> h3. *Persistence*
> VersionedComponentFlowMapper.mapConnector writes:
>  * {{{}managedParameterContext{}}}, {{{}managedFlowBundle{}}}, and 
> {{activeFlowConfiguration}} from one immutable in-memory 
> {{{}ConnectorFlowSnapshot{}}}, never by combining values from different 
> lifecycle phases. The record is a complete Connector flow-persistence value 
> that can be prepared, published as authoritative, or retained for rollback.
>  * {{managedProcessGroup}} from that snapshot, except while the connector is 
> in Troubleshooting, when it is written from the live managed group.
>  * {{bundle}} from the loaded Connector coordinate and {{managedFlowBundle}} 
> from the snapshot. While an upgrade is pending, these fields differ.
> The authoritative snapshot is set after every successful apply, after an 
> upgraded snapshot is committed, and when a persisted authoritative flow is 
> loaded. {{getAuthoritativeFlowSnapshot()}} names the currently committed 
> instance; prepared and rollback snapshots use the same 
> {{ConnectorFlowSnapshot}} value type. It is captured on entering 
> troubleshooting only when none is held. While troubleshooting, the persisted 
> managed group is the live edits and the snapshot bundle stays the version 
> that built the flow. On exiting troubleshooting, the snapshot is refreshed 
> from the installed Active flow, including when an upgrade was pending.
> h3. *Detection*
> During synchronization, the framework resolves the snapshot bundle from 
> {{{}managedFlowBundle{}}}, or infers it once from the persisted {{bundle}} 
> when a managed group exists, including a group persisted while 
> Troubleshooting. The inferred value is frozen before the loaded version can 
> change. No value is inferred when there is no managed group.
> Troubleshooting is restored from {{managedProcessGroup}} and defers evolution 
> until the Connector exits troubleshooting. The same detection rule applies 
> whenever the loaded version changes.
> h3. *Replacing the flow*
> Each node waits for the {{prepareForFlowEvolution}} future before applying 
> the working configuration and preparing the incoming snapshot. A timeout or 
> cancellation cancels the future, retains the authoritative pre-upgrade 
> snapshot, and leaves the Connector stopped and pending. After cluster-wide 
> commit authorization, every node atomically publishes the incoming snapshot 
> and persists its own flow, and START waits for cluster-wide persistence 
> completion before proceeding to running. Publishing the new snapshot makes 
> its managed-flow bundle equal the loaded bundle, so pending becomes false 
> without clearing a stored flag.
> If preparing or committing the upgraded flow fails, the outgoing snapshot 
> remains authoritative or is restored, and the node stays stopped and pending. 
> The existing flow-update verification remains as a backstop for a FlowFile 
> that arrives after the check, or for an {{applyUpdate}} that installs 
> something other than {{getActiveFlow}} on the working context.
> *Exiting troubleshooting while an upgrade is pending*
> {{prepareForFlowEvolution}} is not run. Every queue in the managed flow, 
> including child groups, must be empty. The exit is rejected while any 
> FlowFiles remain, and the Connector stays in Troubleshooting with the upgrade 
> still pending. When the queues are empty, the same synchronous exit used 
> otherwise installs {{{}getActiveFlow{}}}, captures the snapshot with the 
> loaded bundle, and leaves the Connector {{STOPPED}} and no longer pending. 
> This covers a connector that was in troubleshooting when the runtime was 
> upgraded, and a connector whose drain was cancelled so the user could enter 
> troubleshooting from {{{}STOPPED{}}}.
> The hook is skipped because the empty-queue check already makes the 
> replacement safe, and the shipped hooks only drain. Draining on exit would 
> start components the user added and stopped, against a flow whose structure 
> the hook cannot rely on after those edits. The user can empty the queues 
> before exiting, and the exit stays a synchronous verify-then-apply across the 
> cluster.
> h2. *New configuration*
> nifi.components.connectors.flow.evolution.timeout bounds 
> prepareForFlowEvolution. The default is 30 mins.



--
This message was sent by Atlassian Jira
(v8.20.10#820010)

Reply via email to