-
Notifications
You must be signed in to change notification settings - Fork 138
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
Primary setup for Multi-tenancy #3307
Conversation
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@dhrubo-os do we have issues to explain this change? |
RFC: #2358 another issue: #2650 |
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.
I didnt get to reviewing the tests but left some comments hope they help :)
// Whether multi-tenancy is enabled in ML Commons. | ||
// This is a static setting which must be set before starting OpenSearch by (in priority order): | ||
// 1. As a command-line argument using the -E flag (overrides other options): | ||
// ./bin/opensearch -Eplugins.ml_commons.multi_tenancy_enabled=true | ||
// 2. As a system property using OPENSEARCH_JAVA_OPTS (overrides opensearch.yml): | ||
// export OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" | ||
// ./bin/opensearch | ||
// Or inline when starting OpenSearch: | ||
// OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" ./bin/opensearch | ||
// 3. In the opensearch.yml configuration file: | ||
// plugins.ml_commons.multi_tenancy_enabled: true | ||
// After setting it, a full cluster restart is required for the changes to take effect. | ||
public static final Setting<Boolean> ML_COMMONS_MULTI_TENANCY_ENABLED = Setting | ||
.boolSetting("plugins.ml_commons.multi_tenancy_enabled", false, Setting.Property.NodeScope); | ||
|
||
/** This setting sets the remote metadata type */ | ||
public static final Setting<String> REMOTE_METADATA_TYPE = Setting | ||
.simpleString("plugins.ml_commons." + REMOTE_METADATA_TYPE_KEY, Setting.Property.NodeScope, Setting.Property.Final); |
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.
// Whether multi-tenancy is enabled in ML Commons. | |
// This is a static setting which must be set before starting OpenSearch by (in priority order): | |
// 1. As a command-line argument using the -E flag (overrides other options): | |
// ./bin/opensearch -Eplugins.ml_commons.multi_tenancy_enabled=true | |
// 2. As a system property using OPENSEARCH_JAVA_OPTS (overrides opensearch.yml): | |
// export OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" | |
// ./bin/opensearch | |
// Or inline when starting OpenSearch: | |
// OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" ./bin/opensearch | |
// 3. In the opensearch.yml configuration file: | |
// plugins.ml_commons.multi_tenancy_enabled: true | |
// After setting it, a full cluster restart is required for the changes to take effect. | |
public static final Setting<Boolean> ML_COMMONS_MULTI_TENANCY_ENABLED = Setting | |
.boolSetting("plugins.ml_commons.multi_tenancy_enabled", false, Setting.Property.NodeScope); | |
/** This setting sets the remote metadata type */ | |
public static final Setting<String> REMOTE_METADATA_TYPE = Setting | |
.simpleString("plugins.ml_commons." + REMOTE_METADATA_TYPE_KEY, Setting.Property.NodeScope, Setting.Property.Final); | |
/** | |
* Indicates whether multi-tenancy is enabled in ML Commons. | |
* | |
* This is a static setting that must be configured before starting OpenSearch. It can be set in the following ways, in priority order: | |
* | |
* <ol> | |
* <li>As a command-line argument using the <code>-E</code> flag (this overrides other options): | |
* <pre> | |
* ./bin/opensearch -Eplugins.ml_commons.multi_tenancy_enabled=true | |
* </pre> | |
* </li> | |
* <li>As a system property using <code>OPENSEARCH_JAVA_OPTS</code> (this overrides <code>opensearch.yml</code>): | |
* <pre> | |
* export OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" | |
* ./bin/opensearch | |
* </pre> | |
* Or inline when starting OpenSearch: | |
* <pre> | |
* OPENSEARCH_JAVA_OPTS="-Dplugins.ml_commons.multi_tenancy_enabled=true" ./bin/opensearch | |
* </pre> | |
* </li> | |
* <li>In the <code>opensearch.yml</code> configuration file: | |
* <pre> | |
* plugins.ml_commons.multi_tenancy_enabled: true | |
* </pre> | |
* </li> | |
* </ol> | |
* | |
* After setting this option, a full cluster restart is required for the changes to take effect. | |
*/ | |
public static final Setting<Boolean> ML_COMMONS_MULTI_TENANCY_ENABLED = Setting | |
.boolSetting("plugins.ml_commons.multi_tenancy_enabled", false, Setting.Property.NodeScope); |
:)
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.
Nice!!
xContentRegistry, | ||
// Here we assume remote metadata client is only used with tenant awareness. | ||
// This may change in the future allowing more options for this map | ||
ML_COMMONS_MULTI_TENANCY_ENABLED.get(settings) |
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 part is a little bit hard to follow, maybe we can extract it? I say this because you state you are going to add more in the future too.
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.
We are just building the SDKClient here. Similar like this
if (mlFeatureEnabledSetting.isMultiTenancyEnabled() && tenantId == null) { | ||
listener.onFailure(new OpenSearchStatusException("You don't have permission to access this resource", RestStatus.FORBIDDEN)); | ||
return false; | ||
} else |
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.
lets drop the else and just return true
if (mlFeatureEnabledSetting.isMultiTenancyEnabled() && !Objects.equals(tenantIdFromRequest, tenantIdFromResource)) { | ||
listener.onFailure(new OpenSearchStatusException("You don't have permission to access this resource", RestStatus.FORBIDDEN)); | ||
return false; | ||
} else |
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
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 applies here as well. And if wrong tenant id is provided customer shouldn't be able to access the resources.
* @return true if the tenant ID is valid or if multi-tenancy is not enabled; false if the tenant ID is invalid and multi-tenancy is enabled. | ||
*/ | ||
public static boolean validateTenantId(MLFeatureEnabledSetting mlFeatureEnabledSetting, String tenantId, ActionListener<?> listener) { | ||
if (mlFeatureEnabledSetting.isMultiTenancyEnabled() && 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.
i see the javadoc states if the tenant id is valid but looks like the tenantId checks if its null or not. Is it possible to pass a invalid tenantId while having multiTenancy Enabled or is this impossible?
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.
From the plugin level, we don't have visibility if the tenant id is valid or not. We perform this validation from the interceptor level. Here we just check if multi-tenancy is enabled then tenant id needs to be present.
return false; // Tenant filtering is not enabled | ||
} | ||
|
||
public static String getTenantID(Boolean isMultiTenancyEnabled, RestRequest restRequest) { |
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 happens to be a lot of nesting maybe lets refactor to fail early (gaurd clause)?
public static String getTenantID(Boolean isMultiTenancyEnabled, RestRequest restRequest) {
if (!isMultiTenancyEnabled)
return null;
Map<String, List<String>> headers = restRequest.getHeaders();
if (headers == null)
throw new OpenSearchStatusException("Rest request header can't be null", RestStatus.FORBIDDEN);
if (!headers.containsKey(Constants.TENANT_ID_HEADER))
throw new OpenSearchStatusException("Tenant ID header is missing", RestStatus.FORBIDDEN);
List<String> tenantIdList = headers.get(Constants.TENANT_ID_HEADER);
if (tenantIdList == null || tenantIdList.isEmpty())
throw new OpenSearchStatusException("Tenant ID header is missing", RestStatus.FORBIDDEN);
String tenantId = tenantIdList.get(0);
if (tenantId == null)
throw new OpenSearchStatusException("Tenant ID can't be null", RestStatus.FORBIDDEN);
return tenantId
}
My indentation is off but the point is to check your edge cases first and let it trickle down to the happy case
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 this is 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.
if (headers == null)
throw new OpenSearchStatusException("Rest request header can't be null", RestStatus.FORBIDDEN);
FYI, this case is impossible. I tried to write a unit test for the Flow Framework version of this util and could not (short of using reflection) make the headers null. The only way to make it null is using a constructor, where it passes through a headers.get()
to get the content type from the header; and if that didn't stop it, Collections.unmodifiableMap()
would throw an NPE on null headers as well.
if (headers.containsKey(Constants.TENANT_ID_HEADER)) { | ||
List<String> tenantIdList = headers.get(Constants.TENANT_ID_HEADER); | ||
if (tenantIdList != null && !tenantIdList.isEmpty()) { | ||
String tenantId = tenantIdList.get(0); |
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.
A little bit confused on this part why is it a list if we only get one item or is this how it just is?
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.
HTTP headers are represented as a Map<String, List> because the HTTP protocol allows multiple values for a single header key (e.g., Set-Cookie). Even though most headers, like Tenant-ID, typically have only one value, the list structure ensures flexibility and compatibility. In practice, we usually retrieve the first value (list.get(0)) since only one value is needed in most cases.
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
Same flaky errors |
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@@ -87,8 +87,10 @@ lombok { | |||
configurations.all { | |||
resolutionStrategy.force 'com.google.protobuf:protobuf-java:3.25.5' | |||
resolutionStrategy.force 'org.apache.commons:commons-compress:1.26.0' | |||
resolutionStrategy.force 'software.amazon.awssdk:bom:2.29.12' |
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 know why use a force here? If the version matter then you should be aware that it will cause version conflict when goes to the managed service.
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.
The backport to
To backport manually, run these commands in your terminal: # Fetch latest updates from GitHub
git fetch
# Create a new working tree
git worktree add .worktrees/backport-2.x 2.x
# Navigate to the new working tree
cd .worktrees/backport-2.x
# Create a new branch
git switch --create backport/backport-3307-to-2.x
# Cherry-pick the merged commit of this pull request and resolve the conflicts
git cherry-pick -x --mainline 1 80de6e30b59500c2746ca3cecbca080fbcb0f1e0
# Push it to GitHub
git push --set-upstream origin backport/backport-3307-to-2.x
# Go back to the original working tree
cd ../..
# Delete the working tree
git worktree remove .worktrees/backport-2.x Then, create a pull request where the |
* multi-tenancy primary setup Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressed comments Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressed comments + fixed dependency issue Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * adding more log to debug the testVisualizationFound issue Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * changing back Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> --------- Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
* Primary setup for Multi-tenancy (#3307) * multi-tenancy primary setup Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressed comments Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * addressed comments + fixed dependency issue Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * adding more log to debug the testVisualizationFound issue Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * changing back Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> --------- Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> * apply spotless Signed-off-by: Dhrubo Saha <dhrubo@amazon.com> --------- Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
Description
In this PR, I'm just adding the primary setup required for Multi-tenancy feature. After this PR, I'll start raising other PRs for adding multi-tenancy in connector, model, agents etc.
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.