Skip to content

Commit

Permalink
Primary setup for Multi-tenancy (opensearch-project#3307)
Browse files Browse the repository at this point in the history
* 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>
  • Loading branch information
dhrubo-os authored Jan 10, 2025
1 parent 441af6e commit 80de6e3
Show file tree
Hide file tree
Showing 27 changed files with 633 additions and 21 deletions.
1 change: 0 additions & 1 deletion .github/workflows/CI-workflow.yml
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ jobs:
with:
role-to-assume: ${{ secrets.ML_ROLE }}
aws-region: us-west-2

- name: Checkout MLCommons
uses: actions/checkout@v4
with:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ public class CommonValue {
public static final String UNDEPLOYED = "undeployed";
public static final String NOT_FOUND = "not_found";

/** The field name containing the tenant id */
public static final String TENANT_ID_FIELD = "tenant_id";

public static final String MASTER_KEY = "master_key";
public static final String CREATE_TIME_FIELD = "create_time";
public static final String LAST_UPDATE_TIME_FIELD = "last_update_time";
Expand Down Expand Up @@ -63,4 +66,5 @@ public class CommonValue {
public static final Version VERSION_2_16_0 = Version.fromString("2.16.0");
public static final Version VERSION_2_17_0 = Version.fromString("2.17.0");
public static final Version VERSION_2_18_0 = Version.fromString("2.18.0");
public static final Version VERSION_2_19_0 = Version.fromString("2.19.0");
}
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,8 @@ public abstract class AbstractConnector implements Connector {
protected Instant lastUpdateTime;
@Setter
protected ConnectorClientConfig connectorClientConfig;
@Setter
protected String tenantId;

protected Map<String, String> createDecryptedHeaders(Map<String, String> headers) {
if (headers == null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,10 @@ public interface Connector extends ToXContentObject, Writeable {

String getName();

String getTenantId();

void setTenantId(String tenantId);

String getProtocol();

void setCreatedTime(Instant createdTime);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,5 @@ public class Constants {
public static final String AD_TRAINING_DATA_SIZE = "trainingDataSize";
public static final String AD_ANOMALY_SCORE_THRESHOLD = "anomalyScoreThreshold";
public static final String AD_DATE_FORMAT = "dateFormat";
public static final String TENANT_ID_HEADER = "x-tenant-id";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

package org.opensearch.ml.common.settings;

/**
* Interface for handling settings changes in the OpenSearch ML plugin.
*/
public interface SettingsChangeListener {
/**
* Callback method that gets triggered when the multi-tenancy setting changes.
*
* @param isEnabled A boolean value indicating the new state of the multi-tenancy setting:
* <ul>
* <li><code>true</code> if multi-tenancy is enabled</li>
* <li><code>false</code> if multi-tenancy is disabled</li>
* </ul>
*/
void onMultiTenancyEnabledChanged(boolean isEnabled);
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@
package org.opensearch.ml.common.transport.connector;

import static org.opensearch.action.ValidateActions.addValidationError;
import static org.opensearch.ml.common.CommonValue.VERSION_2_19_0;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.UncheckedIOException;

import org.opensearch.Version;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.ActionRequestValidationException;
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
Expand All @@ -26,24 +28,34 @@
public class MLConnectorGetRequest extends ActionRequest {

String connectorId;
String tenantId;
boolean returnContent;

@Builder
public MLConnectorGetRequest(String connectorId, boolean returnContent) {
public MLConnectorGetRequest(String connectorId, String tenantId, boolean returnContent) {
this.connectorId = connectorId;
this.tenantId = tenantId;
this.returnContent = returnContent;
}

public MLConnectorGetRequest(StreamInput in) throws IOException {
super(in);
Version streamInputVersion = in.getVersion();
this.connectorId = in.readString();
if (streamInputVersion.onOrAfter(VERSION_2_19_0)) {
this.tenantId = in.readOptionalString();
}
this.returnContent = in.readBoolean();
}

@Override
public void writeTo(StreamOutput out) throws IOException {
super.writeTo(out);
Version streamOutputVersion = out.getVersion();
out.writeString(this.connectorId);
if (streamOutputVersion.onOrAfter(VERSION_2_19_0)) {
out.writeOptionalString(this.tenantId);
}
out.writeBoolean(returnContent);
}

Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_agent.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 2
"schema_version": 3
},
"properties": {
"name": {
Expand Down Expand Up @@ -33,6 +33,9 @@
"is_hidden": {
"type": "boolean"
},
"tenant_id": {
"type": "keyword"
},
"created_time": {
"type": "date",
"format": "strict_date_time||epoch_millis"
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_config.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 4
"schema_version": 5
},
"properties": {
"master_key": {
Expand All @@ -9,6 +9,9 @@
"config_type": {
"type": "keyword"
},
"tenant_id": {
"type": "keyword"
},
"ml_configuration": {
"type": "flat_object"
},
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_connector.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 3
"schema_version": 4
},
"properties": {
"name": {
Expand Down Expand Up @@ -30,6 +30,9 @@
"client_config": {
"type": "flat_object"
},
"tenant_id": {
"type": "keyword"
},
"actions": {
"type": "flat_object"
},
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_model.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 11
"schema_version": 12
},
"properties": {
"algorithm": {
Expand Down Expand Up @@ -63,6 +63,9 @@
"is_hidden": {
"type": "boolean"
},
"tenant_id": {
"type": "keyword"
},
"model_config": {
"properties": {
"model_type": {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_model_group.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 2
"schema_version": 3
},
"properties": {
"name": {
Expand All @@ -21,6 +21,9 @@
"model_group_id": {
"type": "keyword"
},
"tenant_id": {
"type": "keyword"
},
"backend_roles": {
"type": "text",
"fields": {
Expand Down
5 changes: 4 additions & 1 deletion common/src/main/resources/index-mappings/ml_task.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"_meta": {
"schema_version": 3
"schema_version": 4
},
"properties": {
"model_id": {
Expand Down Expand Up @@ -38,6 +38,9 @@
"error": {
"type": "text"
},
"tenant_id": {
"type": "keyword"
},
"is_async": {
"type": "boolean"
},
Expand Down
1 change: 1 addition & 0 deletions gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@ distributionPath=wrapper/dists
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
2 changes: 2 additions & 0 deletions ml-algorithms/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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'
}


jacocoTestReport {
reports {
xml.getRequired().set(true)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,8 @@ public void setUp() {
when(metadata.indices()).thenReturn(Map.of(ML_AGENT_INDEX, agentindexMetadata, ML_MEMORY_META_INDEX, memorymetaindexMetadata));
when(agentindexMetadata.mapping()).thenReturn(agentmappingMetadata);
when(memorymetaindexMetadata.mapping()).thenReturn(memorymappingMetadata);
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 3)));
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 2)));
settings = Settings.builder().put("test_key", 10).build();
threadContext = new ThreadContext(settings);
when(client.threadPool()).thenReturn(threadPool);
Expand Down
14 changes: 13 additions & 1 deletion plugin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ dependencies {

implementation group: 'org.opensearch', name: 'opensearch', version: "${opensearch_version}"
implementation "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
// Multi-tenant SDK Client
implementation "org.opensearch:opensearch-remote-metadata-sdk:${opensearch_version}"

implementation "org.opensearch:common-utils:${common_utils_version}"
implementation("com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}")
implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}")
Expand Down Expand Up @@ -342,6 +345,7 @@ jacocoTestCoverageVerification {
check.dependsOn jacocoTestCoverageVerification

configurations.all {
exclude group: "org.jetbrains", module: "annotations"
resolutionStrategy.force 'org.apache.commons:commons-lang3:3.10'
resolutionStrategy.force 'commons-logging:commons-logging:1.2'
resolutionStrategy.force 'org.objenesis:objenesis:3.2'
Expand All @@ -354,7 +358,15 @@ configurations.all {
resolutionStrategy.force 'org.slf4j:slf4j-api:1.7.36'
resolutionStrategy.force 'org.codehaus.plexus:plexus-utils:3.3.0'
resolutionStrategy.force 'org.eclipse.platform:org.eclipse.core.runtime:3.29.0'
exclude group: "org.jetbrains", module: "annotations"
resolutionStrategy.force "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5:5.3.1"
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5-h2:5.3.1"
resolutionStrategy.force "org.apache.httpcomponents.client5:httpclient5:5.4.1"
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-core:${versions.jackson_databind}"
resolutionStrategy.force "org.apache.logging.log4j:log4j-api:2.24.2"
resolutionStrategy.force "org.apache.logging.log4j:log4j-core:2.24.2"
resolutionStrategy.force "jakarta.json:jakarta.json-api:2.1.3"
}

apply plugin: 'com.netflix.nebula.ospackage'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import static org.opensearch.ml.utils.MLNodeUtils.createXContentParserFromRegistry;
import static org.opensearch.ml.utils.RestActionUtils.getFetchSourceContext;

import java.util.Objects;

import org.opensearch.OpenSearchStatusException;
import org.opensearch.action.ActionRequest;
import org.opensearch.action.get.GetRequest;
Expand Down Expand Up @@ -65,6 +67,7 @@ public GetConnectorTransportAction(
protected void doExecute(Task task, ActionRequest request, ActionListener<MLConnectorGetResponse> actionListener) {
MLConnectorGetRequest mlConnectorGetRequest = MLConnectorGetRequest.fromActionRequest(request);
String connectorId = mlConnectorGetRequest.getConnectorId();
String tenantId = mlConnectorGetRequest.getTenantId();
FetchSourceContext fetchSourceContext = getFetchSourceContext(mlConnectorGetRequest.isReturnContent());
GetRequest getRequest = new GetRequest(ML_CONNECTOR_INDEX).id(connectorId).fetchSourceContext(fetchSourceContext);
User user = RestActionUtils.getUserContext(client);
Expand All @@ -77,6 +80,15 @@ protected void doExecute(Task task, ActionRequest request, ActionListener<MLConn
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
Connector mlConnector = Connector.createConnector(parser);
mlConnector.removeCredential();
if (!Objects.equals(tenantId, mlConnector.getTenantId())) {
actionListener
.onFailure(
new OpenSearchStatusException(
"You don't have permission to access this connector",
RestStatus.FORBIDDEN
)
);
}
if (connectorAccessControlHelper.hasPermission(user, mlConnector)) {
actionListener.onResponse(MLConnectorGetResponse.builder().mlConnector(mlConnector).build());
} else {
Expand Down
Loading

0 comments on commit 80de6e3

Please sign in to comment.