Skip to content
New issue

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

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

Already on GitHub? # to your account

Closes #268 - Parse the agent configuration based on the MIME type when fetching it via HTTP #1251

Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,12 @@ public class ConfigurationServer {

/**
* Initializer which ensures that the working directory exists prior to initializing the spring context.
* This is required because otherwise the SQLite DB can't create it's database file.
* This is required because otherwise the SQLite DB can't create its database file.
*/
private static final ApplicationContextInitializer<ConfigurableApplicationContext> WORKING_DIRECTORY_INITIALIZER = (ctx) -> {
InspectitServerSettings settings = Binder
.get(ctx.getEnvironment())
.bind("inspectit-config-server", InspectitServerSettings.class).get();
InspectitServerSettings settings = Binder.get(ctx.getEnvironment())
.bind("inspectit-config-server", InspectitServerSettings.class)
.get();
try {
FileUtils.forceMkdir(new File(settings.getWorkingDirectory()));
} catch (Exception e) {
Expand All @@ -37,9 +37,7 @@ public class ConfigurationServer {
};

public static void main(String[] args) {
new SpringApplicationBuilder(ConfigurationServer.class)
.initializers(WORKING_DIRECTORY_INITIALIZER)
.run(args);
new SpringApplicationBuilder(ConfigurationServer.class).initializers(WORKING_DIRECTORY_INITIALIZER).run(args);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public class BeanConfiguration {
/**
* Executor service to use for asynchronous tasks.
*
* @param config the applications configuration, gets autowired
* @param config the application's configuration, gets autowired
*
* @return the executor service
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Optional;

/**
* Accessor to access specific Git revision/commits. Using this class ensures that all operations will be executed
Expand Down Expand Up @@ -110,7 +113,7 @@ public RevisionAccess getCommonAncestor(RevisionAccess other) {
RevWalk walk = new RevWalk(repository);
walk.setRevFilter(RevFilter.MERGE_BASE);
// RevCommits need to be produced by the same RevWalk instance otherwise it can't compare them
walk.markStart(walk.parseCommit(this.revCommit.toObjectId()));
walk.markStart(walk.parseCommit(revCommit.toObjectId()));
walk.markStart(walk.parseCommit(other.revCommit.toObjectId()));
RevCommit mergeBase = walk.next();

Expand Down Expand Up @@ -186,7 +189,7 @@ protected String verifyPath(String relativeBasePath, String relativePath) throws
Path path = Paths.get(relativePath).normalize();

if (StringUtils.isBlank(relativeBasePath)) {
return path.toString().replace("\\\\", "/");
return path.toString().replaceAll("\\\\", "/");
}

Path basePath = Paths.get(relativeBasePath).normalize();
Expand All @@ -196,7 +199,7 @@ protected String verifyPath(String relativeBasePath, String relativePath) throws
throw new IllegalArgumentException("User path escapes the base path: " + relativePath);
}

return resolvedPath.toString().replace("\\\\", "/");
return resolvedPath.toString().replaceAll("\\\\", "/");
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ public void e(Exception e) {
* @return The configuration mapped on the given agent name
*/
@ApiOperation(value = "Fetch the Agent Configuration", notes = "Reads the configuration for the given agent and returns it as a yaml string")
@GetMapping(value = "agent/configuration", produces = "text/plain")
@GetMapping(value = "agent/configuration", produces = "application/x-yaml")
public ResponseEntity<String> fetchConfiguration(@ApiParam("The agent attributes used to select the correct mapping") @RequestParam Map<String, String> attributes, @RequestHeader Map<String, String> headers) {
log.debug("Fetching the agent configuration for agent ({})", attributes.toString());
AgentConfiguration configuration = configManager.getConfiguration(attributes);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,9 +38,8 @@ public class ConfigurationController extends AbstractBaseController {
* Reloads all configuration files present in the servers working directory.
*/
@Secured(UserRoleConfiguration.WRITE_ACCESS_ROLE)
@ApiOperation(value = "Reloads all configuration files.", notes = "Reloads all configuration files present in the " +
"servers working directory and adds them to the workspace revision.")
@GetMapping(value = "configuration/reload", produces = "text/plain")
@ApiOperation(value = "Reloads all configuration files.", notes = "Reloads all configuration files present in the " + "servers working directory and adds them to the workspace revision.")
@GetMapping(value = "configuration/reload", produces = "application/x-yaml")
public void reloadConfiguration() throws GitAPIException {
fileManager.commitWorkingDirectory();
}
Expand All @@ -54,16 +53,15 @@ public void reloadConfiguration() throws GitAPIException {
*
* @return The configuration mapped on the given agent name
*/
@ApiOperation(value = "Fetch the Agent Configuration without logging the access.", notes = "Reads the configuration for the given agent and returns it as a yaml string." +
"Does not log the access in the agent status.")
@GetMapping(value = "configuration/agent-configuration", produces = "text/plain")
@ApiOperation(value = "Fetch the Agent Configuration without logging the access.", notes = "Reads the configuration for the given agent and returns it as a yaml string." + "Does not log the access in the agent status.")
@GetMapping(value = "configuration/agent-configuration", produces = "application/x-yaml")

public ResponseEntity<String> fetchConfiguration(@ApiParam("The agent attributes used to select the correct mapping") @RequestParam Map<String, String> attributes) {
AgentConfiguration configuration = configManager.getConfiguration(attributes);
if (configuration == null) {
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
} else {
return ResponseEntity.ok()
.body(configuration.getConfigYaml());
return ResponseEntity.ok().body(configuration.getConfigYaml());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ URI getCommandUri(InspectitConfig configuration) throws URISyntaxException {

if (settings.isDeriveFromHttpConfigUrl()) {
URL url = configuration.getConfig().getHttp().getUrl();
if (url == null) {
if (url == null || url.getHost() == null) {
throw new IllegalStateException("The URL cannot derived from the HTTP configuration URL because it is null.");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.Header;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.utils.URIBuilder;
import org.apache.http.entity.ContentType;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.util.EntityUtils;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import rocks.inspectit.ocelot.bootstrap.AgentManager;
import rocks.inspectit.ocelot.bootstrap.IAgent;
import rocks.inspectit.ocelot.config.model.config.HttpConfigSettings;
import rocks.inspectit.ocelot.core.config.util.PropertyUtils;

Expand Down Expand Up @@ -104,7 +105,7 @@ public class HttpPropertySourceState {
public HttpPropertySourceState(String name, HttpConfigSettings currentSettings) {
this.name = name;
this.currentSettings = currentSettings;
this.errorCounter = 0;
errorCounter = 0;
//ensure that currentPropertySource is never null, even if the initial fetching fails
currentPropertySource = new PropertiesPropertySource(name, new Properties());
}
Expand All @@ -115,10 +116,12 @@ public HttpPropertySourceState(String name, HttpConfigSettings currentSettings)
* that the configuration has not be changed the property source will not be updated!
*
* @param fallBackToFile if true, the configured persisted configuration will be loaded in case of an error
*
* @return returns true if a new property source has been created, otherwise false.
*/
public boolean update(boolean fallBackToFile) {
String configuration = fetchConfiguration(fallBackToFile);
RawProperties configuration = fetchConfiguration(fallBackToFile);
log.info("configuration={}", configuration);
if (configuration != null) {
try {
Properties properties = parseProperties(configuration);
Expand All @@ -128,7 +131,6 @@ public boolean update(boolean fallBackToFile) {
log.error("Could not parse fetched configuration.", e);
}
}

return false;
}

Expand All @@ -137,6 +139,7 @@ public boolean update(boolean fallBackToFile) {
* or YAML document.
*
* @param rawProperties the properties in a String representation
*
* @return the parsed {@link Properties} object
*/
private Properties parseProperties(String rawProperties) {
Expand All @@ -150,6 +153,20 @@ private Properties parseProperties(String rawProperties) {
}
}

/**
* Parse the given properties represented as {@link RawProperties} into an instance of {@link Properties} using the appropriate parser for the given MIME type.
*
* @param rawProperties the {@link RawProperties} containing the properties in a String representation and the MIME type
*
* @return the parsed {@link Properties} object
*/
private Properties parseProperties(RawProperties rawProperties) throws IOException {
if (StringUtils.isBlank(rawProperties.getRawProperties())) {
return EMPTY_PROPERTIES;
}
return PropertyUtils.read(rawProperties);
}

/**
* Creates the {@link HttpClient} which is used for fetching the configuration.
*
Expand All @@ -176,10 +193,10 @@ private HttpClient createHttpClient() {
* Fetches the configuration by executing a HTTP request against the configured HTTP endpoint. The request contains
* the 'If-Modified-Since' header if a previous response returned a 'Last-Modified' header.
*
* @return The requests response body representing the configuration in a JSON format. null is returned if request fails or the
* @return The request's response body representing the configuration in a JSON/YAML format and the MIME type. null is returned if request fails or the
* server returns 304 (not modified).
*/
private String fetchConfiguration(boolean fallBackToFile) {
private RawProperties fetchConfiguration(boolean fallBackToFile) {
HttpGet httpGet;
try {
URI uri = getEffectiveRequestUri();
Expand All @@ -200,10 +217,27 @@ private String fetchConfiguration(boolean fallBackToFile) {
setAgentMetaHeaders(httpGet);

String configuration = null;
RawProperties rawProp = null;
boolean isError = true;
try {
HttpResponse response = createHttpClient().execute(httpGet);
// try to retrieve the MIME type of the response
ContentType contentType = null;
String mimeType = null;
HttpEntity entity = response.getEntity();
if (entity != null) {
contentType = ContentType.get(entity);
if (contentType != null) {
mimeType = contentType.getMimeType();
}
}

// get the config from the response
configuration = processHttpResponse(response);
// create raw properties file if the response contains a configuration
if (configuration != null) {
rawProp = new RawProperties(configuration, contentType);
}
isError = false;
if (errorCounter != 0) {
log.info("Configuration fetch has been successful after {} unsuccessful attempts.", errorCounter);
Expand All @@ -219,18 +253,18 @@ private String fetchConfiguration(boolean fallBackToFile) {
httpGet.releaseConnection();
}

if (!isError && configuration != null) {
writePersistenceFile(configuration);
if (!isError && rawProp != null) {
writePersistenceFile(rawProp.getRawProperties());
} else if (isError && fallBackToFile) {
configuration = readPersistenceFile();
rawProp = new RawProperties(configuration, ContentType.TEXT_PLAIN);
}

return configuration;
return rawProp;
}

/**
* Injects all the agent's meta information headers, which should be send when fetching a new configuration,
* into the given request request.
* Injects all the agent's meta information headers, which should be sent when fetching a new configuration,
* into the given request.
*
* @param httpGet the request to inject the meat information headers
*/
Expand Down Expand Up @@ -263,11 +297,14 @@ private void logFetchError(String message, Exception exception) {
* Builds the request URI by combining the base URI with the configured attributes.
*
* @return the resulting URI
*
* @throws URISyntaxException if the base URI is malformed
*/
public URI getEffectiveRequestUri() throws URISyntaxException {
URIBuilder uriBuilder = new URIBuilder(currentSettings.getUrl().toURI());
currentSettings.getAttributes().entrySet().stream()
currentSettings.getAttributes()
.entrySet()
.stream()
.filter(pair -> !StringUtils.isEmpty(pair.getValue()))
.forEach(pair -> uriBuilder.setParameter(pair.getKey(), pair.getValue()));
return uriBuilder.build();
Expand All @@ -278,7 +315,9 @@ public URI getEffectiveRequestUri() throws URISyntaxException {
* If the response contains a 'Last-Modified' header, its value will be stored.
*
* @param response the HTTP response object
*
* @return the response body or null in case server sends 304 (not modified)
*
* @throws IOException if an error occurs reading the input stream or if the server returned an unexpected status code
*/
private String processHttpResponse(HttpResponse response) throws IOException {
Expand Down Expand Up @@ -362,5 +401,4 @@ private String readPersistenceFile() {
return null;
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package rocks.inspectit.ocelot.core.config.propertysources.http;

import lombok.AllArgsConstructor;
import lombok.Value;
import org.apache.http.entity.ContentType;

/**
* Class representing the raw inspectit configuration content used to parse {@link java.util.Properties}
*/
@Value
@AllArgsConstructor
public class RawProperties {

/**
* The raw configuration as a string
*/
private String rawProperties;

/**
* The {@link ContentType} of the configuration string
*/
private ContentType contentType;
}
Loading