Skip to content

Commit ad3c528

Browse files
rbhavnazane-neo
andauthored
Add model group rest ITs (#942) (#944)
* Add model group rest ITs * Fix failure ITs * Fix failure ITs * Fix failure ITs --------- (cherry picked from commit 86c8cda) Signed-off-by: Zan Niu <[email protected]> Signed-off-by: Bhavana Ramaram <[email protected]> Co-authored-by: zane-neo <[email protected]>
1 parent ee35cde commit ad3c528

File tree

3 files changed

+339
-5
lines changed

3 files changed

+339
-5
lines changed

plugin/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,7 @@ integTest {
150150
if (System.getProperty("https") == null || System.getProperty("https") == "false") {
151151
filter {
152152
excludeTestsMatching "org.opensearch.ml.rest.SecureMLRestIT"
153+
excludeTestsMatching "org.opensearch.ml.rest.MLModelGroupRestIT"
153154
}
154155
}
155156

plugin/src/test/java/org/opensearch/ml/rest/MLCommonsRestTestCase.java

Lines changed: 25 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@
2323
import java.net.URI;
2424
import java.net.URISyntaxException;
2525
import java.nio.file.Path;
26-
import java.util.ArrayList;
2726
import java.util.Arrays;
2827
import java.util.Collections;
2928
import java.util.List;
@@ -76,6 +75,7 @@
7675
import org.opensearch.ml.common.model.MLModelState;
7776
import org.opensearch.ml.common.model.TextEmbeddingModelConfig;
7877
import org.opensearch.ml.common.transport.model_group.MLRegisterModelGroupInput;
78+
import org.opensearch.ml.common.transport.model_group.MLUpdateModelGroupInput;
7979
import org.opensearch.ml.common.transport.register.MLRegisterModelInput;
8080
import org.opensearch.ml.stats.ActionName;
8181
import org.opensearch.ml.stats.MLActionLevelStat;
@@ -457,7 +457,7 @@ public Response createSearchRole(String role, String index) throws IOException {
457457
);
458458
}
459459

460-
public Response createUser(String name, String password, ArrayList<String> backendRoles) throws IOException {
460+
public Response createUser(String name, String password, List<String> backendRoles) throws IOException {
461461
JsonArray backendRolesString = new JsonArray();
462462
for (int i = 0; i < backendRoles.size(); i++) {
463463
backendRolesString.add(backendRoles.get(i));
@@ -496,7 +496,7 @@ public Response deleteUser(String user) throws IOException {
496496
);
497497
}
498498

499-
public Response createRoleMapping(String role, ArrayList<String> users) throws IOException {
499+
public Response createRoleMapping(String role, List<String> users) throws IOException {
500500
JsonArray usersString = new JsonArray();
501501
for (int i = 0; i < users.size(); i++) {
502502
usersString.add(users.get(i));
@@ -671,14 +671,34 @@ public MLRegisterModelGroupInput createRegisterModelGroupInput(
671671
.build();
672672
}
673673

674+
public MLUpdateModelGroupInput createUpdateModelGroupInput(
675+
String modelGroupId,
676+
String name,
677+
String description,
678+
List<String> backendRoles,
679+
ModelAccessMode modelAccessMode,
680+
Boolean isAddAllBackendRoles
681+
) {
682+
return MLUpdateModelGroupInput
683+
.builder()
684+
.modelGroupID(modelGroupId)
685+
.name(name)
686+
.description(description)
687+
.backendRoles(backendRoles)
688+
.modelAccessMode(modelAccessMode)
689+
.isAddAllBackendRoles(isAddAllBackendRoles)
690+
.build();
691+
}
692+
674693
public void registerModelGroup(RestClient client, String input, Consumer<Map<String, Object>> function) throws IOException {
675694
Response response = TestHelper.makeRequest(client, "POST", "/_plugins/_ml/model_groups/_register", null, input, null);
676695
verifyResponse(function, response);
677696
}
678697

679-
public void updateModelGroup(RestClient client, String modelGroupId, Consumer<Map<String, Object>> function) throws IOException {
698+
public void updateModelGroup(RestClient client, String modelGroupId, String input, Consumer<Map<String, Object>> function)
699+
throws IOException {
680700
Response response = TestHelper
681-
.makeRequest(client, "POST", "/_plugins/_ml/model_groups/" + modelGroupId + "/_update", null, "", null);
701+
.makeRequest(client, "PUT", "/_plugins/_ml/model_groups/" + modelGroupId + "/_update", null, input, null);
682702
verifyResponse(function, response);
683703
}
684704

Lines changed: 313 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
/*
2+
*
3+
* * Copyright OpenSearch Contributors
4+
* * SPDX-License-Identifier: Apache-2.0
5+
*
6+
*/
7+
8+
package org.opensearch.ml.rest;
9+
10+
import java.io.IOException;
11+
import java.util.Map;
12+
13+
import org.apache.http.HttpHeaders;
14+
import org.apache.http.HttpHost;
15+
import org.apache.http.message.BasicHeader;
16+
import org.junit.After;
17+
import org.junit.Before;
18+
import org.junit.Rule;
19+
import org.junit.rules.ExpectedException;
20+
import org.opensearch.client.Response;
21+
import org.opensearch.client.ResponseException;
22+
import org.opensearch.client.RestClient;
23+
import org.opensearch.commons.rest.SecureRestClientBuilder;
24+
import org.opensearch.ml.common.ModelAccessMode;
25+
import org.opensearch.ml.common.transport.model_group.MLRegisterModelGroupInput;
26+
import org.opensearch.ml.common.transport.model_group.MLUpdateModelGroupInput;
27+
import org.opensearch.ml.utils.TestHelper;
28+
29+
import com.google.common.collect.ImmutableList;
30+
31+
public class MLModelGroupRestIT extends MLCommonsRestTestCase {
32+
33+
String mlNoAccessUser = "ml_no_access";
34+
RestClient mlNoAccessClient;
35+
String mlReadOnlyUser = "ml_readonly";
36+
RestClient mlReadOnlyClient;
37+
String mlFullAccessNoIndexAccessUser = "ml_full_access_no_index_access";
38+
RestClient mlFullAccessNoIndexAccessClient;
39+
String mlFullAccessUser = "ml_full_access";
40+
RestClient mlFullAccessClient;
41+
String mlNonAdminFullAccessWithoutBackendRoleUser = "ml_non_admin_full_access_without_backend_role_user";
42+
RestClient mlNonAdminFullAccessWithoutBackendRoleClient;
43+
44+
String mlNonOwnerFullAccessWithBackendRoleUser = "ml_non_owner_full_access_with_backend_role_user";
45+
RestClient mlNonOwnerFullAccessWithBackendRoleClient;
46+
47+
private String indexSearchAccessRole = "ml_test_index_all_search";
48+
49+
private String opensearchBackendRole = "opensearch";
50+
51+
private MLRegisterModelGroupInput mlRegisterModelGroupInput;
52+
53+
private MLUpdateModelGroupInput mlUpdateModelGroupInput;
54+
55+
private final String MATCH_ALL_QUERY = "{\n" + " \"query\": {\n" + " \"match_all\": {}\n" + " }\n" + "}";
56+
57+
@Rule
58+
public ExpectedException exceptionRule = ExpectedException.none();
59+
60+
private String modelGroupId;
61+
62+
@Before
63+
public void setup() throws IOException {
64+
Response response = TestHelper
65+
.makeRequest(
66+
client(),
67+
"PUT",
68+
"_cluster/settings",
69+
null,
70+
"{\"persistent\":{\"plugins.ml_commons.model_access_control_enabled\":true}}",
71+
ImmutableList.of(new BasicHeader(HttpHeaders.USER_AGENT, ""))
72+
);
73+
assertEquals(200, response.getStatusLine().getStatusCode());
74+
75+
if (!isHttps()) {
76+
throw new IllegalArgumentException("Secure Tests are running but HTTPS is not set");
77+
}
78+
createSearchRole(indexSearchAccessRole, "*");
79+
80+
createUser(mlNoAccessUser, mlNoAccessUser, ImmutableList.of(opensearchBackendRole));
81+
mlNoAccessClient = new SecureRestClientBuilder(
82+
getClusterHosts().toArray(new HttpHost[0]),
83+
isHttps(),
84+
mlNoAccessUser,
85+
mlNoAccessUser
86+
).setSocketTimeout(60000).build();
87+
88+
createUser(mlReadOnlyUser, mlReadOnlyUser, ImmutableList.of(opensearchBackendRole));
89+
mlReadOnlyClient = new SecureRestClientBuilder(
90+
getClusterHosts().toArray(new HttpHost[0]),
91+
isHttps(),
92+
mlReadOnlyUser,
93+
mlReadOnlyUser
94+
).setSocketTimeout(60000).build();
95+
96+
createUser(mlFullAccessNoIndexAccessUser, mlFullAccessNoIndexAccessUser, ImmutableList.of(opensearchBackendRole));
97+
mlFullAccessNoIndexAccessClient = new SecureRestClientBuilder(
98+
getClusterHosts().toArray(new HttpHost[0]),
99+
isHttps(),
100+
mlFullAccessNoIndexAccessUser,
101+
mlFullAccessNoIndexAccessUser
102+
).setSocketTimeout(60000).build();
103+
104+
createUser(mlFullAccessUser, mlFullAccessUser, ImmutableList.of(opensearchBackendRole));
105+
mlFullAccessClient = new SecureRestClientBuilder(
106+
getClusterHosts().toArray(new HttpHost[0]),
107+
isHttps(),
108+
mlFullAccessUser,
109+
mlFullAccessUser
110+
).setSocketTimeout(60000).build();
111+
112+
createUser(mlNonAdminFullAccessWithoutBackendRoleUser, mlNonAdminFullAccessWithoutBackendRoleUser, ImmutableList.of());
113+
mlNonAdminFullAccessWithoutBackendRoleClient = new SecureRestClientBuilder(
114+
getClusterHosts().toArray(new HttpHost[0]),
115+
isHttps(),
116+
mlNonAdminFullAccessWithoutBackendRoleUser,
117+
mlNonAdminFullAccessWithoutBackendRoleUser
118+
).setSocketTimeout(60000).build();
119+
120+
createUser(
121+
mlNonOwnerFullAccessWithBackendRoleUser,
122+
mlNonOwnerFullAccessWithBackendRoleUser,
123+
ImmutableList.of(opensearchBackendRole)
124+
);
125+
mlNonOwnerFullAccessWithBackendRoleClient = new SecureRestClientBuilder(
126+
getClusterHosts().toArray(new HttpHost[0]),
127+
isHttps(),
128+
mlNonOwnerFullAccessWithBackendRoleUser,
129+
mlNonOwnerFullAccessWithBackendRoleUser
130+
).setSocketTimeout(60000).build();
131+
132+
createRoleMapping("ml_read_access", ImmutableList.of(mlReadOnlyUser));
133+
createRoleMapping(
134+
"ml_full_access",
135+
ImmutableList
136+
.of(
137+
mlFullAccessNoIndexAccessUser,
138+
mlFullAccessUser,
139+
mlNonAdminFullAccessWithoutBackendRoleUser,
140+
mlNonOwnerFullAccessWithBackendRoleUser
141+
)
142+
);
143+
createRoleMapping(
144+
indexSearchAccessRole,
145+
ImmutableList.of(mlFullAccessUser, mlNonAdminFullAccessWithoutBackendRoleUser, mlNonOwnerFullAccessWithBackendRoleUser)
146+
);
147+
148+
mlRegisterModelGroupInput = createRegisterModelGroupInput(
149+
ImmutableList.of(opensearchBackendRole),
150+
ModelAccessMode.RESTRICTED,
151+
false
152+
);
153+
154+
registerModelGroup(mlFullAccessClient, TestHelper.toJsonString(mlRegisterModelGroupInput), registerModelGroupResult -> {
155+
this.modelGroupId = (String) registerModelGroupResult.get("model_group_id");
156+
});
157+
158+
mlUpdateModelGroupInput = createUpdateModelGroupInput(
159+
this.modelGroupId,
160+
"new_name",
161+
"new description",
162+
ImmutableList.of(opensearchBackendRole),
163+
ModelAccessMode.RESTRICTED,
164+
false
165+
);
166+
}
167+
168+
@After
169+
public void deleteUserSetup() throws IOException {
170+
mlNoAccessClient.close();
171+
mlReadOnlyClient.close();
172+
mlFullAccessNoIndexAccessClient.close();
173+
mlFullAccessClient.close();
174+
mlNonAdminFullAccessWithoutBackendRoleClient.close();
175+
mlNonOwnerFullAccessWithBackendRoleClient.close();
176+
deleteUser(mlNoAccessUser);
177+
deleteUser(mlReadOnlyUser);
178+
deleteUser(mlFullAccessNoIndexAccessUser);
179+
deleteUser(mlFullAccessUser);
180+
deleteUser(mlNonAdminFullAccessWithoutBackendRoleUser);
181+
deleteUser(mlNonOwnerFullAccessWithBackendRoleUser);
182+
}
183+
184+
public void test_registerModelGroup_withNoAccess() throws IOException {
185+
exceptionRule.expect(ResponseException.class);
186+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/register_model_group]");
187+
registerModelGroup(mlNoAccessClient, TestHelper.toJsonString(mlRegisterModelGroupInput), null);
188+
}
189+
190+
public void test_registerModelGroup_WithReadOnlyMLAccess() throws IOException {
191+
exceptionRule.expect(ResponseException.class);
192+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/register_model_group]");
193+
registerModelGroup(mlReadOnlyClient, TestHelper.toJsonString(mlRegisterModelGroupInput), null);
194+
}
195+
196+
public void test_registerModelGroup_withFullAccess() throws IOException {
197+
registerModelGroup(mlFullAccessClient, TestHelper.toJsonString(mlRegisterModelGroupInput), registerModelGroupResult -> {
198+
assertTrue(registerModelGroupResult.containsKey("model_group_id"));
199+
});
200+
}
201+
202+
public void test_updateModelGroup_withNoAccess() throws IOException {
203+
exceptionRule.expect(ResponseException.class);
204+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/update_model_group]");
205+
updateModelGroup(mlNoAccessClient, this.modelGroupId, TestHelper.toJsonString(mlUpdateModelGroupInput), null);
206+
}
207+
208+
public void test_updateModelGroup_WithReadOnlyMLAccess() throws IOException {
209+
exceptionRule.expect(ResponseException.class);
210+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/update_model_group]");
211+
updateModelGroup(mlReadOnlyClient, this.modelGroupId, TestHelper.toJsonString(mlUpdateModelGroupInput), null);
212+
}
213+
214+
public void test_updateModelGroup_userIsOwner() throws IOException {
215+
updateModelGroup(
216+
mlFullAccessClient,
217+
this.modelGroupId,
218+
TestHelper.toJsonString(mlUpdateModelGroupInput),
219+
registerModelGroupResult -> {
220+
assertTrue(registerModelGroupResult.containsKey("status"));
221+
}
222+
);
223+
}
224+
225+
public void test_updateModelGroup_userIsNonOwnerHasBackendRole() throws IOException {
226+
MLUpdateModelGroupInput mlUpdateModelGroupInput = createUpdateModelGroupInput(
227+
this.modelGroupId,
228+
"new_name",
229+
"new description",
230+
null,
231+
null,
232+
null
233+
);
234+
updateModelGroup(
235+
mlNonOwnerFullAccessWithBackendRoleClient,
236+
this.modelGroupId,
237+
TestHelper.toJsonString(mlUpdateModelGroupInput),
238+
registerModelGroupResult -> {
239+
assertTrue(registerModelGroupResult.containsKey("status"));
240+
}
241+
);
242+
}
243+
244+
public void test_updateModelGroup_userIsNonOwnerNoBackendRole_withPermissionFields() throws IOException {
245+
exceptionRule.expect(ResponseException.class);
246+
exceptionRule.expectMessage("Only owner/admin has valid privilege to perform update access control data");
247+
updateModelGroup(
248+
mlNonAdminFullAccessWithoutBackendRoleClient,
249+
this.modelGroupId,
250+
TestHelper.toJsonString(mlUpdateModelGroupInput),
251+
null
252+
);
253+
}
254+
255+
public void test_updateModelGroup_userIsNonOwner_withoutPermissionFields() throws IOException {
256+
exceptionRule.expect(ResponseException.class);
257+
exceptionRule.expectMessage("User doesn't have corresponding backend role to perform update action");
258+
MLUpdateModelGroupInput mlUpdateModelGroupInput = createUpdateModelGroupInput(
259+
this.modelGroupId,
260+
"new_name",
261+
"new description",
262+
null,
263+
null,
264+
null
265+
);
266+
updateModelGroup(
267+
mlNonAdminFullAccessWithoutBackendRoleClient,
268+
this.modelGroupId,
269+
TestHelper.toJsonString(mlUpdateModelGroupInput),
270+
null
271+
);
272+
}
273+
274+
public void test_searchModelGroup_withNoAccess() throws IOException {
275+
exceptionRule.expect(ResponseException.class);
276+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/model_groups/search]");
277+
searchModelGroups(mlNoAccessClient, MATCH_ALL_QUERY, null);
278+
}
279+
280+
public void test_searchModelGroup_WithReadOnlyMLAccess() throws IOException {
281+
exceptionRule.expect(ResponseException.class);
282+
exceptionRule.expectMessage("no permissions for [cluster:admin/opensearch/ml/model_groups/search]");
283+
searchModelGroups(mlReadOnlyClient, MATCH_ALL_QUERY, null);
284+
}
285+
286+
public void test_searchModelGroup_userIsOwner() throws IOException {
287+
searchModelGroups(mlFullAccessClient, MATCH_ALL_QUERY, r -> {
288+
assertTrue(r.containsKey("hits"));
289+
assertTrue(((Map<String, Object>) r.get("hits")).containsKey("total"));
290+
Map<String, Object> total = (Map<String, Object>) ((Map<String, Object>) r.get("hits")).get("total");
291+
assertEquals(1.0, total.get("value"));
292+
});
293+
}
294+
295+
public void test_searchModelGroup_userNonOwnerHasBackendRole() throws IOException {
296+
searchModelGroups(mlNonOwnerFullAccessWithBackendRoleClient, MATCH_ALL_QUERY, r -> {
297+
assertTrue(r.containsKey("hits"));
298+
assertTrue(((Map<String, Object>) r.get("hits")).containsKey("total"));
299+
Map<String, Object> total = (Map<String, Object>) ((Map<String, Object>) r.get("hits")).get("total");
300+
assertEquals(1.0, total.get("value"));
301+
});
302+
}
303+
304+
public void test_searchModelGroup_userHasNoModelAccess() throws IOException {
305+
searchModelGroups(mlNonAdminFullAccessWithoutBackendRoleClient, MATCH_ALL_QUERY, r -> {
306+
assertTrue(r.containsKey("hits"));
307+
assertTrue(((Map<String, Object>) r.get("hits")).containsKey("total"));
308+
Map<String, Object> total = (Map<String, Object>) ((Map<String, Object>) r.get("hits")).get("total");
309+
assertEquals(0.0, total.get("value"));
310+
});
311+
}
312+
313+
}

0 commit comments

Comments
 (0)