-
Notifications
You must be signed in to change notification settings - Fork 143
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
apply multi-tenancy and sdk client in Connector (Create + Get + Delete) #3382
Conversation
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Generally LGTM, with some comments/questions.
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, ActionListener.wrap(deleteResponse -> { | ||
listener.onResponse(deleteResponse); | ||
}, listener::onFailure)); | ||
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, ActionListener.wrap(listener::onResponse, listener::onFailure)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm missing the need to wrap the action listener here without doing anything else with it.
(Same comment for multiple of these.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry I didn't clearly follow you here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you are not writing custom OnResponse or onFailure rule, you can do similar
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, listener);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
^^^ what @mingshl said. No need to wrap it, it adds no value.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well I haven't changed much here. Just added minor changes of code refactoring what intellij editor suggested :D. So I'll keep that way for now. If we need to change, I'll raise another PR only targeting to remove the wrapper.
deleteConnector(connectorId, tenantId, actionFuture); | ||
return actionFuture; | ||
} | ||
|
||
void deleteConnector(String connectorId, ActionListener<DeleteResponse> listener); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do we want this one to delegate to the other one with a default (null) tenant ID?
if (streamInputVersion.onOrAfter(VERSION_2_19_0)) { | ||
this.tenantId = input.readOptionalString(); | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this work with BWC tests on 3.x branch? I seem to remember having to commit with CURRENT and wait until 2.x backport to make this change and once it's on 2.19.0-SNAPSHOT changing it to this on main.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah it needs to eventually be that way.
The issue is for BWC tests on this PR; if this change isn't merged to 2.x yet, it won't try to send/read that optional string on 2.19.0-SNAPSHOT, but a 3.0.0 node will detect that the stream is 2.19.0 and try to send/read. So rolling BWC will fail until it's backported.
@Getter | ||
String connectorId; | ||
private final String connectorId; | ||
private final String tenantId; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
making this final
creates a lot of extra noise elsewhere.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you please clarify in more detail what kind of noise you are referring to?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Having to do the else block below, for example.
this.tenantId = input.readOptionalString(); | ||
} else { | ||
this.tenantId = null; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you choose to keep tenantId
final this may be more readable with a ternary operator
* @param encryptor encryptor | ||
* @param tenantId tenantId | ||
*/ | ||
void initModel(MLModel model, Map<String, Object> params, Encryptor encryptor, String tenantId); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure why we need this new dedicated interface only for multi-tenant. Could we just include the tenantId as a parameter in the existing params Map?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call out. Yeah Initially I wanted to have a separate interface for multi-tenancy but then I ended up having tenantId in the Encryptor class. I will remove this interface class in the next follow up PR if that's ok.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, I am OK.
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, ActionListener.wrap(deleteResponse -> { | ||
listener.onResponse(deleteResponse); | ||
}, listener::onFailure)); | ||
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, ActionListener.wrap(listener::onResponse, listener::onFailure)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if you are not writing custom OnResponse or onFailure rule, you can do similar
client.execute(MLModelDeleteAction.INSTANCE, mlModelDeleteRequest, listener);
searchRequest, | ||
ActionListener.wrap(searchResponse -> { listener.onResponse(searchResponse); }, listener::onFailure) | ||
); | ||
client.execute(MLModelSearchAction.INSTANCE, searchRequest, ActionListener.wrap(listener::onResponse, listener::onFailure)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same here, if you are not writing custom OnResponse or onFailure rule, you can do similar
client.execute(MLModelSearchAction.INSTANCE, searchRequest, listener);
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment here.
client/src/main/java/org/opensearch/ml/client/MachineLearningNodeClient.java
Show resolved
Hide resolved
try (ThreadContext.StoredContext context = client.threadPool().getThreadContext().stashContext()) { | ||
ActionListener<DeleteResponse> restoringListener = ActionListener.runBefore(actionListener, context::restore); | ||
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder(); | ||
sourceBuilder.query(QueryBuilders.matchQuery(MLModel.CONNECTOR_ID_FIELD, connectorId)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
will suggest using term query here because when finding connector id, it's case sensitive, and non-tokenized, it's better to use exact match with term.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Well the scope of this PR is to add multi-tenancy related changes. I haven't modified the search functionality. So I'll keep this as it is.
} catch (Exception e) { | ||
log.error("Failed to validate Access for connector:" + connectorId, e); | ||
listener.onFailure(e); | ||
} | ||
} | ||
|
||
public void validateConnectorAccess( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will the above method be removed once all APIs are refactored?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes
+ Arrays.toString(modelIds.toArray(new String[0])), | ||
RestStatus.CONFLICT | ||
) | ||
new OpenSearchStatusException("Failed to parse search response", RestStatus.INTERNAL_SERVER_ERROR) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If the response has this exception message Failed to parse search response
, user might be confused as to why search is being invoked in delete.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any suggestion?
public Connector getMlConnector() { | ||
return mlConnector; | ||
} | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use @Getter instead of this function?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, will address that in the subsequent PR
) { | ||
|
||
sdkClient | ||
.getDataObjectAsync(getDataObjectRequest, client.threadPool().executor(GENERAL_THREAD_POOL)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It turns out we are using the single general_thread_pool for all create/get/delete actions. The general_thread_pool is for some infrequent usages with small pool size which might cause some performance issues in edge cases.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Do you have any suggestion what should we use here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I ran into issues on flow framework with too-small threadpools I was repurposing. I plan to create a new threadpool just for these client wrapper calls.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, agree with Dan on having a new pool with a larger size which would be better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FixedExecutorBuilder sdkClientThreadPool = new FixedExecutorBuilder(
settings,
SDK_CLIENT_THREAD_POOL,
OpenSearchExecutors.allocatedProcessors(settings) * 4,
10000,
ML_THREAD_POOL_PREFIX + SDK_CLIENT_THREAD_POOL,
false
);
I'm planning to add this in my subsequent PR and to use this for all the sdkClient related operations. Does this sound good?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, LGTM, thanks.
@@ -45,8 +51,8 @@ public List<Route> routes() { | |||
@Override | |||
protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient client) throws IOException { | |||
String connectorId = request.param(PARAMETER_CONNECTOR_ID); | |||
|
|||
MLConnectorDeleteRequest mlConnectorDeleteRequest = new MLConnectorDeleteRequest(connectorId); | |||
String tenantId = getTenantID(mlFeatureEnabledSetting.isMultiTenancyEnabled(), request); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this setting be modified by users?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This won't be a dynamic settings [ref]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Got it, thanks for the ref.
LGTM, please link the following PR to address the remaining comments. |
Description
[apply multi-tenancy and sdk client in Connector (Create + Get + Delete)]
By default, this is single tenant. If we enable multi-tenancy settings, then the request will expect an header
x-tenant-id
. And one tenant won't be able to access other tenant's resource.Related Issues
Resolves #[Issue number to be closed when this PR is merged]
Check List
--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.