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

Primary setup for Multi-tenancy #3307

Merged
merged 5 commits into from
Jan 10, 2025
Merged

Conversation

dhrubo-os
Copy link
Collaborator

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

  • New functionality includes testing.
  • New functionality has been documented.
  • API changes companion pull request created.
  • Commits are signed per the DCO using --signoff.
  • Public documentation issue/PR created.

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.

@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 20:57 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 20:57 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 21:13 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 21:13 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 21:18 — with GitHub Actions Failure
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@mingshl
Copy link
Collaborator

mingshl commented Jan 8, 2025

@dhrubo-os do we have issues to explain this change?

@dhrubo-os
Copy link
Collaborator Author

@dhrubo-os do we have issues to explain this change?

RFC: #2358

another issue: #2650

Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 23:09 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 8, 2025 23:09 — with GitHub Actions Failure
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 8, 2025 23:14 — with GitHub Actions Inactive
Copy link
Contributor

@brianf-aws brianf-aws left a 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 :)

Comment on lines 272 to 289
// 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);
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
// 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);

:)

Copy link
Collaborator Author

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)
Copy link
Contributor

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.

Copy link
Collaborator Author

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
Copy link
Contributor

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
Copy link
Contributor

Choose a reason for hiding this comment

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

same here

Copy link
Collaborator Author

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) {
Copy link
Contributor

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?

Copy link
Collaborator Author

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) {
Copy link
Contributor

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

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yeah this is better.

Copy link
Member

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);
Copy link
Contributor

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?

Copy link
Collaborator Author

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.

@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 13:59 — with GitHub Actions Failure
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 16:00 — with GitHub Actions Failure
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 9, 2025 16:00 — with GitHub Actions Inactive
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 17:03 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 17:36 — with GitHub Actions Failure
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 19:27 — with GitHub Actions Failure
Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 9, 2025 22:19 — with GitHub Actions Inactive
@dhrubo-os dhrubo-os had a problem deploying to ml-commons-cicd-env January 9, 2025 22:19 — with GitHub Actions Failure
@dhrubo-os
Copy link
Collaborator Author

Tests with failures:
 - org.opensearch.ml.tools.VisualizationsToolIT.testVisualizationNotFound
 - org.opensearch.ml.tools.VisualizationsToolIT.testVisualizationFound
111 tests completed, 2 failed, 10 skipped

Same flaky errors

Signed-off-by: Dhrubo Saha <dhrubo@amazon.com>
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 10, 2025 00:27 — with GitHub Actions Inactive
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 10, 2025 00:27 — with GitHub Actions Inactive
@dhrubo-os dhrubo-os temporarily deployed to ml-commons-cicd-env January 10, 2025 01:26 — with GitHub Actions Inactive
@@ -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'
Copy link
Collaborator

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.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@dhrubo-os dhrubo-os merged commit 80de6e3 into opensearch-project:main Jan 10, 2025
7 checks passed
@opensearch-trigger-bot
Copy link
Contributor

The backport to 2.x failed:

The process '/usr/bin/git' failed with exit code 1

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 base branch is 2.x and the compare/head branch is backport/backport-3307-to-2.x.

dhrubo-os added a commit to dhrubo-os/ml-commons that referenced this pull request Jan 10, 2025
* 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>
dhrubo-os added a commit that referenced this pull request Jan 10, 2025
* 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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants