Skip to content

Commit 5a1839d

Browse files
committed
Primary setup for Multi-tenancy (opensearch-project#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>
1 parent 6726a3a commit 5a1839d

27 files changed

+636
-27
lines changed

.github/workflows/CI-workflow.yml

-1
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,6 @@ jobs:
5252
with:
5353
role-to-assume: ${{ secrets.ML_ROLE }}
5454
aws-region: us-west-2
55-
5655
- name: Checkout MLCommons
5756
uses: actions/checkout@v4
5857
with:

common/src/main/java/org/opensearch/ml/common/CommonValue.java

+4
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ public class CommonValue {
2121
public static final String UNDEPLOYED = "undeployed";
2222
public static final String NOT_FOUND = "not_found";
2323

24+
/** The field name containing the tenant id */
25+
public static final String TENANT_ID_FIELD = "tenant_id";
26+
2427
public static final String MASTER_KEY = "master_key";
2528
public static final String CREATE_TIME_FIELD = "create_time";
2629
public static final String LAST_UPDATE_TIME_FIELD = "last_update_time";
@@ -63,4 +66,5 @@ public class CommonValue {
6366
public static final Version VERSION_2_16_0 = Version.fromString("2.16.0");
6467
public static final Version VERSION_2_17_0 = Version.fromString("2.17.0");
6568
public static final Version VERSION_2_18_0 = Version.fromString("2.18.0");
69+
public static final Version VERSION_2_19_0 = Version.fromString("2.19.0");
6670
}

common/src/main/java/org/opensearch/ml/common/connector/AbstractConnector.java

+2
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,8 @@ public abstract class AbstractConnector implements Connector {
7171
protected Instant lastUpdateTime;
7272
@Setter
7373
protected ConnectorClientConfig connectorClientConfig;
74+
@Setter
75+
protected String tenantId;
7476

7577
protected Map<String, String> createDecryptedHeaders(Map<String, String> headers) {
7678
if (headers == null) {

common/src/main/java/org/opensearch/ml/common/connector/Connector.java

+4
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,10 @@ public interface Connector extends ToXContentObject, Writeable {
4343

4444
String getName();
4545

46+
String getTenantId();
47+
48+
void setTenantId(String tenantId);
49+
4650
String getProtocol();
4751

4852
void setCreatedTime(Instant createdTime);

common/src/main/java/org/opensearch/ml/common/input/Constants.java

+1
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,5 @@ public class Constants {
3636
public static final String AD_TRAINING_DATA_SIZE = "trainingDataSize";
3737
public static final String AD_ANOMALY_SCORE_THRESHOLD = "anomalyScoreThreshold";
3838
public static final String AD_DATE_FORMAT = "dateFormat";
39+
public static final String TENANT_ID_HEADER = "x-tenant-id";
3940
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package org.opensearch.ml.common.settings;
7+
8+
/**
9+
* Interface for handling settings changes in the OpenSearch ML plugin.
10+
*/
11+
public interface SettingsChangeListener {
12+
/**
13+
* Callback method that gets triggered when the multi-tenancy setting changes.
14+
*
15+
* @param isEnabled A boolean value indicating the new state of the multi-tenancy setting:
16+
* <ul>
17+
* <li><code>true</code> if multi-tenancy is enabled</li>
18+
* <li><code>false</code> if multi-tenancy is disabled</li>
19+
* </ul>
20+
*/
21+
void onMultiTenancyEnabledChanged(boolean isEnabled);
22+
}

common/src/main/java/org/opensearch/ml/common/transport/connector/MLConnectorGetRequest.java

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,14 @@
66
package org.opensearch.ml.common.transport.connector;
77

88
import static org.opensearch.action.ValidateActions.addValidationError;
9+
import static org.opensearch.ml.common.CommonValue.VERSION_2_19_0;
910

1011
import java.io.ByteArrayInputStream;
1112
import java.io.ByteArrayOutputStream;
1213
import java.io.IOException;
1314
import java.io.UncheckedIOException;
1415

16+
import org.opensearch.Version;
1517
import org.opensearch.action.ActionRequest;
1618
import org.opensearch.action.ActionRequestValidationException;
1719
import org.opensearch.core.common.io.stream.InputStreamStreamInput;
@@ -26,24 +28,34 @@
2628
public class MLConnectorGetRequest extends ActionRequest {
2729

2830
String connectorId;
31+
String tenantId;
2932
boolean returnContent;
3033

3134
@Builder
32-
public MLConnectorGetRequest(String connectorId, boolean returnContent) {
35+
public MLConnectorGetRequest(String connectorId, String tenantId, boolean returnContent) {
3336
this.connectorId = connectorId;
37+
this.tenantId = tenantId;
3438
this.returnContent = returnContent;
3539
}
3640

3741
public MLConnectorGetRequest(StreamInput in) throws IOException {
3842
super(in);
43+
Version streamInputVersion = in.getVersion();
3944
this.connectorId = in.readString();
45+
if (streamInputVersion.onOrAfter(VERSION_2_19_0)) {
46+
this.tenantId = in.readOptionalString();
47+
}
4048
this.returnContent = in.readBoolean();
4149
}
4250

4351
@Override
4452
public void writeTo(StreamOutput out) throws IOException {
4553
super.writeTo(out);
54+
Version streamOutputVersion = out.getVersion();
4655
out.writeString(this.connectorId);
56+
if (streamOutputVersion.onOrAfter(VERSION_2_19_0)) {
57+
out.writeOptionalString(this.tenantId);
58+
}
4759
out.writeBoolean(returnContent);
4860
}
4961

common/src/main/resources/index-mappings/ml-agent.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 2
3+
"schema_version": 3
44
},
55
"properties": {
66
"name": {
@@ -33,6 +33,9 @@
3333
"is_hidden": {
3434
"type": "boolean"
3535
},
36+
"tenant_id": {
37+
"type": "keyword"
38+
},
3639
"created_time": {
3740
"type": "date",
3841
"format": "strict_date_time||epoch_millis"

common/src/main/resources/index-mappings/ml-config.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 4
3+
"schema_version": 5
44
},
55
"properties": {
66
"master_key": {
@@ -9,6 +9,9 @@
99
"config_type": {
1010
"type": "keyword"
1111
},
12+
"tenant_id": {
13+
"type": "keyword"
14+
},
1215
"ml_configuration": {
1316
"type": "flat_object"
1417
},

common/src/main/resources/index-mappings/ml-connector.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 3
3+
"schema_version": 4
44
},
55
"properties": {
66
"name": {
@@ -30,6 +30,9 @@
3030
"client_config": {
3131
"type": "flat_object"
3232
},
33+
"tenant_id": {
34+
"type": "keyword"
35+
},
3336
"actions": {
3437
"type": "flat_object"
3538
},

common/src/main/resources/index-mappings/ml-model-group.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 2
3+
"schema_version": 3
44
},
55
"properties": {
66
"name": {
@@ -21,6 +21,9 @@
2121
"model_group_id": {
2222
"type": "keyword"
2323
},
24+
"tenant_id": {
25+
"type": "keyword"
26+
},
2427
"backend_roles": {
2528
"type": "text",
2629
"fields": {

common/src/main/resources/index-mappings/ml-model.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 11
3+
"schema_version": 12
44
},
55
"properties": {
66
"algorithm": {
@@ -63,6 +63,9 @@
6363
"is_hidden": {
6464
"type": "boolean"
6565
},
66+
"tenant_id": {
67+
"type": "keyword"
68+
},
6669
"model_config": {
6770
"properties": {
6871
"model_type": {

common/src/main/resources/index-mappings/ml-task.json

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"_meta": {
3-
"schema_version": 3
3+
"schema_version": 4
44
},
55
"properties": {
66
"model_id": {
@@ -38,6 +38,9 @@
3838
"error": {
3939
"type": "text"
4040
},
41+
"tenant_id": {
42+
"type": "keyword"
43+
},
4144
"is_async": {
4245
"type": "boolean"
4346
},

gradle/wrapper/gradle-wrapper.properties

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ distributionBase=GRADLE_USER_HOME
77
distributionPath=wrapper/dists
88
distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip
99
networkTimeout=10000
10+
validateDistributionUrl=true
1011
zipStoreBase=GRADLE_USER_HOME
1112
zipStorePath=wrapper/dists
1213
distributionSha256Sum=f397b287023acdba1e9f6fc5ea72d22dd63669d59ed4a289a29b1a76eee151c6

ml-algorithms/build.gradle

+2
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,10 @@ lombok {
8484
configurations.all {
8585
resolutionStrategy.force 'com.google.protobuf:protobuf-java:3.25.5'
8686
resolutionStrategy.force 'org.apache.commons:commons-compress:1.26.0'
87+
resolutionStrategy.force 'software.amazon.awssdk:bom:2.29.12'
8788
}
8889

90+
8991
jacocoTestReport {
9092
reports {
9193
xml.getRequired().set(true)

ml-algorithms/src/test/java/org/opensearch/ml/engine/indices/MLIndicesHandlerTest.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -95,8 +95,8 @@ public void setUp() {
9595
when(metadata.indices()).thenReturn(Map.of(ML_AGENT_INDEX, agentindexMetadata, ML_MEMORY_META_INDEX, memorymetaindexMetadata));
9696
when(agentindexMetadata.mapping()).thenReturn(agentmappingMetadata);
9797
when(memorymetaindexMetadata.mapping()).thenReturn(memorymappingMetadata);
98-
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
99-
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, Integer.valueOf(2))));
98+
when(agentmappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 3)));
99+
when(memorymappingMetadata.getSourceAsMap()).thenReturn(Map.of(META, Map.of(SCHEMA_VERSION_FIELD, 2)));
100100
settings = Settings.builder().put("test_key", 10).build();
101101
threadContext = new ThreadContext(settings);
102102
when(client.threadPool()).thenReturn(threadPool);

plugin/build.gradle

+13
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ dependencies {
5050

5151
implementation group: 'org.opensearch', name: 'opensearch', version: "${opensearch_version}"
5252
implementation "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
53+
// Multi-tenant SDK Client
54+
implementation "org.opensearch:opensearch-remote-metadata-sdk:${opensearch_version}"
55+
5356
implementation "org.opensearch:common-utils:${common_utils_version}"
5457
implementation("com.fasterxml.jackson.core:jackson-annotations:${versions.jackson}")
5558
implementation("com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}")
@@ -336,6 +339,7 @@ jacocoTestCoverageVerification {
336339
check.dependsOn jacocoTestCoverageVerification
337340

338341
configurations.all {
342+
exclude group: "org.jetbrains", module: "annotations"
339343
resolutionStrategy.force 'org.apache.commons:commons-lang3:3.10'
340344
resolutionStrategy.force 'commons-logging:commons-logging:1.2'
341345
resolutionStrategy.force 'org.objenesis:objenesis:3.2'
@@ -348,6 +352,15 @@ configurations.all {
348352
resolutionStrategy.force 'org.slf4j:slf4j-api:1.7.36'
349353
resolutionStrategy.force 'org.codehaus.plexus:plexus-utils:3.3.0'
350354
exclude group: "org.jetbrains", module: "annotations"
355+
resolutionStrategy.force "org.opensearch.client:opensearch-rest-client:${opensearch_version}"
356+
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5:5.3.1"
357+
resolutionStrategy.force "org.apache.httpcomponents.core5:httpcore5-h2:5.3.1"
358+
resolutionStrategy.force "org.apache.httpcomponents.client5:httpclient5:5.4.1"
359+
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-databind:${versions.jackson_databind}"
360+
resolutionStrategy.force "com.fasterxml.jackson.core:jackson-core:${versions.jackson_databind}"
361+
resolutionStrategy.force "org.apache.logging.log4j:log4j-api:2.24.2"
362+
resolutionStrategy.force "org.apache.logging.log4j:log4j-core:2.24.2"
363+
resolutionStrategy.force "jakarta.json:jakarta.json-api:2.1.3"
351364
}
352365

353366
apply plugin: 'com.netflix.nebula.ospackage'

plugin/src/main/java/org/opensearch/ml/action/connector/GetConnectorTransportAction.java

+12
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
import static org.opensearch.ml.utils.MLNodeUtils.createXContentParserFromRegistry;
1111
import static org.opensearch.ml.utils.RestActionUtils.getFetchSourceContext;
1212

13+
import java.util.Objects;
14+
1315
import org.opensearch.OpenSearchStatusException;
1416
import org.opensearch.action.ActionRequest;
1517
import org.opensearch.action.get.GetRequest;
@@ -65,6 +67,7 @@ public GetConnectorTransportAction(
6567
protected void doExecute(Task task, ActionRequest request, ActionListener<MLConnectorGetResponse> actionListener) {
6668
MLConnectorGetRequest mlConnectorGetRequest = MLConnectorGetRequest.fromActionRequest(request);
6769
String connectorId = mlConnectorGetRequest.getConnectorId();
70+
String tenantId = mlConnectorGetRequest.getTenantId();
6871
FetchSourceContext fetchSourceContext = getFetchSourceContext(mlConnectorGetRequest.isReturnContent());
6972
GetRequest getRequest = new GetRequest(ML_CONNECTOR_INDEX).id(connectorId).fetchSourceContext(fetchSourceContext);
7073
User user = RestActionUtils.getUserContext(client);
@@ -77,6 +80,15 @@ protected void doExecute(Task task, ActionRequest request, ActionListener<MLConn
7780
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
7881
Connector mlConnector = Connector.createConnector(parser);
7982
mlConnector.removeCredential();
83+
if (!Objects.equals(tenantId, mlConnector.getTenantId())) {
84+
actionListener
85+
.onFailure(
86+
new OpenSearchStatusException(
87+
"You don't have permission to access this connector",
88+
RestStatus.FORBIDDEN
89+
)
90+
);
91+
}
8092
if (connectorAccessControlHelper.hasPermission(user, mlConnector)) {
8193
actionListener.onResponse(MLConnectorGetResponse.builder().mlConnector(mlConnector).build());
8294
} else {

0 commit comments

Comments
 (0)