diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 47db0e17..09d3d630 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -8,7 +8,6 @@ on: pull_request: branches: - master - jobs: build_rca_pkg: @@ -40,6 +39,13 @@ jobs: - name: Update SHA working-directory: ./tmp/pa run: ./gradlew updateShas + # Explicitly set the docker-compose program path so that our build scripts in RCA can run the program + # This is necessary because of the Github Actions environment and the workingDir of the Gradle environment + - name: Set docker-compose path + run: echo ::set-env name=DOCKER_COMPOSE_LOCATION::$(which docker-compose) + # Set the vm.max_map_count system property to the minimum required to run Elasticsearch + - name: Set vm.max_map_count + run: sudo sysctl -w vm.max_map_count=262144 - name: Start Build working-directory: ./tmp/pa run: ./gradlew build diff --git a/build.gradle b/build.gradle index 14d8ac33..f00f4e48 100644 --- a/build.gradle +++ b/build.gradle @@ -26,6 +26,7 @@ buildscript { dependencies { classpath "org.elasticsearch.gradle:build-tools:${es_version}" + classpath 'org.ajoberstar:gradle-git:0.2.3' } } @@ -146,11 +147,6 @@ dependencyLicenses.doFirst { updateShas.updateShas() } -integTestRunner { - // add "-Dtests.security.manager=false" to VM options if you want to run integ tests in IntelliJ - systemProperty 'tests.security.manager', 'false' -} - bundlePlugin { from ("pa_config") { into "pa_config" @@ -170,7 +166,6 @@ bundlePlugin { from ("config/opendistro_performance_analyzer") { into "config" } - } gradle.startParameter.excludedTaskNames += [ "forbiddenApisMain", @@ -178,6 +173,38 @@ gradle.startParameter.excludedTaskNames += [ "forbiddenApisMain", "thirdPartyAudit", "testingConventions"] +import java.nio.file.Paths +import org.ajoberstar.gradle.git.tasks.GitClone + +String rcaDir + +// The following Gradle tasks are used to create a PA/RCA enabled Elasticsearch cluster +task cloneGitRepo(type: GitClone) { + rcaDir = Paths.get(getProject().getBuildDir().toString(), "performance-analyzer-rca").toString() + def destination = file(rcaDir) + uri = "https://github.com/opendistro-for-elasticsearch/performance-analyzer-rca.git" + destinationPath = destination + bare = false + enabled = !destination.exists() // to clone only once +} + +task setupEsCluster(type: Exec) { + dependsOn(cloneGitRepo) + workingDir(rcaDir) + commandLine './gradlew', 'enableRca' + doLast { + sleep(5000) + } +} + +integTestRunner { + // add "-Dtests.security.manager=false" to VM options if you want to run integ tests in IntelliJ + systemProperty 'tests.security.manager', 'false' + if (System.getProperty("tests.useDockerCluster").equals("true")) { + dependsOn(setupEsCluster) + } +} + // This is afterEvaluate because the bundlePlugin ZIP task is updated afterEvaluate and changes the ZIP name to match the plugin name afterEvaluate { ospackage { @@ -240,3 +267,5 @@ afterEvaluate { tasks = ['build', 'buildRpm', 'buildDeb'] } } + +check.dependsOn integTest diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 00000000..c7283048 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,11 @@ +# The following 3 properties are used for integration testing with Elasticsearch +# The "enablePaAndRca" gradle task in the performance-analyzer-rca repository contains logic to spin up a 2-node +# Elasticsearch cluster with the PA and RCA components enabled. The cluster endpoint for this cluster is localhost:9300 +# and the REST endpoint is localhost:9200. + +# The Elasticsearch cluster endpoint to use for test REST requests +systemProp.tests.rest.cluster=localhost:9200 +# The Elasticsearch cluster node communication endpoint +systemProp.tests.cluster=localhost:9300 +# Whether or not to spin up a new Elasticsearch cluster for integration testing +systemProp.tests.useDockerCluster=true diff --git a/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/PerformanceAnalyzerIT.java b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/PerformanceAnalyzerIT.java new file mode 100644 index 00000000..38265250 --- /dev/null +++ b/src/test/java/com/amazon/opendistro/elasticsearch/performanceanalyzer/PerformanceAnalyzerIT.java @@ -0,0 +1,106 @@ +package com.amazon.opendistro.elasticsearch.performanceanalyzer; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.apache.http.HttpHost; +import org.apache.http.HttpStatus; +import org.apache.http.util.EntityUtils; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.Logger; +import org.elasticsearch.client.Request; +import org.elasticsearch.client.Response; +import org.elasticsearch.client.RestClient; +import org.elasticsearch.test.rest.ESRestTestCase; +import org.junit.AfterClass; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Map; + +public class PerformanceAnalyzerIT extends ESRestTestCase { + private static final Logger LOG = LogManager.getLogger(PerformanceAnalyzerIT.class); + private static final int PORT = 9600; + private static final ObjectMapper mapper = new ObjectMapper(); + private static RestClient paClient; + + @Before + public void initPaClient() throws IOException { + String cluster = System.getProperty("tests.rest.cluster"); + logger.info("Cluster is {}", cluster); + if (cluster == null) { + throw new RuntimeException("Must specify [tests.rest.cluster] system property with a comma delimited list of [host:port] " + + "to which to send REST requests"); + } + List hosts = Collections.singletonList( + buildHttpHost(cluster.substring(0, cluster.lastIndexOf(":")), PORT)); + logger.info("initializing PerformanceAnalyzer client against {}", hosts); + paClient = buildClient(restClientSettings(), hosts.toArray(new HttpHost[0])); + } + + + public static void ensurePaAndRcaEnabled() throws Exception { + // TODO replace with waitFor with a 1min timeout + for (int i = 0; i < 60; i++) { + Response resp = client().performRequest(new Request("GET", "_opendistro/_performanceanalyzer/cluster/config")); + Map respMap = mapper.readValue(EntityUtils.toString(resp.getEntity(), "UTF-8"), + new TypeReference>(){}); + if (respMap.get("currentPerformanceAnalyzerClusterState").equals(3)) { + break; + } + Thread.sleep(1000L); + } + Response resp = client().performRequest(new Request("GET", "_opendistro/_performanceanalyzer/cluster/config")); + Map respMap = mapper.readValue(EntityUtils.toString(resp.getEntity(), "UTF-8"), + new TypeReference>(){}); + if (!respMap.get("currentPerformanceAnalyzerClusterState").equals(3)) { + throw new Exception("PA and RCA are not enabled on the target cluster!"); + } + } + + @Test + public void checkMetrics() throws Exception { + ensurePaAndRcaEnabled(); + Request request = new Request("GET", + "/_opendistro/_performanceanalyzer/metrics/?metrics=Disk_Utilization&agg=max&dim=&nodes=all"); + Response resp = paClient.performRequest(request); + Assert.assertEquals(HttpStatus.SC_OK, resp.getStatusLine().getStatusCode()); + String jsonString = EntityUtils.toString(resp.getEntity()); + JsonNode root = mapper.readTree(jsonString); + root.forEach( entry -> { + JsonNode data = entry.get(TestUtils.DATA); + Assert.assertEquals(1, data.get(TestUtils.FIELDS).size()); + JsonNode field = data.get(TestUtils.FIELDS).get(0); + Assert.assertEquals(TestUtils.M_DISKUTIL, field.get(TestUtils.FIELD_NAME).asText()); + Assert.assertEquals(TestUtils.DOUBLE_TYPE, field.get(TestUtils.FIELD_TYPE).asText()); + JsonNode records = data.get(TestUtils.RECORDS); + Assert.assertEquals(1, records.size()); + records.get(0).forEach(record -> Assert.assertTrue(record.asDouble() >= 0)); + }); + } + + @AfterClass + public static void closePaClient() throws Exception { + ESRestTestCase.closeClients(); + paClient.close(); + LOG.debug("AfterClass has run"); + } + + private static class TestUtils { + public static final String DATA = "data"; + public static final String RECORDS = "records"; + + // Field related strings + public static final String FIELDS = "fields"; + public static final String FIELD_NAME = "name"; + public static final String FIELD_TYPE = "type"; + public static final String DOUBLE_TYPE = "DOUBLE"; + + // Metrics related strings + public static final String M_DISKUTIL = "Disk_Utilization"; + } +}