From a51aa93146fa8a6d9645e6990e5e84cac9ccff7e Mon Sep 17 00:00:00 2001 From: Eduard Schander <66794307+EddeCCC@users.noreply.github.com> Date: Tue, 27 Feb 2024 09:33:46 +0100 Subject: [PATCH] Patch config fetch (#1647) * remove extended logging * refactor AgentConfigurationReloadTask * add docsObjectsTask test * optimize task and fix test * optimize cached agent mappings * remove redundant logs * update naming convention * add requested changes * refactor supplier * refactor AgentConfiguration * remove static supplier * rename method * move AgentDocumentation to configdocsgenerator * add tests * refactor tests * remove deprecated test * make documentation volatile --- .../ConfigDocsGenerator.java | 12 +- .../model/AgentDocumentation.java | 24 ++ .../ConfigDocsGeneratorTest.java | 6 +- .../components/views/status/StatusTable.js | 15 +- .../AgentConfiguration.java | 157 ++++++++- .../AgentConfigurationManager.java | 9 +- .../AgentConfigurationReloadTask.java | 149 +------- .../AgentDocumentationSupplier.java | 26 ++ .../agentconfiguration/DocsObjectsLoader.java | 14 +- .../ObjectStructureMerger.java | 34 ++ .../agentstatus/AgentStatusManager.java | 1 - .../mappings/AgentMappingSerializer.java | 6 +- .../ocelot/rest/agent/AgentController.java | 1 - .../ConfigurationController.java | 15 +- .../src/main/resources/logback.xml | 4 - .../AgentConfigurationReloadTaskTest.java | 331 +++++++----------- .../AgentConfigurationTest.java | 240 +++++++++++++ .../DocsObjectsLoaderTest.java | 83 +++-- .../agentstatus/AgentStatusManagerTest.java | 23 +- .../rest/agent/AgentControllerTest.java | 24 +- .../ocelot/rest/agent/AgentServiceTest.java | 13 +- .../ConfigurationControllerTest.java | 90 ++--- .../docs/instrumentation/instrumentation.md | 7 +- 23 files changed, 779 insertions(+), 505 deletions(-) create mode 100644 components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/model/AgentDocumentation.java create mode 100644 components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentDocumentationSupplier.java create mode 100644 components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationTest.java diff --git a/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGenerator.java b/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGenerator.java index ae2d8e95f1..1aba83e490 100644 --- a/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGenerator.java +++ b/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGenerator.java @@ -55,10 +55,10 @@ public class ConfigDocsGenerator { Map specialInputDescriptionsRegex = Collections.singletonMap("_arg\\d", "The _argN-th argument with which the instrumented method was called within which this action is getting executed."); /** - * Map of files, which contain a set of defined objects like actions, scopes, rules & metrics + * Set of documentations, which contain a set of documentable objects like actions, scopes, rules & metrics for every file */ @Setter - private Map> docsObjectsByFile = new HashMap<>(); + private Set agentDocumentations = new HashSet<>(); /** * Generates a ConfigDocumentation from a YAML String describing an {@link InspectitConfig}. @@ -233,11 +233,11 @@ private List generateActionDocs(Map a * @return a set of files, which contain the provided object */ private Set findFiles(String name) { - Set files = docsObjectsByFile.entrySet().stream() - .filter(entry -> entry.getValue().contains(name)) - .map(Map.Entry::getKey) + Set files = agentDocumentations.stream() + .filter(documentation -> documentation.getObjects().contains(name)) + .map(AgentDocumentation::getFilePath) .collect(Collectors.toSet()); - if(files.isEmpty()) log.warn("No file found with definition of " + name); + if(files.isEmpty()) log.debug("No file found with definition of " + name); return files; } diff --git a/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/model/AgentDocumentation.java b/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/model/AgentDocumentation.java new file mode 100644 index 0000000000..51b371fa10 --- /dev/null +++ b/components/inspectit-ocelot-configdocsgenerator/src/main/java/inspectit/ocelot/configdocsgenerator/model/AgentDocumentation.java @@ -0,0 +1,24 @@ +package inspectit.ocelot.configdocsgenerator.model; + +import lombok.Value; + +import java.util.Set; + +/** + * Model, to store documentable objects of a specific yaml file. + * Documentable objects can be actions, scopes, rules & metrics. + * + */ +@Value +public class AgentDocumentation { + + /** + * file, which contains the documentable objects + */ + private String filePath; + + /** + * documentable objects of the file + */ + private Set objects; +} diff --git a/components/inspectit-ocelot-configdocsgenerator/src/test/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGeneratorTest.java b/components/inspectit-ocelot-configdocsgenerator/src/test/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGeneratorTest.java index bf9acebb0e..ab0364b420 100644 --- a/components/inspectit-ocelot-configdocsgenerator/src/test/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGeneratorTest.java +++ b/components/inspectit-ocelot-configdocsgenerator/src/test/java/inspectit/ocelot/configdocsgenerator/ConfigDocsGeneratorTest.java @@ -371,8 +371,10 @@ void verifyFindFiles() throws IOException { docsObjects.add(ruleDocParent.getName()); docsObjects.add(metricDoc.getName()); - Map> docsObjectsByFile = Collections.singletonMap(file, docsObjects); - configDocsGenerator.setDocsObjectsByFile(docsObjectsByFile); + AgentDocumentation documentation = new AgentDocumentation(file, docsObjects); + Set agentDocumentations = Collections.singleton(documentation); + + configDocsGenerator.setAgentDocumentations(agentDocumentations); when(actionWithDocInYaml.getFiles()).thenReturn(Collections.singleton(file)); when(actionWithoutDocInYaml.getFiles()).thenReturn(Collections.singleton(file)); when(scopeDoc.getFiles()).thenReturn(Collections.singleton(file)); diff --git a/components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusTable.js b/components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusTable.js index b7ad9f1a32..6be7443a78 100644 --- a/components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusTable.js +++ b/components/inspectit-ocelot-configurationserver-ui/src/components/views/status/StatusTable.js @@ -293,19 +293,10 @@ class StatusTable extends React.Component { agentHealthTemplate = (rowData) => { const { onShowHealthStateDialog } = this.props; - const { onShowDownloadDialog } = this.props; - const { metaInformation } = rowData; + const { healthState, metaInformation } = rowData; + const { health } = healthState; const { agentId } = metaInformation; - // TODO Remove console.error() after agent health issues are solved - let health; - let healthState; - try { - healthState = rowData['healthState']; - health = healthState['health']; - } catch (error) { - console.error(`Could not read agent health from ${agentId}`, error); - console.error(rowData); - } + const { onShowDownloadDialog } = this.props; let { agentCommandsEnabled, supportArchiveAvailable } = this.resolveServiceAvailability(metaInformation); diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfiguration.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfiguration.java index 2fa970a553..74cf0bcb21 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfiguration.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfiguration.java @@ -1,33 +1,50 @@ package rocks.inspectit.ocelot.agentconfiguration; -import lombok.Builder; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; import lombok.Value; +import lombok.extern.slf4j.Slf4j; import org.springframework.util.DigestUtils; +import org.yaml.snakeyaml.Yaml; +import rocks.inspectit.ocelot.file.FileInfo; +import rocks.inspectit.ocelot.file.accessor.AbstractFileAccessor; import rocks.inspectit.ocelot.mappings.model.AgentMapping; import java.nio.charset.Charset; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.function.Predicate; +import java.util.stream.Collectors; /** * An {@link AgentMapping} which has its configuration loaded in-memory. * In addition, a cryptographic hash is computed to detect changes of configurations. */ @Value +@Slf4j public class AgentConfiguration { + /** + * Used as maker in {@link AgentConfigurationManager#attributesToConfigurationCache} to mark attribute-maps + * for which no mapping matches. + */ + public static AgentConfiguration NO_MATCHING_MAPPING = createDefault(); + + /** + * Predicate to check if a given file path ends with .yml or .yaml + */ + private static final Predicate HAS_YAML_ENDING = filePath -> filePath.toLowerCase() + .endsWith(".yml") || filePath.toLowerCase().endsWith(".yaml"); + /** * The agent mapping for which this instance represents the loaded configuration. */ private AgentMapping mapping; /** - * The set of defined documentable objects in this configuration for each file.
- * - Key: the file path
- * - Value: the set of objects, like actions, scopes, rules & metrics + * The set of suppliers for documentable objects. + * There is a set of defined documentable objects in this configuration for each file.
+ * The documentable objects will be loaded lazy. */ - private Map> docsObjectsByFile; + private Set documentationSuppliers; /** * The merged YAML configuration for the given mapping. @@ -39,11 +56,127 @@ public class AgentConfiguration { */ private String hash; - @Builder - private AgentConfiguration(AgentMapping mapping, Map> docsObjectsByFile, String configYaml) { + private AgentConfiguration(AgentMapping mapping, Set documentationSuppliers, String configYaml, String hash) { this.mapping = mapping; - this.docsObjectsByFile = docsObjectsByFile; + this.documentationSuppliers = documentationSuppliers; this.configYaml = configYaml; - hash = DigestUtils.md5DigestAsHex(configYaml.getBytes(Charset.defaultCharset())); + this.hash = hash; + } + + /** + * Factory method to create AgentConfigurations. Also creates a cryptographic hash. + * + * @param mapping The agent mapping for which this instance represents the loaded configuration + * @param fileAccessor The accessor to use for reading the files + * + * @return Created AgentConfiguration + */ + public static AgentConfiguration create(AgentMapping mapping, AbstractFileAccessor fileAccessor) { + Set documentationSuppliers = new HashSet<>(); + Object yamlResult = null; + + LinkedHashSet allYamlFiles = getAllYamlFilesForMapping(fileAccessor, mapping); + for (String path : allYamlFiles) { + String src = fileAccessor.readConfigurationFile(path).orElse(""); + yamlResult = ObjectStructureMerger.loadAndMergeYaml(src, yamlResult, path); + + AgentDocumentationSupplier supplier = new AgentDocumentationSupplier(() -> loadDocumentation(path, src)); + documentationSuppliers.add(supplier); + } + + String configYaml = yamlResult == null ? "" : new Yaml().dump(yamlResult); + String hash = DigestUtils.md5DigestAsHex(configYaml.getBytes(Charset.defaultCharset())); + + return new AgentConfiguration(mapping, documentationSuppliers, configYaml, hash); + } + + /** + * Factory method to create a default AgentConfiguration. + * + * @return Created default AgentConfiguration + */ + private static AgentConfiguration createDefault() { + String configYaml = ""; + String hash = DigestUtils.md5DigestAsHex(configYaml.getBytes(Charset.defaultCharset())); + return new AgentConfiguration(null, new HashSet<>(), configYaml, hash); + } + + /** + * Get the current agent documentations. The documentations will be loaded lazy. + * + * @return The loaded agent documentations + */ + public synchronized Set getDocumentations() { + if (documentationSuppliers == null) return Collections.emptySet(); + return documentationSuppliers.stream().map(AgentDocumentationSupplier::get).collect(Collectors.toSet()); + } + + /** + * Returns the set of yaml files, which is defined in the agent mapping sources. + * + * @param fileAccessor the accessor to use for reading the file + * @param mapping the mapping, which contains a list of source file paths + * + * @return the set of yaml file paths for the provided mapping + * If this task has been canceled, null is returned. + */ + private static LinkedHashSet getAllYamlFilesForMapping(AbstractFileAccessor fileAccessor, AgentMapping mapping) { + LinkedHashSet allYamlFiles = new LinkedHashSet<>(); + for (String path : mapping.sources()) { + List yamlFiles = getAllYamlFiles(fileAccessor, path); + allYamlFiles.addAll(yamlFiles); + } + return allYamlFiles; + } + + /** + * If the given path is a yaml file, a list containing only it is returned. + * If the path is a directory, the absolute path of all contained yaml files is returned in alphabetical order. + * If it is neither, an empty list is returned. + * + * @param path the path to check for yaml files, can start with a slash which will be ignored + * + * @return a list of absolute paths of contained YAML files + */ + private static List getAllYamlFiles(AbstractFileAccessor fileAccessor, String path) { + String cleanedPath; + if (path.startsWith("/")) { + cleanedPath = path.substring(1); + } else { + cleanedPath = path; + } + + if (fileAccessor.configurationFileExists(cleanedPath)) { + if (fileAccessor.configurationFileIsDirectory(cleanedPath)) { + List fileInfos = fileAccessor.listConfigurationFiles(cleanedPath); + + return fileInfos.stream() + .flatMap(file -> file.getAbsoluteFilePaths(cleanedPath)) + .filter(HAS_YAML_ENDING) + .sorted() + .collect(Collectors.toList()); + } else if (HAS_YAML_ENDING.test(cleanedPath)) { + return Collections.singletonList(cleanedPath); + } + } + return Collections.emptyList(); + } + + /** + * Loads all documentable objects of the yaml source string for the provided file. + * + * @param filePath the path to the yaml file + * @param src the yaml string + * + * @return the set of documentable objects for the provided file + */ + private static AgentDocumentation loadDocumentation(String filePath, String src) { + Set objects = Collections.emptySet(); + try { + objects = DocsObjectsLoader.loadObjects(src); + } catch (Exception e) { + log.warn("Could not parse configuration: {}", filePath, e); + } + return new AgentDocumentation(filePath, objects); } } diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationManager.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationManager.java index 9561fac1aa..b08151223b 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationManager.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationManager.java @@ -24,6 +24,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit; +import static rocks.inspectit.ocelot.agentconfiguration.AgentConfiguration.NO_MATCHING_MAPPING; + /** * Manager responsible for serving the agent configuration based on the set of {@link AgentMapping}s. */ @@ -31,11 +33,6 @@ @Slf4j public class AgentConfigurationManager { - /** - * Used as maker in {@link #attributesToConfigurationCache} to mark attribute-maps for which no mapping matches. - */ - private static final AgentConfiguration NO_MATCHING_MAPPING = AgentConfiguration.builder().configYaml("").build(); - @Autowired @VisibleForTesting InspectitServerSettings config; @@ -120,7 +117,7 @@ private synchronized void replaceConfigurations(List newConf attributesToConfigurationCache = CacheBuilder.newBuilder() .maximumSize(config.getMaxAgents()) .expireAfterAccess(config.getAgentEvictionDelay().toMillis(), TimeUnit.MILLISECONDS) - .build(new CacheLoader, AgentConfiguration>() { + .build(new CacheLoader<>() { @Override public AgentConfiguration load(Map agentAttributes) { return newConfigurations.stream() diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTask.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTask.java index 20dd499dfe..3dbfce633d 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTask.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTask.java @@ -1,9 +1,6 @@ package rocks.inspectit.ocelot.agentconfiguration; -import com.google.common.annotations.VisibleForTesting; import lombok.extern.slf4j.Slf4j; -import org.yaml.snakeyaml.Yaml; -import rocks.inspectit.ocelot.file.FileInfo; import rocks.inspectit.ocelot.file.FileManager; import rocks.inspectit.ocelot.file.accessor.AbstractFileAccessor; import rocks.inspectit.ocelot.file.accessor.git.RevisionAccess; @@ -12,9 +9,9 @@ import rocks.inspectit.ocelot.utils.CancellableTask; import java.util.*; +import java.util.concurrent.CompletableFuture; import java.util.function.Consumer; -import java.util.function.Predicate; -import java.util.stream.Collectors; +import java.util.function.Supplier; /** * A task for asynchronously loading the configurations based on a given list of mappings. @@ -22,12 +19,6 @@ @Slf4j class AgentConfigurationReloadTask extends CancellableTask> { - /** - * Predicate to check if a given file path ends with .yml or .yaml - */ - private static final Predicate HAS_YAML_ENDING = filePath -> filePath.toLowerCase() - .endsWith(".yml") || filePath.toLowerCase().endsWith(".yaml"); - private FileManager fileManager; private AgentMappingSerializer mappingsSerializer; @@ -63,145 +54,43 @@ public void run() { List mappingsToLoad = mappingsSerializer.readAgentMappings(fileAccess); List newConfigurations = new ArrayList<>(); for (AgentMapping mapping : mappingsToLoad) { + if(mapping == null) { + log.debug("Could not load null agent mapping"); + continue; + } try { - AgentConfiguration agentConfiguration = createAgentConfiguration(mapping); if (isCanceled()) { log.debug("Configuration reloading canceled"); return; } + + AbstractFileAccessor fileAccessor = getFileAccessorForMapping(mapping); + if(fileAccessor == null) { + log.debug("No file accessor provided for mapping {}. Cannot read files", mapping); + continue; + } + + AgentConfiguration agentConfiguration = AgentConfiguration.create(mapping, fileAccessor); newConfigurations.add(agentConfiguration); } catch (Exception e) { - log.error("Could not load agent configuration for agent mapping '{}'.", mapping.name(), e); + log.error("Could not load agent mapping '{}'.", mapping.name(), e); } } - onTaskSuccess(newConfigurations); - } - - /** - * Creates the configuration for one agent with the provided agent mapping - * @param mapping the mapping to load - * - * @return Configuration for the agent mapping - */ - @VisibleForTesting - AgentConfiguration createAgentConfiguration(AgentMapping mapping) { - AbstractFileAccessor fileAccessor = getFileAccessorForMapping(mapping); - - LinkedHashSet allYamlFiles = new LinkedHashSet<>(); - for (String path : mapping.sources()) { - if (isCanceled()) return null; - allYamlFiles.addAll(getAllYamlFiles(fileAccessor, path)); - } - - Object yamlResult = null; - Map> docsObjectsByFile = new HashMap<>(); - - for (String path : allYamlFiles) { - if (isCanceled()) return null; - String src = fileAccessor.readConfigurationFile(path).orElse(""); - - Set loadedObjects = loadDocsObjects(src, path); - docsObjectsByFile.put(path, loadedObjects); - yamlResult = loadAndMergeYaml(yamlResult, src, path); - } - String configYaml = yamlResult == null ? "" : new Yaml().dump(yamlResult); - - return AgentConfiguration.builder() - .mapping(mapping) - .docsObjectsByFile(docsObjectsByFile) - .configYaml(configYaml) - .build(); - } - /** - * Loads all documentable objects of the yaml source string - * - * @param src the yaml string - * @param filePath the path to the yaml file - * - * @return the set of documentable objects - */ - private Set loadDocsObjects(String src, String filePath) { - Set objects = Collections.emptySet(); - try { - objects = DocsObjectsLoader.loadObjects(src); - } catch (Exception e) { - log.warn("Could not parse configuration: {}", filePath, e); - } - return objects; + onTaskSuccess(newConfigurations); } /** - * Loads a yaml file as a Map/List structure and merges it with an existing map/list structure + * Returns the file accessor with regard to the source branch of the agent mapping. * - * @param toMerge the existing structure of nested maps / lists with which the loaded yaml will be merged. - * @param src the yaml string - * @param path the path of the yaml file to load + * @param mapping the agent mapping with source branch * - * @return the merged structure + * @return the file accessor for the mapping source branch */ - private Object loadAndMergeYaml(Object toMerge, String src, String path) { - Yaml yaml = new Yaml(); - try { - Map loadedYaml = yaml.load(src); - if (toMerge == null) { - return loadedYaml; - } else { - return ObjectStructureMerger.merge(toMerge, loadedYaml); - } - } catch (Exception e) { - throw new InvalidConfigurationFileException(path, e); - } - } - private AbstractFileAccessor getFileAccessorForMapping(AgentMapping mapping) { return switch (mapping.sourceBranch()) { case LIVE -> fileManager.getLiveRevision(); case WORKSPACE -> fileManager.getWorkspaceRevision(); - default -> throw new UnsupportedOperationException("Unhandled branch: " + mapping.sourceBranch()); }; } - - /** - * If the given path is a yaml file, a list containing only it is returned. - * If the path is a directory, the absolute path of all contained yaml files is returned in alphabetical order. - * If it is neither, an empty list is returned. - * - * @param path the path to check for yaml files, can start with a slash which will be ignored - * - * @return a list of absolute paths of contained YAML files - */ - private List getAllYamlFiles(AbstractFileAccessor fileAccessor, String path) { - String cleanedPath; - if (path.startsWith("/")) { - cleanedPath = path.substring(1); - } else { - cleanedPath = path; - } - - if (fileAccessor.configurationFileExists(cleanedPath)) { - if (fileAccessor.configurationFileIsDirectory(cleanedPath)) { - List fileInfos = fileAccessor.listConfigurationFiles(cleanedPath); - - return fileInfos.stream() - .flatMap(file -> file.getAbsoluteFilePaths(cleanedPath)) - .filter(HAS_YAML_ENDING) - .sorted() - .collect(Collectors.toList()); - } else if (HAS_YAML_ENDING.test(cleanedPath)) { - return Collections.singletonList(cleanedPath); - } - } - return Collections.emptyList(); - } - - /** - * This exception will be thrown if a configuration file cannot be parsed, e.g. it contains invalid characters. - */ - static class InvalidConfigurationFileException extends RuntimeException { - - public InvalidConfigurationFileException(String path, Exception e) { - super(String.format("The configuration file '%s' is invalid and cannot be parsed.", path), e); - } - } } diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentDocumentationSupplier.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentDocumentationSupplier.java new file mode 100644 index 0000000000..44931d75c2 --- /dev/null +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/AgentDocumentationSupplier.java @@ -0,0 +1,26 @@ +package rocks.inspectit.ocelot.agentconfiguration; + +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; + +import java.util.function.Supplier; + +/** + * Supplier to load an agent documentation lazy. + * The documentation will be persisted after initial loading. + */ +public class AgentDocumentationSupplier implements Supplier { + + private final Supplier supplier; + + private volatile AgentDocumentation documentation; + + public AgentDocumentationSupplier(Supplier supplier) { + this.supplier = supplier; + } + + @Override + public AgentDocumentation get() { + if(documentation == null) documentation = supplier.get(); + return documentation; + } +} diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoader.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoader.java index db06d1d703..81435936aa 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoader.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoader.java @@ -1,5 +1,6 @@ package rocks.inspectit.ocelot.agentconfiguration; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; import inspectit.ocelot.configdocsgenerator.parsing.ConfigParser; import lombok.extern.slf4j.Slf4j; import org.yaml.snakeyaml.Yaml; @@ -18,10 +19,11 @@ public class DocsObjectsLoader { /** * Use the same constant as the ui in 'src/data/constants.js' */ - final static String OCELOT_DEFAULT_CONFIG_PREFIX = "/$%$%$%$%Ocelot-default-key/"; + public final static String OCELOT_DEFAULT_CONFIG_PREFIX = "/$%$%$%$%Ocelot-default-key/"; /** * Loads all documentable objects, like actions, scopes, rules & metrics from the provided inspectIT yaml + * * @param src the source yaml * @return a set of defined objects in this yaml */ @@ -49,11 +51,12 @@ public static Set loadObjects(String src) throws IOException { /** * Loads all documentable objects of each file for the current agent + * * @param defaultYamls A map of the default file paths and their yaml content * @return A set of defined objects for each file */ - public static Map> loadDefaultDocsObjectsByFile(Map defaultYamls) { - Map> defaultDocsObjectsByFile = new HashMap<>(); + public static Set loadDefaultAgentDocumentations(Map defaultYamls) { + Set defaultDocumentations = new HashSet<>(); for(Map.Entry entry : defaultYamls.entrySet()) { String path = OCELOT_DEFAULT_CONFIG_PREFIX + entry.getKey(); String src = entry.getValue(); @@ -64,9 +67,10 @@ public static Map> loadDefaultDocsObjectsByFile(Map loadedYaml = yaml.load(src); + if (toMerge == null) { + return loadedYaml; + } else { + return ObjectStructureMerger.merge(toMerge, loadedYaml); + } + } catch (Exception e) { + throw new InvalidConfigurationFileException(path, e); + } + } + /** * Tries to merge the given two Object structure, giving precedence to the first one. * If both Objects are maps or bot hare lists, they will be deeply merged. @@ -82,4 +107,13 @@ private static List mergeLists(List first, List second) { return result; } + /** + * This exception will be thrown if a configuration file cannot be parsed, e.g. it contains invalid characters. + */ + static class InvalidConfigurationFileException extends RuntimeException { + + public InvalidConfigurationFileException(String path, Exception e) { + super(String.format("The configuration file '%s' is invalid and cannot be parsed.", path), e); + } + } } diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManager.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManager.java index 00b2c26ec6..ac9c5e585d 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManager.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManager.java @@ -97,7 +97,6 @@ public void notifyAgentConfigurationFetched(Map agentAttributes, logHealthIfChanged(statusKey, agentHealthState); } - log.debug("Storing agent status of {}: ({})", statusKey, agentStatus); attributesToAgentStatusCache.put(statusKey, agentStatus); } diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/mappings/AgentMappingSerializer.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/mappings/AgentMappingSerializer.java index 724699a792..4692d2653e 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/mappings/AgentMappingSerializer.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/mappings/AgentMappingSerializer.java @@ -89,9 +89,9 @@ public void postConstruct() throws IOException { * * @return List of current {@link AgentMapping}s representing the content of the given file */ - public List readCachedAgentMappings(){ - if(currentMappings != null) return currentMappings; - else return readAgentMappings(fileManager.getWorkspaceRevision()); + public List readCachedAgentMappings() { + if(currentMappings == null) currentMappings = readAgentMappings(fileManager.getWorkspaceRevision()); + return currentMappings; } /** diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/agent/AgentController.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/agent/AgentController.java index cc37ea00a5..29d8fd7fcf 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/agent/AgentController.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/agent/AgentController.java @@ -62,7 +62,6 @@ public void e(Exception e) { @GetMapping(value = {"agent/configuration", "agent/configuration/"}, produces = "application/x-yaml") public ResponseEntity fetchConfiguration(@Parameter(description = "The agent attributes used to select the correct mapping") @RequestParam Map attributes, @RequestHeader Map headers) { log.debug("Fetching the agent configuration for agent ({})", attributes.toString()); - log.debug("Receiving agent headers ({})", headers.toString()); AgentConfiguration configuration = configManager.getConfiguration(attributes); statusManager.notifyAgentConfigurationFetched(attributes, headers, configuration); if (configuration == null) { diff --git a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationController.java b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationController.java index 9d6483b631..701c9d03e1 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationController.java +++ b/components/inspectit-ocelot-configurationserver/src/main/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationController.java @@ -1,6 +1,7 @@ package rocks.inspectit.ocelot.rest.configuration; import inspectit.ocelot.configdocsgenerator.ConfigDocsGenerator; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; import inspectit.ocelot.configdocsgenerator.model.ConfigDocumentation; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; @@ -15,9 +16,7 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; import org.yaml.snakeyaml.Yaml; -import rocks.inspectit.ocelot.agentconfiguration.AgentConfiguration; -import rocks.inspectit.ocelot.agentconfiguration.AgentConfigurationManager; -import rocks.inspectit.ocelot.agentconfiguration.ObjectStructureMerger; +import rocks.inspectit.ocelot.agentconfiguration.*; import rocks.inspectit.ocelot.config.model.InspectitConfig; import rocks.inspectit.ocelot.file.FileManager; import rocks.inspectit.ocelot.mappings.AgentMappingManager; @@ -25,9 +24,9 @@ import rocks.inspectit.ocelot.rest.AbstractBaseController; import rocks.inspectit.ocelot.rest.file.DefaultConfigController; import rocks.inspectit.ocelot.security.config.UserRoleConfiguration; -import rocks.inspectit.ocelot.agentconfiguration.DocsObjectsLoader; import java.util.*; +import java.util.stream.Collectors; /** * Controller for endpoints related to configuration files. @@ -110,7 +109,7 @@ public ResponseEntity getConfigDocumentation( AgentConfiguration configuration = configManager.getConfigurationForMapping(agentMapping.get()); String configYaml = configuration.getConfigYaml(); - Map> docsObjectsByFile = configuration.getDocsObjectsByFile(); + Set agentDocumentations = configuration.getDocumentations(); try { if (includeDefault) { @@ -125,11 +124,11 @@ public ResponseEntity getConfigDocumentation( combined = ObjectStructureMerger.merge(combined, loadedYaml); } configYaml = yaml.dump(combined); - Map> defaultObjectsByFile = DocsObjectsLoader.loadDefaultDocsObjectsByFile(defaultYamls); - docsObjectsByFile.putAll(defaultObjectsByFile); + Set defaultAgentDocumentations = DocsObjectsLoader.loadDefaultAgentDocumentations(defaultYamls); + agentDocumentations.addAll(defaultAgentDocumentations); } - configDocsGenerator.setDocsObjectsByFile(docsObjectsByFile); + configDocsGenerator.setAgentDocumentations(agentDocumentations); configDocumentation = configDocsGenerator.generateConfigDocs(configYaml); } catch (Exception e) { diff --git a/components/inspectit-ocelot-configurationserver/src/main/resources/logback.xml b/components/inspectit-ocelot-configurationserver/src/main/resources/logback.xml index 3d3d591731..1e6cdfb1c3 100644 --- a/components/inspectit-ocelot-configurationserver/src/main/resources/logback.xml +++ b/components/inspectit-ocelot-configurationserver/src/main/resources/logback.xml @@ -54,10 +54,6 @@ - - - - diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTaskTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTaskTest.java index 615a3d3155..bfbaa033c6 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTaskTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationReloadTaskTest.java @@ -1,30 +1,25 @@ package rocks.inspectit.ocelot.agentconfiguration; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; import org.apache.commons.lang3.mutable.MutableObject; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import rocks.inspectit.ocelot.file.FileInfo; import rocks.inspectit.ocelot.file.FileManager; import rocks.inspectit.ocelot.file.accessor.git.RevisionAccess; -import rocks.inspectit.ocelot.file.versioning.Branch; import rocks.inspectit.ocelot.mappings.AgentMappingSerializer; import rocks.inspectit.ocelot.mappings.model.AgentMapping; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collections; -import java.util.List; -import java.util.Optional; +import java.util.*; import java.util.function.Consumer; import java.util.stream.Stream; +import static java.util.Collections.emptySet; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.mockito.ArgumentMatchers.*; import static org.mockito.Mockito.*; import static rocks.inspectit.ocelot.file.versioning.Branch.WORKSPACE; @@ -32,9 +27,6 @@ @ExtendWith(MockitoExtension.class) public class AgentConfigurationReloadTaskTest { - @InjectMocks - AgentConfigurationReloadTask reloadTask; - @Mock AgentMappingSerializer serializer; @@ -47,24 +39,96 @@ public class AgentConfigurationReloadTaskTest { @Mock RevisionAccess workspaceAccessor; + final String file = "/test.yml"; + @BeforeEach - public void beforeEach() { + void beforeEach() { lenient().when(fileManager.getWorkspaceRevision()).thenReturn(workspaceAccessor); lenient().when(fileManager.getLiveRevision()).thenReturn(liveAccessor); lenient().when(serializer.getRevisionAccess()).thenReturn(workspaceAccessor); + lenient().when(workspaceAccessor.agentMappingsExist()).thenReturn(true); + + FileInfo fileInfo = mock(FileInfo.class); + lenient().when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of(file), Stream.of(file)); + lenient().when(workspaceAccessor.configurationFileExists(anyString())).thenReturn(true); + lenient().when(workspaceAccessor.configurationFileIsDirectory(anyString())).thenReturn(true); + lenient().when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); } @Nested class Run { @Test - public void loadWithException() { - FileInfo fileInfo = mock(FileInfo.class); - when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of("/test.yml"), Stream.of("/test.yml")); - when(workspaceAccessor.agentMappingsExist()).thenReturn(true); - when(workspaceAccessor.configurationFileExists(anyString())).thenReturn(true); - when(workspaceAccessor.configurationFileIsDirectory(anyString())).thenReturn(true); - when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + void runTaskWithValidOutput() throws Exception { + when(workspaceAccessor.readConfigurationFile(anyString())).thenReturn(Optional.of("key: valid")); + + AgentMapping mapping = AgentMapping.builder() + .name("test") + .source("/test") + .sourceBranch(WORKSPACE) + .build(); + doReturn(Collections.singletonList(mapping)).when(serializer).readAgentMappings(any()); + + AgentDocumentation documentation = new AgentDocumentation(file, Collections.emptySet()); + + MutableObject> configurations = new MutableObject<>(); + Consumer> consumer = configurations::setValue; + AgentConfigurationReloadTask task = new AgentConfigurationReloadTask(serializer, fileManager, consumer); + + task.run(); + + List configurationList = configurations.getValue(); + assertThat(configurationList).isNotEmpty(); + + AgentConfiguration configuration = configurationList.get(0); + + assertThat(configuration.getMapping()).isEqualTo(mapping); + assertThat(configuration.getConfigYaml()).isEqualTo("{key: valid}\n"); + assertThat(configuration.getDocumentationSuppliers()).isNotEmpty(); + + AgentDocumentationSupplier supplier = configuration.getDocumentationSuppliers().iterator().next(); + + assertThat(supplier.get()).isEqualTo(documentation); + } + + @Test + void runTaskWithNullMapping() { + doReturn(Collections.singletonList(null)).when(serializer).readAgentMappings(any()); + + MutableObject> configurations = new MutableObject<>(); + Consumer> consumer = configurations::setValue; + + AgentConfigurationReloadTask task = new AgentConfigurationReloadTask(serializer, fileManager, consumer); + + task.run(); + + List configurationList = configurations.getValue(); + assertThat(configurationList).isEmpty(); + } + + @Test + void runTaskWithoutFileAccessor() { + when(fileManager.getWorkspaceRevision()).thenReturn(null); + AgentMapping mapping = AgentMapping.builder() + .name("test") + .source("/test") + .sourceBranch(WORKSPACE) + .build(); + doReturn(Collections.singletonList(mapping)).when(serializer).readAgentMappings(any()); + + MutableObject> configurations = new MutableObject<>(); + Consumer> consumer = configurations::setValue; + + AgentConfigurationReloadTask task = new AgentConfigurationReloadTask(serializer, fileManager, consumer); + + task.run(); + + List configurationList = configurations.getValue(); + assertThat(configurationList).isEmpty(); + } + + @Test + void loadTabWithException() { // the first call will return a broken file when(workspaceAccessor.readConfigurationFile(anyString())).thenReturn(Optional.of("key:\tbroken"), Optional.of("key: valid")); @@ -80,6 +144,8 @@ public void loadWithException() { .build(); doReturn(Arrays.asList(mapping, mapping2)).when(serializer).readAgentMappings(any()); + AgentDocumentation documentation = new AgentDocumentation(file, Collections.emptySet()); + MutableObject> configurations = new MutableObject<>(); Consumer> consumer = configurations::setValue; @@ -89,19 +155,20 @@ public void loadWithException() { List configurationList = configurations.getValue(); assertThat(configurationList).hasSize(1); - assertThat(configurationList).element(0) - .extracting(AgentConfiguration::getConfigYaml) - .isEqualTo("{key: valid}\n"); + + AgentConfiguration configuration = configurationList.get(0); + + assertThat(configuration.getMapping()).isEqualTo(mapping2); + assertThat(configuration.getConfigYaml()).isEqualTo("{key: valid}\n"); + assertThat(configuration.getDocumentationSuppliers()).hasSize(1); + + AgentDocumentationSupplier supplier = configuration.getDocumentationSuppliers().iterator().next(); + + assertThat(supplier.get()).isEqualTo(documentation); } @Test - public void loadWithExceptionOnlyString() { - FileInfo fileInfo = mock(FileInfo.class); - when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of("/test.yml"), Stream.of("/test.yml")); - when(workspaceAccessor.agentMappingsExist()).thenReturn(true); - when(workspaceAccessor.configurationFileExists(anyString())).thenReturn(true); - when(workspaceAccessor.configurationFileIsDirectory(anyString())).thenReturn(true); - when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + void loadOnlyStringWithException() { // the first call will return an invalid file only containing a string when(workspaceAccessor.readConfigurationFile(anyString())).thenReturn(Optional.of("onlystring"), Optional.of("key: valid")); @@ -117,6 +184,8 @@ public void loadWithExceptionOnlyString() { .build(); doReturn(Arrays.asList(mapping, mapping2)).when(serializer).readAgentMappings(any()); + AgentDocumentation documentation = new AgentDocumentation(file, Collections.emptySet()); + MutableObject> configurations = new MutableObject<>(); Consumer> consumer = configurations::setValue; @@ -126,19 +195,20 @@ public void loadWithExceptionOnlyString() { List configurationList = configurations.getValue(); assertThat(configurationList).hasSize(1); - assertThat(configurationList).element(0) - .extracting(AgentConfiguration::getConfigYaml) - .isEqualTo("{key: valid}\n"); + + AgentConfiguration configuration = configurationList.get(0); + + assertThat(configuration.getMapping()).isEqualTo(mapping2); + assertThat(configuration.getConfigYaml()).isEqualTo("{key: valid}\n"); + assertThat(configuration.getDocumentationSuppliers()).hasSize(1); + + AgentDocumentationSupplier supplier = configuration.getDocumentationSuppliers().iterator().next(); + + assertThat(supplier.get()).isEqualTo(documentation); } @Test - public void loadWithExceptionOnlyList() { - FileInfo fileInfo = mock(FileInfo.class); - when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of("/test.yml"), Stream.of("/test.yml")); - when(workspaceAccessor.agentMappingsExist()).thenReturn(true); - when(workspaceAccessor.configurationFileExists(anyString())).thenReturn(true); - when(workspaceAccessor.configurationFileIsDirectory(anyString())).thenReturn(true); - when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + void loadOnlyListWithException() { // the first call will return an invalid file only containing a list when(workspaceAccessor.readConfigurationFile(anyString())).thenReturn(Optional.of("- listentry1\n listentry2"), Optional.of("key: valid")); @@ -154,6 +224,8 @@ public void loadWithExceptionOnlyList() { .build(); doReturn(Arrays.asList(mapping, mapping2)).when(serializer).readAgentMappings(any()); + AgentDocumentation documentation = new AgentDocumentation(file, Collections.emptySet()); + MutableObject> configurations = new MutableObject<>(); Consumer> consumer = configurations::setValue; @@ -163,15 +235,24 @@ public void loadWithExceptionOnlyList() { List configurationList = configurations.getValue(); assertThat(configurationList).hasSize(1); - assertThat(configurationList).element(0) - .extracting(AgentConfiguration::getConfigYaml) - .isEqualTo("{key: valid}\n"); + + AgentConfiguration configuration = configurationList.get(0); + + assertThat(configuration.getMapping()).isEqualTo(mapping2); + assertThat(configuration.getConfigYaml()).isEqualTo("{key: valid}\n"); + assertThat(configuration.getDocumentationSuppliers()).hasSize(1); + + AgentDocumentationSupplier supplier = configuration.getDocumentationSuppliers().iterator().next(); + + assertThat(supplier.get()).isEqualTo(documentation); } + } - @Test - public void loadMappingFromWorkspace() { - when(workspaceAccessor.agentMappingsExist()).thenReturn(true); + @Nested + class MappingRevisionAccess { + @Test + void loadMappingFromWorkspace() { AgentMapping mapping = AgentMapping.builder() .name("test") .source("/test") @@ -189,8 +270,8 @@ public void loadMappingFromWorkspace() { } @Test - public void loadMappingFromLive() { - lenient().when(serializer.getRevisionAccess()).thenReturn(liveAccessor); + void loadMappingFromLive() { + when(serializer.getRevisionAccess()).thenReturn(liveAccessor); when(liveAccessor.agentMappingsExist()).thenReturn(true); AgentMapping mapping = AgentMapping.builder() @@ -209,160 +290,4 @@ public void loadMappingFromLive() { verify(serializer, times(1)).readAgentMappings(liveAccessor); } } - - @Nested - class LoadAndMergeYaml { - - @Test - public void loadYaml() { - FileInfo fileInfo = mock(FileInfo.class); - when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of("/test.yml")); - when(workspaceAccessor.configurationFileExists("test")).thenReturn(true); - when(workspaceAccessor.configurationFileIsDirectory("test")).thenReturn(true); - when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); - when(workspaceAccessor.readConfigurationFile("/test.yml")).thenReturn(Optional.of("key: value")); - - AgentMapping mapping = AgentMapping.builder() - .name("test") - .source("/test") - .sourceBranch(WORKSPACE) - .build(); - AgentConfiguration config = reloadTask.createAgentConfiguration(mapping); - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEqualTo("{key: value}\n"); - } - - @Test - public void yamlWithTab() { - FileInfo fileInfo = mock(FileInfo.class); - when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of("/test.yml")); - when(workspaceAccessor.configurationFileExists("test")).thenReturn(true); - when(workspaceAccessor.configurationFileIsDirectory("test")).thenReturn(true); - when(workspaceAccessor.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); - when(workspaceAccessor.readConfigurationFile("/test.yml")).thenReturn(Optional.of("key:\tvalue")); - - AgentMapping mapping = AgentMapping.builder() - .name("test") - .source("/test") - .sourceBranch(WORKSPACE) - .build(); - - assertThatExceptionOfType(AgentConfigurationReloadTask.InvalidConfigurationFileException.class).isThrownBy(() -> reloadTask.createAgentConfiguration(mapping)) - .withMessage("The configuration file '/test.yml' is invalid and cannot be parsed."); - } - } - - @Nested - class LoadConfigForMapping { - - @Test - void noSourcesSpecified() { - AgentConfiguration config = reloadTask.createAgentConfiguration(AgentMapping.builder().build()); - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEmpty(); - } - - @Test - void liveBranchSpecified() { - AgentMapping mapping = AgentMapping.builder().source("a.yml").sourceBranch(Branch.LIVE).build(); - - doReturn(true).when(liveAccessor).configurationFileExists("a.yml"); - doReturn(false).when(liveAccessor).configurationFileIsDirectory("a.yml"); - doReturn(Optional.of("key: value")).when(liveAccessor).readConfigurationFile("a.yml"); - - AgentConfiguration config = reloadTask.createAgentConfiguration(mapping); - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEqualToIgnoringWhitespace("{key: value}"); - } - - @Test - void nonExistingSourcesSpecified() { - doReturn(false).when(workspaceAccessor).configurationFileExists("a.yml"); - doReturn(false).when(workspaceAccessor).configurationFileExists("some/folder"); - - AgentConfiguration config = reloadTask.createAgentConfiguration(AgentMapping.builder() - .source("a.yml") - .source("/some/folder") - .sourceBranch(WORKSPACE) - .build()); - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEmpty(); - } - - @Test - void nonYamlIgnored() { - doReturn(true).when(workspaceAccessor).configurationFileExists(any()); - doReturn(false).when(workspaceAccessor).configurationFileIsDirectory(any()); - doReturn(Optional.of("")).when(workspaceAccessor).readConfigurationFile(any()); - - AgentConfiguration config = reloadTask.createAgentConfiguration(AgentMapping.builder() - .source("a.yml") - .source("b.YmL") - .source("c.yaml") - .source("d.txt") - .sourceBranch(WORKSPACE) - .build()); - - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEmpty(); - verify(workspaceAccessor).readConfigurationFile("a.yml"); - verify(workspaceAccessor).readConfigurationFile("b.YmL"); - verify(workspaceAccessor).readConfigurationFile("c.yaml"); - - verify(workspaceAccessor, never()).readConfigurationFile("d.txt"); - } - - @Test - void leadingSlashesInSourcesRemoved() { - doReturn(false).when(workspaceAccessor).configurationFileExists("a.yml"); - - lenient().doThrow(new RuntimeException()).when(workspaceAccessor).configurationFileExists(startsWith("/")); - - reloadTask.createAgentConfiguration(AgentMapping.builder() - .source("/a.yml") - .sourceBranch(WORKSPACE) - .build()); - - verify(workspaceAccessor).configurationFileExists(eq("a.yml")); - } - - @Test - void priorityRespected() { - - when(workspaceAccessor.configurationFileExists(any())).thenReturn(true); - - doReturn(true).when(workspaceAccessor).configurationFileIsDirectory("folder"); - doReturn(false).when(workspaceAccessor).configurationFileIsDirectory("z.yml"); - - List fileInfos = Arrays.asList(FileInfo.builder() - .type(FileInfo.Type.FILE) - .name("b.yml") - .build(), FileInfo.builder().type(FileInfo.Type.FILE).name("a.yml").build(), FileInfo.builder() - .type(FileInfo.Type.FILE) - .name("somethingelse") - .build()); - - when(workspaceAccessor.listConfigurationFiles("folder")).thenReturn(fileInfos); - - doReturn(Optional.of("{ val1: z}")).when(workspaceAccessor).readConfigurationFile("z.yml"); - doReturn(Optional.of("{ val1: a, val2: a}")).when(workspaceAccessor).readConfigurationFile("folder/a.yml"); - doReturn(Optional.of("{ val1: b, val2: b, val3: b}")).when(workspaceAccessor) - .readConfigurationFile("folder/b.yml"); - - AgentConfiguration config = reloadTask.createAgentConfiguration(AgentMapping.builder() - .source("/z.yml") - .source("/folder") - .sourceBranch(WORKSPACE) - .build()); - String yaml = config.getConfigYaml(); - - assertThat(yaml).isEqualTo("{val1: z, val2: a, val3: b}\n"); - verify(workspaceAccessor, never()).readConfigurationFile("folder/somethingelse"); - } - } } diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationTest.java new file mode 100644 index 0000000000..0bbb732385 --- /dev/null +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/AgentConfigurationTest.java @@ -0,0 +1,240 @@ +package rocks.inspectit.ocelot.agentconfiguration; + +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import rocks.inspectit.ocelot.file.FileInfo; +import rocks.inspectit.ocelot.file.accessor.git.RevisionAccess; +import rocks.inspectit.ocelot.mappings.model.AgentMapping; + +import java.util.*; +import java.util.stream.Stream; + +import static org.assertj.core.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.*; +import static rocks.inspectit.ocelot.file.versioning.Branch.WORKSPACE; + +@ExtendWith(MockitoExtension.class) +public class AgentConfigurationTest { + + @Mock + RevisionAccess revisionAccess; + + final String file = "/test.yml"; + + @Nested + class Create { + @Test + void verifyCreateHappyPath() { + FileInfo fileInfo = mock(FileInfo.class); + when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of(file)); + when(revisionAccess.configurationFileExists("test")).thenReturn(true); + when(revisionAccess.configurationFileIsDirectory("test")).thenReturn(true); + when(revisionAccess.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + when(revisionAccess.readConfigurationFile(file)).thenReturn(Optional.of("key: value")); + + AgentMapping mapping = AgentMapping.builder() + .name("test") + .source("/test") + .sourceBranch(WORKSPACE) + .build(); + + AgentDocumentation documentation = new AgentDocumentation(file, Collections.emptySet()); + + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + + assertThat(config.getMapping()).isEqualTo(mapping); + assertThat(config.getConfigYaml()).isEqualTo("{key: value}\n"); + assertThat(config.getHash()).isNotBlank(); + assertThat(config.getDocumentationSuppliers()).isNotEmpty(); + + AgentDocumentationSupplier createdSupplier = config.getDocumentationSuppliers().stream().findFirst().get(); + + assertThat(createdSupplier.get()).isEqualTo(documentation); + } + } + + @Nested + class LoadAndMergeYaml { + + @Test + void yamlWithTab() { + FileInfo fileInfo = mock(FileInfo.class); + when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of(file)); + when(revisionAccess.configurationFileExists("test")).thenReturn(true); + when(revisionAccess.configurationFileIsDirectory("test")).thenReturn(true); + when(revisionAccess.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + when(revisionAccess.readConfigurationFile(file)).thenReturn(Optional.of("key:\tvalue")); + + AgentMapping mapping = AgentMapping.builder() + .name("test") + .source("/test") + .sourceBranch(WORKSPACE) + .build(); + + assertThatExceptionOfType(ObjectStructureMerger.InvalidConfigurationFileException.class).isThrownBy( + () -> AgentConfiguration.create(mapping, revisionAccess) + ).withMessage("The configuration file '%s' is invalid and cannot be parsed.", file); + } + } + + @Nested + class LoadConfigForMapping { + + @Test + void noSourcesSpecified() { + AgentMapping mapping = AgentMapping.builder().build(); + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + String result = config.getConfigYaml(); + + assertThat(result).isEmpty(); + } + + @Test + void nonExistingSourcesSpecified() { + doReturn(false).when(revisionAccess).configurationFileExists("a.yml"); + doReturn(false).when(revisionAccess).configurationFileExists("some/folder"); + + AgentMapping mapping = AgentMapping.builder() + .source("a.yml") + .source("/some/folder") + .sourceBranch(WORKSPACE) + .build(); + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + String result = config.getConfigYaml(); + + assertThat(result).isEmpty(); + } + + @Test + void nonYamlIgnored() { + doReturn(true).when(revisionAccess).configurationFileExists(any()); + doReturn(false).when(revisionAccess).configurationFileIsDirectory(any()); + doReturn(Optional.of("")).when(revisionAccess).readConfigurationFile(any()); + + AgentMapping mapping = AgentMapping.builder() + .source("a.yml") + .source("b.YmL") + .source("c.yaml") + .source("d.txt") + .sourceBranch(WORKSPACE) + .build(); + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + String result = config.getConfigYaml(); + + assertThat(result).isEmpty(); + verify(revisionAccess).readConfigurationFile("a.yml"); + verify(revisionAccess).readConfigurationFile("b.YmL"); + verify(revisionAccess).readConfigurationFile("c.yaml"); + + verify(revisionAccess, never()).readConfigurationFile("d.txt"); + } + + @Test + void leadingSlashesInSourcesRemoved() { + doReturn(false).when(revisionAccess).configurationFileExists("a.yml"); + + lenient().doThrow(new RuntimeException()).when(revisionAccess).configurationFileExists(startsWith("/")); + + AgentMapping mapping = AgentMapping.builder() + .source("/a.yml") + .sourceBranch(WORKSPACE) + .build(); + AgentConfiguration.create(mapping, revisionAccess); + + verify(revisionAccess).configurationFileExists(eq("a.yml")); + } + + @Test + void priorityRespected() { + when(revisionAccess.configurationFileExists(any())).thenReturn(true); + + doReturn(true).when(revisionAccess).configurationFileIsDirectory("folder"); + doReturn(false).when(revisionAccess).configurationFileIsDirectory("z.yml"); + + List fileInfos = Arrays.asList(FileInfo.builder() + .type(FileInfo.Type.FILE) + .name("b.yml") + .build(), FileInfo.builder().type(FileInfo.Type.FILE).name("a.yml").build(), FileInfo.builder() + .type(FileInfo.Type.FILE) + .name("somethingelse") + .build()); + + when(revisionAccess.listConfigurationFiles("folder")).thenReturn(fileInfos); + + doReturn(Optional.of("{ val1: z}")).when(revisionAccess).readConfigurationFile("z.yml"); + doReturn(Optional.of("{ val1: a, val2: a}")).when(revisionAccess).readConfigurationFile("folder/a.yml"); + doReturn(Optional.of("{ val1: b, val2: b, val3: b}")).when(revisionAccess) + .readConfigurationFile("folder/b.yml"); + + AgentMapping mapping = AgentMapping.builder() + .source("/z.yml") + .source("/folder") + .sourceBranch(WORKSPACE) + .build(); + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + String result = config.getConfigYaml(); + + assertThat(result).isEqualTo("{val1: z, val2: a, val3: b}\n"); + verify(revisionAccess, never()).readConfigurationFile("folder/somethingelse"); + } + } + + @Nested + class getDocumentations { + + private final String srcYaml = """ + inspectit: + instrumentation: + scopes: + s_jdbc_statement_execute: + docs: + description: 'Scope for executed JDBC statements.' + methods: + - name: execute + """; + + @Test + void verifyGetEmptyDocumentations() { + AgentConfiguration config = AgentConfiguration.NO_MATCHING_MAPPING; + + assertThat(config.getDocumentations()).isEmpty(); + } + + @Test + void verifyGetDocumentations() { + FileInfo fileInfo = mock(FileInfo.class); + when(fileInfo.getAbsoluteFilePaths(any())).thenReturn(Stream.of(file)); + when(revisionAccess.configurationFileExists("test")).thenReturn(true); + when(revisionAccess.configurationFileIsDirectory("test")).thenReturn(true); + when(revisionAccess.listConfigurationFiles(anyString())).thenReturn(Collections.singletonList(fileInfo)); + when(revisionAccess.readConfigurationFile(file)).thenReturn(Optional.of(srcYaml)); + + AgentDocumentation documentation = new AgentDocumentation(file, Collections.singleton("s_jdbc_statement_execute")); + + AgentMapping mapping = AgentMapping.builder() + .name("test-mapping") + .source("/test") + .sourceBranch(WORKSPACE) + .build(); + + AgentConfiguration config = AgentConfiguration.create(mapping, revisionAccess); + + assertThat(config.getDocumentations()).containsExactly(documentation); + } + + @Test + void verifyGetDocumentationsWithoutFileAccessor() { + AgentMapping mapping = AgentMapping.builder().build(); + + AgentConfiguration config = AgentConfiguration.create(mapping, null); + + assertThat(config.getDocumentations()).isEmpty(); + } + } +} diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoaderTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoaderTest.java index e2796da909..8c4c3245ce 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoaderTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentconfiguration/DocsObjectsLoaderTest.java @@ -1,19 +1,19 @@ package rocks.inspectit.ocelot.agentconfiguration; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import java.io.IOException; import java.util.*; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static rocks.inspectit.ocelot.agentconfiguration.DocsObjectsLoader.OCELOT_DEFAULT_CONFIG_PREFIX; public class DocsObjectsLoaderTest { - private final String srcYaml = """ + private final String srcYamlWithDocsObject = """ inspectit: instrumentation: scopes: @@ -32,34 +32,67 @@ public class DocsObjectsLoaderTest { free: true """; - @Test - void verifyLoadObjectsSuccessful() throws IOException { - Set objects = DocsObjectsLoader.loadObjects(srcYaml); - assertTrue(objects.contains("s_jdbc_statement_execute")); - } + @Nested + class LoadObjects { + @Test + void verifyLoadObjectsSuccessful() throws IOException { + Set objects = DocsObjectsLoader.loadObjects(srcYamlWithDocsObject); - @Test - void verifyLoadObjectsEmpty() throws IOException { - Set objects = DocsObjectsLoader.loadObjects(srcYamlWithoutDocsObjects); + assertTrue(objects.contains("s_jdbc_statement_execute")); + } - assertTrue(objects.isEmpty()); - } + @Test + void verifyLoadObjectsEmptyDocs() throws IOException { + Set objects = DocsObjectsLoader.loadObjects(srcYamlWithoutDocsObjects); + + assertTrue(objects.isEmpty()); + } + + @Test + void verifyLoadObjectsEmptyString() throws IOException { + Set objects = DocsObjectsLoader.loadObjects(""); + + assertTrue(objects.isEmpty()); + } - @Test - void verifyLoadThrowsException() { - assertThrows(NoSuchElementException.class, () -> DocsObjectsLoader.loadObjects("invalid-config")); + @Test + void verifyLoadObjectsInvalidConfig() { + assertThrows(NoSuchElementException.class, () -> DocsObjectsLoader.loadObjects("invalid-config")); + } } - @Test - void verifyLoadDefaultDocsObjectsByFile() { - String file = "test.yml"; - String fileWithPrefix = OCELOT_DEFAULT_CONFIG_PREFIX + file; - Map configs = new HashMap<>(); - configs.put(file, srcYaml); + @Nested + class DefaultAgentDocumentation { + @Test + void verifyLoadDefaultAgentDocumentations() { + String file = "test.yml"; + String fileWithPrefix = OCELOT_DEFAULT_CONFIG_PREFIX + file; + Map configs = new HashMap<>(); + configs.put(file, srcYamlWithDocsObject); + + Set documentations = DocsObjectsLoader.loadDefaultAgentDocumentations(configs); + assertTrue(documentations.stream().anyMatch(doc -> doc.getFilePath().equals(fileWithPrefix))); + assertTrue(documentations.stream().anyMatch(doc -> doc.getObjects().equals(Collections.singleton("s_jdbc_statement_execute")))); + } + + @Test + void verifyLoadDefaultAgentDocumentationsEmptyDocs() { + String file = "test.yml"; + String fileWithPrefix = OCELOT_DEFAULT_CONFIG_PREFIX + file; + Map configs = new HashMap<>(); + configs.put(file, srcYamlWithoutDocsObjects); + + Set documentations = DocsObjectsLoader.loadDefaultAgentDocumentations(configs); + assertTrue(documentations.stream().anyMatch(doc -> doc.getFilePath().equals(fileWithPrefix))); + assertTrue(documentations.stream().anyMatch(doc -> doc.getObjects().equals(Collections.emptySet()))); + } + + @Test + void verifyLoadDefaultAgentDocumentationsEmptyMap() { + Set documentations = DocsObjectsLoader.loadDefaultAgentDocumentations(new HashMap<>()); - Map> docsObjectsByFile = DocsObjectsLoader.loadDefaultDocsObjectsByFile(configs); - assertTrue(docsObjectsByFile.containsKey(fileWithPrefix)); - assertTrue(docsObjectsByFile.containsValue(Collections.singleton("s_jdbc_statement_execute"))); + assertTrue(documentations.isEmpty()); + } } } diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManagerTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManagerTest.java index 68e4623736..976e101bd5 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManagerTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/agentstatus/AgentStatusManagerTest.java @@ -6,6 +6,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.mockito.InjectMocks; +import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import rocks.inspectit.ocelot.agentconfiguration.AgentConfiguration; import rocks.inspectit.ocelot.config.model.InspectitServerSettings; @@ -13,11 +14,10 @@ import rocks.inspectit.ocelot.mappings.model.AgentMapping; import java.time.Duration; -import java.util.Collections; -import java.util.Date; -import java.util.Map; +import java.util.*; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; @ExtendWith(MockitoExtension.class) public class AgentStatusManagerTest { @@ -27,6 +27,9 @@ public class AgentStatusManagerTest { @InjectMocks AgentStatusManager manager; + @Mock + AgentConfiguration agentConfiguration; + @BeforeEach void init() { manager.config = InspectitServerSettings.builder() @@ -43,10 +46,10 @@ class NotifyAgentConfigurationFetched { void testWithAgentIdHeader() { Branch testBranch = Branch.WORKSPACE; AgentMapping agentMapping = AgentMapping.builder().name("test-conf").sourceBranch(testBranch).build(); - AgentConfiguration config = AgentConfiguration.builder().mapping(agentMapping).configYaml("").build(); - Map attributes = ImmutableMap.of("service", "test"); + when(agentConfiguration.getMapping()).thenReturn(agentMapping); - manager.notifyAgentConfigurationFetched(attributes, Collections.singletonMap(HEADER_AGENT_ID, "aid"), config); + Map attributes = ImmutableMap.of("service", "test"); + manager.notifyAgentConfigurationFetched(attributes, Collections.singletonMap(HEADER_AGENT_ID, "aid"), agentConfiguration); assertThat(manager.getAgentStatuses()).hasSize(1).anySatisfy(status -> { assertThat(status.getAttributes()).isEqualTo(attributes); @@ -75,10 +78,10 @@ void testNoMappingFound() { void testMappingFound() { Branch testBranch = Branch.WORKSPACE; AgentMapping agentMapping = AgentMapping.builder().name("test-conf").sourceBranch(testBranch).build(); - AgentConfiguration conf = AgentConfiguration.builder().mapping(agentMapping).configYaml("").build(); + AgentConfiguration config = AgentConfiguration.create(agentMapping, null); Map attributes = ImmutableMap.of("service", "test"); - manager.notifyAgentConfigurationFetched(attributes, Collections.emptyMap(), conf); + manager.notifyAgentConfigurationFetched(attributes, Collections.emptyMap(), config); assertThat(manager.getAgentStatuses()).hasSize(1).anySatisfy(status -> { assertThat(status.getAttributes()).isEqualTo(attributes); @@ -92,7 +95,7 @@ void testMappingFound() { void testOverriding() throws Exception { Branch testBranch = Branch.WORKSPACE; AgentMapping agentMapping = AgentMapping.builder().name("test-conf").sourceBranch(testBranch).build(); - AgentConfiguration conf = AgentConfiguration.builder().mapping(agentMapping).configYaml("").build(); + AgentConfiguration config = AgentConfiguration.create(agentMapping, null); Map attributes = ImmutableMap.of("service", "test"); manager.notifyAgentConfigurationFetched(attributes, Collections.emptyMap(), null); @@ -107,7 +110,7 @@ void testOverriding() throws Exception { Thread.sleep(1); - manager.notifyAgentConfigurationFetched(attributes, Collections.emptyMap(), conf); + manager.notifyAgentConfigurationFetched(attributes, Collections.emptyMap(), config); assertThat(manager.getAgentStatuses()).hasSize(1).anySatisfy(status -> { assertThat(status.getAttributes()).isEqualTo(attributes); diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentControllerTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentControllerTest.java index 509daf04e9..9695111dfd 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentControllerTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentControllerTest.java @@ -19,6 +19,7 @@ import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; @@ -33,6 +34,9 @@ public class AgentControllerTest { @Mock AgentConfigurationManager configManager; + @Mock + AgentConfiguration agentConfiguration; + @Mock AgentStatusManager statusManager; @@ -45,6 +49,8 @@ public class AgentControllerTest { @Nested public class FetchConfiguration { + String srcYaml = "foo : bar"; + @Test public void noMappingFound() { doReturn(null).when(configManager).getConfiguration(anyMap()); @@ -58,29 +64,31 @@ public void noMappingFound() { @Test public void mappingFound() { - AgentConfiguration config = AgentConfiguration.builder().configYaml("foo : bar").build(); - doReturn(config).when(configManager).getConfiguration(anyMap()); + doReturn(agentConfiguration).when(configManager).getConfiguration(anyMap()); + doReturn(srcYaml).when(agentConfiguration).getConfigYaml(); HashMap attributes = new HashMap<>(); ResponseEntity result = controller.fetchConfiguration(attributes, Collections.emptyMap()); assertThat(result.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(result.getBody()).isEqualTo("foo : bar"); - verify(statusManager).notifyAgentConfigurationFetched(same(attributes), eq(Collections.emptyMap()), same(config)); + assertThat(result.getBody()).isEqualTo(srcYaml); + verify(statusManager).notifyAgentConfigurationFetched(same(attributes), eq(Collections.emptyMap()), same(agentConfiguration)); } @Test public void etagPresent() { - AgentConfiguration config = AgentConfiguration.builder().configYaml("foo : bar").build(); - doReturn(config).when(configManager).getConfiguration(anyMap()); + String hash = "1234"; + doReturn(agentConfiguration).when(configManager).getConfiguration(anyMap()); + doReturn(srcYaml).when(agentConfiguration).getConfigYaml(); + doReturn(hash).when(agentConfiguration).getHash(); ResponseEntity firstResult = controller.fetchConfiguration(new HashMap<>(), Collections.emptyMap()); ResponseEntity secondResult = controller.fetchConfiguration(new HashMap<>(), Collections.emptyMap()); assertThat(firstResult.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(firstResult.getBody()).isEqualTo("foo : bar"); + assertThat(firstResult.getBody()).isEqualTo(srcYaml); assertThat(secondResult.getStatusCode()).isEqualTo(HttpStatus.OK); - assertThat(secondResult.getBody()).isEqualTo("foo : bar"); + assertThat(secondResult.getBody()).isEqualTo(srcYaml); assertThat(firstResult.getHeaders().getFirst("ETag")).isNotBlank() .isEqualTo(secondResult.getHeaders().getFirst("ETag")); } diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentServiceTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentServiceTest.java index 45e0580a79..ce7fe177b7 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentServiceTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/agent/AgentServiceTest.java @@ -18,6 +18,7 @@ import java.lang.management.ManagementFactory; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.concurrent.ExecutionException; @@ -37,6 +38,9 @@ public class AgentServiceTest { @Mock AgentConfigurationManager configManager; + @Mock + AgentConfiguration config; + @Mock InspectitServerSettings configuration; @@ -45,8 +49,6 @@ public class BuildSupportArchive { AgentService serviceSpy; - AgentConfiguration config; - Map attributes; String logs; @@ -57,11 +59,10 @@ public class BuildSupportArchive { public void setupTestData(boolean logPreloadingEnabled) { serviceSpy = Mockito.spy(cut); + String configYaml = String.format("inspectit.log-preloading: {enabled: %b}", logPreloadingEnabled); //set config - config = AgentConfiguration.builder() - .configYaml(String.format("inspectit.log-preloading: {enabled: %b}", logPreloadingEnabled)) - .build(); + when(config.getConfigYaml()).thenReturn(configYaml); //set attributes attributes = new HashMap() {{ @@ -111,7 +112,7 @@ public void shouldBuildSupportArchive() throws ExecutionException { DeferredResult> actualResult = serviceSpy.buildSupportArchive(attributes, configManager); ResponseEntity unwrappedResult = (ResponseEntity) actualResult.getResult(); - + assertThat(unwrappedResult.getStatusCode()).isEqualTo(HttpStatus.OK); assertThat(unwrappedResult.getBody()).isEqualTo(expectedResult); } diff --git a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationControllerTest.java b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationControllerTest.java index 556d71e01f..18a7f794fd 100644 --- a/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationControllerTest.java +++ b/components/inspectit-ocelot-configurationserver/src/test/java/rocks/inspectit/ocelot/rest/configuration/ConfigurationControllerTest.java @@ -2,10 +2,10 @@ import com.fasterxml.jackson.core.JsonProcessingException; import inspectit.ocelot.configdocsgenerator.ConfigDocsGenerator; +import inspectit.ocelot.configdocsgenerator.model.AgentDocumentation; import inspectit.ocelot.configdocsgenerator.model.ConfigDocumentation; import org.assertj.core.api.AssertionsForClassTypes; import org.eclipse.jgit.api.errors.GitAPIException; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; @@ -16,9 +16,7 @@ import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.yaml.snakeyaml.Yaml; -import rocks.inspectit.ocelot.agentconfiguration.AgentConfiguration; -import rocks.inspectit.ocelot.agentconfiguration.AgentConfigurationManager; -import rocks.inspectit.ocelot.agentconfiguration.ObjectStructureMerger; +import rocks.inspectit.ocelot.agentconfiguration.*; import rocks.inspectit.ocelot.file.FileManager; import rocks.inspectit.ocelot.mappings.AgentMappingManager; import rocks.inspectit.ocelot.mappings.model.AgentMapping; @@ -31,6 +29,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.*; +import static rocks.inspectit.ocelot.agentconfiguration.DocsObjectsLoader.OCELOT_DEFAULT_CONFIG_PREFIX; @ExtendWith(MockitoExtension.class) public class ConfigurationControllerTest { @@ -41,6 +40,9 @@ public class ConfigurationControllerTest { @Mock AgentConfigurationManager agentConfigurationManager; + @Mock + AgentConfiguration agentConfiguration; + @Mock FileManager fileManager; @@ -61,11 +63,8 @@ public class FetchConfiguration { @Test public void returningConfiguration() { - AgentConfiguration configuration = AgentConfiguration.builder() - .configYaml("yaml") - .docsObjectsByFile(new HashMap<>()) - .build(); - when(agentConfigurationManager.getConfiguration(any())).thenReturn(configuration); + when(agentConfiguration.getConfigYaml()).thenReturn("yaml"); + when(agentConfigurationManager.getConfiguration(any())).thenReturn(agentConfiguration); ResponseEntity output = configurationController.fetchConfiguration(null); @@ -97,29 +96,33 @@ public void initiatesCommit() throws GitAPIException { @Nested public class GetConfigDocumentationTest { - private final static Map> docsObjectsByFile = new HashMap<>(); + private final String mappingName = "name"; + private final AgentMapping agentMapping = AgentMapping.builder().build(); + private final String configYaml = "yaml"; + private Set agentDocumentations; + - @BeforeAll - static void setUp() { + + @BeforeEach + void setUp() { + String filePath = "test.yml"; Set objects = Collections.singleton("yaml"); - docsObjectsByFile.put("test.yml", objects); + agentDocumentations = new HashSet<>(); + AgentDocumentation documentation = new AgentDocumentation(filePath, objects); + agentDocumentations.add(documentation); + + lenient().when(agentConfiguration.getMapping()).thenReturn(agentMapping); + lenient().when(agentConfiguration.getConfigYaml()).thenReturn(configYaml); + lenient().when(agentConfiguration.getDocumentations()).thenReturn(agentDocumentations); } @Test void withDefaultConfig() throws IOException { - - final String mappingName = "name"; - AgentMapping agentMapping = AgentMapping.builder().build(); - - final String configYaml = "yaml"; - AgentConfiguration agentConfiguration = AgentConfiguration.builder() - .configYaml(configYaml) - .docsObjectsByFile(docsObjectsByFile) - .build(); - Map defaultYamls = new HashMap<>(); final String defaultYamlContent = "defaultYaml"; defaultYamls.put("firstYaml", defaultYamlContent); + AgentDocumentation documentation = new AgentDocumentation(OCELOT_DEFAULT_CONFIG_PREFIX + "firstYaml", Collections.emptySet()); + agentDocumentations.add(documentation); Map configYamlMap = new HashMap<>(); configYamlMap.put("entry", "value"); @@ -151,7 +154,7 @@ void withDefaultConfig() throws IOException { verify(yaml).load(eq(configYaml)); verify(yaml).dump(eq(combinedYamls)); verifyNoMoreInteractions(yaml); - verify(configDocsGenerator).setDocsObjectsByFile(eq(docsObjectsByFile)); + verify(configDocsGenerator).setAgentDocumentations(eq(agentDocumentations)); verify(configDocsGenerator).generateConfigDocs(eq(combinedYamlString)); verifyNoMoreInteractions(configDocsGenerator); @@ -161,20 +164,8 @@ void withDefaultConfig() throws IOException { @Test void errorWhenGettingDefaultConfig() throws IOException { - - final String mappingName = "name"; - AgentMapping agentMapping = AgentMapping.builder().build(); - - final String configYaml = "yaml"; - AgentConfiguration agentConfiguration = AgentConfiguration.builder() - .configYaml(configYaml) - .docsObjectsByFile(docsObjectsByFile) - .build(); - IOException exception = new IOException(); - ConfigDocumentation configDocumentationMock = mock(ConfigDocumentation.class); - when(mappingManager.getAgentMapping(mappingName)).thenReturn(Optional.of(agentMapping)); when(agentConfigurationManager.getConfigurationForMapping(agentMapping)).thenReturn(agentConfiguration); when(defaultConfigController.getDefaultConfigContent()).thenThrow(exception); @@ -197,16 +188,6 @@ void errorWhenGettingDefaultConfig() throws IOException { @Test void withoutDefaultConfig() throws IOException { - - final String mappingName = "name"; - AgentMapping agentMapping = AgentMapping.builder().build(); - - final String configYaml = "yaml"; - AgentConfiguration agentConfiguration = AgentConfiguration.builder() - .configYaml(configYaml) - .docsObjectsByFile(docsObjectsByFile) - .build(); - ConfigDocumentation configDocumentationMock = mock(ConfigDocumentation.class); when(mappingManager.getAgentMapping(mappingName)).thenReturn(Optional.of(agentMapping)); @@ -221,7 +202,7 @@ void withoutDefaultConfig() throws IOException { verifyNoMoreInteractions(mappingManager); verify(agentConfigurationManager).getConfigurationForMapping(eq(agentMapping)); verifyNoMoreInteractions(agentConfigurationManager); - verify(configDocsGenerator).setDocsObjectsByFile(eq(docsObjectsByFile)); + verify(configDocsGenerator).setAgentDocumentations(eq(agentDocumentations)); verify(configDocsGenerator).generateConfigDocs(eq(configYaml)); verifyNoMoreInteractions(configDocsGenerator); @@ -231,9 +212,6 @@ void withoutDefaultConfig() throws IOException { @Test void agentMappingNotFound() { - - final String mappingName = "name"; - when(mappingManager.getAgentMapping(mappingName)).thenReturn(Optional.empty()); ResponseEntity result = configurationController.getConfigDocumentation(mappingName, false); @@ -249,16 +227,6 @@ void agentMappingNotFound() { @Test void invalidYaml() throws IOException { - - final String mappingName = "name"; - AgentMapping agentMapping = AgentMapping.builder().build(); - - final String configYaml = "yaml"; - AgentConfiguration agentConfiguration = AgentConfiguration.builder() - .configYaml(configYaml) - .docsObjectsByFile(docsObjectsByFile) - .build(); - JsonProcessingException exception = mock(JsonProcessingException.class); final String errorMessage = "JsonProcessingException: Yaml could not be processed."; @@ -275,7 +243,7 @@ void invalidYaml() throws IOException { verifyNoMoreInteractions(mappingManager); verify(agentConfigurationManager).getConfigurationForMapping(eq(agentMapping)); verifyNoMoreInteractions(agentConfigurationManager); - verify(configDocsGenerator).setDocsObjectsByFile(eq(docsObjectsByFile)); + verify(configDocsGenerator).setAgentDocumentations(eq(agentDocumentations)); verify(configDocsGenerator).generateConfigDocs(eq(configYaml)); verifyNoMoreInteractions(configDocsGenerator); diff --git a/inspectit-ocelot-documentation/docs/instrumentation/instrumentation.md b/inspectit-ocelot-documentation/docs/instrumentation/instrumentation.md index 049cb1af0a..3a2316338a 100644 --- a/inspectit-ocelot-documentation/docs/instrumentation/instrumentation.md +++ b/inspectit-ocelot-documentation/docs/instrumentation/instrumentation.md @@ -43,8 +43,11 @@ Therefore, to increase readability of your configuration files the following nam * Action names always start with `a_`, e.g. `a_my_action`. * Rule names always start with `r_`, e.g. `r_my_rule`. * Context variables names should start with `d_`, e.g. `d_transaction_name`. -* Fields which are defined by the user should always be put in single quotations marks, e.g. `input: 'my_input'`. This rule also applies to keys which - can be entirely defined by the user, for example when defining the name of a custom action or attribute names. +* Fields which are defined by the user should always be put in single quotations marks, e.g. `input: 'my_input'`. +* This rule also applies to keys which can be entirely defined by the user, for example when defining the name of a + custom action or attribute names. +* Field names should be written in **snake case**, e.g. `s_my_scope`. **Do not use** dots instead of underscores, since + this will create a _nested structure_ in yaml and might lead to configuration errors. This naming convention is used both in this documentation and the default configuration provided.