Skip to content

NIFI-16310 Preserve Controller Services created by migrateProperties during versioned flow synchronization - #11670

Open
awelless wants to merge 1 commit into
apache:mainfrom
awelless:NIFI-16310-preserve-migration-created-controller-services
Open

awelless wants to merge 1 commit into
apache:mainfrom
awelless:NIFI-16310-preserve-migration-created-controller-services

Conversation

@awelless

Copy link
Copy Markdown
Contributor

Summary

NIFI-16310

Controller Services created by migrateProperties method were always recreated during a versioned process group upgrade, causing the trigger of @OnRemoved hook and loss of component state.
Now migration-created services are matched with their counterparts in a versioned process group. If match is successful, a local controller service assumes a versioned id without being recreated.

If no match happens, a controller services is kept unversioned, but not removed.

Verification

  • Synchronizer unit tests.
  • System tests.

Additional changes

FileSystemFlowRegistryClient which is used in system tests was moved into a separate NAR, to be available before and after NAR swap.

Suggested review order

  • MigrationCreatedControllerServiceVersioningIT and its dependencies to see the relevant use cases.
  • StandardVersionedComponentSynchronizer for actual implementation.
  • StandardVersionedComponentSynchronizerTest to see the desired behavior for edge cases.

Tracking

Please complete the following tracking steps prior to pull request creation.

Issue Tracking

Pull Request Tracking

  • Pull Request title starts with Apache NiFi Jira issue number, such as NIFI-00000
  • Pull Request commit message starts with Apache NiFi Jira issue number, as such NIFI-00000
  • Pull request contains commits signed with a registered key indicating Verified status

Pull Request Formatting

  • Pull Request based on current revision of the main branch
  • Pull Request refers to a feature branch with one commit containing changes

Verification

Please indicate the verification steps performed prior to pull request creation.

Build

  • Build completed using ./mvnw clean install -P contrib-check
    • JDK 21
    • JDK 25

Licensing

  • New dependencies are compatible with the Apache License 2.0 according to the License Policy
  • New dependencies are documented in applicable LICENSE and NOTICE files

Documentation

  • Documentation formatting appears as expected in rendered files

* can observe whether store contents survive flow and runtime upgrades.
*/
@InputRequirement(Requirement.INPUT_FORBIDDEN)
public class MigrateToControllerService extends AbstractProcessor {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initially I wanted to reuse MigrateProperties processor, but some of its modifications blocked flow upgrade, so I proceeded with a dedicated processor - controller service pair

Comment on lines +1381 to +1382
LOG.info("Keeping {} in {} because it was created by property migration and is not present in the proposed flow",
service, group);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reckon that we shouldn't just drop migration services if they're absent in a versioned flow. So that if we run into any edge case, the service is preserved and a user has a chance to resolve the issue.

@exceptionfactory exceptionfactory left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for working on this issue @awelless. The Synchronizer changes have a number of nuances, and although the general direction seems sound, I plan on taking another pass through the approach. I noted a handful of recommendations, mostly focusing on the tests.

Comment on lines +33 to +37
/**
* Pre-upgrade shape of a processor that keeps its store location in a plain property.
* The post-upgrade shape of the same processor, in the alternate-config extensions bundle,
* migrates that property into a Controller Service.
*/

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It would be better to use this as the Capability Description annotation, instead of the class comment for semantic definition.

* declares the service, so the flow relies entirely on property migration to create it, and the later version only
* adds an unrelated processor.
*/
public class MigrationCreatedControllerServiceVersioningIT extends AbstractNarSwapMigrationIT {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For a new test class, the public modifiers are not needed at the class and method level.

* Polls the given condition until it holds, failing the test once the timeout elapses. Conditions query a NiFi that
* may still be starting up or replacing components, so a failing query is treated as the condition not holding yet.
*/
private void waitForCondition(final ExceptionalBooleanSupplier condition, final String conditionDescription) throws InterruptedException {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method appears to be duplicative of the waitFor() method in the base class.

getClientUtil().assertFlowUpToDate(flow.groupId());

final ControllerServiceEntity serviceAfterUpgrade = waitForSingleStoreService(flow.groupId());
assertEquals(DECLARED_SERVICE_VERSIONED_ID, serviceAfterUpgrade.getComponent().getVersionedComponentId(),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Many of these assertion methods are more verbose than necessary and should be shortened.

}

@Override
public synchronized void append(final String row) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this synchronized, is it necessary?

when(serviceNode.getCanonicalClassName()).thenReturn("ControllerServiceImpl");
when(serviceNode.getProperties()).thenReturn(Map.of(descriptor, new PropertyConfiguration("123", null, null, null)));
when(serviceNode.getRawPropertyValues()).thenReturn(Map.of(descriptor, "123"));
when(serviceNode.getVersionedComponentId()).thenReturn(Optional.empty());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Repeated literals in multiple methods should be declared as static final variables and reused.

*/
private void assignVersionedIdsToMigrationCreatedControllerServices(final ProcessGroup group, final VersionedProcessGroup proposed) {
final Collection<ControllerServiceNode> groupServices = group.getControllerServices(false);
if (groupServices == null || groupServices.isEmpty()) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method should never return null

localService.getVersionedComponentId().ifPresent(claimedVersionedIds::add);
}

final Map<String, VersionedConfigurableExtension> proposedComponentsByVersionedId = indexByVersionedId(proposed.getControllerServices(), proposed.getProcessors());

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is on the longer side, it looks an opportunity to break it up into two methods after the initial checks.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants