diff --git a/build.gradle.kts b/build.gradle.kts index 05f615d0..9977f623 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -74,8 +74,15 @@ dependencies { // Test testImplementation("org.junit.jupiter:junit-jupiter-engine:6.0.3") - testImplementation("org.junit.platform:junit-platform-launcher:1.14.4") + testImplementation("org.junit.platform:junit-platform-launcher:2.0.3") testImplementation("org.wiremock:wiremock:3.13.2") + + // Testcontainers + testImplementation(platform("org.testcontainers:testcontainers-bom:1.21.3")) + testImplementation("org.testcontainers:junit-jupiter") + testImplementation("org.testcontainers:testcontainers") + // SLF4J 2.x binding so Testcontainers debug logs are visible (log4j-slf4j-impl targets 1.x) + testImplementation("org.apache.logging.log4j:log4j-slf4j2-impl:$log4jVersion") } // --------------------------------------------------------------------------- @@ -180,6 +187,23 @@ tasks.withType { useJUnitPlatform() } +tasks.named("test") { + useJUnitPlatform { excludeTags("e2e") } +} + +val e2eTest by tasks.registering(Test::class) { + description = "Runs end-to-end tests that require Docker (via Testcontainers)" + group = "verification" + useJUnitPlatform { includeTags("e2e") } + jvmArgs("-Djava.net.preferIPv4Stack=true", "-Djava.security.egd=file:/dev/./urandom") + maxHeapSize = "1g" + testClassesDirs = sourceSets["test"].output.classesDirs + classpath = sourceSets["test"].runtimeClasspath + // docker-java reads API version from the "api.version" system property (not env var). + // Docker Desktop 4.71+ requires >= 1.40; docker-java defaults to 1.32 without this. + jvmArgs("-Dapi.version=1.47") +} + tasks.withType { options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Xlint:unchecked")) options.isFork = true diff --git a/src/main/java/co/elastic/support/monitoring/MonitoringImportProcessor.java b/src/main/java/co/elastic/support/monitoring/MonitoringImportProcessor.java index 1cc99a61..20bae311 100644 --- a/src/main/java/co/elastic/support/monitoring/MonitoringImportProcessor.java +++ b/src/main/java/co/elastic/support/monitoring/MonitoringImportProcessor.java @@ -17,10 +17,14 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.io.*; +import java.io.BufferedReader; +import java.io.File; +import java.io.FileInputStream; +import java.io.InputStream; +import java.io.InputStreamReader; import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; -import java.util.Vector; public class MonitoringImportProcessor { @@ -46,7 +50,7 @@ public MonitoringImportProcessor(MonitoringImportConfig config, MonitoringImport checkForExtractTemplates(); } - public void exec(Vector files) { + public void exec(List files) { try { for (File file : files) { diff --git a/src/main/java/co/elastic/support/monitoring/MonitoringImportService.java b/src/main/java/co/elastic/support/monitoring/MonitoringImportService.java index 83967365..395882b5 100644 --- a/src/main/java/co/elastic/support/monitoring/MonitoringImportService.java +++ b/src/main/java/co/elastic/support/monitoring/MonitoringImportService.java @@ -6,10 +6,10 @@ */ package co.elastic.support.monitoring; -import co.elastic.support.diagnostics.commands.CheckElasticsearchVersion; +import co.elastic.support.Constants; import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.diagnostics.commands.CheckElasticsearchVersion; import co.elastic.support.rest.ElasticRestClientService; -import co.elastic.support.Constants; import co.elastic.support.rest.RestClient; import co.elastic.support.util.ArchiveUtils; import co.elastic.support.util.JsonYamlUtils; @@ -21,11 +21,12 @@ import org.semver4j.Semver; import java.io.File; +import java.util.ArrayList; +import java.util.List; import java.util.Map; -import java.util.Vector; public class MonitoringImportService extends ElasticRestClientService { - private Logger logger = LogManager.getLogger(MonitoringImportService.class); + private final Logger logger = LogManager.getLogger(MonitoringImportService.class); void execImport(MonitoringImportInputs inputs) throws DiagnosticException { Map configMap = JsonYamlUtils.readYamlFromClasspath(Constants.DIAG_CONFIG, true); @@ -65,11 +66,9 @@ void execImport(MonitoringImportInputs inputs) throws DiagnosticException { } } - private Vector getDirectoryEntries(String dir) { + private List getDirectoryEntries(String dir) { File targetDir = new File(dir); - Vector files = new Vector<>(); - files.addAll(FileUtils.listFiles(targetDir, null, true)); - return files; + return new ArrayList<>(FileUtils.listFiles(targetDir, null, true)); } private RestClient getClient(MonitoringImportInputs inputs, MonitoringImportConfig config){ diff --git a/src/main/java/co/elastic/support/scrub/ScrubConfig.java b/src/main/java/co/elastic/support/scrub/ScrubConfig.java deleted file mode 100644 index d804c4af..00000000 --- a/src/main/java/co/elastic/support/scrub/ScrubConfig.java +++ /dev/null @@ -1,32 +0,0 @@ -/* - * 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 co.elastic.support.scrub; - - -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -import java.util.Vector; - -public class ScrubConfig { - - private static final Logger logger = LogManager.getLogger((ScrubConfig.class)); - - private Vector remove = new Vector(); - private Vector tokens = new Vector<>(); - private Vector autoScrub = new Vector<>(); - - public ScrubConfig(){ - - - - - - } - - -} diff --git a/src/main/java/co/elastic/support/scrub/ScrubProcessor.java b/src/main/java/co/elastic/support/scrub/ScrubProcessor.java index dfc5d387..9d126303 100644 --- a/src/main/java/co/elastic/support/scrub/ScrubProcessor.java +++ b/src/main/java/co/elastic/support/scrub/ScrubProcessor.java @@ -15,7 +15,15 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Random; +import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -28,18 +36,16 @@ public class ScrubProcessor { private static Map scrubConfig; private static ConcurrentHashMap ipv4 = new ConcurrentHashMap<>(); - private static Vector autoScrub = new Vector<>(); - private static Vector globalExclude = new Vector<>(); - private static Vector remove = new Vector<>(); - private static Vector tokens = new Vector<>(); + private static List autoScrub = new ArrayList<>(); + private static List globalExclude = new ArrayList<>(); + private static List remove = new ArrayList<>(); + private static List tokens = new ArrayList<>(); private static ConcurrentHashMap clusterInfoCache = new ConcurrentHashMap<>(); private static ConcurrentHashMap tokenCache = new ConcurrentHashMap<>(); private static ConcurrentHashMap ipv4TokenCache = new ConcurrentHashMap<>(); private static ConcurrentHashMap ipv6TokenCache = new ConcurrentHashMap<>(); private static ConcurrentHashMap macTokenCache = new ConcurrentHashMap<>(); - - public ScrubProcessor(String nodes) throws DiagnosticException { this(); @@ -111,36 +117,29 @@ private void initScrubTokens() { tokens.add(new ScrubTokenEntry(tkn, inc, exc)); } if (tokens.isEmpty()) { - if (tokens.size() == 0) { - logger.info(Constants.CONSOLE, "Scrubbing was enabled but no tokens were defined. Bypassing custom token processing."); - } + logger.info(Constants.CONSOLE, "Scrubbing was enabled but no tokens were defined. Bypassing custom token processing."); } logger.debug(tokens); - } private void initIpv4() { - Random random = new Random(); IntStream intStream = random.ints(300, 556).distinct().limit(256); - int key = 0; int[] vals = intStream.toArray(); for (int i = 0; i < 256; i++) { ipv4.put(i, vals[i]); } - } - public boolean isMatch(Vector regexs, String entry) { + public boolean isMatch(List regexs, String entry) { for (String regx : regexs) { if (entry.matches(regx)) { return true; } } return false; - } public boolean isRemove(String entry) { @@ -151,40 +150,6 @@ public boolean isExclude(String entry) { return isMatch(globalExclude, entry); } - public String scrubIPv4(String input) { - - StringBuffer newIp = new StringBuffer(); - String[] ipSegments = input.split("\\."); - for (int i = 0; i < 4; i++) { - int set = Integer.parseInt(ipSegments[i]); - if (!ipv4.containsKey(set)) { - logger.info("Error converting ip segment {} from address: {}", Integer.toString(set)); - throw new RuntimeException("Error scrubbing IP Addresses"); - } - int replace = ipv4.get(set); - newIp.append(replace); - if (i < 3) { - newIp.append("."); - } - } - return newIp.toString(); - } - - public String scrubIPv6(String input) { - - String[] ipSegments = input.split(":"); - int sz = ipSegments.length; - StringBuilder newIp = new StringBuilder(); - - for (int i = 0; i < sz; i++) { - newIp.append(generateToken(ipSegments[i])); - if (i < (sz - 1)) { - newIp.append(":"); - } - } - return newIp.toString(); - } - public String generateToken(String token) { if (StringUtils.isEmpty(token)) { @@ -251,71 +216,21 @@ public String processContentWithTokens(String content, String entry) { logger.debug("Entry: {} - Pattern:{} Found:{} Replacement: {}", entry, token.pattern.toString(), hit, replacement); content = content.replaceAll(hit, replacement ); } - -/* while (matcher.find()) { - String group = matcher.group(); - String replacement = tokenCache.computeIfAbsent(group, k -> generateToken(k)); - logger.debug("Entry: {} - Pattern:{} Found:{} Replacement: {}", entry, token.pattern.toString(), group, replacement); - content = content.replaceFirst(group, replacement); - }*/ - - - } return content; } public String processMacddresses(String content) { - -/* Pattern pattern = Pattern.compile(Constants.MacAddrRegex); - Matcher matcher = pattern.matcher(content); - - while(matcher.find()){ - String group = matcher.group(); - content.replaceAll(group, "XX:XX:XX:XX:XX:XX"); - }*/ - content = processTokens(content, macTokenCache, Constants.MacAddrRegex, tokenGen); - - return content; - + return processTokens(content, macTokenCache, Constants.MacAddrRegex, tokenGen); } private String processIpv4Addresses(String content) { - -/* Pattern pattern = Pattern.compile(Constants.IPv4Regex); - Matcher matcher = pattern.matcher(content); - Set ips = new HashSet<>(); - while(matcher.find()){ - ips.add(matcher.group()); - } - for(String ip: ips){ - String replacement = ipv4TokenCache.computeIfAbsent(ip, k -> scrubIPv4(k)); - content = content.replaceAll(ip, replacement ); - }*/ - content = processTokens(content, ipv4TokenCache, Constants.IPv4Regex, ipv4Gen); - - - return content; - + return processTokens(content, ipv4TokenCache, Constants.IPv4Regex, ipv4Gen); } private String processIpv6Addresses(String content) { - -/* Pattern pattern = Pattern.compile(Constants.IPv6Regex); - Matcher matcher = pattern.matcher(content); - - Set ips = new HashSet<>(); - while(matcher.find()){ - ips.add(matcher.group()); - } - for(String ip: ips){ - String replacement = ipv6TokenCache.computeIfAbsent(ip, k -> scrubIPv6(k)); - content = content.replaceAll(ip, replacement ); - }*/ - content = processTokens(content, ipv6TokenCache, Constants.IPv6Regex, ipv6Gen); - - return content; + return processTokens(content, ipv6TokenCache, Constants.IPv6Regex, ipv6Gen); } private String processTokens(String content, Map cache, String regexString, TokenGenerator generator){ @@ -332,7 +247,6 @@ private String processTokens(String content, Map cache, String r } return content; - } @@ -343,7 +257,6 @@ private String processClusterArtifacts(String input) { } return content; - } public String processAutoscrub(String input) { @@ -409,6 +322,3 @@ public String generate(String input) { }; } - - - diff --git a/src/main/java/co/elastic/support/scrub/ScrubService.java b/src/main/java/co/elastic/support/scrub/ScrubService.java index dd1e25a8..faff71ad 100644 --- a/src/main/java/co/elastic/support/scrub/ScrubService.java +++ b/src/main/java/co/elastic/support/scrub/ScrubService.java @@ -6,14 +6,14 @@ */ package co.elastic.support.scrub; +import co.elastic.support.BaseService; +import co.elastic.support.Constants; import co.elastic.support.diagnostics.DiagnosticException; import co.elastic.support.util.FileTaskEntry; import co.elastic.support.util.SystemProperties; import co.elastic.support.util.SystemUtils; import co.elastic.support.util.TaskEntry; import co.elastic.support.util.ZipFileTaskEntry; -import co.elastic.support.BaseService; -import co.elastic.support.Constants; import org.apache.commons.compress.archivers.zip.ZipArchiveEntry; import org.apache.commons.compress.archivers.zip.ZipFile; import org.apache.commons.io.FileUtils; @@ -24,14 +24,16 @@ import java.io.File; import java.io.IOException; -import java.util.*; +import java.util.ArrayList; +import java.util.Enumeration; +import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ScrubService extends BaseService { - private Logger logger = LogManager.getLogger(ScrubService.class); + private final Logger logger = LogManager.getLogger(ScrubService.class); public File exec(ScrubInputs inputs) throws DiagnosticException { ExecutorService executorService = null; @@ -48,7 +50,7 @@ public File exec(ScrubInputs inputs) throws DiagnosticException { logger.info(Constants.CONSOLE, "Threadpool configured with {} workers.", inputs.workers); // Get a collection of entries to send parcel out to the task collection - Vector entriesToScrub; + List entriesToScrub; String nodeString = ""; switch (inputs.type) { case "zip": @@ -97,9 +99,9 @@ public File exec(ScrubInputs inputs) throws DiagnosticException { } } - public Vector collectDirEntries(String filename, String scrubDir) { + public List collectDirEntries(String filename, String scrubDir) { - Vector entries = new Vector<>(); + List entries = new ArrayList<>(); File file = new File(filename); String path = file.getAbsolutePath(); @@ -120,8 +122,8 @@ public Vector collectDirEntries(String filename, String scrubDir) { return entries; } - public Vector collectZipEntries(String filename, String scrubDir) { - Vector archiveEntries = new Vector<>(); + public List collectZipEntries(String filename, String scrubDir) { + List archiveEntries = new ArrayList<>(); try { ZipFile zf = new ZipFile.Builder().setFile(filename).get(); Enumeration entries = zf.getEntries(); @@ -137,8 +139,7 @@ public Vector collectZipEntries(String filename, String scrubDir) { } } } catch (IOException e) { - logger.error(Constants.CONSOLE, "Error obtaining zip file entries"); - logger.error(e); + logger.error(Constants.CONSOLE, "Error obtaining zip file entries", e); } return archiveEntries; @@ -153,8 +154,7 @@ private String getNodeInfoFromZip(String zipFile) { ZipArchiveEntry nodeEntry = zf.getEntry(rootPath + "nodes.json"); return IOUtils.toString(zf.getInputStream(nodeEntry), "UTF-8"); } catch (IOException e) { - logger.error(Constants.CONSOLE, "Couldn't retrieve node artifacts for default scrub"); - logger.error(e); + logger.error(Constants.CONSOLE, "Couldn't retrieve node artifacts for default scrub", e); } return ""; } diff --git a/src/test/java/co/elastic/support/diagnostics/DiagnosticContextTest.java b/src/test/java/co/elastic/support/diagnostics/DiagnosticContextTest.java new file mode 100644 index 00000000..a36b7455 --- /dev/null +++ b/src/test/java/co/elastic/support/diagnostics/DiagnosticContextTest.java @@ -0,0 +1,79 @@ +/* + * 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 co.elastic.support.diagnostics; + +import co.elastic.support.diagnostics.chain.DiagnosticContext; +import co.elastic.support.util.ResourceCache; +import org.junit.jupiter.api.Test; + +import static co.elastic.support.testutil.ContainerTestHelper.loadDiagConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertSame; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DiagnosticContextTest { + + @Test + void construction_setsAllFields() { + DiagConfig config = loadDiagConfig(); + DiagnosticInputs inputs = new DiagnosticInputs(); + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext(config, inputs, cache, true); + + assertSame(config, context.diagsConfig); + assertSame(inputs, context.diagnosticInputs); + assertSame(cache, context.resourceCache); + assertTrue(context.includeLogs); + } + } + + @Test + void defaultValues_runSystemCalls() { + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext( + loadDiagConfig(), new DiagnosticInputs(), cache, false); + assertTrue(context.runSystemCalls); + } + } + + @Test + void defaultValues_isAuthorized() { + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext( + loadDiagConfig(), new DiagnosticInputs(), cache, false); + assertTrue(context.isAuthorized); + } + } + + @Test + void defaultValues_dockerPresent() { + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext( + loadDiagConfig(), new DiagnosticInputs(), cache, false); + assertFalse(context.dockerPresent); + } + } + + @Test + void includeLogs_false() { + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext( + loadDiagConfig(), new DiagnosticInputs(), cache, false); + assertFalse(context.includeLogs); + } + } + + @Test + void clusterName_defaultsToEmpty() { + try (ResourceCache cache = new ResourceCache()) { + DiagnosticContext context = new DiagnosticContext( + loadDiagConfig(), new DiagnosticInputs(), cache, false); + assertEquals("", context.clusterName); + } + } +} diff --git a/src/test/java/co/elastic/support/diagnostics/DiagnosticInputsTest.java b/src/test/java/co/elastic/support/diagnostics/DiagnosticInputsTest.java new file mode 100644 index 00000000..9da0bcce --- /dev/null +++ b/src/test/java/co/elastic/support/diagnostics/DiagnosticInputsTest.java @@ -0,0 +1,96 @@ +/* + * 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 co.elastic.support.diagnostics; + +import co.elastic.support.Constants; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class DiagnosticInputsTest { + + @Test + void defaultPort_isElasticsearch() { + DiagnosticInputs inputs = new DiagnosticInputs(); + assertEquals(9200, inputs.port); + } + + @Test + void setDefaultPort_logstash() { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.setDefaultPortForDiagType(Constants.logstashApi); + assertEquals(Constants.LOGSTASH_PORT, inputs.port); + } + + @Test + void setDefaultPort_kibana() { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.setDefaultPortForDiagType(Constants.kibanaApi); + assertEquals(Constants.KIBANA_PORT, inputs.port); + } + + @Test + void setDefaultPort_doesNotOverrideNonDefault() { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.port = 19200; + inputs.setDefaultPortForDiagType(Constants.logstashApi); + assertEquals(19200, inputs.port, "Custom port should not be overridden"); + } + + @Test + void validateDiagType_valid() { + DiagnosticInputs inputs = new DiagnosticInputs(); + for (String type : DiagnosticInputs.diagnosticTypeValues) { + List errors = inputs.validateDiagType(type); + assertNull(errors, "Expected no errors for type: " + type); + } + } + + @Test + void validateDiagType_invalid() { + DiagnosticInputs inputs = new DiagnosticInputs(); + List errors = inputs.validateDiagType("not-a-real-type"); + assertNotNull(errors); + assertEquals(1, errors.size()); + } + + @Test + void validateMode_lightAndFull() { + DiagnosticInputs inputs = new DiagnosticInputs(); + assertNull(inputs.validateMode("light")); + assertNull(inputs.validateMode("full")); + } + + @Test + void validateMode_invalid() { + DiagnosticInputs inputs = new DiagnosticInputs(); + List errors = inputs.validateMode("medium"); + assertNotNull(errors); + assertEquals(1, errors.size()); + } + + @Test + void validateRemoteUser_requiredForRemote() { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.diagType = Constants.remote; + List errors = inputs.validateRemoteUser(""); + assertNotNull(errors); + assertTrue(errors.size() > 0); + } + + @Test + void validateRemoteUser_notRequiredForApi() { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.diagType = Constants.api; + assertNull(inputs.validateRemoteUser("")); + } +} diff --git a/src/test/java/co/elastic/support/diagnostics/TestDiagnosticService.java b/src/test/java/co/elastic/support/diagnostics/TestDiagnosticService.java deleted file mode 100644 index 4416d128..00000000 --- a/src/test/java/co/elastic/support/diagnostics/TestDiagnosticService.java +++ /dev/null @@ -1,260 +0,0 @@ -/* - * 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 co.elastic.support.diagnostics; - -import co.elastic.support.Constants; -import co.elastic.support.diagnostics.chain.DiagnosticContext; -import co.elastic.support.util.JsonYamlUtils; -import co.elastic.support.util.ResourceCache; -import com.github.tomakehurst.wiremock.WireMockServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.junit.jupiter.api.io.TempDir; - -import java.io.File; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.Enumeration; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; -import java.util.function.Function; -import java.util.stream.Collectors; -import java.util.zip.ZipEntry; -import java.util.zip.ZipFile; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.any; -import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.fail; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -class TestDiagnosticService { - private WireMockServer wireMockServer; - - @TempDir - private Path folder; - - private static final String headerKey1 = "k1"; - private static final String headerVal1 = "v1"; - private static final String headerKey2 = "k2"; - private static final String headerVal2 = "v2"; - - @BeforeAll - public void globalSetup() { - wireMockServer = new WireMockServer(wireMockConfig().port(9880)); - wireMockServer.start(); - } - - @AfterAll - public void globalTeardown() { - wireMockServer.stop(); - } - - private DiagConfig newDiagConfig() { - try { - return new DiagConfig( - JsonYamlUtils.readYamlFromClasspath(Constants.DIAG_CONFIG, true)); - } catch (DiagnosticException e) { - fail(e); - return null; // unreachable because of fail(e) - } - } - - private DiagnosticInputs newDiagnosticInputs() { - DiagnosticInputs diagnosticInputs = new DiagnosticInputs(); - diagnosticInputs.port = 9880; - diagnosticInputs.scheme = "http"; - diagnosticInputs.diagType = Constants.api; - try { - Path outputDir = Files.createTempDirectory(folder, "diag"); - diagnosticInputs.outputDir = outputDir.toString(); - } catch (IOException e) { - fail("Unable to create temp directory", e); - } - return diagnosticInputs; - } - - private void setupResponse(boolean withHeaders) { - if (withHeaders) { - wireMockServer.stubFor(any(urlEqualTo("/")) - .withHeader(headerKey1, equalTo(headerVal1)) - .withHeader(headerKey2, equalTo(headerVal2)) - .willReturn(aResponse() - .withBody("{\"version\": {\"number\": \"7.14.0\"}}"))); - wireMockServer.stubFor(any(urlEqualTo("/_nodes/os,process,settings,transport,http")) - .withHeader(headerKey1, equalTo(headerVal1)) - .withHeader(headerKey2, equalTo(headerVal2)) - .willReturn(aResponse() - .withBody("{}"))); - wireMockServer.stubFor(any(anyUrl()) - .withHeader(headerKey1, equalTo(headerVal1)) - .withHeader(headerKey2, equalTo(headerVal2)) - .atPriority(10) - .willReturn(aResponse() - .withBody("some_response_body"))); - } else { - wireMockServer.stubFor(any(urlEqualTo("/")) - .willReturn(aResponse() - .withBody("{\"version\": {\"number\": \"7.14.0\"}}"))); - wireMockServer.stubFor(any(urlEqualTo("/_nodes/os,process,settings,transport,http")) - .willReturn(aResponse() - .withBody("{}"))); - wireMockServer.stubFor(any(anyUrl()) - .atPriority(10) - .willReturn(aResponse() - .withBody("some_response_body"))); - } - } - - public HashMap zipFileContents(File result) throws IOException { - try (ZipFile zipFile = new ZipFile(result, ZipFile.OPEN_READ)) { - HashMap contents = new HashMap<>(); - - Enumeration entries = zipFile.entries(); - - while (entries.hasMoreElements()) { - ZipEntry entry = entries.nextElement(); - assertFalse(entry.getName().startsWith("./"), entry.getName()); - - if (!entry.isDirectory()) { - // Add file path without leading directory - contents.put(entry.getName().replaceFirst("^(.+/)(.+)", "$2"), entry); - } - } - - return contents; - } - } - - public void checkResult(File result, boolean withLogFile) { - assertTrue(result.toString().matches(".*\\.zip$"), result.toString()); - try { - Map contents = zipFileContents(result); - - assertTrue(contents.containsKey("diagnostic_manifest.json"), - () -> String.join(", ", contents.keySet())); - - assertTrue(contents.containsKey("manifest.json")); - if (withLogFile) { - assertTrue(contents.containsKey("diagnostics.log")); - } else { - assertFalse(contents.containsKey("diagnostics.log")); - } - contents.forEach((key, entry) -> { - if (!entry.isDirectory()) { - assertTrue(entry.getSize() > 0, key); - } - }); - } catch (IOException e) { - fail("Error processing result zip file", e); - } - } - - @Test - public void testWithExtraHeaders() { - setupResponse(true); - - Map extraHeaders = new HashMap<>(); - extraHeaders.put(headerKey1, headerVal1); - extraHeaders.put(headerKey2, headerVal2); - DiagConfig diagConfig = newDiagConfig(); - diagConfig.extraHeaders = extraHeaders; - DiagnosticService diag = new DiagnosticService(); - - try (ResourceCache resourceCache = new ResourceCache()) { - DiagnosticContext context = new DiagnosticContext(diagConfig, newDiagnosticInputs(), resourceCache, true); - File result = diag.exec(context); - checkResult(result, true); - } catch (DiagnosticException e) { - fail(e); - } - } - - @Test - public void testWithoutExtraHeaders() { - setupResponse(false); - - DiagnosticService diag = new DiagnosticService(); - - try (ResourceCache resourceCache = new ResourceCache()) { - DiagnosticContext context = new DiagnosticContext(newDiagConfig(), newDiagnosticInputs(), resourceCache, - true); - File result = diag.exec(context); - checkResult(result, true); - } catch (DiagnosticException e) { - fail(e); - } - } - - @Test - public void testConcurrentExecutions() { - setupResponse(false); - - ConcurrentHashMap results = new ConcurrentHashMap<>(); - - Function task = (Integer i) -> () -> { - DiagnosticService diag = new DiagnosticService(); - - try (ResourceCache resourceCache = new ResourceCache()) { - DiagnosticContext context = new DiagnosticContext(newDiagConfig(), newDiagnosticInputs(), - resourceCache, false); - File result = diag.exec(context); - results.put(i, result); - } catch (DiagnosticException e) { - System.out.println(e.getStackTrace()); - fail(e); - } - }; - - List threads = List.of(new Thread(task.apply(0)), new Thread(task.apply(1)), new Thread(task.apply(2))); - threads.forEach(Thread::start); - - for (Thread t : threads) { - try { - t.join(3000); - } catch (InterruptedException e) { - fail("Thread interrupted", e); - } finally { - if (t.isAlive()) { - t.interrupt(); - fail("Thread got stuck"); - } - } - } - assertEquals(results.size(), threads.size()); - results.forEach((i, result) -> checkResult(result, false)); - try { - Enumeration resultFiles = results.elements(); - // Take one zip file to use as a reference for comparisons with the other ones - Map reference = zipFileContents(resultFiles.nextElement()); - while (resultFiles.hasMoreElements()) { - Map other = zipFileContents(resultFiles.nextElement()); - assertEquals(reference.keySet(), other.keySet(), () -> reference.keySet().stream() - .filter(file -> !other.containsKey(file)).collect(Collectors.joining(", "))); - reference.keySet().forEach((key) -> { - if (!key.equals("manifest.json") && !key.equals("diagnostic_manifest.json")) { - assertEquals(reference.get(key).getSize(), other.get(key).getSize(), key); - assertEquals(reference.get(key).getCrc(), other.get(key).getCrc(), key); - } - }); - } - } catch (IOException e) { - fail("Exception while comparing result contents", e); - } - } -} diff --git a/src/test/java/co/elastic/support/diagnostics/commands/CheckVersionParsingTest.java b/src/test/java/co/elastic/support/diagnostics/commands/CheckVersionParsingTest.java new file mode 100644 index 00000000..52f1f5a5 --- /dev/null +++ b/src/test/java/co/elastic/support/diagnostics/commands/CheckVersionParsingTest.java @@ -0,0 +1,135 @@ +/* + * 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 co.elastic.support.diagnostics.commands; + +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.rest.RestClient; +import com.github.tomakehurst.wiremock.WireMockServer; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.semver4j.Semver; + +import static co.elastic.support.testutil.ContainerTestHelper.loadDiagConfig; +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.any; +import static com.github.tomakehurst.wiremock.client.WireMock.anyUrl; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +class CheckVersionParsingTest { + + private WireMockServer wireMockServer; + + @BeforeAll + void startMockServer() { + wireMockServer = new WireMockServer(wireMockConfig().bindAddress("127.0.0.1").dynamicPort()); + wireMockServer.start(); + } + + @AfterAll + void stopMockServer() { + wireMockServer.stop(); + } + + @BeforeEach + void resetStubs() { + wireMockServer.resetAll(); + } + + private RestClient clientForMock() { + var config = loadDiagConfig(); + return RestClient.getClient( + "127.0.0.1", wireMockServer.port(), "http", + "", "", "", 8080, "", "", "", "", + false, config.extraHeaders, + config.connectionTimeout, config.connectionRequestTimeout, config.socketTimeout); + } + + @Test + void elasticsearch_parsesSemanticVersion() throws DiagnosticException { + wireMockServer.stubFor(any(anyUrl()) + .willReturn(aResponse().withBody("{\"version\":{\"number\":\"9.3.0\"}}"))); + try (RestClient client = clientForMock()) { + Semver version = CheckElasticsearchVersion.getElasticsearchVersion(client); + assertEquals(9, version.getMajor()); + assertEquals(3, version.getMinor()); + assertEquals(0, version.getPatch()); + } + } + + @Test + void elasticsearch_preReleaseVersionStripped() throws DiagnosticException { + wireMockServer.stubFor(any(anyUrl()) + .willReturn(aResponse().withBody("{\"version\":{\"number\":\"9.3.0-SNAPSHOT\"}}"))); + try (RestClient client = clientForMock()) { + Semver version = CheckElasticsearchVersion.getElasticsearchVersion(client); + // withClearedPreRelease() removes the -SNAPSHOT suffix + assertTrue(version.getPreRelease().isEmpty(), + "Pre-release should be cleared, got: " + version); + } + } + + @Test + void elasticsearch_invalidResponse_throwsDiagnosticException() { + wireMockServer.stubFor(any(anyUrl()) + .willReturn(aResponse().withStatus(503).withBody("unavailable"))); + try (RestClient client = clientForMock()) { + assertThrows(DiagnosticException.class, + () -> CheckElasticsearchVersion.getElasticsearchVersion(client)); + } + } + + @Test + void kibana_parsesSemanticVersion() throws DiagnosticException { + wireMockServer.stubFor(any(urlEqualTo("/api/stats")) + .willReturn(aResponse().withBody("{\"kibana\":{\"version\":\"9.3.0\"}}"))); + try (RestClient client = clientForMock()) { + Semver version = CheckKibanaVersion.getKibanaVersion(client); + assertEquals(9, version.getMajor()); + assertEquals(3, version.getMinor()); + } + } + + @Test + void kibana_invalidVersion_throwsDiagnosticException() { + wireMockServer.stubFor(any(anyUrl()) + .willReturn(aResponse().withBody("{\"kibana\":{\"version\":\"not-semver\"}}"))); + try (RestClient client = clientForMock()) { + assertThrows(DiagnosticException.class, + () -> CheckKibanaVersion.getKibanaVersion(client)); + } + } + + @Test + void logstash_parsesSemanticVersion() throws DiagnosticException { + wireMockServer.stubFor(any(urlEqualTo("/")) + .willReturn(aResponse().withBody("{\"version\":\"9.3.0\",\"host\":\"logstash\"}"))); + try (RestClient client = clientForMock()) { + Semver version = CheckLogstashVersion.getLogstashVersion(client); + assertEquals(9, version.getMajor()); + assertEquals(3, version.getMinor()); + } + } + + @Test + void logstash_preReleaseVersionStripped() throws DiagnosticException { + wireMockServer.stubFor(any(anyUrl()) + .willReturn(aResponse().withBody("{\"version\":\"9.3.0-SNAPSHOT\"}"))); + try (RestClient client = clientForMock()) { + Semver version = CheckLogstashVersion.getLogstashVersion(client); + assertTrue(version.getPreRelease().isEmpty(), + "Pre-release should be cleared, got: " + version); + } + } +} diff --git a/src/test/java/co/elastic/support/diagnostics/commands/TestCheckKibanaVersion.java b/src/test/java/co/elastic/support/diagnostics/commands/TestCheckKibanaVersion.java deleted file mode 100644 index a6e62a64..00000000 --- a/src/test/java/co/elastic/support/diagnostics/commands/TestCheckKibanaVersion.java +++ /dev/null @@ -1,156 +0,0 @@ -/* - * 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 co.elastic.support.diagnostics.commands; - -import co.elastic.support.diagnostics.DiagnosticException; -import co.elastic.support.rest.RestClient; -import com.github.tomakehurst.wiremock.WireMockServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.semver4j.Semver; - -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestCheckKibanaVersion { - private WireMockServer wireMockServer; - private RestClient httpRestClient; - - @BeforeAll - public void globalSetup() { - wireMockServer = new WireMockServer(wireMockConfig().port(9880)); - wireMockServer.start(); - } - - @AfterAll - public void globalTeardown() { - wireMockServer.stop(); - } - - @BeforeEach - public void setup() { - httpRestClient = RestClient.getClient( - "localhost", - 9880, - "http", - "elastic", - "elastic", - "", - 0, - "", - "", - "", - "", - true, - Map.of(), - 3000, - 3000, - 3000 - ); - } - - @AfterEach - public void tearDown() { - wireMockServer.resetAll(); - } - - private void initializeKibanaSettings(String version) { - - wireMockServer.stubFor(get(urlEqualTo("/api/settings")).willReturn(aResponse().withBody( - "{\"cluster_uuid\":\"RLtzkhfBRUadN4WZ8fnnog\",\"settings\":{\"xpack\":{\"default_admin_email\":null},\"kibana\":{\"uuid\":\"a4f369ef-fecd-46b7-8b16-c6c3f885d9ec\",\"name\":\"13d5e793ea51\",\"index\":\".kibana\",\"host\":\"0.0.0.0\",\"port\":18648,\"locale\":\"en\",\"transport_address\":\"0.0.0.0:18648\",\"version\":\"" - + version + "\",\"snapshot\":false,\"status\":\"green\"}}}").withStatus(200))); - } - - private void initializeKibanaStats(String version) { - - wireMockServer.stubFor(get(urlEqualTo("/api/stats")).willReturn(aResponse().withBody( - "{\"kibana\":{\"uuid\":\"669ae985-31f7-493b-9910-522cac4d5479\",\"name\":\"6f5485cce678\",\"index\":\".kibana\",\"host\":\"0.0.0.0\",\"locale\":\"en\",\"transport_address\":\"0.0.0.0:18117\",\"version\":\"" - + version + "\",\"snapshot\":false,\"status\":\"green\"}}").withStatus(200))); - } - - @Test - public void testQueriesForKibanaWhenStats() throws DiagnosticException { - initializeKibanaStats("8.1.2"); - Semver version = new CheckKibanaVersion().getKibanaVersion(httpRestClient); - assertEquals("8.1.2", version.getVersion()); - } - - @Test - public void testQueriesForKibanaWhenStatsWithRC() throws DiagnosticException { - initializeKibanaStats("9.0.0-beta1"); - Semver version = new CheckKibanaVersion().getKibanaVersion(httpRestClient); - assertEquals("9.0.0", version.getVersion()); - } - - @Test - public void testQueriesForKibanaWhenStatsAndSettings() throws DiagnosticException { - initializeKibanaStats("6.5.0"); - initializeKibanaSettings("6.5.0"); - Semver version = new CheckKibanaVersion().getKibanaVersion(httpRestClient); - assertEquals("6.5.0", version.getVersion()); - } - - /** - * version is Mandatory as we use the version to define the APIs that will be executed - */ - @Test - public void testQueriesForKibanaEmptyVersion() { - // The response body contains no version - wireMockServer.stubFor(get(urlEqualTo("/api/stats")).willReturn(aResponse().withBody("{\"kibana\": {}}").withStatus(200))); - - try { - new CheckKibanaVersion().getKibanaVersion(httpRestClient); - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals(e.getMessage(), "Kibana version format is wrong - unable to continue. ()"); - } - } - - /** - * version is Mandatory as we use the version to define the APIs that will be executed - * The format of our version is stable and numeric - */ - @Test - public void testQueriesForKibanaCorruptedVersion() { - initializeKibanaStats("a.v.c"); - try { - new CheckKibanaVersion().getKibanaVersion(httpRestClient); - // if they are more than one node in Kibana we need to throw an Exception - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals(e.getMessage(), "Kibana version format is wrong - unable to continue. (a.v.c)"); - } - } - - /** - * version is Mandatory as we use the version to define the APIs that will be executed - * The format of our version is stable and numeric - */ - @Test - public void testQueriesForKibanaTextWithVersion() { - initializeKibanaStats("test-6.5.1"); - - try { - new CheckKibanaVersion().getKibanaVersion(httpRestClient); - // if they are more than one node in Kibana we need to throw an Exception - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals(e.getMessage(), "Kibana version format is wrong - unable to continue. (test-6.5.1)"); - } - } -} diff --git a/src/test/java/co/elastic/support/diagnostics/commands/TestCheckLogstashVersionTest.java b/src/test/java/co/elastic/support/diagnostics/commands/TestCheckLogstashVersionTest.java deleted file mode 100644 index 2a0a5ec9..00000000 --- a/src/test/java/co/elastic/support/diagnostics/commands/TestCheckLogstashVersionTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package co.elastic.support.diagnostics.commands; - -import co.elastic.support.diagnostics.DiagnosticException; -import co.elastic.support.rest.RestClient; -import com.github.tomakehurst.wiremock.WireMockServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; -import org.semver4j.Semver; - -import java.util.Collections; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.fail; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestCheckLogstashVersionTest { - private WireMockServer wireMockServer; - private RestClient httpRestClient; - - @BeforeAll - public void globalSetup() { - wireMockServer = new WireMockServer(wireMockConfig().port(9881)); - wireMockServer.start(); - } - - @AfterAll - public void globalTeardown() { - wireMockServer.stop(); - } - - @BeforeEach - public void setup() { - httpRestClient = RestClient.getClient( - "localhost", - 9881, - "http", - "elastic", - "elastic", - "", - 0, - "", - "", - "", - "", - true, - Collections.emptyMap(), - 3000, - 3000, - 3000 - ); - } - - @AfterEach - public void tearDown() { - wireMockServer.resetAll(); - } - - private void initializeLogstashMainHandler(String version) { - wireMockServer.stubFor(get(urlEqualTo("/")).willReturn(aResponse().withBody("{\"host\":\"Test\",\"version\":\"" + version - + "\",\"http_address\":\"127.0.0.1:9600\",\"id\":\"9ac54ae7-377e-4352-9727-15db6344332a\",\"name\":\"LucaMBP\",\"ephemeral_id\":\"3f1d87db-07c0-4015-941a-4005bbf908fc\",\"snapshot\":false,\"status\":\"yellow\",\"pipeline\":{\"workers\":11,\"batch_size\":125,\"batch_delay\":50},\"build_date\":\"2025-06-17T14:07:37+00:00\",\"build_sha\":\"01b7a2d93e4cf143d4964c71259655cf4575b709\",\"build_snapshot\":false}") - .withStatus(200))); - } - - @Test - public void testQueriesForLogstashVersionNormal() throws DiagnosticException { - initializeLogstashMainHandler("8.1.2"); - Semver version = new CheckLogstashVersion().getLogstashVersion(httpRestClient); - assertEquals("8.1.2", version.getVersion()); - } - - @Test - public void testQueriesForLogstashVersionWithRC() throws DiagnosticException { - initializeLogstashMainHandler("9.0.0-beta1"); - Semver version = new CheckLogstashVersion().getLogstashVersion(httpRestClient); - assertEquals("9.0.0", version.getVersion()); - } - - @Test - public void testQueriesForLogstashEmptyVersion() { - wireMockServer.stubFor(get(urlEqualTo("/")).willReturn(aResponse().withBody("{}").withStatus(200))); - - try { - new CheckLogstashVersion().getLogstashVersion(httpRestClient); - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals("Logstash version format is wrong - unable to continue. ()", e.getMessage()); - } - } - - @Test - public void testQueriesForLogstashCorruptedVersion() { - initializeLogstashMainHandler("a.v.c"); - try { - new CheckLogstashVersion().getLogstashVersion(httpRestClient); - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals("Logstash version format is wrong - unable to continue. (a.v.c)", e.getMessage()); - } - } - - @Test - public void testQueriesForLogstashTextWithVersion() { - initializeLogstashMainHandler("test-6.5.1"); - - try { - new CheckLogstashVersion().getLogstashVersion(httpRestClient); - fail("Expected to fail"); - } catch (DiagnosticException e) { - assertEquals("Logstash version format is wrong - unable to continue. (test-6.5.1)", e.getMessage()); - } - } -} diff --git a/src/test/java/co/elastic/support/diagnostics/commands/TestKibanaGetDetails.java b/src/test/java/co/elastic/support/diagnostics/commands/TestKibanaGetDetails.java deleted file mode 100644 index 55ab9263..00000000 --- a/src/test/java/co/elastic/support/diagnostics/commands/TestKibanaGetDetails.java +++ /dev/null @@ -1,179 +0,0 @@ -/* - * 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 co.elastic.support.diagnostics.commands; - -import co.elastic.support.Constants; -import co.elastic.support.diagnostics.DiagConfig; -import co.elastic.support.diagnostics.DiagnosticException; -import co.elastic.support.diagnostics.DiagnosticInputs; -import co.elastic.support.diagnostics.ProcessProfile; -import co.elastic.support.diagnostics.chain.DiagnosticContext; -import co.elastic.support.rest.RestClient; -import co.elastic.support.rest.RestEntry; -import co.elastic.support.rest.RestEntryConfig; -import co.elastic.support.util.JsonYamlUtils; -import co.elastic.support.util.ResourceCache; -import com.fasterxml.jackson.databind.JsonNode; -import com.github.tomakehurst.wiremock.WireMockServer; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestKibanaGetDetails { - - private WireMockServer wireMockServer; - private RestClient httpRestClient; - - @BeforeAll - public void globalSetup() { - wireMockServer = new WireMockServer(wireMockConfig().port(9880)); - wireMockServer.start(); - } - - @AfterAll - public void globalTeardown() { - wireMockServer.stop(); - } - - @BeforeEach - public void setup() { - - httpRestClient = RestClient.getClient( - "localhost", - 9880, - "http", - "elastic", - "elastic", - "", - 0, - "", - "", - "", - "", - true, - Collections.emptyMap(), - 3000, - 3000, - 3000); - } - - @AfterEach - public void tearDown() { - wireMockServer.resetAll(); - } - - @Test - public void testGetNodeNetworkAndLogInfo() { - - String kibana_stats = "{\"process\":{\"memory\":{\"heap\":{\"total_bytes\":470466560,\"used_bytes\":343398224,\"size_limit\":1740165498},\"resident_set_size_bytes\":587878400},\"pid\":32,\"event_loop_delay\":0.280181884765625,\"uptime_ms\":97230924},\"os\":{\"platform\":\"win32\",\"platform_release\":\"linux-4.15.0-1032-gcp\",\"load\":{\"1m\":1.37451171875,\"5m\":1.43408203125,\"15m\":1.34375},\"memory\":{\"total_bytes\":147879931904,\"free_bytes\":45620334592,\"used_bytes\":102259597312},\"uptime_ms\":18093713000,\"distro\":\"Centos\",\"distro_release\":\"Centos-7.8.2003\"},\"requests\":{\"disconnects\":0,\"total\":1,\"status_codes\":{\"302\":1}},\"concurrent_connections\":8,\"timestamp\":\"2021-01-06T01:35:11.324Z\",\"kibana\":{\"uuid\":\"a4f369ef-fecd-46b7-8b16-c6c3f885d9ec\",\"name\":\"13d5e793ea51\",\"index\":\".kibana\",\"host\":\"0.0.0.0\",\"locale\":\"en\",\"transport_address\":\"0.0.0.0:18648\",\"version\":\"7.9.0\",\"snapshot\":false,\"status\":\"green\"},\"last_updated\":\"2021-01-06T01:35:15.911Z\",\"collection_interval_ms\":5000,\"cluster_uuid\":\"RfBRUssssadN4WZssnnog\"}"; - JsonNode infoNodes = JsonYamlUtils.createJsonNodeFromString(kibana_stats); - KibanaGetDetails testClass = new KibanaGetDetails(); - List nodeProfiles = new ArrayList<>(); - nodeProfiles = testClass.getNodeNetworkAndLogInfo(infoNodes); - - assertEquals(nodeProfiles.size(), 1); - assertEquals(nodeProfiles.get(0).pid, "32"); - assertEquals(nodeProfiles.get(0).name, "13d5e793ea51"); - assertEquals(nodeProfiles.get(0).isDocker, false); - assertEquals(nodeProfiles.get(0).os, Constants.winPlatform); - assertEquals(nodeProfiles.get(0).networkHost, "0.0.0.0"); - assertEquals(nodeProfiles.get(0).httpPublishAddr, "0.0.0.0"); - assertEquals(nodeProfiles.get(0).httpPort, 18648); - } - - @Test - public void testFindTargetNode() { - - KibanaGetDetails testClass = new KibanaGetDetails(); - List nodeProfiles = new ArrayList<>(); - ProcessProfile diagNode = new ProcessProfile(); - diagNode.name = "kibana-name"; - diagNode.pid = "1"; - diagNode.isDocker = true; - nodeProfiles.add(diagNode); - ProcessProfile testedNodeProfiles = testClass.findTargetNode(nodeProfiles); - assertEquals(testedNodeProfiles.pid, "1"); - assertEquals(testedNodeProfiles.name, "kibana-name"); - assertEquals(testedNodeProfiles.isDocker, true); - } - - /** - * Kibana is working in single mode, so the target node will be the only host - * stored on the nodeProfiles List - * if they are two nodes is an error. - */ - @Test - public void testClusterFindTargetNode() { - KibanaGetDetails testClass = new KibanaGetDetails(); - List nodeProfiles = new ArrayList<>(); - ProcessProfile diagNode = new ProcessProfile(); - diagNode.name = "kibana-name"; - diagNode.pid = "1"; - diagNode.isDocker = true; - nodeProfiles.add(diagNode); - - ProcessProfile diagNode2 = new ProcessProfile(); - diagNode2.name = "kibana-name"; - diagNode2.pid = "1"; - diagNode2.isDocker = true; - nodeProfiles.add(diagNode2); - - try { - ProcessProfile testedNodeProfiles = testClass.findTargetNode(nodeProfiles); - // if they are more than one node in Kibana we need to throw an Exception - assertTrue(false); - } catch (RuntimeException e) { - assertEquals(e.getMessage(), "Unable to get Kibana process profile."); - } - } - - @Test - public void testFunctionGetStats() throws DiagnosticException { - wireMockServer.stubFor(get(urlEqualTo("/api/stats")) - .willReturn(aResponse() - .withBody( - "{\"process\":{\"memory\":{\"heap\":{\"total_bytes\":470466560,\"used_bytes\":343398224,\"size_limit\":1740165498},\"resident_set_size_bytes\":587878400},\"pid\":32,\"event_loop_delay\":0.280181884765625,\"uptime_ms\":97230924},\"os\":{\"platform\":\"linux\",\"platform_release\":\"linux-4.15.0-1032-gcp\",\"load\":{\"1m\":1.37451171875,\"5m\":1.43408203125,\"15m\":1.34375},\"memory\":{\"total_bytes\":147879931904,\"free_bytes\":45620334592,\"used_bytes\":102259597312},\"uptime_ms\":18093713000,\"distro\":\"Centos\",\"distro_release\":\"Centos-7.8.2003\"},\"requests\":{\"disconnects\":0,\"total\":1,\"status_codes\":{\"302\":1}},\"concurrent_connections\":8,\"timestamp\":\"2021-01-06T01:35:11.324Z\",\"kibana\":{\"uuid\":\"a4f369ef-fecd-46b7-8b16-c6c3f885d9ec\",\"name\":\"13d5e793ea51\",\"index\":\".kibana\",\"host\":\"0.0.0.0\",\"locale\":\"en\",\"transport_address\":\"0.0.0.0:18648\",\"version\":\"7.9.0\",\"snapshot\":false,\"status\":\"green\"},\"last_updated\":\"2021-01-06T01:35:15.911Z\",\"collection_interval_ms\":5000,\"cluster_uuid\":\"RfBRUssssadN4WZssnnog\"}") - .withStatus(401))); - - Map diagMap = JsonYamlUtils.readYamlFromClasspath(Constants.DIAG_CONFIG, true); - Map restCalls = JsonYamlUtils.readYamlFromClasspath(Constants.KIBANA_REST, true); - RestEntryConfig builder = new RestEntryConfig("7.10.0"); - Map entries = builder.buildEntryMap(restCalls); - - KibanaGetDetails testClass = new KibanaGetDetails(); - try ( - ResourceCache resourceCache = new ResourceCache();) { - DiagnosticContext context = new DiagnosticContext( - new DiagConfig(diagMap), - new DiagnosticInputs("testKibanaGetDetails"), - resourceCache, - true); - context.elasticRestCalls = entries; - context.fullElasticRestCalls = entries; - resourceCache.addRestClient(Constants.restInputHost, httpRestClient); - testClass.getStats(context); - } catch (DiagnosticException e) { - assertEquals(e.getMessage(), "Kibana responded with [401] for [/api/stats]. Unable to proceed."); - } - } -} diff --git a/src/test/java/co/elastic/support/e2e/ElasticsearchE2ETest.java b/src/test/java/co/elastic/support/e2e/ElasticsearchE2ETest.java new file mode 100644 index 00000000..5a5f66da --- /dev/null +++ b/src/test/java/co/elastic/support/e2e/ElasticsearchE2ETest.java @@ -0,0 +1,66 @@ +/* + * 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 co.elastic.support.e2e; + +import co.elastic.support.Constants; +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.diagnostics.DiagnosticInputs; +import co.elastic.support.diagnostics.DiagnosticService; +import co.elastic.support.diagnostics.commands.CheckElasticsearchVersion; +import co.elastic.support.rest.RestClient; +import co.elastic.support.util.ResourceCache; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.semver4j.Semver; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; + +import static co.elastic.support.testutil.ContainerTestHelper.STACK_VERSION; +import static co.elastic.support.testutil.ContainerTestHelper.assertZipContains; +import static co.elastic.support.testutil.ContainerTestHelper.clientFor; +import static co.elastic.support.testutil.ContainerTestHelper.contextFor; +import static co.elastic.support.testutil.ContainerTestHelper.inputsFor; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("e2e") +@Testcontainers +class ElasticsearchE2ETest { + @Container + static final GenericContainer ES_CONTAINER = + new GenericContainer<>("docker.elastic.co/elasticsearch/elasticsearch:" + STACK_VERSION) + .withEnv("discovery.type", "single-node") + .withEnv("http.port", "19200") + .withEnv("xpack.security.enabled", "false") + .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") + .withExposedPorts(19200) + .waitingFor(Wait.forHttp("/").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(3))); + + @Test + void checkElasticsearchVersion() throws DiagnosticException { + try (RestClient client = clientFor(ES_CONTAINER, 19200)) { + Semver version = CheckElasticsearchVersion.getElasticsearchVersion(client); + assertTrue(version.getMajor() >= 8, "Expected version >= 8 but got: " + version); + } + } + + @Test + void diagnosticServiceApiMode(@TempDir Path outputDir) throws DiagnosticException, IOException { + DiagnosticInputs inputs = inputsFor(ES_CONTAINER, 19200, Constants.api, outputDir); + try (ResourceCache cache = new ResourceCache()) { + File zip = new DiagnosticService().exec(contextFor(inputs, cache)); + assertZipContains(zip, "diagnostic_manifest.json", "manifest.json"); + } + } +} diff --git a/src/test/java/co/elastic/support/e2e/KibanaE2ETest.java b/src/test/java/co/elastic/support/e2e/KibanaE2ETest.java new file mode 100644 index 00000000..b4881c8c --- /dev/null +++ b/src/test/java/co/elastic/support/e2e/KibanaE2ETest.java @@ -0,0 +1,82 @@ +/* + * 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 co.elastic.support.e2e; + +import co.elastic.support.Constants; +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.diagnostics.DiagnosticInputs; +import co.elastic.support.diagnostics.DiagnosticService; +import co.elastic.support.diagnostics.commands.CheckKibanaVersion; +import co.elastic.support.rest.RestClient; +import co.elastic.support.util.ResourceCache; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.semver4j.Semver; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.Network; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; + +import static co.elastic.support.testutil.ContainerTestHelper.STACK_VERSION; +import static co.elastic.support.testutil.ContainerTestHelper.assertZipContains; +import static co.elastic.support.testutil.ContainerTestHelper.clientFor; +import static co.elastic.support.testutil.ContainerTestHelper.contextFor; +import static co.elastic.support.testutil.ContainerTestHelper.inputsFor; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("e2e") +@Testcontainers +class KibanaE2ETest { + static final Network network = Network.newNetwork(); + + @Container + static final GenericContainer ES_CONTAINER = + new GenericContainer<>("docker.elastic.co/elasticsearch/elasticsearch:" + STACK_VERSION) + .withEnv("discovery.type", "single-node") + .withEnv("http.port", "19200") + .withEnv("xpack.security.enabled", "false") + .withEnv("ES_JAVA_OPTS", "-Xms512m -Xmx512m") + .withNetwork(network) + .withNetworkAliases("elasticsearch-diag") + .withExposedPorts(19200) + .waitingFor(Wait.forHttp("/").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(3))); + + @Container + static final GenericContainer KIBANA_CONTAINER = new GenericContainer<>("docker.elastic.co/kibana/kibana:" + STACK_VERSION) + .withEnv("ELASTICSEARCH_HOSTS", "http://elasticsearch-diag:19200") + .withEnv("SERVER_PORT", "15601") + .withEnv("XPACK_SECURITY_ENCRYPTIONKEY", "abcdefghijklmnopqrstuvwxyz123456") + .withEnv("XPACK_ENCRYPTEDSAVEDOBJECTS_ENCRYPTIONKEY", "abcdefghijklmnopqrstuvwxyz123456") + .withEnv("XPACK_REPORTING_ENCRYPTIONKEY", "abcdefghijklmnopqrstuvwxyz123456") + .withNetwork(network) + .withExposedPorts(15601) + .waitingFor(Wait.forHttp("/api/status").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(5))); + + @Test + void checkKibanaVersion() throws DiagnosticException { + try (RestClient client = clientFor(KIBANA_CONTAINER, 15601)) { + Semver version = CheckKibanaVersion.getKibanaVersion(client); + assertTrue(version.getMajor() >= 8, "Expected version >= 8 but got: " + version); + } + } + + @Test + void diagnosticServiceKibanaApiMode(@TempDir Path outputDir) throws DiagnosticException, IOException { + DiagnosticInputs inputs = inputsFor(KIBANA_CONTAINER, 15601, Constants.kibanaApi, outputDir); + try (ResourceCache cache = new ResourceCache()) { + File zip = new DiagnosticService().exec(contextFor(inputs, cache)); + assertZipContains(zip, "diagnostic_manifest.json", "manifest.json"); + } + } +} diff --git a/src/test/java/co/elastic/support/e2e/LogstashE2ETest.java b/src/test/java/co/elastic/support/e2e/LogstashE2ETest.java new file mode 100644 index 00000000..c852cf3b --- /dev/null +++ b/src/test/java/co/elastic/support/e2e/LogstashE2ETest.java @@ -0,0 +1,65 @@ +/* + * 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 co.elastic.support.e2e; + +import co.elastic.support.Constants; +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.diagnostics.DiagnosticInputs; +import co.elastic.support.diagnostics.DiagnosticService; +import co.elastic.support.diagnostics.commands.CheckLogstashVersion; +import co.elastic.support.rest.RestClient; +import co.elastic.support.util.ResourceCache; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; +import org.semver4j.Semver; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.time.Duration; + +import static co.elastic.support.testutil.ContainerTestHelper.STACK_VERSION; +import static co.elastic.support.testutil.ContainerTestHelper.assertZipContains; +import static co.elastic.support.testutil.ContainerTestHelper.clientFor; +import static co.elastic.support.testutil.ContainerTestHelper.contextFor; +import static co.elastic.support.testutil.ContainerTestHelper.inputsFor; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@Tag("e2e") +@Testcontainers +class LogstashE2ETest { + @Container + static final GenericContainer LOGSTASH_CONTAINER = + new GenericContainer<>("docker.elastic.co/logstash/logstash:" + STACK_VERSION) + .withEnv("xpack.monitoring.enabled", "false") + .withEnv("API_HTTP_PORT", "19600") + .withEnv("LOG_LEVEL", "error") + .withExposedPorts(19600) + .waitingFor(Wait.forHttp("/").forStatusCode(200).withStartupTimeout(Duration.ofMinutes(5))); + + @Test + void checkLogstashVersion() throws DiagnosticException { + try (RestClient client = clientFor(LOGSTASH_CONTAINER, 19600)) { + Semver version = CheckLogstashVersion.getLogstashVersion(client); + assertTrue(version.getMajor() >= 8, "Expected version >= 8 but got: " + version); + } + } + + @Test + void diagnosticServiceLogstashApiMode(@TempDir Path outputDir) throws DiagnosticException, IOException { + DiagnosticInputs inputs = inputsFor(LOGSTASH_CONTAINER, 19600, Constants.logstashApi, outputDir); + try (ResourceCache cache = new ResourceCache()) { + File zip = new DiagnosticService().exec(contextFor(inputs, cache)); + assertZipContains(zip, "diagnostic_manifest.json", "manifest.json"); + } + } +} diff --git a/src/test/java/co/elastic/support/rest/RestEntryConfigTest.java b/src/test/java/co/elastic/support/rest/RestEntryConfigTest.java new file mode 100644 index 00000000..0b84e195 --- /dev/null +++ b/src/test/java/co/elastic/support/rest/RestEntryConfigTest.java @@ -0,0 +1,74 @@ +/* + * 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 co.elastic.support.rest; + +import co.elastic.support.Constants; +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.util.JsonYamlUtils; +import org.junit.jupiter.api.Test; + +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class RestEntryConfigTest { + private Map load(String resource) throws DiagnosticException { + return JsonYamlUtils.readYamlFromClasspath(resource, true); + } + + @Test + void buildEntryMap_elasticsearch_containsCommonKeys() throws DiagnosticException { + Map raw = load(Constants.ES_REST); + Map entries = new RestEntryConfig("9.3.0").buildEntryMap(raw); + + assertTrue(entries.containsKey("cat_health"), "Expected cat_health in ES entry map"); + assertTrue(entries.containsKey("cat_indices"), "Expected cat_indices in ES entry map"); + assertTrue(entries.containsKey("cat_nodes"), "Expected cat_nodes in ES entry map"); + } + + @Test + void buildEntryMap_kibana_containsCommonKeys() throws DiagnosticException { + Map raw = load(Constants.KIBANA_REST); + Map entries = new RestEntryConfig("9.3.0").buildEntryMap(raw); + + assertFalse(entries.isEmpty(), "Expected non-empty Kibana entry map"); + assertTrue(entries.containsKey("kibana_alerts_health"), "Expected kibana_alerts_health in map"); + } + + @Test + void buildEntryMap_logstash_containsCommonKeys() throws DiagnosticException { + Map raw = load(Constants.LS_REST); + Map entries = new RestEntryConfig("9.3.0").buildEntryMap(raw); + + assertTrue(entries.containsKey("logstash_node"), "Expected logstash_node in entry map"); + assertTrue(entries.containsKey("logstash_node_stats"), "Expected logstash_node_stats in entry map"); + } + + @Test + void buildEntryMap_versionFilter_excludesOldOnlyEntries() throws DiagnosticException { + Map raw = load(Constants.ES_REST); + // cat_aliases has ">= 0.9.0 < 5.1.1" and ">= 5.1.1 < 7.7.0" and ">= 7.7.0" entries + // building for 9.3.0 should include the ">= 7.7.0" variant, not the older ones + Map entries = new RestEntryConfig("9.3.0").buildEntryMap(raw); + + assertTrue(entries.containsKey("cat_aliases"), "cat_aliases should be present for 9.3.0"); + // The URL for >= 7.7.0 includes expand_wildcards + assertTrue(entries.get("cat_aliases").getUrl().contains("expand_wildcards"), + "Expected >=7.7.0 URL variant for cat_aliases"); + } + + @Test + void buildEntryMap_modeFilter_excludesFullOnlyInLightMode() throws DiagnosticException { + Map raw = load(Constants.ES_REST); + Map fullEntries = new RestEntryConfig("9.3.0", "full").buildEntryMap(raw); + Map lightEntries = new RestEntryConfig("9.3.0", "light").buildEntryMap(raw); + + assertTrue(fullEntries.size() >= lightEntries.size(), + "Full mode should have >= entries compared to light mode"); + } +} diff --git a/src/test/java/co/elastic/support/rest/TestRestConfigFileValidity.java b/src/test/java/co/elastic/support/rest/TestRestConfigFileValidity.java deleted file mode 100644 index 403af333..00000000 --- a/src/test/java/co/elastic/support/rest/TestRestConfigFileValidity.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * 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 co.elastic.support.rest; - -import co.elastic.support.diagnostics.DiagnosticException; -import co.elastic.support.util.JsonYamlUtils; -import org.junit.jupiter.api.Test; -import org.semver4j.Semver; - -import java.util.Arrays; -import java.util.Map; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -public class TestRestConfigFileValidity { - protected static Semver sem = new Semver("9.9.999"); - - @Test - public void validateElasticConfigVersioning() throws DiagnosticException { - // validates each set of version entries. - for (String yamlFile : Arrays.asList("elastic-rest.yml", "logstash-rest.yml", "kibana-rest.yml", "monitoring-rest.yml")) { - Map restEntriesConfig = JsonYamlUtils.readYamlFromClasspath(yamlFile, true); - validateEntries(yamlFile, restEntriesConfig); - } - } - - @SuppressWarnings("unchecked") - private void validateEntries(String file, Map config) { - for (Map.Entry entry : config.entrySet()) { - Map values = (Map) entry.getValue(); - Map versions = (Map) values.get("versions"); - - int valid = 0; - - // Urls should have a leading / - // For each entry there should be at most 1 valid url. - for (Map.Entry versionNode : versions.entrySet()) { - if (sem.satisfies(versionNode.getKey())) { - valid++; - } - - if (versionNode.getValue() instanceof String) { - String url = (String) versionNode.getValue(); - assertTrue(url.startsWith("/"), url); - } else if (versionNode.getValue() instanceof Map) { - Map entryVersion = (Map) versionNode.getValue(); - String url = (String) entryVersion.get("url"); - Object spaceaware = entryVersion.get("spaceaware"); - Object paginate = entryVersion.get("paginate"); - - assertNotNull(url, entry.getKey() + "[" + versionNode.getKey() + "]"); - assertTrue(url.startsWith("/"), url); - assertTrue(spaceaware == null || spaceaware instanceof Boolean, "spaceaware is not a Boolean"); - assertTrue(paginate == null || paginate instanceof String, "paginate is not a String"); - } - } - - // should be at most 1 valid URL (0 if it's not available anymore) - assertTrue(valid <= 1, "[" + file + "][" + entry.getKey() + "] matches " + valid); - } - - } - -} diff --git a/src/test/java/co/elastic/support/rest/TestRestExecCalls.java b/src/test/java/co/elastic/support/rest/TestRestExecCalls.java deleted file mode 100644 index 1c7a545b..00000000 --- a/src/test/java/co/elastic/support/rest/TestRestExecCalls.java +++ /dev/null @@ -1,204 +0,0 @@ -/* - * 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 co.elastic.support.rest; - -import co.elastic.support.diagnostics.commands.RunClusterQueries; -import co.elastic.support.util.SystemProperties; -import com.github.tomakehurst.wiremock.WireMockServer; -import com.github.tomakehurst.wiremock.stubbing.Scenario; -import org.apache.commons.codec.binary.Base64; -import org.apache.commons.io.FileUtils; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.AfterEach; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestInstance; - -import java.io.File; -import java.io.IOException; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; - -import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; -import static com.github.tomakehurst.wiremock.client.WireMock.equalTo; -import static com.github.tomakehurst.wiremock.client.WireMock.get; -import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; -import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; - -@TestInstance(TestInstance.Lifecycle.PER_CLASS) -public class TestRestExecCalls { - private WireMockServer wireMockServer; - private RestClient httpRestClient, httpsRestClient; - private String temp = SystemProperties.userDir + SystemProperties.fileSeparator + "temp"; - private File tempDir = new File(temp); - private String authStringEnc = new String(Base64.encodeBase64("elastic:elastic".getBytes())); - - @BeforeAll - public void globalSetup() { - wireMockServer = new WireMockServer(wireMockConfig().port(9880).httpsPort(9443)); - wireMockServer.start(); - } - - @AfterAll - public void globalTeardown() { - wireMockServer.stop(); - } - - @BeforeEach - public void setup() { - - httpRestClient = RestClient.getClient( - "localhost", - 9880, - "http", - "elastic", - "elastic", - "", - 0, - "", - "", - "", - "", - true, - Collections.emptyMap(), - 3000, - 3000, - 3000 - ); - httpsRestClient = RestClient.getClient( - "localhost", - 9443, - "https", - "elastic", - "elastic", - "", - 0, - "", - "", - "", - "", - true, - Collections.emptyMap(), - 3000, - 3000, - 3000 - ); - - tempDir.mkdir(); - } - - @AfterEach - public void tearDown() { - wireMockServer.resetAll(); - FileUtils.deleteQuietly(tempDir); - } - - @Test - public void testSimpleQuery() { - wireMockServer.stubFor(get(urlEqualTo("/")).withHeader("Authorization", equalTo("Basic " + authStringEnc)) - .willReturn(aResponse().withBody("some_response_body"))); - - RestResult result = httpRestClient.execQuery("/"); - assertEquals(200, result.getStatus()); - assertEquals("some_response_body", result.toString()); - } - - @Test - public void testSecuredQuery() { - String url = "/"; - - wireMockServer.stubFor(get(urlEqualTo("/")).withHeader("Authorization", equalTo("Basic " + authStringEnc)) - .willReturn(aResponse().withBody("some_response_body"))); - - RestResult result = httpRestClient.execQuery(url); - assertEquals(200, result.getStatus()); - assertEquals("some_response_body", result.toString()); - } - - @Test - public void testHttpsQuery() { - String url = "/_nodes"; - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).withHeader("Authorization", equalTo("Basic " + authStringEnc)) - .willReturn(aResponse().withBody("some_response_body"))); - - RestResult result = httpsRestClient.execQuery(url); - assertEquals(200, result.getStatus()); - assertEquals("some_response_body", result.toString()); - } - - @Test - public void testFailThenSucceed() { - RunClusterQueries cmd = new RunClusterQueries(); - List entries = new ArrayList<>(); - entries.add(new RestEntry("nodes", "", ".json", true, "/_nodes", false)); - - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).inScenario("retry") - .whenScenarioStateIs(Scenario.STARTED) - .willReturn(aResponse().withBody("error_response_body").withStatus(502)) - .willSetStateTo("failed-once")); - - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).inScenario("retry") - .whenScenarioStateIs("failed-once") - .willReturn(aResponse().withBody("error_response_body").withStatus(502)) - .willSetStateTo("failed-twice")); - - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).inScenario("retry") - .whenScenarioStateIs("failed-twice") - .willReturn(aResponse().withBody("node_response_body").withStatus(200))); - - String targetFilename = temp + SystemProperties.fileSeparator + "nodes.json"; - int totalRetries = cmd.runQueries(httpRestClient, entries, temp, 3, 500); - assertEquals(2, totalRetries); - assertTrue(fileExistsWithText(targetFilename, "node_response_body")); - } - - @Test - public void testRetryAllFail() { - RunClusterQueries cmd = new RunClusterQueries(); - List entries = new ArrayList<>(); - entries.add(new RestEntry("nodes", "", ".json", true, "/_nodes", false)); - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).willReturn(aResponse().withBody("error_response_body").withStatus(502))); - - String targetFilename = temp + SystemProperties.fileSeparator + "nodes.json"; - - int totalRetries = cmd.runQueries(httpRestClient, entries, temp, 3, 500); - assertEquals(4, totalRetries); - assertTrue(fileExistsWithText(targetFilename, "error_response_body")); - } - - @Test - public void testAuthFailure() { - RunClusterQueries cmd = new RunClusterQueries(); - List entries = new ArrayList<>(); - entries.add(new RestEntry("nodes", "", ".json", true, "/_nodes", false)); - - wireMockServer.stubFor(get(urlEqualTo("/_nodes")).willReturn(aResponse().withBody("autherror_response_body").withStatus(401))); - - int totalRetries = cmd.runQueries(httpRestClient, entries, temp, 3, 500); - assertEquals(0, totalRetries); - - String targetFilename = temp + SystemProperties.fileSeparator + "nodes.json"; - assertTrue(fileExistsWithText(targetFilename, "autherror_response_body")); - } - - private boolean fileExistsWithText(String filename, String compare) { - try { - String fileContents = FileUtils.readFileToString(new File(filename), "UTF8"); - if (!fileContents.contains(compare)) { - return false; - } - } catch (IOException e) { - return false; - } - - return true; - } -} diff --git a/src/test/java/co/elastic/support/testutil/ContainerTestHelper.java b/src/test/java/co/elastic/support/testutil/ContainerTestHelper.java new file mode 100644 index 00000000..1d93e3ac --- /dev/null +++ b/src/test/java/co/elastic/support/testutil/ContainerTestHelper.java @@ -0,0 +1,86 @@ +/* + * 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 co.elastic.support.testutil; + +import co.elastic.support.Constants; +import co.elastic.support.diagnostics.DiagConfig; +import co.elastic.support.diagnostics.DiagnosticException; +import co.elastic.support.diagnostics.DiagnosticInputs; +import co.elastic.support.diagnostics.chain.DiagnosticContext; +import co.elastic.support.rest.RestClient; +import co.elastic.support.util.JsonYamlUtils; +import co.elastic.support.util.ResourceCache; +import org.testcontainers.containers.GenericContainer; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Path; +import java.util.Enumeration; +import java.util.HashSet; +import java.util.Set; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import static org.junit.jupiter.api.Assertions.assertTrue; + +public class ContainerTestHelper { + + public static final String STACK_VERSION = + System.getenv("ELASTIC_STACK_VERSION") != null ? System.getenv("ELASTIC_STACK_VERSION") : "9.3.0"; + + public static RestClient clientFor(GenericContainer container, int internalPort) { + DiagConfig config = loadDiagConfig(); + return RestClient.getClient( + container.getHost(), + container.getMappedPort(internalPort), + "http", + "", "", "", Constants.DEEFAULT_PROXY_PORT, "", "", "", "", + false, + config.extraHeaders, + config.connectionTimeout, + config.connectionRequestTimeout, + config.socketTimeout); + } + + public static DiagnosticInputs inputsFor(GenericContainer container, int internalPort, + String diagType, Path outputDir) { + DiagnosticInputs inputs = new DiagnosticInputs(); + inputs.host = container.getHost(); + inputs.port = container.getMappedPort(internalPort); + inputs.scheme = "http"; + inputs.diagType = diagType; + inputs.outputDir = outputDir.toString(); + return inputs; + } + + public static DiagConfig loadDiagConfig() { + try { + return new DiagConfig(JsonYamlUtils.readYamlFromClasspath(Constants.DIAG_CONFIG, true)); + } catch (DiagnosticException e) { + throw new RuntimeException("Failed to load diag config", e); + } + } + + public static DiagnosticContext contextFor(DiagnosticInputs inputs, ResourceCache cache) { + return new DiagnosticContext(loadDiagConfig(), inputs, cache, false); + } + + public static void assertZipContains(File zip, String... entryNames) throws IOException { + try (ZipFile zipFile = new ZipFile(zip)) { + Set names = new HashSet<>(); + Enumeration entries = zipFile.entries(); + while (entries.hasMoreElements()) { + ZipEntry entry = entries.nextElement(); + names.add(entry.getName().replaceFirst("^(.+/)(.+)", "$2")); + } + for (String name : entryNames) { + assertTrue(names.contains(name), + "Expected zip to contain '" + name + "' but found: " + names); + } + } + } +} diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 00000000..4dcf34e6 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.timeout.default = 10m +junit.jupiter.execution.timeout.lifecycle.method.default = 30s