Skip to content

Support alternative extension implementations#5816

Merged
kkondaka merged 5 commits into
opensearch-project:mainfrom
kkondaka:extensions-provider
Jun 30, 2025
Merged

Support alternative extension implementations#5816
kkondaka merged 5 commits into
opensearch-project:mainfrom
kkondaka:extensions-provider

Conversation

@kkondaka
Copy link
Copy Markdown
Collaborator

Description

Support alternative extension implementations. This allows multiple implementations for the same extension class with the extension with config provided is preferred over extension(s) without config provided. If multiple extensions with config are provided, the behavior is same as today, which is, it depends on the order in which the extensions are loaded. In future we will have a way to handle this.

Issues Resolved

Resolves #5792

Check List

  • New functionality includes testing.
  • New functionality has a documentation issue. Please link to it in this PR.
    • New functionality has javadoc added
  • [X ] Commits are signed with a real name per the DCO

By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license.
For more information on following Developer Certificate of Origin and signing off your commits, please check here.

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
return null;
}
})
.sorted(Comparator.comparing(ExtensionPluginWithContext::isConfigured).reversed())
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

It would be ideal to have this Comparator in another class so that it can grow over time without having to change this method. And it could have it's own unit tests.

You could create a named bean in PluginCreatorContext.

@Bean(name = "extensionsLoaderComparator")
public Comparator<ExtensionLoader.ExtensionPluginWithContext> extensionsLoaderComparator() {
  return Comparator.comparing(ExtensionPluginWithContext::isConfigured).reversed();
}

private static final ExtensionProvider.Context EMPTY_CONTEXT = new EmptyContext();
private final GenericApplicationContext sharedApplicationContext;
private final GenericApplicationContext coreApplicationContext;
private Map<Class, Optional<Object>> providers;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think a Set<Class<? extends ExtensionProvider>> should be sufficient. You don't need the value and you can avoid the call to extensionProvider.provideInstance(EMPTY_CONTEXT).

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
@kkondaka kkondaka marked this pull request as ready for review June 25, 2025 23:58
this.pluginErrorsHandler = pluginErrorsHandler;
}

ExtensionLoader(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Let's only have one constructor.

You can add this to the constructor above:

@Named("extensionsLoaderComparator" final Comparator<ExtensionLoader.ExtensionPluginWithContext> extensionsLoaderComparator


List<? extends ExtensionPlugin> result = null;
if (extensionPluginsWithContext != null && extensionPluginsWithContext.size() > 1 &&
extensionsLoaderComparator != null) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I think we don't want this null check. This sorter always needs to be there. We could even add this to the constructor:

this.extensionsLoaderComparator = Objects.requireNonNull(extensionsLoaderComparator);

I think we can just drop the entire conditional.

return new PluginCreator(pluginConfigurationObservableRegister);
}

@Bean(name = "extensionsLoaderComparator")
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You can add a simple unit test for this in PluginCreatorContextTest (add it if doesn't exist).

final String expectedPluginName = "test_extension_with_config";
when(extensionPluginConfigurationConverter.convert(eq(true), eq(TestExtensionConfig.class),
anyString())).thenReturn(new TestExtensionConfig());
//when(extensionPluginConfigurationConverter.convert(eq(true), eq(TestExtension.class),
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Removed commented out code.

@BeforeEach
void setUp() {
pluginErrorCollector = new PluginErrorCollector();
pluginCreatorContext = new PluginCreatorContext();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You could use a simple Comparator that sorts by name. That way these tests are not coupled with the sort logic.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Yes, but this way we are testing the actual sorting logic. I think this is better.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That is fine. But, we should have tests for the sort specifically. As the sort logic changes over time it will be much easier to isolate to just the Comparator test.


private PluginCreatorContext pluginCreatorContext;

@Mock
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You are not using this as a mock, so you don't need this annotation.

private ArgumentCaptor<Collection<PluginError>> pluginErrorsArgumentCaptor;
private PluginErrorCollector pluginErrorCollector;

private PluginCreatorContext pluginCreatorContext;
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

You don't need this field. You can move it to a local variable in setUp. Also, you might not need it if you use a different Comparator.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

I think I will keep the same comparator.

@dlvenable
Copy link
Copy Markdown
Member

@kkondaka , Once you address these comments, this should be good to go. I think this is a solid change to extensions.

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
@Test
public void test_extensionsLoaderComparator() {
Comparator<ExtensionLoader.ExtensionPluginWithContext> extensionsLoaderComparator = pluginCreatorContext.extensionsLoaderComparator();
assertNotNull(extensionsLoaderComparator);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

We should include tests that verify the sorting here. That way we can verify different sort combinations.

You don't have to test using sorts per se.

It could be as simple as these some of these combinations on compareTo:

  • Two objects both with isConfigured==true should have compareTo==0
  • Two objects both with isConfigured==false should have compareTo==0
  • Two objects, one with isConfigured==false and isConfigured==true, compareTo < 0 when true is on the left
  • Two objects, one with isConfigured==false and isConfigured==true, compareTo > 0 when true is on the right

Copy link
Copy Markdown
Member

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 put these as different @Test cases, but I'm ok with it.

@BeforeEach
void setUp() {
pluginErrorCollector = new PluginErrorCollector();
pluginCreatorContext = new PluginCreatorContext();
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

That is fine. But, we should have tests for the sort specifically. As the sort logic changes over time it will be much easier to isolate to just the Comparator test.

kkondaka added 2 commits June 26, 2025 23:06
Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
@Test
public void test_extensionsLoaderComparator() {
Comparator<ExtensionLoader.ExtensionPluginWithContext> extensionsLoaderComparator = pluginCreatorContext.extensionsLoaderComparator();
assertNotNull(extensionsLoaderComparator);
Copy link
Copy Markdown
Member

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 put these as different @Test cases, but I'm ok with it.

@kkondaka kkondaka merged commit 9409ba7 into opensearch-project:main Jun 30, 2025
47 of 51 checks passed
@kkondaka kkondaka deleted the extensions-provider branch July 1, 2025 17:04
JonahCalvo pushed a commit to JonahCalvo/os-data-prepper that referenced this pull request Jul 17, 2025
* Support alternative extension implementations

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>

* Addressed review comments and added tests

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>

* Addressed review comments and added tests

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>

* Addressed review comments

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>

* Added tests

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>

---------

Signed-off-by: Krishna Kondaka <krishkdk@amazon.com>
Signed-off-by: Jonah Calvo <caljonah@amazon.com>
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.

Support alternative extension implementations

4 participants