All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.sentrysoftware.metricshub.agent.helper.ConfigHelper Maven / Gradle / Ivy

package org.sentrysoftware.metricshub.agent.helper;

/*-
 * ╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲╱╲
 * 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 static org.sentrysoftware.metricshub.agent.helper.AgentConstants.CONFIG_DIRECTORY_NAME;
import static org.sentrysoftware.metricshub.agent.helper.AgentConstants.CONFIG_EXAMPLE_FILENAME;
import static org.sentrysoftware.metricshub.agent.helper.AgentConstants.DEFAULT_CONFIG_FILENAME;
import static org.sentrysoftware.metricshub.agent.helper.AgentConstants.FILE_PATH_FORMAT;
import static org.sentrysoftware.metricshub.agent.helper.AgentConstants.LOG_DIRECTORY_NAME;
import static org.sentrysoftware.metricshub.agent.helper.AgentConstants.PRODUCT_WIN_DIR_NAME;

import com.fasterxml.jackson.databind.DeserializationFeature;
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.io.File;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.AclEntry;
import java.nio.file.attribute.AclEntryPermission;
import java.nio.file.attribute.AclEntryType;
import java.nio.file.attribute.AclFileAttributeView;
import java.nio.file.attribute.GroupPrincipal;
import java.security.MessageDigest;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import lombok.NonNull;
import lombok.extern.slf4j.Slf4j;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.ThreadContext;
import org.sentrysoftware.metricshub.agent.config.AgentConfig;
import org.sentrysoftware.metricshub.agent.config.AlertingSystemConfig;
import org.sentrysoftware.metricshub.agent.config.ResourceConfig;
import org.sentrysoftware.metricshub.agent.config.ResourceGroupConfig;
import org.sentrysoftware.metricshub.agent.context.MetricDefinitions;
import org.sentrysoftware.metricshub.agent.security.PasswordEncrypt;
import org.sentrysoftware.metricshub.engine.common.exception.InvalidConfigurationException;
import org.sentrysoftware.metricshub.engine.common.helpers.JsonHelper;
import org.sentrysoftware.metricshub.engine.common.helpers.LocalOsHandler;
import org.sentrysoftware.metricshub.engine.common.helpers.MetricsHubConstants;
import org.sentrysoftware.metricshub.engine.common.helpers.ResourceHelper;
import org.sentrysoftware.metricshub.engine.configuration.ConnectorVariables;
import org.sentrysoftware.metricshub.engine.configuration.HostConfiguration;
import org.sentrysoftware.metricshub.engine.configuration.IConfiguration;
import org.sentrysoftware.metricshub.engine.connector.model.Connector;
import org.sentrysoftware.metricshub.engine.connector.model.ConnectorStore;
import org.sentrysoftware.metricshub.engine.connector.model.common.DeviceKind;
import org.sentrysoftware.metricshub.engine.connector.model.identity.ConnectorIdentity;
import org.sentrysoftware.metricshub.engine.connector.model.metric.MetricDefinition;
import org.sentrysoftware.metricshub.engine.extension.ExtensionLoader;
import org.sentrysoftware.metricshub.engine.extension.ExtensionManager;
import org.sentrysoftware.metricshub.engine.security.SecurityManager;
import org.sentrysoftware.metricshub.engine.telemetry.TelemetryManager;
import org.springframework.core.io.ClassPathResource;

/**
 * Helper class for managing configuration-related operations in the MetricsHub agent.
 * This class provides methods for retrieving directories, creating paths, and handling configuration files.
 * It also includes utility methods for encryption and other configuration-related tasks.
 * The class is designed with a private constructor and static utility methods.
 *
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
@Slf4j
public class ConfigHelper {

	public static final String TOP_LEVEL_VIRTUAL_RESOURCE_GROUP_KEY = "metricshub-top-level-rg";

	/**
	 * Get the default output directory for logging.
* On Windows, if the LOCALAPPDATA path is not valid then the output directory will be located * under the installation directory.
* On Linux, the output directory is located under the installation directory. * * @return {@link Path} instance */ public static Path getDefaultOutputDirectory() { if (LocalOsHandler.isWindows()) { final String localAppDataPath = System.getenv("LOCALAPPDATA"); // Make sure the LOCALAPPDATA path is valid if (localAppDataPath != null && !localAppDataPath.isBlank()) { return createDirectories(Paths.get(localAppDataPath, PRODUCT_WIN_DIR_NAME, "logs")); } } return getSubDirectory(LOG_DIRECTORY_NAME, true); } /** * Get a sub directory under the install directory * * @param dir the directory assumed under the product directory. E.g. logs * assumed under /opt/metricshub * @param create indicate if we should create the sub directory or not * @return The absolute path of the sub directory */ public static Path getSubDirectory(@NonNull final String dir, boolean create) { Path subDirectory = getSubPath(dir); if (!create) { return subDirectory; } return createDirectories(subDirectory); } /** * Create directories of the given path * * @param path Directories path * @return {@link Path} instance */ public static Path createDirectories(final Path path) { try { return Files.createDirectories(path).toRealPath(); } catch (IOException e) { throw new IllegalStateException("Could not create directory '" + path + "'.", e); } } /** * Get the sub path under the home directory. E.g. * /opt/metricshub/lib/app/../config on linux install * * @param subPath sub path to the directory or the file * @return {@link Path} instance */ public static Path getSubPath(@NonNull final String subPath) { final File sourceDirectory = getSourceDirectory(); final Path path = sourceDirectory.getAbsoluteFile().toPath(); Path parentLibPath = path.getParent(); // No parent? let's work with the current directory if (parentLibPath == null) { parentLibPath = path; } return parentLibPath.resolve("../" + subPath); } /** * Retrieves the directory containing the current source file, whether it's located * within a JAR file or a regular directory.
* * This method attempts to locate the source directory associated with the calling class, which can be * helpful for accessing resources and configuration files. * * @return A {@link File} instance representing the source directory. * * @throws IllegalStateException if the source directory cannot be determined. */ public static File getSourceDirectory() { final File sourceDirectory; try { sourceDirectory = ResourceHelper.findSourceDirectory(ConfigHelper.class); } catch (Exception e) { throw new IllegalStateException("Error detected when getting local source file: ", e); } if (sourceDirectory == null) { throw new IllegalStateException("Could not get the local source file."); } return sourceDirectory; } /** * Get the default configuration file path either in the Windows ProgramData\metricshub * directory or under the install directory /opt/metricshub on Linux systems. * * @param directory Directory of the configuration file. (e.g. config or otel) * @param configFilename Configuration file name (e.g. metricshub.yaml or otel-config.yaml) * @return new {@link Path} instance */ public static Path getDefaultConfigFilePath(final String directory, final String configFilename) { if (LocalOsHandler.isWindows()) { return getProgramDataConfigFile(directory, configFilename); } return ConfigHelper.getSubPath(String.format(FILE_PATH_FORMAT, directory, configFilename)); } /** * Get the configuration file under the ProgramData windows directory.
* If the ProgramData path is not valid then the configuration file will be located * under the install directory. * * @param directory Directory of the configuration file. (e.g. config or otel) * @param configFilename Configuration file name (e.g. metricshub.yaml or otel-config.yaml) * @return new {@link Path} instance */ static Path getProgramDataConfigFile(final String directory, final String configFilename) { return getProgramDataPath() .stream() .map(path -> Paths.get( createDirectories(Paths.get(path, PRODUCT_WIN_DIR_NAME, directory)).toAbsolutePath().toString(), configFilename ) ) .findFirst() .orElseGet(() -> ConfigHelper.getSubPath(String.format(FILE_PATH_FORMAT, directory, configFilename))); } /** * Get the %PROGRAMDATA% path. If the ProgramData path is not valid * then Optional.empty() is returned. * * @return {@link Optional} containing a string value (path) */ public static Optional getProgramDataPath() { final String programDataPath = System.getenv("ProgramData"); if (programDataPath != null && !programDataPath.isBlank()) { return Optional.of(programDataPath); } return Optional.empty(); } /** * Decrypt the given encrypted password. * * @param encrypted The encrypted password * @return char array The decrypted password */ public static char[] decrypt(final char[] encrypted) { try { return SecurityManager.decrypt(encrypted, PasswordEncrypt.getKeyStoreFile(false)); } catch (Exception e) { // This is a real problem, let's log the error log.error("Could not decrypt password: {}", e.getMessage()); log.debug("Exception", e); return encrypted; } } /** * Find the application's configuration file (metricshub.yaml).
*
    *
  1. If the user has configured the configFilePath via --config=$filePath then it is the chosen file
  2. *
  3. Else if config/metricshub.yaml path exists, the resulting File is the one representing this path
  4. *
  5. Else we copy config/metricshub-example.yaml to the host file config/metricshub.yaml then we return the resulting host file
  6. *
* * The program fails if *
    *
  • The configured file path doesn't exist
  • *
  • config/metricshub-example.yaml is not present
  • *
  • If an I/O error occurs
  • *
* * @param configFilePath The configuration file passed by the user. E.g. --config=/opt/PRODUCT-CODE/config/my-metricshub.yaml * @return {@link File} instance * @throws IOException This exception is thrown is the file is not found */ public static File findConfigFile(final String configFilePath) throws IOException { // The user has configured a configuration file path if (configFilePath != null && !configFilePath.isBlank()) { final File configFile = new File(configFilePath); if (configFile.exists()) { return configFile; } throw new IllegalStateException( String.format("Cannot find %s. Please make sure the file exists on your system", configFilePath) ); } // Get the configuration file config/metricshub.yaml return getDefaultConfigFile(CONFIG_DIRECTORY_NAME, DEFAULT_CONFIG_FILENAME, CONFIG_EXAMPLE_FILENAME); } /** * Get the default configuration file. * * @param directory Directory of the configuration file. (e.g. config or otel) * @param configFilename Configuration file name (e.g. metricshub.yaml or otel-config.yaml) * @param configFilenameExample Configuration file name example (e.g. metricshub-example.yaml) * @return {@link File} instance * @throws IOException if the copy fails */ public static File getDefaultConfigFile( final String directory, final String configFilename, final String configFilenameExample ) throws IOException { // Get the configuration file absolute path final Path configPath = getDefaultConfigFilePath(directory, configFilename); // If it exists then we are good we can just return the resulting File if (Files.exists(configPath)) { // At this time, we don't know who created the configuration file and what permissions are applied. // So let's skip the error logging to avoid unnecessary noise. That's why we call the method with // logError = false setUserPermissionsOnWindows(configPath, false); return configPath.toFile(); } // Now we will proceed with a copy of the example file (e.g. metricshub-example.yaml to config/metricshub.yaml) final Path exampleConfigPath = ConfigHelper.getSubPath( String.format(FILE_PATH_FORMAT, directory, configFilenameExample) ); // Bad configuration if (!Files.exists(exampleConfigPath)) { throw new IllegalStateException( String.format( "Cannot find '%s' . Please create the configuration file '%s' before starting the MetricsHub Agent.", exampleConfigPath.toAbsolutePath(), configPath.toAbsolutePath() ) ); } final File configFile = Files.copy(exampleConfigPath, configPath, StandardCopyOption.REPLACE_EXISTING).toFile(); setUserPermissionsOnWindows(configPath, true); return configFile; } /** * Set write permissions for metricshub.yaml deployed on a Windows machine running the agent * * @param configPath the configuration file absolute path * @param logError whether we should log the error or not. If logError is false, an info message is logged. */ private static void setUserPermissionsOnWindows(final Path configPath, boolean logError) { if (LocalOsHandler.isWindows()) { setUserPermissions(configPath, logError); } } /** * Set write permission for metricshub.yaml * * @param configPath the configuration file absolute path * @param logError whether we should log the error or not. If logError is false, an info message is logged. */ private static void setUserPermissions(final Path configPath, boolean logError) { try { final GroupPrincipal users = configPath .getFileSystem() .getUserPrincipalLookupService() .lookupPrincipalByGroupName("Users"); // get view final AclFileAttributeView view = Files.getFileAttributeView(configPath, AclFileAttributeView.class); // create ACE to give "Users" access final AclEntry entry = AclEntry .newBuilder() .setType(AclEntryType.ALLOW) .setPrincipal(users) .setPermissions( AclEntryPermission.WRITE_DATA, AclEntryPermission.WRITE_ATTRIBUTES, AclEntryPermission.WRITE_ACL, AclEntryPermission.WRITE_OWNER, AclEntryPermission.WRITE_NAMED_ATTRS, AclEntryPermission.READ_DATA, AclEntryPermission.READ_ACL, AclEntryPermission.READ_ATTRIBUTES, AclEntryPermission.READ_NAMED_ATTRS, AclEntryPermission.DELETE, AclEntryPermission.APPEND_DATA, AclEntryPermission.DELETE ) .build(); // read ACL, insert ACE, re-write ACL final List acl = view.getAcl(); // insert before any DENY entries acl.add(0, entry); view.setAcl(acl); } catch (Exception e) { if (logError) { log.error("Could not set write permissions to file: {}. Error: {}", configPath.toString(), e.getMessage()); log.error("Exception: ", e); } else { log.info("Could not set write permissions to file: {}. Message: {}", configPath.toString(), e.getMessage()); } } } /** * 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(); } /** * Normalizes the agent configuration and sets global values if no specific * values are specified on the resource groups or resources * * @param agentConfig The whole configuration of the MetricsHub agent */ public static void normalizeAgentConfiguration(final AgentConfig agentConfig) { // Normalize the top level resources using agent configuration agentConfig .getResources() .entrySet() .forEach(resourceConfigEntry -> normalizeResourceConfigUsingAgentConfig(agentConfig, resourceConfigEntry)); // Normalize the resources using resource groups agentConfig .getResourceGroups() .entrySet() .forEach(resourceGroupConfigEntry -> { final ResourceGroupConfig resourceGroupConfig = resourceGroupConfigEntry.getValue(); normalizeResourceGroupConfig(agentConfig, resourceGroupConfig); resourceGroupConfig .getResources() .entrySet() .forEach(resourceConfigEntry -> normalizeResourceConfig(resourceGroupConfigEntry, resourceConfigEntry)); }); } /** * Normalizes the top level resource configuration and sets global values if no specific * values are specified on the agent configuration * @param agentConfig MetricsHub agent configuration * @param resourceConfigEntry A given resource configuration entry (resourceKey, resourceConfig) */ private static void normalizeResourceConfigUsingAgentConfig( final AgentConfig agentConfig, final Entry resourceConfigEntry ) { final ResourceConfig resourceConfig = resourceConfigEntry.getValue(); // Set agent configuration's collect period if there is no specific collect period on the resource configuration if (resourceConfig.getCollectPeriod() == null) { resourceConfig.setCollectPeriod(agentConfig.getCollectPeriod()); } // Set agent configuration's discovery cycle if there is no specific collect period on the resource group if (resourceConfig.getDiscoveryCycle() == null) { resourceConfig.setDiscoveryCycle(agentConfig.getDiscoveryCycle()); } // Set agent configuration's logger level in the resource configuration if (resourceConfig.getLoggerLevel() == null) { resourceConfig.setLoggerLevel(agentConfig.getLoggerLevel()); } // Set agent configuration's output directory in the resource configuration if (resourceConfig.getOutputDirectory() == null) { resourceConfig.setOutputDirectory(agentConfig.getOutputDirectory()); } // Set agent configuration's sequential flag in the resource configuration if (resourceConfig.getSequential() == null) { resourceConfig.setSequential(agentConfig.isSequential()); } final AlertingSystemConfig resourceGroupAlertingSystemConfig = agentConfig.getAlertingSystemConfig(); final AlertingSystemConfig alertingSystemConfig = resourceConfig.getAlertingSystemConfig(); // Set agent configuration's alerting system in the resource configuration if (alertingSystemConfig == null) { resourceConfig.setAlertingSystemConfig(resourceGroupAlertingSystemConfig); } else if (alertingSystemConfig.getProblemTemplate() == null) { // Set the problem template of the alerting system alertingSystemConfig.setProblemTemplate(resourceGroupAlertingSystemConfig.getProblemTemplate()); } else if (alertingSystemConfig.getDisable() == null) { // Set the disable flag of the altering system alertingSystemConfig.setDisable(resourceGroupAlertingSystemConfig.getDisable()); } // Set the resolve host name to FQDN flag if (resourceConfig.getResolveHostnameToFqdn() == null) { resourceConfig.setResolveHostnameToFqdn(agentConfig.isResolveHostnameToFqdn()); } // Set the job timeout value if (resourceConfig.getJobTimeout() == null) { resourceConfig.setJobTimeout(agentConfig.getJobTimeout()); } // Set the state set compression if (resourceConfig.getStateSetCompression() == null) { resourceConfig.setStateSetCompression(agentConfig.getStateSetCompression()); } // Set agent attributes in the agent configuration attributes map final Map attributes = new HashMap<>(); mergeAttributes(agentConfig.getAttributes(), attributes); mergeAttributes(resourceConfig.getAttributes(), attributes); resourceConfig.setAttributes(attributes); // Create an identity for the configured connector normalizeConfiguredConnector( TOP_LEVEL_VIRTUAL_RESOURCE_GROUP_KEY, resourceConfigEntry.getKey(), resourceConfig.getConnector() ); } /** * Normalizes the resource configuration and sets resource group configuration values if no specific * values are specified on this resource configuration.
* If a new connector is configured then it is automatically added to the connector store. * * @param resourceGroupConfigEntry The resource group configuration entry * @param resourceConfigEntry The individual resource configuration entry */ private static void normalizeResourceConfig( final Entry resourceGroupConfigEntry, final Entry resourceConfigEntry ) { final ResourceGroupConfig resourceGroupConfig = resourceGroupConfigEntry.getValue(); final ResourceConfig resourceConfig = resourceConfigEntry.getValue(); // Set resource group configuration's collect period if there is no specific collect period on the resource configuration if (resourceConfig.getCollectPeriod() == null) { resourceConfig.setCollectPeriod(resourceGroupConfig.getCollectPeriod()); } // Set resource group configuration's discovery cycle if there is no specific collect period on the resource group if (resourceConfig.getDiscoveryCycle() == null) { resourceConfig.setDiscoveryCycle(resourceGroupConfig.getDiscoveryCycle()); } // Set resource group configuration's logger level in the resource configuration if (resourceConfig.getLoggerLevel() == null) { resourceConfig.setLoggerLevel(resourceGroupConfig.getLoggerLevel()); } // Set resource group configuration's output directory in the resource configuration if (resourceConfig.getOutputDirectory() == null) { resourceConfig.setOutputDirectory(resourceGroupConfig.getOutputDirectory()); } // Set resource group configuration's sequential flag in the resource configuration if (resourceConfig.getSequential() == null) { resourceConfig.setSequential(resourceGroupConfig.getSequential()); } final AlertingSystemConfig resourceGroupAlertingSystemConfig = resourceGroupConfig.getAlertingSystemConfig(); final AlertingSystemConfig alertingSystemConfig = resourceConfig.getAlertingSystemConfig(); // Set resource group configuration's alerting system in the resource configuration if (alertingSystemConfig == null) { resourceConfig.setAlertingSystemConfig(resourceGroupAlertingSystemConfig); } else if (alertingSystemConfig.getProblemTemplate() == null) { // Set the problem template of the alerting system alertingSystemConfig.setProblemTemplate(resourceGroupAlertingSystemConfig.getProblemTemplate()); } else if (alertingSystemConfig.getDisable() == null) { // Set the disable flag of the altering system alertingSystemConfig.setDisable(resourceGroupAlertingSystemConfig.getDisable()); } // Set the resolve host name to FQDN flag if (resourceConfig.getResolveHostnameToFqdn() == null) { resourceConfig.setResolveHostnameToFqdn(resourceGroupConfig.getResolveHostnameToFqdn()); } // Set the job timeout value if (resourceConfig.getJobTimeout() == null) { resourceConfig.setJobTimeout(resourceGroupConfig.getJobTimeout()); } // Set the state set compression if (resourceConfig.getStateSetCompression() == null) { resourceConfig.setStateSetCompression(resourceGroupConfig.getStateSetCompression()); } // Set agent attributes in the resource group attributes map final Map attributes = new HashMap<>(); mergeAttributes(resourceGroupConfig.getAttributes(), attributes); mergeAttributes(resourceConfig.getAttributes(), attributes); resourceConfig.setAttributes(attributes); // Create an identity for the configured connector normalizeConfiguredConnector( resourceGroupConfigEntry.getKey(), resourceConfigEntry.getKey(), resourceConfig.getConnector() ); } /** * Normalizes the resource group configuration and sets agent configuration's values if no specific * values are specified on this resource group configuration * * @param agentConfig The whole configuration the MetricsHub agent * @param resourceGroupConfig The individual resource group configuration */ private static void normalizeResourceGroupConfig( final AgentConfig agentConfig, final ResourceGroupConfig resourceGroupConfig ) { // Set global collect period if there is no specific collect period on the resource group configuration if (resourceGroupConfig.getCollectPeriod() == null) { resourceGroupConfig.setCollectPeriod(agentConfig.getCollectPeriod()); } // Set global discovery cycle if there is no specific collect period on the resource group configuration if (resourceGroupConfig.getDiscoveryCycle() == null) { resourceGroupConfig.setDiscoveryCycle(agentConfig.getDiscoveryCycle()); } // Set the global level in the resource group configuration if (resourceGroupConfig.getLoggerLevel() == null) { resourceGroupConfig.setLoggerLevel(agentConfig.getLoggerLevel()); } // Set the global output directory in the resource group configuration if (resourceGroupConfig.getOutputDirectory() == null) { resourceGroupConfig.setOutputDirectory(agentConfig.getOutputDirectory()); } // Set global sequential flag in the resource group configuration if (resourceGroupConfig.getSequential() == null) { resourceGroupConfig.setSequential(agentConfig.isSequential()); } final AlertingSystemConfig alertingSystemConfig = resourceGroupConfig.getAlertingSystemConfig(); final AlertingSystemConfig globalAlertingSystemConfig = agentConfig.getAlertingSystemConfig(); // Set global configuration's alerting system in the resource group configuration if (alertingSystemConfig == null) { resourceGroupConfig.setAlertingSystemConfig(globalAlertingSystemConfig); } else if (alertingSystemConfig.getProblemTemplate() == null) { // Set the problem template of the alerting system alertingSystemConfig.setProblemTemplate(globalAlertingSystemConfig.getProblemTemplate()); } else if (alertingSystemConfig.getDisable() == null) { // Set the disable flag of the altering system alertingSystemConfig.setDisable(globalAlertingSystemConfig.getDisable()); } // Set the resolve host name to FQDN flag if (resourceGroupConfig.getResolveHostnameToFqdn() == null) { resourceGroupConfig.setResolveHostnameToFqdn(agentConfig.isResolveHostnameToFqdn()); } // Set the job timeout value if (resourceGroupConfig.getJobTimeout() == null) { resourceGroupConfig.setJobTimeout(agentConfig.getJobTimeout()); } // Set the state set compression if (resourceGroupConfig.getStateSetCompression() == null) { resourceGroupConfig.setStateSetCompression(agentConfig.getStateSetCompression()); } // Set agent attributes in the resource group attributes map final Map attributes = new HashMap<>(); mergeAttributes(agentConfig.getAttributes(), attributes); mergeAttributes(resourceGroupConfig.getAttributes(), attributes); resourceGroupConfig.setAttributes(attributes); } /** * Merge the given attributes into the destination attributes * * @param attributes Map of key-pair values defining the attributes at a certain level * @param destinationAttributes Map of key-pair values defining the destination */ public static void mergeAttributes( final Map attributes, final Map destinationAttributes ) { destinationAttributes.putAll(attributes); } /** * Configure the 'org.sentrysoftware' logger based on the user's command.
* See src/main/resources/log4j2.xml * * @param loggerLevelStr Logger level from the configuration as {@link String} * @param outputDirectory The output directory as String */ public static void configureGlobalLogger(final String loggerLevelStr, final String outputDirectory) { final Level loggerLevel = getLoggerLevel(loggerLevelStr); ThreadContext.put("logId", "metricshub-agent-global"); ThreadContext.put("loggerLevel", loggerLevel.toString()); if (outputDirectory != null) { ThreadContext.put("outputDirectory", outputDirectory); } } /** * Get the Log4j log level from the configured logLevel string * * @param loggerLevel string value from the configuration (e.g. off, debug, info, warn, error, trace, all) * @return log4j {@link Level} instance */ public static Level getLoggerLevel(final String loggerLevel) { final Level level = loggerLevel != null ? Level.getLevel(loggerLevel.toUpperCase()) : null; return level != null ? level : Level.OFF; } /** * Build the {@link TelemetryManager} map. * * @param agentConfig Wraps the agent configuration for all the resources * @param connectorStore Wraps all the connectors * @return Map of {@link TelemetryManager} instances indexed by group id then by resource id */ public static Map> buildTelemetryManagers( @NonNull final AgentConfig agentConfig, @NonNull final ConnectorStore connectorStore ) { final Map> telemetryManagers = new HashMap<>(); agentConfig .getResourceGroups() .forEach((resourceGroupKey, resourceKeyGroupConfig) -> { final Map resourceGroupTelemetryManagers = new HashMap<>(); telemetryManagers.put(resourceGroupKey, resourceGroupTelemetryManagers); resourceKeyGroupConfig .getResources() .forEach((resourceKey, resourceConfig) -> updateResourceGroupTelemetryManagers( resourceGroupTelemetryManagers, resourceGroupKey, resourceKey, resourceConfig, connectorStore ) ); }); // Initialize top level resources telemetry managers map final Map topLevelResourcesTelemetryManagers = new HashMap<>(); // Update top level resources telemetry managers agentConfig .getResources() .forEach((resourceKey, resourceConfig) -> updateResourceGroupTelemetryManagers( topLevelResourcesTelemetryManagers, TOP_LEVEL_VIRTUAL_RESOURCE_GROUP_KEY, resourceKey, resourceConfig, connectorStore ) ); // Put the top level resources map in the main/common telemetry managers map telemetryManagers.put(TOP_LEVEL_VIRTUAL_RESOURCE_GROUP_KEY, topLevelResourcesTelemetryManagers); return telemetryManagers; } /** * Update the given resource group {@link TelemetryManager} map if the configuration is valid * * @param resourceGroupTelemetryManagers {@link Map} of {@link TelemetryManager} per resource group configuration * @param resourceGroupKey The unique identifier of the resource group * @param resourceKey The unique identifier of the resource * @param resourceConfig The resource configuration * @param connectorStore Wraps all the connectors */ private static void updateResourceGroupTelemetryManagers( @NonNull final Map resourceGroupTelemetryManagers, @NonNull final String resourceGroupKey, @NonNull final String resourceKey, final ResourceConfig resourceConfig, @NonNull final ConnectorStore connectorStore ) { if (resourceConfig == null) { return; } try { // Create a new connector store for this resource configuration final ConnectorStore resourceConnectorStore = connectorStore.newConnectorStore(); final HostConfiguration hostConfiguration = buildHostConfiguration( resourceConfig, resourceConfig.getConnectors(), resourceKey ); // Validate protocols and update the configuration's hostname if required. validateAndNormalizeProtocols(resourceKey, resourceConfig, hostConfiguration.getHostname()); addConfiguredConnector(resourceConnectorStore, resourceConfig.getConnector()); // Retrieve connectors variables map from the resource configuration final Map connectorVariablesMap = resourceConfig.getVariables(); // If connectors variables exist then merge the existing connector store with a new one containing custom connectors if (connectorVariablesMap != null && !connectorVariablesMap.isEmpty()) { // Call ConnectorTemplateLibraryParser and parse the custom connectors final ConnectorTemplateLibraryParser connectorTemplateLibraryParser = new ConnectorTemplateLibraryParser(); final Map customConnectors = connectorTemplateLibraryParser.parse( ConfigHelper.getSubDirectory("connectors", false), connectorVariablesMap ); // Overwrite resourceConnectorStore updateConnectorStore(resourceConnectorStore, customConnectors); } resourceGroupTelemetryManagers.putIfAbsent( resourceKey, TelemetryManager.builder().connectorStore(resourceConnectorStore).hostConfiguration(hostConfiguration).build() ); } catch (Exception e) { log.warn( "Resource {} - Under the resource group configuration {}, the resource configuration {}" + " has been staged as invalid. Reason: {}", resourceKey, resourceGroupKey, resourceKey, e.getMessage() ); } } /** * Add the custom connectors to the resource's connector store. * * @param resourceConnectorStore The connector store of the resource * @param customConnectors Map of customized connectors. E.g. Custom connectors that contain template variables */ protected static void updateConnectorStore( final ConnectorStore resourceConnectorStore, final Map customConnectors ) { // Add custom connectors resourceConnectorStore.addMany(customConnectors); } /** * Normalizes the configuration of a configured connector by creating a * unique identifier for it. * * @param resourceGroupKey The resource group key. * @param resourceKey The resource key. * @param configuredConnector The configured connector to be normalized. */ static void normalizeConfiguredConnector( final String resourceGroupKey, final String resourceKey, final Connector configuredConnector ) { // Check if a configured connector exists if (configuredConnector != null) { // Create a unique connector identifier based on resource keys final ConnectorIdentity identity = configuredConnector.getOrCreateConnectorIdentity(); final String connectorId = String.format("MetricsHub-Configured-Connector-%s-%s", resourceGroupKey, resourceKey); final String connectorName = String.format( "Configured Connector on resource %s (Group %s)", resourceKey, resourceGroupKey ); // Set the compiled filename of the connector to the unique identifier identity.setCompiledFilename(connectorId); // Set the display name of the connector identity.setDisplayName(connectorName); } } /** * Add the configured connector to the resource's connector store. * * @param resourceConnectorStore The resource's ConnectorStore * @param configuredConnector Configured connector */ static void addConfiguredConnector(final ConnectorStore resourceConnectorStore, final Connector configuredConnector) { // Check if a configured connector is available if (configuredConnector != null) { // Add the configured connector resourceConnectorStore.addOne( configuredConnector.getConnectorIdentity().getCompiledFilename(), configuredConnector ); } } /** * Validates the protocols configured under the given {@link ResourceConfig} instance. * Also, it normalizes the configuration's hostname by duplicating the hostname attribute on each configuration. * This duplication is done only if the configuration's hostname is null. * * @param resourceKey Resource unique identifier * @param resourceConfig {@link ResourceConfig} instance configured by the user. * @param hostname The hostname that will be duplicated on each configuration if required. * @throws InvalidConfigurationException thrown if a configuration validation fails. */ private static void validateAndNormalizeProtocols( @NonNull final String resourceKey, final ResourceConfig resourceConfig, final String hostname ) throws InvalidConfigurationException { final Map protocols = resourceConfig.getProtocols(); if (protocols == null) { return; } for (Map.Entry entry : protocols.entrySet()) { IConfiguration protocolConfig = entry.getValue(); if (protocolConfig != null) { protocolConfig.validateConfiguration(resourceKey); if (protocolConfig.getHostname() == null) { protocolConfig.setHostname(hostname); } } } } /** * Build the {@link HostConfiguration} expected by the internal engine * * @param resourceConfig User's resource configuration * @param connectorsConfiguration User's connectors configuration directives. * @param resourceKey Resource unique identifier * @return new {@link HostConfiguration} instance */ static HostConfiguration buildHostConfiguration( final ResourceConfig resourceConfig, final Set connectorsConfiguration, final String resourceKey ) { final Map protocols = resourceConfig.getProtocols(); final Map, IConfiguration> protocolConfigurations = protocols == null ? new HashMap<>() : protocols .values() .stream() .filter(Objects::nonNull) .collect(Collectors.toMap(IConfiguration::getClass, Function.identity())); final Map attributes = resourceConfig.getAttributes(); // Get the host name and make sure it is always set because the engine needs a hostname String hostname = attributes.get(MetricsHubConstants.HOST_NAME); if (hostname == null) { hostname = resourceKey; } // If we haven't a host.id then it will be set to the resource key String hostId = attributes.get("host.id"); if (hostId == null) { hostId = resourceKey; } // Manage the device kind final DeviceKind hostType; String hostTypeAttribute = attributes.get("host.type"); if (hostTypeAttribute == null) { hostType = DeviceKind.OTHER; } else { hostType = detectHostTypeFromAttribute(hostTypeAttribute); } String configuredConnectorId = null; // Retrieve the connector specified by the user in the metricshub.yaml configuration final Connector configuredConnector = resourceConfig.getConnector(); // Check if a custom connector is defined if (configuredConnector != null) { // The custom connector is considered a selected connector from the engine's perspective configuredConnectorId = configuredConnector.getCompiledFilename(); } return HostConfiguration .builder() .strategyTimeout(resourceConfig.getJobTimeout()) .configurations(protocolConfigurations) .connectors(connectorsConfiguration) .hostname(hostname) .hostId(hostId) .hostType(hostType) .sequential(Boolean.TRUE.equals(resourceConfig.getSequential())) .configuredConnectorId(configuredConnectorId) .connectorVariables(resourceConfig.getVariables()) .build(); } /** * Try to detect the {@link DeviceKind} from the user input * * @param hostTypeAttribute * @return {@link DeviceKind} enumeration value */ private static DeviceKind detectHostTypeFromAttribute(String hostTypeAttribute) { try { return DeviceKind.detect(hostTypeAttribute); } catch (Exception e) { return DeviceKind.OTHER; } } /** * Utility method to get the directory path of the given file. * * @param file The file for which the directory path is needed. * @return A {@link Path} instance representing the directory path of the given file. */ public static Path getDirectoryPath(final File file) { return file.getAbsoluteFile().toPath().getParent(); } /** * Read {@link MetricDefinitions} for the root monitor instance (Endpoint) * which is automatically created by the MetricsHub engine * This method deserializes the metrics configuration from the "metricshub-host-metrics.yaml" file. * * @return A new {@link MetricDefinitions} instance representing the host metric definitions. * @throws IOException If an I/O error occurs while reading the configuration file. */ public static MetricDefinitions readHostMetricDefinitions() throws IOException { return JsonHelper.deserialize( newObjectMapper(), new ClassPathResource("metricshub-host-metrics.yaml").getInputStream(), MetricDefinitions.class ); } /** * Retrieves the metric definition map associated with the specified connector identifier * within the provided {@link ConnectorStore}.
* This method ensures that the metric for connector status is always included in the * returned map, regardless of whether the specified connector has additional metrics defined or not. * * @param connectorStore Wrapper for all connectors. * @param connectorId The unique identifier of the connector. * @return A Map of metric names to their definitions. */ public static Map fetchMetricDefinitions( final ConnectorStore connectorStore, final String connectorId ) { final Map metricDefinitions = new HashMap<>(); if (connectorStore != null && connectorId != null) { final Connector connector = connectorStore.getStore().get(connectorId); if (connector != null) { final Map connectorMetricDefinitions = connector.getMetrics(); if (connectorMetricDefinitions != null) { metricDefinitions.putAll(connectorMetricDefinitions); } } } // If the connector status metric is not already associated with a definition, // attempt to compute its definition. metricDefinitions.computeIfAbsent( MetricsHubConstants.CONNECTOR_STATUS_METRIC_KEY, key -> MetricsHubConstants.CONNECTOR_STATUS_METRIC_DEFINITION ); return metricDefinitions; } /** * Calculates the MD5 checksum of the specified file. * * @param file The file for which the MD5 checksum is to be calculated. * @return The MD5 checksum as a hexadecimal string or null if the calculation has failed. */ public static String calculateMD5Checksum(final File file) { try { byte[] data = Files.readAllBytes(file.toPath()); byte[] hash = MessageDigest.getInstance("MD5").digest(data); return new BigInteger(1, hash).toString(16); } catch (Exception e) { return null; } } /** * Load the {@link ExtensionManager} instance from the extensions directory. * * @return new {@link ExtensionManager} instance. */ public static ExtensionManager loadExtensionManager() { try { return new ExtensionLoader(getSubDirectory("extensions", false).toFile()).load(); } catch (Throwable e) { throw new IllegalStateException("Cannot load extensions.", e); } } /** * Constructs and populates a {@link ConnectorStore} by aggregating connector * stores from various extensions managed by the provided {@link ExtensionManager} * and from a specific subdirectory defined for connectors. This method first * aggregates all extension-based connector stores into one central store and * then adds additional connectors found in a designated subdirectory. * * @param extensionManager The manager responsible for handling all * extension-based connector stores. * @param connectorsPatchPath The connectors Patch Path. * @return A fully populated {@link ConnectorStore} containing connectors from * various sources. */ public static ConnectorStore buildConnectorStore( final ExtensionManager extensionManager, final String connectorsPatchPath ) { // Get extension connector stores final ConnectorStore connectorStore = extensionManager.aggregateExtensionConnectorStores(); // Parse and add connectors from a specific subdirectory connectorStore.addMany(new ConnectorStore(getSubDirectory("connectors", false)).getStore()); // Add user's connectors if the connectors patch path is specified. if (connectorsPatchPath != null) { connectorStore.addMany(new ConnectorStore(Path.of(connectorsPatchPath)).getStore()); } return connectorStore; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy