Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #115: Develop HTTP Extension #153

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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions metricshub-agent/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>org.sentrysoftware</groupId>
<artifactId>http</artifactId>
</dependency>

<!-- OpenTelemetry -->
<dependency>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,8 @@ public class ProtocolsConfig {
private WmiProtocolConfig wmi;

@JsonSetter(nulls = SKIP)
private HttpProtocolConfig http;
@JsonDeserialize(using = ExtensionConfigDeserializer.class)
private IConfiguration http;

@JsonSetter(nulls = SKIP)
private OsCommandProtocolConfig osCommand;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
import org.sentrysoftware.metricshub.agent.config.ResourceConfig;
import org.sentrysoftware.metricshub.agent.config.ResourceGroupConfig;
import org.sentrysoftware.metricshub.agent.config.protocols.AbstractProtocolConfig;
import org.sentrysoftware.metricshub.agent.config.protocols.HttpProtocolConfig;
import org.sentrysoftware.metricshub.agent.config.protocols.IpmiProtocolConfig;
import org.sentrysoftware.metricshub.agent.config.protocols.OsCommandProtocolConfig;
import org.sentrysoftware.metricshub.agent.config.protocols.ProtocolsConfig;
Expand Down Expand Up @@ -110,7 +109,6 @@
public class ConfigHelper {

private static final String OS_COMMAND = "OSCommand";
private static final String HTTP_PROTOCOL = "HTTP";
private static final String WMI_PROTOCOL = "WMI";
private static final String WBEM_PROTOCOL = "WBEM";
private static final String SSH_PROTOCOL = "SSH";
Expand Down Expand Up @@ -971,9 +969,9 @@ private static void validateProtocols(@NonNull final String resourceKey, final R
validateWmiInfo(resourceKey, wmiConfig.getTimeout());
}

final HttpProtocolConfig httpConfig = protocolsConfig.getHttp();
final IConfiguration httpConfig = protocolsConfig.getHttp();
if (httpConfig != null) {
validateHttpInfo(resourceKey, httpConfig.getTimeout(), httpConfig.getPort());
httpConfig.validateConfiguration(resourceKey);
}

final OsCommandProtocolConfig osCommandConfig = protocolsConfig.getOsCommand();
Expand Down Expand Up @@ -1125,29 +1123,6 @@ static void validateWmiInfo(final String resourceKey, final Long timeout) throws
);
}

/**
* Validate the given HTTP information (timeout and port)
*
* @param resourceKey Resource unique identifier
* @param timeout How long until the HTTP request times out
* @param port The HTTP port number used to perform REST queries
* @throws InvalidConfigurationException
*/
static void validateHttpInfo(final String resourceKey, final Long timeout, final Integer port)
throws InvalidConfigurationException {
StringHelper.validateConfigurationAttribute(
timeout,
INVALID_TIMEOUT_CHECKER,
() -> String.format(TIMEOUT_ERROR, resourceKey, HTTP_PROTOCOL, timeout)
);

StringHelper.validateConfigurationAttribute(
port,
INVALID_PORT_CHECKER,
() -> String.format(PORT_ERROR, resourceKey, HTTP_PROTOCOL, port)
);
}

/**
* Validate the given OS Command information: timeout
*
Expand Down Expand Up @@ -1184,7 +1159,6 @@ static HostConfiguration buildHostConfiguration(
Stream
.of(
protocols.getSsh(),
protocols.getHttp(),
protocols.getWbem(),
protocols.getWmi(),
protocols.getOsCommand(),
Expand All @@ -1202,7 +1176,7 @@ static HostConfiguration buildHostConfiguration(
? new HashMap<>()
: new HashMap<>(
Stream
.of(protocols.getSnmp())
.of(protocols.getSnmp(), protocols.getHttp())
.filter(Objects::nonNull)
.collect(Collectors.toMap(IConfiguration::getClass, Function.identity()))
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,17 +21,23 @@
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

import com.fasterxml.jackson.databind.node.BooleanNode;
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 lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import org.sentrysoftware.metricshub.engine.configuration.HttpConfiguration;
import org.sentrysoftware.metricshub.cli.service.CliExtensionManager;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;
import org.sentrysoftware.metricshub.engine.configuration.IConfiguration;
import picocli.CommandLine.ArgGroup;
import picocli.CommandLine.Option;

/**
* This class is used by MetricsHubCliService to configure Http protocol when using the MetricsHub CLI.
* It create the engine's {@link HttpConfiguration} object that is used to monitor a specific resource through REST.
* It create the engine's {@link IConfiguration} for HTTP object that is used to monitor a specific resource through REST.
*/
@Data
@EqualsAndHashCode(callSuper = true)
Expand Down Expand Up @@ -92,23 +98,31 @@ public static class HttpOrHttps {
defaultValue = "" + DEFAULT_TIMEOUT,
description = "Timeout in seconds for HTTP operations (default: ${DEFAULT-VALUE} s)"
)
private long timeout;
private String timeout;

