From c6440e56f6e8478111d73e0e29c784275bb96f7c Mon Sep 17 00:00:00 2001 From: Elyes Cherfa Date: Thu, 28 Nov 2024 15:07:40 +0100 Subject: [PATCH] Issue #424: Add CLI options for specific query execution * Added IQuery interface to handle protocols queries. * Developed SnmpCli executable which enables to execute SNMP get, getNext & walk commands through CLI. * Tested the generated SNMP.exe with different commands. --- .../cli/service/MetricsHubCliService.java | 6 +- .../cli/service/protocol/SnmpConfigCli.java | 14 ++ .../metricshub/cli/snmp/SnmpCli.java | 167 ++++++++++++++++++ .../cli/service/MetricsHubCliServiceTest.java | 17 +- .../metricshub/cli/snmp/SnmpCliTest.java | 123 +++++++++++++ metricshub-doc/pom.xml | 2 +- .../metricshub/engine/common/IQuery.java | 23 +++ .../engine/extension/IProtocolExtension.java | 11 ++ .../extension/http/HttpExtension.java | 7 + .../extension/ipmi/IpmiExtension.java | 7 + metricshub-linux/pom.xml | 2 + .../jpackage/metricshub-snmp.properties | 2 + .../oscommand/OsCommandExtension.java | 7 + .../extension/ping/PingExtension.java | 7 + .../snmp/AbstractSnmpRequestExecutor.java | 35 +++- .../extension/snmp/SnmpConfiguration.java | 27 ++- .../extension/snmp/SnmpExtension.java | 63 +++++++ .../extension/snmp/SnmpRequestExecutor.java | 2 +- .../extension/snmp/SnmpExtensionTest.java | 97 ++++++++++ .../extension/snmpv3/SnmpV3Extension.java | 7 + .../extension/wbem/WbemExtension.java | 7 + metricshub-windows/pom.xml | 2 + .../jpackage/metricshub-snmp.properties | 4 + .../extension/winrm/WinRmExtension.java | 7 + .../extension/wmi/WmiExtension.java | 7 + 25 files changed, 635 insertions(+), 18 deletions(-) create mode 100644 metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCli.java create mode 100644 metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCliTest.java create mode 100644 metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/common/IQuery.java create mode 100644 metricshub-linux/src/main/resources/jpackage/metricshub-snmp.properties create mode 100644 metricshub-windows/src/main/resources/jpackage/metricshub-snmp.properties diff --git a/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliService.java b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliService.java index 9dfda4f42..9b8e24338 100644 --- a/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliService.java +++ b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliService.java @@ -270,7 +270,7 @@ public Integer call() throws Exception { validate(); // Setup Log4j - setLogLevel(); + setLogLevel(verbose); final HostConfiguration hostConfiguration = HostConfiguration .builder() @@ -527,8 +527,10 @@ private void validate() { /** * Set Log4j logging level according to the verbose flags + * + * @param verbose array of boolean values specifying logger level. */ - void setLogLevel() { + public static void setLogLevel(final boolean[] verbose) { // Disable ANSI in the logging if we don't have a console ThreadContext.put("disableAnsi", Boolean.toString(!ConsoleService.hasConsole())); diff --git a/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/protocol/SnmpConfigCli.java b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/protocol/SnmpConfigCli.java index 1f2fc8cf5..6f9c40854 100644 --- a/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/protocol/SnmpConfigCli.java +++ b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/service/protocol/SnmpConfigCli.java @@ -21,10 +21,12 @@ * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ */ +import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import java.util.Arrays; import lombok.Data; import org.sentrysoftware.metricshub.cli.service.CliExtensionManager; import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException; @@ -79,6 +81,14 @@ public class SnmpConfigCli implements IProtocolConfigCli { ) String timeout; + @Option( + names = { "--snmp-retry-intervals", "--retry" }, + order = 2, + paramLabel = "RETRYINTERVALS", + description = "Timeout in milliseconds after which the elementary operations will be retried" + ) + int[] retryIntervals; + /** * This method creates an {@link IConfiguration} for a given username and a given password * @@ -97,6 +107,10 @@ public IConfiguration toProtocol(final String defaultUsername, final char[] defa } configuration.set("port", new IntNode(port)); configuration.set("timeout", new TextNode(timeout)); + if (retryIntervals != null) { + final ArrayNode retryIntervalsArrayNode = configuration.putArray("retryIntervals"); + Arrays.stream(retryIntervals).forEach(retryIntervalsArrayNode::add); + } return CliExtensionManager .getExtensionManagerSingleton() diff --git a/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCli.java b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCli.java new file mode 100644 index 000000000..667229eeb --- /dev/null +++ b/metricshub-agent/src/main/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCli.java @@ -0,0 +1,167 @@ +package org.sentrysoftware.metricshub.cli.snmp; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * MetricsHub Agent + * ჻჻჻჻჻჻ + * Copyright 2023 - 2024 Sentry Software + * ჻჻჻჻჻჻ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.fasterxml.jackson.databind.node.TextNode; + +import lombok.Data; + +import java.io.PrintWriter; +import java.util.Objects; +import java.util.concurrent.Callable; +import java.util.stream.Stream; +import org.fusesource.jansi.AnsiConsole; +import org.sentrysoftware.metricshub.cli.service.CliExtensionManager; +import org.sentrysoftware.metricshub.cli.service.PrintExceptionMessageHandlerService; +import org.sentrysoftware.metricshub.cli.service.protocol.SnmpConfigCli; +import org.sentrysoftware.metricshub.engine.common.IQuery; +import org.sentrysoftware.metricshub.engine.configuration.IConfiguration; +import picocli.CommandLine; +import picocli.CommandLine.ArgGroup; +import picocli.CommandLine.Model.CommandSpec; +import picocli.CommandLine.Option; +import picocli.CommandLine.ParameterException; +import picocli.CommandLine.Parameters; +import picocli.CommandLine.Spec; + +@Data +public class SnmpCli implements IQuery, Callable { + + @Parameters(index = "0", paramLabel = "HOSTNAME", description = "Hostname or IP address of the host to monitor") + String hostname; + + @Spec + CommandSpec spec; + + @Option(names = "--snmp-get", order = 1, paramLabel = "OID", description = "SNMP Get request") + String get; + + @Option(names = "--snmp-getnext", order = 2, paramLabel = "OID", description = "SNMP Get Next request") + String getNext; + + @Option(names = "--snmp-walk", order = 3, paramLabel = "OID", description = "SNMP Walk request") + String walk; + + @ArgGroup(exclusive = false, heading = "%n@|bold,underline SNMP Options|@:%n") + SnmpConfigCli snmpConfigCli; + + @Option(names = { "-h", "-?", "--help" }, usageHelp = true, description = "Shows this help message and exits") + boolean usageHelpRequested; + + @Option(names = "-v", order = 7, description = "Verbose mode (repeat the option to increase verbosity)") + boolean[] verbose; + + public JsonNode getQuery() { + final ObjectNode queryNode = JsonNodeFactory.instance.objectNode(); + String action; + String oid; + + if (get != null) { + action = "get"; + oid = get; + } else if (getNext != null) { + action = "getNext"; + oid = getNext; + } else { + action = "walk"; + oid = walk; + } + + queryNode.set("action", new TextNode(action)); + queryNode.set("oid", new TextNode(oid)); + + return queryNode; + } + + void validate() throws ParameterException { + if (snmpConfigCli == null) { + throw new ParameterException(spec.commandLine(), "SNMP protocol must be configured: --snmp."); + } + + Stream.of(get, getNext, walk) + .filter(Objects::nonNull) + .reduce((a, b) -> { + throw new ParameterException( + spec.commandLine(), + "Only one SNMP query can be specified at a time: --snmp-get, --snmp-getnext, --snmp-walk." + ); + }) + .orElseThrow(() -> new ParameterException( + spec.commandLine(), + "At least one SNMP query must be specified: --snmp-get, --snmp-getnext, --snmp-walk." + )); + } + + public static void main(String[] args) { + System.setProperty("log4j2.configurationFile", "log4j2-cli.xml"); + + // Enable colors on Windows terminal + AnsiConsole.systemInstall(); + + final CommandLine cli = new CommandLine(new SnmpCli()); + + // Keep the below line commented for future reference + // Using JAnsi on Windows breaks the output of Unicode (UTF-8) chars + // It can be fixed using the below line... when running in Windows Terminal + // and not CMD.EXE. + // As this is poorly documented, we keep this for future improvement. + // cli.setOut(new PrintWriter(AnsiConsole.out(), true, StandardCharsets.UTF_8)); // NOSONAR on commented code + + // Set the exception handler + cli.setExecutionExceptionHandler(new PrintExceptionMessageHandlerService()); + + // Allow case insensitive enum values + cli.setCaseInsensitiveEnumValuesAllowed(true); + + // Execute the command + final int exitCode = cli.execute(args); + + // Cleanup Windows terminal settings + AnsiConsole.systemUninstall(); + + System.exit(exitCode); + } + + @Override + public Integer call() throws Exception { + final PrintWriter printWriter = spec.commandLine().getOut(); + validate(); + CliExtensionManager + .getExtensionManagerSingleton() + .findExtensionByType("snmp") + .ifPresent(extension -> { + try { + IConfiguration protocol = snmpConfigCli.toProtocol(null, null); + protocol.setHostname(hostname); + extension.executeQuery(protocol, getQuery(), printWriter); + } catch (Exception e) { + printWriter.print("Invalid configuration detected"); + printWriter.flush(); + throw new IllegalStateException("Invalid configuration detected.", e); + } + }); + return CommandLine.ExitCode.OK; + } +} diff --git a/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliServiceTest.java b/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliServiceTest.java index d7f476c46..aa7f2f657 100644 --- a/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliServiceTest.java +++ b/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/service/MetricsHubCliServiceTest.java @@ -59,27 +59,20 @@ void testListAllConnectors() { @Test void testSetLogLevel() { - final MetricsHubCliService metricsHubCliService = new MetricsHubCliService(); - assertDoesNotThrow(() -> { - metricsHubCliService.verbose = new boolean[] {}; - metricsHubCliService.setLogLevel(); + MetricsHubCliService.setLogLevel(new boolean[] {}); }); assertDoesNotThrow(() -> { - metricsHubCliService.verbose = new boolean[] { true }; - metricsHubCliService.setLogLevel(); + MetricsHubCliService.setLogLevel(new boolean[] { true }); }); assertDoesNotThrow(() -> { - metricsHubCliService.verbose = new boolean[] { true, true }; - metricsHubCliService.setLogLevel(); + MetricsHubCliService.setLogLevel(new boolean[] { true, true }); }); assertDoesNotThrow(() -> { - metricsHubCliService.verbose = new boolean[] { true, true, true }; - metricsHubCliService.setLogLevel(); + MetricsHubCliService.setLogLevel(new boolean[] { true, true, true }); }); assertDoesNotThrow(() -> { - metricsHubCliService.verbose = new boolean[] { true, true, true, true }; - metricsHubCliService.setLogLevel(); + MetricsHubCliService.setLogLevel(new boolean[] { true, true, true, true }); }); } diff --git a/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCliTest.java b/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCliTest.java new file mode 100644 index 000000000..8e119abed --- /dev/null +++ b/metricshub-agent/src/test/java/org/sentrysoftware/metricshub/cli/snmp/SnmpCliTest.java @@ -0,0 +1,123 @@ +package org.sentrysoftware.metricshub.cli.snmp; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import org.junit.jupiter.api.Test; +import org.sentrysoftware.metricshub.cli.service.protocol.SnmpConfigCli; + +import com.fasterxml.jackson.databind.JsonNode; +import picocli.CommandLine; +import picocli.CommandLine.ParameterException; + +class SnmpCliTest { + + SnmpCli snmpCli; + CommandLine commandLine; + + static String SNMP_OID = "1.3.6.1.4.1.674.10892.5.5.1.20.130.4"; + static String SNMP_VERSION = "v2c"; + static String SNMP_COMMUNITY = "public"; + + void initCli() { + snmpCli = new SnmpCli(); + commandLine = new CommandLine(snmpCli); + } + + void initSnmpGet() { + initCli(); + + commandLine.execute( + "hostname", + "--snmp-get", + SNMP_OID, + "--snmp", + SNMP_VERSION, + "--community", + SNMP_COMMUNITY, + "--retry", + "1000", + "--retry", + "6000" + ); + } + + void initSnmpGetNext() { + initCli(); + + commandLine.execute( + "hostname", + "--snmp-getnext", + SNMP_OID, + "--snmp", + SNMP_VERSION, + "--community", + SNMP_COMMUNITY, + "--retry", + "1000", + "--retry", + "6000" + ); + } + + void initSnmpWalk() { + initCli(); + + commandLine.execute( + "hostname", + "--snmp-walk", + SNMP_OID, + "--snmp", + SNMP_VERSION, + "--community", + SNMP_COMMUNITY, + "--retry", + "1000", + "--retry", + "6000" + ); + } + @Test + void testExecute() { + initSnmpGet(); + assertEquals(SNMP_OID, snmpCli.get); + initSnmpGetNext(); + assertEquals(SNMP_OID, snmpCli.getNext); + initSnmpWalk(); + assertEquals(SNMP_OID, snmpCli.walk); + } + + @Test + void testGetQuery() { + initSnmpGet(); + JsonNode snmpQuery = snmpCli.getQuery(); + assertEquals("get", snmpQuery.get("action").asText()); + assertEquals(SNMP_OID, snmpQuery.get("oid").asText()); + + initSnmpGetNext(); + snmpQuery = snmpCli.getQuery(); + assertEquals("getNext", snmpQuery.get("action").asText()); + assertEquals(SNMP_OID, snmpQuery.get("oid").asText()); + + initSnmpWalk(); + snmpQuery = snmpCli.getQuery(); + assertEquals("walk", snmpQuery.get("action").asText()); + assertEquals(SNMP_OID, snmpQuery.get("oid").asText()); + } + + @Test + void testValidate() { + initCli(); + ParameterException snmpConfigException = assertThrows(ParameterException.class, () -> snmpCli.validate()); + assertEquals("SNMP protocol must be configured: --snmp.", snmpConfigException.getMessage()); + snmpCli.setSnmpConfigCli(new SnmpConfigCli()); + ParameterException noQueriesException = assertThrows(ParameterException.class, () -> snmpCli.validate()); + assertEquals("At least one SNMP query must be specified: --snmp-get, --snmp-getnext, --snmp-walk.", noQueriesException.getMessage()); + snmpCli.setGet(SNMP_OID); + assertDoesNotThrow(() -> snmpCli.validate()); + snmpCli.setGetNext(SNMP_OID); + ParameterException manyQueriesException = assertThrows(ParameterException.class, () -> snmpCli.validate()); + assertEquals("Only one SNMP query can be specified at a time: --snmp-get, --snmp-getnext, --snmp-walk.", manyQueriesException.getMessage()); + } +} diff --git a/metricshub-doc/pom.xml b/metricshub-doc/pom.xml index 38bbd3de0..03ddd3366 100644 --- a/metricshub-doc/pom.xml +++ b/metricshub-doc/pom.xml @@ -119,7 +119,7 @@ org.sentrysoftware.maven metricshub-connector-maven-plugin - 1.0.05 + 1.0.07-SNAPSHOT ${project.basedir}/../metricshub-agent/target/connectors diff --git a/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/common/IQuery.java b/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/common/IQuery.java new file mode 100644 index 000000000..4c16d1c74 --- /dev/null +++ b/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/common/IQuery.java @@ -0,0 +1,23 @@ +package org.sentrysoftware.metricshub.engine.common; + +/*- + * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲ + * MetricsHub Engine + * ჻჻჻჻჻჻ + * Copyright 2023 - 2024 Sentry Software + * ჻჻჻჻჻჻ + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + * ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱ + */ +public interface IQuery {} diff --git a/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/extension/IProtocolExtension.java b/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/extension/IProtocolExtension.java index b9fc81214..8d4d67426 100644 --- a/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/extension/IProtocolExtension.java +++ b/metricshub-engine/src/main/java/org/sentrysoftware/metricshub/engine/extension/IProtocolExtension.java @@ -22,6 +22,7 @@ */ import com.fasterxml.jackson.databind.JsonNode; +import java.io.PrintWriter; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -149,4 +150,14 @@ IConfiguration buildConfiguration(String configurationType, JsonNode jsonNode, U * @return The protocol identifier as a string. */ String getIdentifier(); + + /** + * Executes a query based on the provided configuration and query parameters. + * + * @param configuration the IConfiguration object containing the configuration details. + * @param query a JsonNode representing the query to be executed. + * @param printWriter the PrintWriter used to display the result. + * @throws Exception if the query execution fails due to an error or unexpected condition. + */ + String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception; } diff --git a/metricshub-http-extension/src/main/java/org/sentrysoftware/metricshub/extension/http/HttpExtension.java b/metricshub-http-extension/src/main/java/org/sentrysoftware/metricshub/extension/http/HttpExtension.java index 0e316de5d..b24836e7c 100644 --- a/metricshub-http-extension/src/main/java/org/sentrysoftware/metricshub/extension/http/HttpExtension.java +++ b/metricshub-http-extension/src/main/java/org/sentrysoftware/metricshub/extension/http/HttpExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -217,4 +218,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-ipmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/ipmi/IpmiExtension.java b/metricshub-ipmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/ipmi/IpmiExtension.java index 4b74f8e6a..32e02301b 100644 --- a/metricshub-ipmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/ipmi/IpmiExtension.java +++ b/metricshub-ipmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/ipmi/IpmiExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -259,4 +260,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-linux/pom.xml b/metricshub-linux/pom.xml index a357d0244..5408ff0d4 100644 --- a/metricshub-linux/pom.xml +++ b/metricshub-linux/pom.xml @@ -246,6 +246,8 @@ metricshub-encrypt=metricshub-encrypt.properties --add-launcher service=metricshub-agent.properties + --add-launcher + snmp=metricshub-snmp.properties @jpackage.txt ${project.build.directory}/jpackage diff --git a/metricshub-linux/src/main/resources/jpackage/metricshub-snmp.properties b/metricshub-linux/src/main/resources/jpackage/metricshub-snmp.properties new file mode 100644 index 000000000..441878697 --- /dev/null +++ b/metricshub-linux/src/main/resources/jpackage/metricshub-snmp.properties @@ -0,0 +1,2 @@ +main-class=org.sentrysoftware.metricshub.cli.snmp.SnmpCli +java-options=-javaagent:$APPDIR/../extensions/metricshub-classloader-agent-${project.version}.jar --add-exports java.xml/com.sun.org.apache.xerces.internal.parsers=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-opens java.base/sun.security.ssl=ALL-UNNAMED \ No newline at end of file diff --git a/metricshub-oscommand-extension/src/main/java/org/sentrysoftware/metricshub/extension/oscommand/OsCommandExtension.java b/metricshub-oscommand-extension/src/main/java/org/sentrysoftware/metricshub/extension/oscommand/OsCommandExtension.java index c0bdbb2f7..69129afca 100644 --- a/metricshub-oscommand-extension/src/main/java/org/sentrysoftware/metricshub/extension/oscommand/OsCommandExtension.java +++ b/metricshub-oscommand-extension/src/main/java/org/sentrysoftware/metricshub/extension/oscommand/OsCommandExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -311,4 +312,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return "ssh"; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-ping-extension/src/main/java/org/sentrysoftware/metricshub/extension/ping/PingExtension.java b/metricshub-ping-extension/src/main/java/org/sentrysoftware/metricshub/extension/ping/PingExtension.java index 063376bc2..4e0c69a3e 100644 --- a/metricshub-ping-extension/src/main/java/org/sentrysoftware/metricshub/extension/ping/PingExtension.java +++ b/metricshub-ping-extension/src/main/java/org/sentrysoftware/metricshub/extension/ping/PingExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -167,4 +168,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-snmp-extension-common/src/main/java/org/sentrysoftware/metricshub/extension/snmp/AbstractSnmpRequestExecutor.java b/metricshub-snmp-extension-common/src/main/java/org/sentrysoftware/metricshub/extension/snmp/AbstractSnmpRequestExecutor.java index 29b8e42e6..160892599 100644 --- a/metricshub-snmp-extension-common/src/main/java/org/sentrysoftware/metricshub/extension/snmp/AbstractSnmpRequestExecutor.java +++ b/metricshub-snmp-extension-common/src/main/java/org/sentrysoftware/metricshub/extension/snmp/AbstractSnmpRequestExecutor.java @@ -212,6 +212,8 @@ protected T executeSnmpGetRequest( return snmpClient.getNext(oid); case TABLE: return snmpClient.table(oid, selectColumnArray); + case WALK: + return snmpClient.walk(oid); default: throw new IllegalArgumentException("Not implemented."); } @@ -254,6 +256,37 @@ public enum SnmpGetRequest { * Represents an SNMP TABLE request. * Used to retrieve a table of SNMP objects. */ - TABLE + TABLE, + /** + * + */ + WALK + } + + @WithSpan("SNMP Walk") + public String executeSNMPWalk( + @NonNull @SpanAttribute("snmp.oid") final String oid, + @NonNull @SpanAttribute("snmp.config") final ISnmpConfiguration configuration, + @NonNull @SpanAttribute("host.hostname") final String hostname, + final boolean logMode + ) throws InterruptedException, ExecutionException, TimeoutException { + LoggingHelper.trace(() -> log.trace("Executing SNMP Walk request:\n- OID: {}\n", oid)); + + final long startTime = System.currentTimeMillis(); + + String result = executeSnmpGetRequest(SnmpGetRequest.WALK, oid, configuration, hostname, null, logMode); + + final long responseTime = System.currentTimeMillis() - startTime; + + LoggingHelper.trace(() -> + log.trace( + "Executed SNMP Walk request:\n- OID: {}\n- Result: {}\n- response-time: {}\n", + oid, + result, + responseTime + ) + ); + + return result; } } diff --git a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpConfiguration.java b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpConfiguration.java index 58e43c64f..bf5fb3978 100644 --- a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpConfiguration.java +++ b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpConfiguration.java @@ -25,6 +25,7 @@ import com.fasterxml.jackson.annotation.JsonSetter; import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import java.util.Arrays; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Builder.Default; @@ -70,6 +71,10 @@ public class SnmpConfiguration implements ISnmpConfiguration { private String hostname; + @Default + @JsonSetter(nulls = SKIP) + private int[] retryIntervals = new int[] { 500, 1000, 2000, 5000, 5000 }; + @Override public String toString() { return version.getDisplayName() + " (" + new String(community) + ")"; @@ -164,6 +169,19 @@ public void validateConfiguration(final String resourceKey) throws InvalidConfig timeout ) ); + + StringHelper.validateConfigurationAttribute( + retryIntervals, + attr -> Arrays.stream(attr).allMatch(value -> value < 1), + () -> + String.format( + "Resource %s - retryIntervals value is invalid for protocol %s." + + " retryIntervals value returned: %s. This resource will not be monitored. Please verify the configured retryIntervals value.", + resourceKey, + displayName, + retryIntervals + ) + ); } @Override @@ -173,6 +191,13 @@ public int getIntVersion() { @Override public IConfiguration copy() { - return SnmpConfiguration.builder().community(community).port(port).timeout(timeout).version(version).build(); + return SnmpConfiguration + .builder() + .community(community) + .port(port) + .timeout(timeout) + .retryIntervals(retryIntervals) + .version(version) + .build(); } } diff --git a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtension.java b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtension.java index 890d269ac..b2e49a81f 100644 --- a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtension.java +++ b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtension.java @@ -22,6 +22,7 @@ */ import com.fasterxml.jackson.databind.JsonNode; +import java.io.PrintWriter; import java.util.function.UnaryOperator; import lombok.AllArgsConstructor; import lombok.NonNull; @@ -42,6 +43,10 @@ public class SnmpExtension extends AbstractSnmpExtension { */ private static final String IDENTIFIER = "snmp"; + public static final String GET = "get"; + public static final String GET_NEXT = "getNext"; + public static final String WALK = "walk"; + @NonNull private SnmpRequestExecutor snmpRequestExecutor; @@ -101,4 +106,62 @@ public String getIdentifier() { protected AbstractSnmpRequestExecutor getRequestExecutor() { return snmpRequestExecutor; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + final SnmpConfiguration snmpConfiguration = (SnmpConfiguration) configuration; + final String hostname = configuration.getHostname(); + String result = "Failed Executing SNMP query"; + final String action = query.get("action").asText(); + + final String oId = query.get("oid").asText(); + final String exceptionMessage = "Hostname {} - Error while executing SNMP {} query. Message: {}"; + + printWriter.println("Executing query from SNMP Extension"); + printWriter.flush(); + + switch (action) { + case GET: + try { + displayQuery(printWriter, hostname, GET, oId); + result = snmpRequestExecutor.executeSNMPGet(oId, snmpConfiguration, hostname, false); + displayQueryResult(printWriter, result); + } catch (Exception e) { + log.debug(exceptionMessage, hostname, GET, e); + } + break; + case GET_NEXT: + try { + displayQuery(printWriter, hostname, GET_NEXT, oId); + result = snmpRequestExecutor.executeSNMPGetNext(oId, snmpConfiguration, hostname, false); + displayQueryResult(printWriter, result); + } catch (Exception e) { + log.debug(exceptionMessage, hostname, GET_NEXT, e); + } + break; + case WALK: + try { + displayQuery(printWriter, hostname, WALK, oId); + result = snmpRequestExecutor.executeSNMPWalk(oId, snmpConfiguration, hostname, false); + displayQueryResult(printWriter, result); + } catch (Exception e) { + log.debug(exceptionMessage, hostname, WALK, e); + } + break; + default: + throw new IllegalArgumentException("Hostname {} - Invalid SNMP Operation"); + } + return result; + } + + public void displayQuery(final PrintWriter printWriter, final String hostname, final String query, final String oId) { + printWriter.println(String.format("Hostname %s - Executing SNMP %s query:\n", hostname, query)); + printWriter.println(String.format("OID: %s", oId)); + printWriter.flush(); + } + + public void displayQueryResult(final PrintWriter printWriter, final String result) { + printWriter.println(String.format("Result: %s", result)); + printWriter.flush(); + } } diff --git a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpRequestExecutor.java b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpRequestExecutor.java index 669508ebd..9cb44b6dc 100644 --- a/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpRequestExecutor.java +++ b/metricshub-snmp-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmp/SnmpRequestExecutor.java @@ -38,7 +38,7 @@ protected SnmpClient createSnmpClient(ISnmpConfiguration protocol, String hostna hostname, snmpConfig.getPort(), snmpConfig.getIntVersion(), - null, + snmpConfig.getRetryIntervals(), String.valueOf(snmpConfig.getCommunity()), null, null, diff --git a/metricshub-snmp-extension/src/test/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtensionTest.java b/metricshub-snmp-extension/src/test/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtensionTest.java index 6db40a900..0eaa72a6b 100644 --- a/metricshub-snmp-extension/src/test/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtensionTest.java +++ b/metricshub-snmp-extension/src/test/java/org/sentrysoftware/metricshub/extension/snmp/SnmpExtensionTest.java @@ -3,6 +3,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNull; +import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; @@ -11,11 +12,16 @@ import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doThrow; import static org.sentrysoftware.metricshub.engine.common.helpers.KnownMonitorType.HOST; +import static org.sentrysoftware.metricshub.extension.snmp.SnmpExtension.GET; +import static org.sentrysoftware.metricshub.extension.snmp.SnmpExtension.GET_NEXT; +import static org.sentrysoftware.metricshub.extension.snmp.SnmpExtension.WALK; import com.fasterxml.jackson.databind.node.IntNode; import com.fasterxml.jackson.databind.node.JsonNodeFactory; import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.TextNode; +import java.io.PrintWriter; +import java.io.StringWriter; import java.util.HashMap; import java.util.Map; import java.util.Optional; @@ -49,6 +55,7 @@ class SnmpExtensionTest { public static final String SNMP_GET_NEXT_FOURTH_RESULT = "1.3.6.1.4.1.674.10893.1.20.1 ASN_OCT"; public static final String EXECUTE_SNMP_GET_RESULT = "CMC DELL"; public static final String SNMP_VERSION = "2.4.6"; + public static final String SUCCESSFUL_SNMP_QUERY_RESULT = "Successful SNMP %s Query"; @Mock private SnmpRequestExecutor snmpRequestExecutorMock; @@ -581,4 +588,94 @@ void testBuildConfiguration() throws InvalidConfigurationException { snmpExtension.buildConfiguration("snmp", configuration, null) ); } + + @Test + void testExecuteGetQuery() throws Exception { + final String message = String.format(SUCCESSFUL_SNMP_QUERY_RESULT, GET); + initSnmp(); + doReturn(message) + .when(snmpRequestExecutorMock) + .executeSNMPGet(anyString(), any(SnmpConfiguration.class), anyString(), eq(false)); + + SnmpConfiguration snmpConfiguration = SnmpConfiguration.builder().hostname(HOST_NAME).build(); + ObjectNode snmpQueryConfiguration = JsonNodeFactory.instance.objectNode(); + snmpQueryConfiguration.set("action", new TextNode(GET)); + snmpQueryConfiguration.set("oid", new TextNode(SnmpExtension.SNMP_OID)); + + assertEquals( + message, + snmpExtension.executeQuery(snmpConfiguration, snmpQueryConfiguration, new PrintWriter(new StringWriter())) + ); + } + + @Test + void testExecuteGetNextQuery() throws Exception { + final String message = String.format(SUCCESSFUL_SNMP_QUERY_RESULT, GET_NEXT); + initSnmp(); + doReturn(message) + .when(snmpRequestExecutorMock) + .executeSNMPGetNext(anyString(), any(SnmpConfiguration.class), anyString(), eq(false)); + + SnmpConfiguration snmpConfiguration = SnmpConfiguration.builder().hostname(HOST_NAME).build(); + ObjectNode snmpQueryConfiguration = JsonNodeFactory.instance.objectNode(); + snmpQueryConfiguration.set("action", new TextNode(GET_NEXT)); + snmpQueryConfiguration.set("oid", new TextNode(SnmpExtension.SNMP_OID)); + + assertEquals( + message, + snmpExtension.executeQuery(snmpConfiguration, snmpQueryConfiguration, new PrintWriter(new StringWriter())) + ); + } + + @Test + void testExecuteWalkQuery() throws Exception { + final String message = String.format(SUCCESSFUL_SNMP_QUERY_RESULT, WALK); + initSnmp(); + doReturn(message) + .when(snmpRequestExecutorMock) + .executeSNMPWalk(anyString(), any(SnmpConfiguration.class), anyString(), eq(false)); + + SnmpConfiguration snmpConfiguration = SnmpConfiguration.builder().hostname(HOST_NAME).build(); + ObjectNode snmpQueryConfiguration = JsonNodeFactory.instance.objectNode(); + snmpQueryConfiguration.set("action", new TextNode(WALK)); + snmpQueryConfiguration.set("oid", new TextNode(SnmpExtension.SNMP_OID)); + + assertEquals( + message, + snmpExtension.executeQuery(snmpConfiguration, snmpQueryConfiguration, new PrintWriter(new StringWriter())) + ); + } + + @Test + void testThrowsExceptionWithQuery() throws Exception { + initSnmp(); + doThrow(new InterruptedException()) + .when(snmpRequestExecutorMock) + .executeSNMPWalk(anyString(), any(SnmpConfiguration.class), anyString(), eq(false)); + + SnmpConfiguration snmpConfiguration = SnmpConfiguration.builder().hostname(HOST_NAME).build(); + ObjectNode snmpQueryConfiguration = JsonNodeFactory.instance.objectNode(); + snmpQueryConfiguration.set("action", new TextNode(WALK)); + snmpQueryConfiguration.set("oid", new TextNode(SnmpExtension.SNMP_OID)); + + assertEquals( + "Failed Executing SNMP query", + snmpExtension.executeQuery(snmpConfiguration, snmpQueryConfiguration, new PrintWriter(new StringWriter())) + ); + } + + @Test + void testThrowsExceptionQuery() throws Exception { + initSnmp(); + + SnmpConfiguration snmpConfiguration = SnmpConfiguration.builder().hostname(HOST_NAME).build(); + ObjectNode snmpQueryConfiguration = JsonNodeFactory.instance.objectNode(); + snmpQueryConfiguration.set("action", new TextNode("")); + snmpQueryConfiguration.set("oid", new TextNode(SnmpExtension.SNMP_OID)); + + assertThrows( + Exception.class, + () -> snmpExtension.executeQuery(snmpConfiguration, snmpQueryConfiguration, new PrintWriter(new StringWriter())) + ); + } } diff --git a/metricshub-snmpv3-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmpv3/SnmpV3Extension.java b/metricshub-snmpv3-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmpv3/SnmpV3Extension.java index 6ba13927c..644795700 100644 --- a/metricshub-snmpv3-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmpv3/SnmpV3Extension.java +++ b/metricshub-snmpv3-extension/src/main/java/org/sentrysoftware/metricshub/extension/snmpv3/SnmpV3Extension.java @@ -22,6 +22,7 @@ */ import com.fasterxml.jackson.databind.JsonNode; +import java.io.PrintWriter; import java.util.function.UnaryOperator; import lombok.NonNull; import lombok.extern.slf4j.Slf4j; @@ -106,4 +107,10 @@ protected AbstractSnmpRequestExecutor getRequestExecutor() { protected Class getConfigurationClass() { return SnmpV3Configuration.class; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-wbem-extension/src/main/java/org/sentrysoftware/metricshub/extension/wbem/WbemExtension.java b/metricshub-wbem-extension/src/main/java/org/sentrysoftware/metricshub/extension/wbem/WbemExtension.java index 09c458475..f350acdd1 100644 --- a/metricshub-wbem-extension/src/main/java/org/sentrysoftware/metricshub/extension/wbem/WbemExtension.java +++ b/metricshub-wbem-extension/src/main/java/org/sentrysoftware/metricshub/extension/wbem/WbemExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -239,4 +240,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-windows/pom.xml b/metricshub-windows/pom.xml index 61844ee85..1ac185010 100644 --- a/metricshub-windows/pom.xml +++ b/metricshub-windows/pom.xml @@ -268,6 +268,8 @@ MetricsHub-Encrypt=metricshub-encrypt.properties --add-launcher MetricsHubServiceManager=metricshub-agent.properties + --add-launcher + Snmp=metricshub-snmp.properties @jpackage.txt ${project.build.directory}/jpackage diff --git a/metricshub-windows/src/main/resources/jpackage/metricshub-snmp.properties b/metricshub-windows/src/main/resources/jpackage/metricshub-snmp.properties new file mode 100644 index 000000000..1154fc19c --- /dev/null +++ b/metricshub-windows/src/main/resources/jpackage/metricshub-snmp.properties @@ -0,0 +1,4 @@ +launcher-as-service=false +win-console=true +main-class=org.sentrysoftware.metricshub.cli.snmp.SnmpCli +java-options=-javaagent:$APPDIR\\..\\extensions\\metricshub-classloader-agent-${project.version}.jar --add-exports java.xml/com.sun.org.apache.xerces.internal.parsers=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/sun.net.www.protocol.http=ALL-UNNAMED --add-opens java.base/sun.security.ssl=ALL-UNNAMED \ No newline at end of file diff --git a/metricshub-winrm-extension/src/main/java/org/sentrysoftware/metricshub/extension/winrm/WinRmExtension.java b/metricshub-winrm-extension/src/main/java/org/sentrysoftware/metricshub/extension/winrm/WinRmExtension.java index fe8dd4666..de9a6abcd 100644 --- a/metricshub-winrm-extension/src/main/java/org/sentrysoftware/metricshub/extension/winrm/WinRmExtension.java +++ b/metricshub-winrm-extension/src/main/java/org/sentrysoftware/metricshub/extension/winrm/WinRmExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -272,4 +273,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } } diff --git a/metricshub-wmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/wmi/WmiExtension.java b/metricshub-wmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/wmi/WmiExtension.java index 0a7bc01c1..60cf953d0 100644 --- a/metricshub-wmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/wmi/WmiExtension.java +++ b/metricshub-wmi-extension/src/main/java/org/sentrysoftware/metricshub/extension/wmi/WmiExtension.java @@ -27,6 +27,7 @@ import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.json.JsonMapper; import com.fasterxml.jackson.dataformat.yaml.YAMLFactory; +import java.io.PrintWriter; import java.util.List; import java.util.Map; import java.util.Optional; @@ -275,4 +276,10 @@ public static JsonMapper newObjectMapper() { public String getIdentifier() { return IDENTIFIER; } + + @Override + public String executeQuery(IConfiguration configuration, JsonNode query, PrintWriter printWriter) throws Exception { + // TODO Auto-generated method stub + return null; + } }