Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature/extensions] Extensibility support for getNamedWriteables extension point #3925

Merged
merged 25 commits into from
Aug 8, 2022

Conversation

joshpalis
Copy link
Member

@joshpalis joshpalis commented Jul 15, 2022

Description

Adds support for registering named writeable entries from extensions and transporting parse requests to extensions to deserialize named writeables that they have registered.

Issues Resolved

#3495

Check List

  • New functionality includes testing.
    • All tests pass
  • New functionality has been documented.
    • New functionality has javadoc added
  • Commits are signed per the DCO using --signoff

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.

…transport mechanisms to handle named writeable entry registration from extensions and parse requests.

Signed-off-by: Joshua Palis <jpalis@amazon.com>
…testing purposes, exten response will now only include the parse action status

Signed-off-by: Joshua Palis <jpalis@amazon.com>
Signed-off-by: Joshua Palis <jpalis@amazon.com>
Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

@saratvemulapalli saratvemulapalli changed the title Extensibility support for getNamedWriteables extension point [Feature/extensions] Extensibility support for getNamedWriteables extension point Jul 15, 2022
if (response.getRegistry().isEmpty() == false) {

// Extension has sent over entries to register, initialize inner category map
Map<Class<?>, Map<String, ExtensionReader>> categoryMap = new HashMap<>();
Copy link
Member

Choose a reason for hiding this comment

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

What's the need to have Class type as wildcard and not specific here and the for the rest?

// add name and callback method reference to inner reader map
// Since appending the new reader entry returns the previous value associated with key, or null if there was no
// mapping for key, validate that name has not yet been associated with a callback method to it's associated
// extension
Copy link
Member

Choose a reason for hiding this comment

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

Let's use multi line comment and follow javadocs rules to write comments

// extension
ExtensionReader newReader = (en, cc, context) -> parseNamedWriteable(en, cc, context);
ExtensionReader oldReader = readers.put(name, newReader);
if (oldReader != null) {
Copy link
Member

Choose a reason for hiding this comment

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

Better way to check

Suggested change
if (oldReader != null) {
if (oldReader != null && oldReader.getClass()) {

Copy link
Member

@dbwiddis dbwiddis Jul 16, 2022

Choose a reason for hiding this comment

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

(I think the suggestion needs another != null there?)

}
}

// handle last category and reader entry
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// handle last category and reader entry
// Handle last category and reader entry

// handle last category and reader entry
categoryMap.put(currentCategory, readers);

// attach extension node to categoryMap
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// attach extension node to categoryMap
// Attach extension node to categoryMap

};

try {
logger.info("Sending extension request type: " + REQUEST_EXTENSION_NAMED_WRITEABLE_REGISTRY);
Copy link
Member

Choose a reason for hiding this comment

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

Let's add this logger for all the request we are sending from OS to SDK.

new NamedWriteableRegistryRequest(true),
namedWriteableRegistryResponseHandler
);
// wait for response from SDK
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// wait for response from SDK
// Wait for response from SDK

}

@Override
public void handleResponse(NamedWriteableRegistryResponse response) {
Copy link
Member

Choose a reason for hiding this comment

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

Let's create a separate class/file for handleResponse and not increase the size of this file

Copy link
Member Author

Choose a reason for hiding this comment

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

Good call, I'll separate out all the response handlers into different files and place them within the Extensions directory

this.context = in.readByteArray();
}

