Skip to content

[8.19] [ML] Yaml test that runs inference as a non-admin user (#128363) #128395

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

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions x-pack/plugin/inference/qa/inference-with-security/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apply plugin: 'elasticsearch.internal-yaml-rest-test'

dependencies {
testImplementation(testArtifact(project(xpackModule('core'))))
testImplementation(testArtifact(project(':server')))
testImplementation(project(':x-pack:plugin:inference:qa:test-service-plugin'))
testImplementation project(':modules:reindex')
testImplementation project(':modules:mapper-extras')
clusterPlugins project(':x-pack:plugin:inference:qa:test-service-plugin')
}

tasks.named('yamlRestTest') {
usesDefaultDistribution("to be triaged")
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/*
* Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one
* or more contributor license agreements. Licensed under the Elastic License
* 2.0; you may not use this file except in compliance with the Elastic License
* 2.0.
*/

package org.elasticsearch.xpack.inference;

import com.carrotsearch.randomizedtesting.annotations.ParametersFactory;

import org.elasticsearch.client.Request;
import org.elasticsearch.common.Strings;
import org.elasticsearch.common.settings.SecureString;
import org.elasticsearch.common.settings.Settings;
import org.elasticsearch.common.util.concurrent.ThreadContext;
import org.elasticsearch.test.cluster.ElasticsearchCluster;
import org.elasticsearch.test.cluster.local.distribution.DistributionType;
import org.elasticsearch.test.cluster.util.resource.Resource;
import org.elasticsearch.test.rest.yaml.ClientYamlTestCandidate;
import org.elasticsearch.test.rest.yaml.ESClientYamlSuiteTestCase;
import org.junit.After;
import org.junit.ClassRule;

import java.io.IOException;
import java.util.List;
import java.util.Map;

public class InferenceWithSecurityRestIT extends ESClientYamlSuiteTestCase {

private static final String TEST_ADMIN_USERNAME = "x_pack_rest_user";
private static final String TEST_ADMIN_PASSWORD = "x-pack-test-password";

private static final String INFERENCE_USERNAME = "inference_user";
private static final String INFERENCE_PASSWORD = "inference_user_password";

@ClassRule
public static ElasticsearchCluster cluster = ElasticsearchCluster.local()
.systemProperty("tests.seed", System.getProperty("tests.seed"))
.setting("xpack.security.enabled", "true")
.setting("xpack.license.self_generated.type", "trial")
.rolesFile(Resource.fromClasspath("roles.yml"))
.user(TEST_ADMIN_USERNAME, TEST_ADMIN_PASSWORD) // admin user for setup and teardown
.user(INFERENCE_USERNAME, INFERENCE_PASSWORD, "monitor_only_user", false)
.plugin("inference-service-test")
.distribution(DistributionType.DEFAULT)
.build();

public InferenceWithSecurityRestIT(final ClientYamlTestCandidate testCandidate) {
super(testCandidate);
}

@Override
protected Settings restClientSettings() {
String token = basicAuthHeaderValue(INFERENCE_USERNAME, new SecureString(INFERENCE_PASSWORD.toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}

@Override
protected Settings restAdminSettings() {
String token = basicAuthHeaderValue(TEST_ADMIN_USERNAME, new SecureString(TEST_ADMIN_PASSWORD.toCharArray()));
return Settings.builder().put(ThreadContext.PREFIX + ".Authorization", token).build();
}

@Override
protected String getTestRestCluster() {
return cluster.getHttpAddresses();
}

@ParametersFactory
public static Iterable<Object[]> parameters() throws Exception {
return ESClientYamlSuiteTestCase.createParameters();
}

@After
public void cleanup() throws Exception {
for (var model : getAllModels()) {
var inferenceId = model.get("inference_id");
try {
var endpoint = Strings.format("_inference/%s?force", inferenceId);
adminClient().performRequest(new Request("DELETE", endpoint));
} catch (Exception ex) {
logger.warn(() -> "failed to delete inference endpoint " + inferenceId, ex);
}
}
}

@SuppressWarnings("unchecked")
static List<Map<String, Object>> getAllModels() throws IOException {
var request = new Request("GET", "_inference/_all");
var response = client().performRequest(request);
return (List<Map<String, Object>>) entityAsMap(response).get("endpoints");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
setup:
- skip:
features:
- close_to
- requires:
test_runner_features: "close_to"

- do:
inference.put:
task_type: rerank
inference_id: my-rerank-model
body: >
{
"service": "test_reranking_service",
"service_settings": {
"model_id": "my_model",
"api_key": "abc64"
},
"task_settings": {
}
}

- do:
indices.create:
index: test-index
body:
mappings:
properties:
text:
type: text
topic:
type: keyword
subtopic:
type: keyword
inference_text_field:
type: text

- do:
index:
index: test-index
id: doc_2
body:
text: "The phases of the Moon come from the position of the Moon relative to the Earth and Sun."
topic: [ "science" ]
subtopic: [ "astronomy" ]
inference_text_field: "0"
refresh: true

- do:
index:
index: test-index
id: doc_3
body:
text: "Sun Moon Lake is a lake in Nantou County, Taiwan. It is the largest lake in Taiwan."
topic: [ "geography" ]
inference_text_field: "1"
refresh: true

- do:
index:
index: test-index
id: doc_1
body:
text: "As seen from Earth, a solar eclipse happens when the Moon is directly between the Earth and the Sun."
topic: [ "science" ]
subtopic: [ "technology" ]
inference_text_field: "-1"
refresh: true

---
"Simple text similarity rank retriever":

- requires:
cluster_features: "test_reranking_service.parse_text_as_score"
reason: test_reranking_service can now parse provided input as score to provide deterministic ranks

- do:
search:
index: test-index
body:
track_total_hits: true
fields: [ "text", "topic" ]
retriever:
text_similarity_reranker:
retriever:
# this one returns docs 1 and 2
standard:
query:
bool: {
should: [
{
constant_score: {
filter: {
term: {
subtopic: "technology"
}
},
boost: 10
}
},
{
constant_score: {
filter: {
term: {
subtopic: "astronomy"
}
},
boost: 1
}
}
]
}
rank_window_size: 10
inference_id: my-rerank-model
inference_text: "How often does the moon hide the sun?"
field: inference_text_field
size: 10

- match: { hits.total.value: 2 }
- length: { hits.hits: 2 }

- match: { hits.hits.0._id: "doc_2" }
- match: { hits.hits.1._id: "doc_1" }
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
monitor_only_user:
cluster:
- monitor
- manage_inference
indices:
- names: [ 'test-index*' ]
privileges:
- all