/**
* @param defaultUsername Username specified at the top level of the CLI (with the --username option)
* @param defaultPassword Password specified at the top level of the CLI (with the --password option)
* @return an HttpProtocol instance corresponding to the options specified by the user in the CLI
* @throws InvalidConfigurationException
*/
@Override
public IConfiguration toProtocol(String defaultUsername, char[] defaultPassword) {
return HttpConfiguration
.builder()
.https(isHttps())
.port(getOrDeducePortNumber())
.username(username == null ? defaultUsername : username)
.password(username == null ? defaultPassword : password)
.timeout(timeout)
.build();
public IConfiguration toProtocol(String defaultUsername, char[] defaultPassword)
throws InvalidConfigurationException {
final ObjectNode configuration = JsonNodeFactory.instance.objectNode();
configuration.set("https", BooleanNode.valueOf(isHttps()));
configuration.set("username", new TextNode(username == null ? defaultUsername : username));
configuration.set(
"password",
new TextNode(username == null ? String.valueOf(defaultPassword) : String.valueOf(password))
);
configuration.set("port", new IntNode(getOrDeducePortNumber()));
configuration.set("timeout", new TextNode(timeout));

return CliExtensionManager
.getExtensionManagerSingleton()
.buildConfigurationFromJsonNode("http", configuration, value -> value)
.orElseThrow();
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
package org.sentrysoftware.metricshub.agent.extension;

import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Builder.Default;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;
import org.sentrysoftware.metricshub.engine.common.helpers.StringHelper;
import org.sentrysoftware.metricshub.engine.configuration.IConfiguration;
import org.sentrysoftware.metricshub.engine.deserialization.TimeDeserializer;

/**
* The HttpConfiguration class represents the configuration for HTTP connections in the MetricsHub engine.
*/
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class HttpTestConfiguration implements IConfiguration {

@Default
private final Boolean https = true;

@Default
private final Integer port = 443;

@Default
@JsonDeserialize(using = TimeDeserializer.class)
private final Long timeout = 120L;

private String username;
private char[] password;

@Override
public String toString() {
return String.format(
"%s/%d%s",
Boolean.TRUE.equals(https) ? "HTTPS" : "HTTP",
port,
username != null ? " as " + username : ""
);
}

@Override
public void validateConfiguration(String resourceKey) throws InvalidConfigurationException {
StringHelper.validateConfigurationAttribute(
timeout,
attr -> attr == null || attr < 0L,
() ->
String.format(
"Resource %s - Timeout value is invalid for protocol %s." +
" Timeout value returned: %s. This resource will not be monitored. Please verify the configured timeout value.",
resourceKey,
"HTTP",
timeout
)
);

StringHelper.validateConfigurationAttribute(
port,
attr -> attr == null || attr < 1 || attr > 65535,
() ->
String.format(
"Resource %s - Invalid port configured for protocol %s. Port value returned: %s." +
" This resource will not be monitored. Please verify the configured port value.",
resourceKey,
"HTTP",
port
)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,150 @@
package org.sentrysoftware.metricshub.agent.extension;

/*-
* ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
* MetricsHub HTTP Extension
* ჻჻჻჻჻჻
* 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 <http://www.gnu.org/licenses/>.
* ╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱
*/

import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.MapperFeature;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.json.JsonMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import java.util.Map;
import java.util.Set;
import java.util.function.UnaryOperator;
import lombok.extern.slf4j.Slf4j;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;
import org.sentrysoftware.metricshub.engine.configuration.IConfiguration;
import org.sentrysoftware.metricshub.engine.connector.model.identity.criterion.Criterion;
import org.sentrysoftware.metricshub.engine.connector.model.identity.criterion.HttpCriterion;
import org.sentrysoftware.metricshub.engine.connector.model.monitor.task.source.HttpSource;
import org.sentrysoftware.metricshub.engine.connector.model.monitor.task.source.Source;
import org.sentrysoftware.metricshub.engine.extension.IProtocolExtension;
import org.sentrysoftware.metricshub.engine.strategy.detection.CriterionTestResult;
import org.sentrysoftware.metricshub.engine.strategy.source.SourceTable;
import org.sentrysoftware.metricshub.engine.telemetry.TelemetryManager;

/**
* This class implements the {@link IProtocolExtension} contract, reports the supported features,
* processes HTTP sources and criteria.
*/
@Slf4j
public class HttpTestExtension implements IProtocolExtension {

/**
* Protocol up status value '1.0'
*/
public static final Double UP = 1.0;

/**
* Protocol down status value '0.0'
*/
public static final Double DOWN = 0.0;

/**
* Up metric name format that will be saved by the metric factory
*/
public static final String HTTP_UP_METRIC = "metricshub.host.up{protocol=\"http\"}";

@Override
public boolean isValidConfiguration(IConfiguration configuration) {
return configuration instanceof HttpTestConfiguration;
}

@Override
public Set<Class<? extends Source>> getSupportedSources() {
return Set.of(HttpSource.class);
}

@Override
public Map<Class<? extends IConfiguration>, Set<Class<? extends Source>>> getConfigurationToSourceMapping() {
return Map.of(HttpTestConfiguration.class, Set.of(HttpSource.class));
}

@Override
public Set<Class<? extends Criterion>> getSupportedCriteria() {
return Set.of(HttpCriterion.class);
}

@Override
public void checkProtocol(TelemetryManager telemetryManager, Long collectTime) {}

@Override
public SourceTable processSource(Source source, String connectorId, TelemetryManager telemetryManager) {
return SourceTable.empty();
}

@Override
public CriterionTestResult processCriterion(
Criterion criterion,
String connectorId,
TelemetryManager telemetryManager
) {
return CriterionTestResult.empty();
}

@Override
public boolean isSupportedConfigurationType(String configurationType) {
return "http".equalsIgnoreCase(configurationType);
}

@Override
public IConfiguration buildConfiguration(String configurationType, JsonNode jsonNode, UnaryOperator<char[]> decrypt)
throws InvalidConfigurationException {
try {
final HttpTestConfiguration httpConfiguration = newObjectMapper()
.treeToValue(jsonNode, HttpTestConfiguration.class);

if (decrypt != null) {
// Decrypt the community
final char[] passwordDecypted = decrypt.apply(httpConfiguration.getPassword());
httpConfiguration.setPassword(passwordDecypted);
}

return httpConfiguration;
} catch (Exception e) {
final String errorMessage = String.format(
"Error while reading HTTP Configuration: %s. Error: %s",
jsonNode,
e.getMessage()
);
log.error(errorMessage);
log.debug("Error while reading HTTP Configuration: {}. Stack trace:", jsonNode, e);
throw new InvalidConfigurationException(errorMessage, e);
}
}

/**
* Creates and configures a new instance of the Jackson ObjectMapper for handling YAML data.
*
* @return A configured ObjectMapper instance.
*/
public static JsonMapper newObjectMapper() {
return JsonMapper
.builder(new YAMLFactory())
.enable(MapperFeature.ACCEPT_CASE_INSENSITIVE_ENUMS)
.enable(SerializationFeature.INDENT_OUTPUT)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.configure(DeserializationFeature.FAIL_ON_INVALID_SUBTYPE, false)
.build();
}
}
Loading