public Class<?> getCategoryClass() throws ClassNotFoundException {
Copy link
Member

Choose a reason for hiding this comment

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

Wildcard type? Can we have a specific type here for the Class?

Copy link
Member

Choose a reason for hiding this comment

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

Looking at opensearch-project/opensearch-sdk-java#54, it seems the type should be NamedWriteable?

Copy link
Member Author

Choose a reason for hiding this comment

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

Category Classes are expected to extend the NamedWriteable class, I'll change all wildcard type references to reflect this.

*
* @opensearch.internal
*/
public class NamedWriteableRegistryParseResponse extends TransportResponse {
Copy link
Member

Choose a reason for hiding this comment

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

Let's not create a separate class for every boolean response. We already have IndicesModuleNameResponse doing something similar. We can make it more generic and use it for IndicesModuleNameResponse and NamedWriteableRegistryParseResponse

Copy link
Member

Choose a reason for hiding this comment

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

BooleanResponse<NamedWritableRegistry> and BooleanResponse<IndicesModuleName>?

*
* @opensearch.internal
*/
public class NamedWriteableRegistryRequest extends TransportRequest {
Copy link
Member

Choose a reason for hiding this comment

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

Let's create a generic request class for Boolean Request

}
}

// TODO : enable dynamic registration of named writeables by returning the extension registry map within Node.java
Copy link
Member

Choose a reason for hiding this comment

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

If this is not the part of the PR. Let's create an issue and link here to not miss the work

Copy link
Member

Choose a reason for hiding this comment

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

Great comment/advice, @owaiskazi19. Any temporary placeholder or TODO code should have an issue tracking it and put the link to the issue in a comment.

Copy link
Member

@dbwiddis dbwiddis left a comment

Choose a reason for hiding this comment

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

Great start!

/**
* Extensibility support for Named Writeable Registry: request to extensions to parse context
*
* @opensearch.internal
Copy link
Member

Choose a reason for hiding this comment

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

For my own education, when is this annotation used?

}

public byte[] getContext() {
return this.context;
Copy link
Member

Choose a reason for hiding this comment

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

This returns a reference to this.context which would allow whoever received it to alter the values inside the array. You should use Arrays.copyOf(this.context, this.context.length);

}

public Class<?> getCategoryClass() throws ClassNotFoundException {
Class<?> categoryClass = Class.forName(this.categoryClassName);
Copy link
Member

@dbwiddis dbwiddis Jul 16, 2022

Choose a reason for hiding this comment

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

Checking whether a valid class exists for categoryClassName may be better placed in the constructor where the String is passed (or read from a stream). You could then catch the exception and then throw an IllegalArgumentException (which you should document with @throws in the javadoc for the constructor!)

Although this opens the question of how to handle the class name when read from a stream. How do we make sure that nodes on both sides of the stream have the same classes? (Relevant to @owaiskazi19 comment above on restricting class type.)

And this opens an even bigger issue is dealing with same-named classes and getting name collisions. You probably want the fully qualified class name here. (EDIT: I see that the intention is to use Class.getName() for this which is fully qualified. But the string parameter should have that documented clearly.)

And given that the parsing signature in ExtensionsReader uses a Class rather than a String we might consider using that here as well. That would both assure a valid class as well as easily give access to the fully qualified name.

Copy link
Member Author

Choose a reason for hiding this comment

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

Category Class definitions reside within the OpenSearch JVM and are currently exposed to the SDK via the maven local repository, thus ensuring that the extension has the same class. I'll definitely add the check for valid classes within the constructor and also add more clarification with the javadocs.


@Override
public String toString() {
return "NamedWriteableRegistryParseRequest{"
Copy link
Member

Choose a reason for hiding this comment

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

Question for my own education: do we have a standard format for toStrings? Should we?

Copy link
Member Author

Choose a reason for hiding this comment

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

The pattern we have been following is to print out the request/response type along with the contents of the request/response

Copy link
Member

Choose a reason for hiding this comment

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

It's less the type(class name) and field listing it's more the delimiters. I've seen curly braces, square braces, newlines. Most IDEs will auto-generate a toString from fields. I'm wondering which particular format we are using, if any.

/**
* Transports category class, and byte array (context), to the extension identified by the Discovery Node
*
* @param extensionNode Discovery Node identifying the Extension
Copy link
Member

Choose a reason for hiding this comment

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

(Style) leave an extra space between parameter name and its description.

*
*/

void parse(DiscoveryNode extensionNode, Class<?> categoryClass, byte[] context) throws UnknownHostException;
Copy link
Member

Choose a reason for hiding this comment

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

The exception should be added to the javadoc with a @throws. As this is an interface, this javadoc will be inherited by implementations.

* Transports category class, and byte array (context), to the extension identified by the Discovery Node
*
* @param extensionNode Discovery Node identifying the Extension
* @param categoryClass Super class that the reader extends
Copy link
Member

Choose a reason for hiding this comment

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

Should probably have an interface for the superclasses the readers extend, and use that for the type parameter. (And the type parameter needs its own javadoc.)

// extension
ExtensionReader newReader = (en, cc, context) -> parseNamedWriteable(en, cc, context);
ExtensionReader oldReader = readers.put(name, newReader);
if (oldReader != null) {
Copy link
Member

@dbwiddis dbwiddis Jul 16, 2022

Choose a reason for hiding this comment

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

(I think the suggestion needs another != null there?)

);
}
} catch (ClassNotFoundException e) {
logger.error(e.toString());
Copy link
Member

Choose a reason for hiding this comment

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

May want an explanatory text string in addition to the error here.

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

…oject/OpenSearch into namedwriteables

rebasing fork with feature/extensions
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

*/
public NamedWriteableRegistryResponse(StreamInput in) throws IOException {
super(in);
// stream output for registry map begins with a variable integer that tells us the number of entries being sent across the wire
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
// stream output for registry map begins with a variable integer that tells us the number of entries being sent across the wire
// Stream output for registry map begins with a variable integer that tells us the number of entries being sent across the wire

Here and everywhere 🙂

*
* @opensearch.internal
*/
public class BooleanResponse extends TransportResponse {
Copy link
Member

@owaiskazi19 owaiskazi19 Jul 21, 2022

Choose a reason for hiding this comment

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

We can use the same response for IndicesModuleNameResponse and get rid of that file?

Copy link
Member

@owaiskazi19 owaiskazi19 Jul 21, 2022

Choose a reason for hiding this comment

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

Also, a better name here OpenSearchBooleanResponse?

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

…for OpenSearchRequest

Signed-off-by: Joshua Palis <jpalis@amazon.com>
…nsionDir initialization within set up

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

Gradle Check (Jenkins) Run Completed with:

*/
public class NamedWriteableRegistryParseRequest extends TransportRequest {

private final Class categoryClass;
Copy link
Member

Choose a reason for hiding this comment

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

Over on your PR on the SDK side this class is restricted to one that extends NamedWriteable so you should add that restriction here (in the constructor, throwing IllegalArgumentException if it's not a compatible class)

Copy link
Member Author

Choose a reason for hiding this comment

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

I've removed the generic casting on the SDK end, as per your comment on the corresponding SDK PR. Since we already ensure that all category class entries sent over by the extension utilize the NamedWriteable framework, these restrictions are not necessary.

public NamedWriteableRegistryParseRequest(StreamInput in) throws IOException {
super(in);
try {
this.categoryClass = Class.forName(in.readString());
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment to above, add some type safety here if you are going to cast to a superclass on the SDK side.


@Override
public String toString() {
return "NamedWriteableRegistryParseRequest{"
Copy link
Member

Choose a reason for hiding this comment

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

It's less the type(class name) and field listing it's more the delimiters. I've seen curly braces, square braces, newlines. Most IDEs will auto-generate a toString from fields. I'm wondering which particular format we are using, if any.

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

github-actions bot commented Aug 1, 2022

Gradle Check (Jenkins) Run Completed with:

public void getNamedWriteables() {

// Retrieve named writeable registry entries from each extension
for (DiscoveryNode extensionNode : extensionsList) {
Copy link
Member

Choose a reason for hiding this comment

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

You can iterate over extensionsInitializedList here, just in case extensions failed to initialize you won't waste time attempting to getNamedWriteables().

…hat we are only sending extension point requests to initialized nodes

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

github-actions bot commented Aug 1, 2022

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

github-actions bot commented Aug 2, 2022

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

github-actions bot commented Aug 2, 2022

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

github-actions bot commented Aug 2, 2022

Gradle Check (Jenkins) Run Completed with:

…he extensionsInitializedList

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

github-actions bot commented Aug 2, 2022

Gradle Check (Jenkins) Run Completed with:

Copy link
Member

@saratvemulapalli saratvemulapalli left a comment

Choose a reason for hiding this comment

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

Thanks @joshpalis.
I did one pass, most the code looks good, really great work!!

I've dropped a couple of questions/suggestions.
I'll do another pass.

+ "categoryClass="
+ categoryClass.getName()
+ ", context length="
+ context.length
Copy link
Member

Choose a reason for hiding this comment

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

Is context printable?

Copy link
Member Author

Choose a reason for hiding this comment

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

the context is a byte array, not sure if printing out the entire array would provide any useful information, so I decided to just print the length of the array. I am open to suggestions on how to represent the context.

@@ -1155,6 +1155,7 @@ public Node start() throws NodeValidationException {
: "clusterService has a different local node than the factory provided";
transportService.acceptIncomingRequests();
extensionsOrchestrator.extensionsInitialize();
extensionsOrchestrator.getNamedWriteables();
Copy link
Member

Choose a reason for hiding this comment

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

How are these namedWritables registered with OpenSearch NamedWritableRegistry?

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently this is a placeholder trigger. Registering extensions named writeables within the central named writeable registry is a larger problem that I have linked this issue to.

* @throws UnknownHostException if connection to the extension node failed
* @return A map of category classes and their associated names and readers for this discovery node
*/
public Map<DiscoveryNode, Map<Class, Map<String, ExtensionReader>>> getNamedWriteables(DiscoveryNode extensionNode)
Copy link
Member

Choose a reason for hiding this comment

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

Does this method have to be public?

Copy link
Member Author

Choose a reason for hiding this comment

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

Currently I target this method within my unit tests since extensionsInitializedList cannot be populated unless extensionsInitialize() occurs. The method which calls this iterates through initialized discovery nodes and sends a request to the extension associated with the node, therefore, in order to test this, I would have to call this method directly, which is only possible if it is public

/**
* Iterates through all discovered extensions, sends transport requests for named writeables and consolidates all entires into a central named writeable registry for extensions.
*/
public void getNamedWriteables() {
Copy link
Member

Choose a reason for hiding this comment

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

Similar comment as the SDK.
Can we fold all of this logic to Another Class under extensions package?

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

Gradle Check (Jenkins) Run Completed with:

@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

Gradle Check (Jenkins) Run Completed with:

…extension orchestrator to new class, updated tests to reflect change, fixed toString parse request override

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

github-actions bot commented Aug 3, 2022

Gradle Check (Jenkins) Run Completed with:

@codecov-commenter
Copy link

Codecov Report

Merging #3925 (39ab25c) into feature/extensions (eba15fe) will decrease coverage by 0.09%.
The diff coverage is 46.35%.

@@                   Coverage Diff                    @@
##             feature/extensions    #3925      +/-   ##
========================================================
- Coverage                 70.66%   70.56%   -0.10%     
+ Complexity                57107    57079      -28     
========================================================
  Files                      4606     4612       +6     
  Lines                    274633   274810     +177     
  Branches                  40210    40228      +18     
========================================================
- Hits                     194059   193913     -146     
- Misses                    64331    64629     +298     
- Partials                  16243    16268      +25     
Impacted Files Coverage Δ
...pensearch/extensions/ExtensionBooleanResponse.java 0.00% <0.00%> (ø)
...mmon/io/stream/NamedWriteableRegistryResponse.java 17.85% <17.85%> (ø)
.../io/stream/NamedWriteableRegistryParseRequest.java 20.68% <20.68%> (ø)
...a/org/opensearch/extensions/OpenSearchRequest.java 23.52% <23.52%> (ø)
...ns/NamedWriteableRegistryParseResponseHandler.java 50.00% <50.00%> (ø)
...ch/extensions/ExtensionNamedWriteableRegistry.java 65.90% <65.90%> (ø)
...ensions/NamedWriteableRegistryResponseHandler.java 79.48% <79.48%> (ø)
.../opensearch/extensions/ExtensionsOrchestrator.java 73.37% <81.81%> (+1.07%) ⬆️
server/src/main/java/org/opensearch/node/Node.java 86.31% <100.00%> (+0.02%) ⬆️
...search/indices/recovery/RecoveryTargetHandler.java 0.00% <0.00%> (-100.00%) ⬇️
... and 478 more

Help us with your feedback. Take ten seconds to tell us how you rate us. Have a feature suggestion? Share it here.

@joshpalis joshpalis requested a review from ryanbogan August 4, 2022 20:45
…cronous since APIs will be utlizing the extensionsInitializedList to identify the correct node to send requests to. Since arraylists are not thread safe, this is achieved by adding countdown latches

Signed-off-by: Joshua Palis <jpalis@amazon.com>
@github-actions
Copy link
Contributor

github-actions bot commented Aug 8, 2022

Gradle Check (Jenkins) Run Completed with:

Copy link
Member

@saratvemulapalli saratvemulapalli left a comment

Choose a reason for hiding this comment

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

Thanks @joshpalis for taking care of this.
This is great work!!

For next time, resolve the conversations you've taken care of.
It helps the reviewer to understand what has to be addressed.

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.

6 participants