|  | @@ -0,0 +1,163 @@
 | 
	
		
			
				|  |  | +/*
 | 
	
		
			
				|  |  | + * 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.ml.integration;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import org.apache.http.util.EntityUtils;
 | 
	
		
			
				|  |  | +import org.elasticsearch.client.Request;
 | 
	
		
			
				|  |  | +import org.elasticsearch.client.RequestOptions;
 | 
	
		
			
				|  |  | +import org.elasticsearch.client.Response;
 | 
	
		
			
				|  |  | +import org.elasticsearch.test.rest.ESRestTestCase;
 | 
	
		
			
				|  |  | +import org.junit.Before;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import java.io.IOException;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +import static org.hamcrest.Matchers.containsString;
 | 
	
		
			
				|  |  | +import static org.hamcrest.Matchers.not;
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +public class DatafeedWithoutSecurityRestIT extends ESRestTestCase {
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    @Before
 | 
	
		
			
				|  |  | +    public void setUpData() throws Exception {
 | 
	
		
			
				|  |  | +        addAirlineData();
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    /**
 | 
	
		
			
				|  |  | +     * The main purpose this test is to ensure the X-elastic-product
 | 
	
		
			
				|  |  | +     * header is returned when security is disabled. The vast majority
 | 
	
		
			
				|  |  | +     * of Datafeed test coverage is in DatafeedJobsRestIT but that
 | 
	
		
			
				|  |  | +     * suite runs with security enabled.
 | 
	
		
			
				|  |  | +     */
 | 
	
		
			
				|  |  | +    public void testPreviewMissingHeader() throws Exception {
 | 
	
		
			
				|  |  | +        String jobId = "missing-header";
 | 
	
		
			
				|  |  | +        Request createJobRequest = new Request("PUT", "/_ml/anomaly_detectors/" + jobId);
 | 
	
		
			
				|  |  | +        createJobRequest.setJsonEntity("""
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +              "description": "Aggs job",
 | 
	
		
			
				|  |  | +              "analysis_config": {
 | 
	
		
			
				|  |  | +                "bucket_span": "1h",
 | 
	
		
			
				|  |  | +                "detectors": [
 | 
	
		
			
				|  |  | +                  {
 | 
	
		
			
				|  |  | +                    "function": "count",
 | 
	
		
			
				|  |  | +                    "partition_field_name": "airline"
 | 
	
		
			
				|  |  | +                  }
 | 
	
		
			
				|  |  | +                ],
 | 
	
		
			
				|  |  | +                "influencers": [
 | 
	
		
			
				|  |  | +                  "airline"
 | 
	
		
			
				|  |  | +                ],
 | 
	
		
			
				|  |  | +                "model_prune_window": "30d"
 | 
	
		
			
				|  |  | +              },
 | 
	
		
			
				|  |  | +                  "model_plot_config": {
 | 
	
		
			
				|  |  | +                    "enabled": false,
 | 
	
		
			
				|  |  | +                    "annotations_enabled": false
 | 
	
		
			
				|  |  | +                  },
 | 
	
		
			
				|  |  | +                  "analysis_limits": {
 | 
	
		
			
				|  |  | +                    "model_memory_limit": "11mb",
 | 
	
		
			
				|  |  | +                    "categorization_examples_limit": 4
 | 
	
		
			
				|  |  | +                  },
 | 
	
		
			
				|  |  | +              "data_description" : {"time_field": "time stamp"}
 | 
	
		
			
				|  |  | +            }""");
 | 
	
		
			
				|  |  | +        client().performRequest(createJobRequest);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        String datafeedId = "datafeed-" + jobId;
 | 
	
		
			
				|  |  | +        Request createDatafeedRequest = new Request("PUT", "/_ml/datafeeds/" + datafeedId);
 | 
	
		
			
				|  |  | +        createDatafeedRequest.setJsonEntity("""
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +                "job_id": "missing-header",
 | 
	
		
			
				|  |  | +                "query": {
 | 
	
		
			
				|  |  | +                    "bool": {
 | 
	
		
			
				|  |  | +                        "must": [
 | 
	
		
			
				|  |  | +                            {
 | 
	
		
			
				|  |  | +                                "match_all": {}
 | 
	
		
			
				|  |  | +                            }
 | 
	
		
			
				|  |  | +                        ]
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +                "indices": [
 | 
	
		
			
				|  |  | +                    "airline-data"
 | 
	
		
			
				|  |  | +                ],
 | 
	
		
			
				|  |  | +                "scroll_size": 1000
 | 
	
		
			
				|  |  | +            }
 | 
	
		
			
				|  |  | +            """);
 | 
	
		
			
				|  |  | +        client().performRequest(createDatafeedRequest);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        Request getFeed = new Request("GET", "/_ml/datafeeds/" + datafeedId + "/_preview");
 | 
	
		
			
				|  |  | +        RequestOptions.Builder options = getFeed.getOptions().toBuilder();
 | 
	
		
			
				|  |  | +        getFeed.setOptions(options);
 | 
	
		
			
				|  |  | +        var previewResponse = client().performRequest(getFeed);
 | 
	
		
			
				|  |  | +        assertXProductResponseHeader(previewResponse);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        client().performRequest(new Request("POST", "/_ml/anomaly_detectors/" + jobId + "/_open"));
 | 
	
		
			
				|  |  | +        Request startRequest = new Request("POST", "/_ml/datafeeds/" + datafeedId + "/_start");
 | 
	
		
			
				|  |  | +        Response startDatafeedResponse = client().performRequest(startRequest);
 | 
	
		
			
				|  |  | +        assertXProductResponseHeader(startDatafeedResponse);
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void assertXProductResponseHeader(Response response) {
 | 
	
		
			
				|  |  | +        assertEquals("Elasticsearch", response.getHeader("X-elastic-product"));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void addAirlineData() throws IOException {
 | 
	
		
			
				|  |  | +        StringBuilder bulk = new StringBuilder();
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        // Create index with source = enabled, doc_values = enabled, stored = false + multi-field
 | 
	
		
			
				|  |  | +        Request createAirlineDataRequest = new Request("PUT", "/airline-data");
 | 
	
		
			
				|  |  | +        // space in 'time stamp' is intentional
 | 
	
		
			
				|  |  | +        createAirlineDataRequest.setJsonEntity("""
 | 
	
		
			
				|  |  | +            {
 | 
	
		
			
				|  |  | +              "mappings": {
 | 
	
		
			
				|  |  | +                "runtime": {
 | 
	
		
			
				|  |  | +                  "airline_lowercase_rt": {
 | 
	
		
			
				|  |  | +                    "type": "keyword",
 | 
	
		
			
				|  |  | +                    "script": {
 | 
	
		
			
				|  |  | +                      "source": "emit(params._source.airline.toLowerCase())"
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                  }
 | 
	
		
			
				|  |  | +                },
 | 
	
		
			
				|  |  | +                "properties": {
 | 
	
		
			
				|  |  | +                  "time stamp": {
 | 
	
		
			
				|  |  | +                    "type": "date"
 | 
	
		
			
				|  |  | +                  },
 | 
	
		
			
				|  |  | +                  "airline": {
 | 
	
		
			
				|  |  | +                    "type": "text",
 | 
	
		
			
				|  |  | +                    "fields": {
 | 
	
		
			
				|  |  | +                      "text": {
 | 
	
		
			
				|  |  | +                        "type": "text"
 | 
	
		
			
				|  |  | +                      },
 | 
	
		
			
				|  |  | +                      "keyword": {
 | 
	
		
			
				|  |  | +                        "type": "keyword"
 | 
	
		
			
				|  |  | +                      }
 | 
	
		
			
				|  |  | +                    }
 | 
	
		
			
				|  |  | +                  },
 | 
	
		
			
				|  |  | +                  "responsetime": {
 | 
	
		
			
				|  |  | +                    "type": "float"
 | 
	
		
			
				|  |  | +                  }
 | 
	
		
			
				|  |  | +                }
 | 
	
		
			
				|  |  | +              }
 | 
	
		
			
				|  |  | +            }""");
 | 
	
		
			
				|  |  | +        client().performRequest(createAirlineDataRequest);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        bulk.append("""
 | 
	
		
			
				|  |  | +            {"index": {"_index": "airline-data", "_id": 1}}
 | 
	
		
			
				|  |  | +            {"time stamp":"2016-06-01T00:00:00Z","airline":"AAA","responsetime":135.22}
 | 
	
		
			
				|  |  | +            {"index": {"_index": "airline-data", "_id": 2}}
 | 
	
		
			
				|  |  | +            {"time stamp":"2016-06-01T01:59:00Z","airline":"AAA","responsetime":541.76}
 | 
	
		
			
				|  |  | +            """);
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        bulkIndex(bulk.toString());
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    private void bulkIndex(String bulk) throws IOException {
 | 
	
		
			
				|  |  | +        Request bulkRequest = new Request("POST", "/_bulk");
 | 
	
		
			
				|  |  | +        bulkRequest.setJsonEntity(bulk);
 | 
	
		
			
				|  |  | +        bulkRequest.addParameter("refresh", "true");
 | 
	
		
			
				|  |  | +        bulkRequest.addParameter("pretty", null);
 | 
	
		
			
				|  |  | +        String bulkResponse = EntityUtils.toString(client().performRequest(bulkRequest).getEntity());
 | 
	
		
			
				|  |  | +        assertThat(bulkResponse, not(containsString("\"errors\": false")));
 | 
	
		
			
				|  |  | +    }
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +}
 |