diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/common/SidecarPluginConfiguration.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/common/SidecarPluginConfiguration.java index a3c54a8aa141..4f880c84a172 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/common/SidecarPluginConfiguration.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/common/SidecarPluginConfiguration.java @@ -33,7 +33,7 @@ public class SidecarPluginConfiguration implements PluginConfigBean { private Duration cacheTime = Duration.hours(1L); @Parameter(value = PREFIX + "cache_max_size", validator = PositiveIntegerValidator.class) - private int cacheMaxSize = 100; + private int cacheMaxSize = 5000; public Duration getCacheTime() { return cacheTime; diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Configuration.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Configuration.java index ff7238efee70..78f0434a971e 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Configuration.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Configuration.java @@ -25,6 +25,7 @@ import org.mongojack.ObjectId; import javax.annotation.Nullable; +import java.util.Set; @AutoValue @WithBeanGetter @@ -35,6 +36,7 @@ public abstract class Configuration { public static final String FIELD_NAME = "name"; public static final String FIELD_COLOR = "color"; public static final String FIELD_TEMPLATE = "template"; + public static final String FIELD_TAGS = "tags"; @Id @ObjectId @@ -54,30 +56,37 @@ public abstract class Configuration { @JsonProperty(FIELD_TEMPLATE) public abstract String template(); + @JsonProperty(FIELD_TAGS) + public abstract Set tags(); + @JsonCreator public static Configuration create(@JsonProperty(FIELD_ID) String id, @JsonProperty(FIELD_COLLECTOR_ID) String collectorId, @JsonProperty(FIELD_NAME) String name, @JsonProperty(FIELD_COLOR) String color, - @JsonProperty(FIELD_TEMPLATE) String template) { + @JsonProperty(FIELD_TEMPLATE) String template, + @JsonProperty(FIELD_TAGS) @Nullable Set tags) { return builder() .id(id) .collectorId(collectorId) .name(name) .color(color) .template(template) + .tags(tags == null ? Set.of() : tags) .build(); } - public static Configuration create(String collectorId, - String name, - String color, - String template) { + public static Configuration createWithoutId(String collectorId, + String name, + String color, + String template, + Set tags) { return create(new org.bson.types.ObjectId().toHexString(), collectorId, name, color, - template); + template, + tags); } public static Builder builder() { @@ -98,6 +107,8 @@ public abstract static class Builder { public abstract Builder template(String template); + public abstract Builder tags(Set tags); + public abstract Configuration build(); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/ConfigurationSummary.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/ConfigurationSummary.java index 4a1f407fb0e9..7d88d65e5baf 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/ConfigurationSummary.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/ConfigurationSummary.java @@ -22,6 +22,8 @@ import org.mongojack.Id; import org.mongojack.ObjectId; +import java.util.Set; + @AutoValue public abstract class ConfigurationSummary { @JsonProperty("id") @@ -38,12 +40,16 @@ public abstract class ConfigurationSummary { @JsonProperty("color") public abstract String color(); + @JsonProperty("tags") + public abstract Set tags(); + @JsonCreator public static ConfigurationSummary create(@JsonProperty("id") @Id @ObjectId String id, @JsonProperty("name") String name, @JsonProperty("collector_id") String collectorId, - @JsonProperty("color") String color) { - return new AutoValue_ConfigurationSummary(id, name, collectorId, color); + @JsonProperty("color") String color, + @JsonProperty("tags") Set tags) { + return new AutoValue_ConfigurationSummary(id, name, collectorId, color, tags); } public static ConfigurationSummary create(Configuration configuration) { @@ -51,7 +57,8 @@ public static ConfigurationSummary create(Configuration configuration) { configuration.id(), configuration.name(), configuration.collectorId(), - configuration.color()); + configuration.color(), + configuration.tags()); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/NodeDetails.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/NodeDetails.java index 8439b69f5cc9..9418b7faeab4 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/NodeDetails.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/NodeDetails.java @@ -18,6 +18,7 @@ import com.fasterxml.jackson.annotation.JsonAutoDetect; import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonProperty; import com.google.auto.value.AutoValue; @@ -29,6 +30,7 @@ @AutoValue @JsonAutoDetect +@JsonIgnoreProperties(ignoreUnknown = true) public abstract class NodeDetails { @JsonProperty("operating_system") @NotNull @@ -52,7 +54,6 @@ public abstract class NodeDetails { public abstract CollectorStatusList statusList(); @JsonProperty("tags") - @Nullable public abstract Set tags(); @JsonProperty("collector_configuration_directory") @@ -67,6 +68,6 @@ public static NodeDetails create(@JsonProperty("operating_system") String operat @JsonProperty("status") @Nullable CollectorStatusList statusList, @JsonProperty("tags") @Nullable Set tags, @JsonProperty("collector_configuration_directory") @Nullable String configDir) { - return new AutoValue_NodeDetails(operatingSystem, ip, metrics, logFileList, statusList, tags, configDir); + return new AutoValue_NodeDetails(operatingSystem, ip, metrics, logFileList, statusList, tags == null ? Set.of() : tags, configDir); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Sidecar.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Sidecar.java index 76777511cb5d..bfb4909d597c 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Sidecar.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/models/Sidecar.java @@ -92,7 +92,6 @@ public static Status fromString(String statusString) { public abstract NodeDetails nodeDetails(); @JsonProperty - @Nullable public abstract List assignments(); @JsonProperty @@ -133,7 +132,7 @@ public static Sidecar create(@JsonProperty(FIELD_ID) @Id @ObjectId String id, .nodeId(nodeId) .nodeName(nodeName) .nodeDetails(nodeDetails) - .assignments(assignments) + .assignments(assignments == null ? List.of() : assignments) .sidecarVersion(sidecarVersion) .lastSeen(lastSeen) .build(); @@ -151,6 +150,7 @@ public static Sidecar create(@JsonProperty(FIELD_NODE_ID) String nodeId, .nodeDetails(nodeDetails) .sidecarVersion(sidecarVersion) .lastSeen(DateTime.now(DateTimeZone.UTC)) + .assignments(List.of()) .build(); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/requests/ConfigurationAssignment.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/requests/ConfigurationAssignment.java index 1aa264919aaa..ee14796e310e 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/requests/ConfigurationAssignment.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/requests/ConfigurationAssignment.java @@ -21,6 +21,9 @@ import com.fasterxml.jackson.annotation.JsonProperty; import com.google.auto.value.AutoValue; +import javax.annotation.Nullable; +import java.util.Set; + @AutoValue @JsonAutoDetect public abstract class ConfigurationAssignment { @@ -30,9 +33,13 @@ public abstract class ConfigurationAssignment { @JsonProperty public abstract String configurationId(); + @JsonProperty + public abstract Set assignedFromTags(); + @JsonCreator public static ConfigurationAssignment create(@JsonProperty("collector_id") String collectorId, - @JsonProperty("configuration_id") String configurationId) { - return new AutoValue_ConfigurationAssignment(collectorId, configurationId); + @JsonProperty("configuration_id") String configurationId, + @JsonProperty("assigned_from_tags") @Nullable Set assignedFromTags) { + return new AutoValue_ConfigurationAssignment(collectorId, configurationId, assignedFromTags == null ? Set.of() : assignedFromTags); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/AdministrationResource.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/AdministrationResource.java index 32b30f0d7d25..951aa770faa7 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/AdministrationResource.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/AdministrationResource.java @@ -44,6 +44,7 @@ import org.graylog.plugins.sidecar.services.ActionService; import org.graylog.plugins.sidecar.services.CollectorService; import org.graylog.plugins.sidecar.services.ConfigurationService; +import org.graylog.plugins.sidecar.services.EtagService; import org.graylog.plugins.sidecar.services.SidecarService; import org.graylog.plugins.sidecar.system.SidecarConfiguration; import org.graylog2.audit.jersey.AuditEvent; @@ -90,6 +91,7 @@ public class AdministrationResource extends RestResource implements PluginRestRe private final AdministrationFiltersFactory administrationFiltersFactory; private final ActiveSidecarFilter activeSidecarFilter; private final SidecarStatusMapper sidecarStatusMapper; + private final EtagService etagService; @Inject public AdministrationResource(SidecarService sidecarService, @@ -98,7 +100,8 @@ public AdministrationResource(SidecarService sidecarService, ActionService actionService, AdministrationFiltersFactory administrationFiltersFactory, ClusterConfigService clusterConfigService, - SidecarStatusMapper sidecarStatusMapper) { + SidecarStatusMapper sidecarStatusMapper, + EtagService etagService) { final SidecarConfiguration sidecarConfiguration = clusterConfigService.getOrDefault(SidecarConfiguration.class, SidecarConfiguration.defaultConfiguration()); this.sidecarService = sidecarService; this.configurationService = configurationService; @@ -106,6 +109,7 @@ public AdministrationResource(SidecarService sidecarService, this.actionService = actionService; this.administrationFiltersFactory = administrationFiltersFactory; this.sidecarStatusMapper = sidecarStatusMapper; + this.etagService = etagService; this.activeSidecarFilter = new ActiveSidecarFilter(sidecarConfiguration.sidecarInactiveThreshold()); this.searchQueryParser = new SearchQueryParser(Sidecar.FIELD_NODE_NAME, SidecarResource.SEARCH_FIELD_MAPPING); } @@ -166,6 +170,7 @@ public Response setAction(@ApiParam(name = "JSON body", required = true) .collect(Collectors.toList()); final CollectorActions collectorActions = actionService.fromRequest(bulkActionRequest.sidecarId(), actions); actionService.saveAction(collectorActions); + etagService.invalidateRegistration(bulkActionRequest.sidecarId()); } return Response.accepted().build(); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/CollectorResource.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/CollectorResource.java index e35ffe32904f..517e4ed25076 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/CollectorResource.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/CollectorResource.java @@ -17,9 +17,9 @@ package org.graylog.plugins.sidecar.rest.resources; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; -import com.google.common.hash.Hashing; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -136,15 +136,15 @@ public Collector getCollector(@ApiParam(name = "id", required = true) @RequiresPermissions(SidecarRestPermissions.COLLECTORS_READ) @Produces(MediaType.APPLICATION_JSON) @ApiOperation(value = "List all collectors") - public Response listCollectors(@Context HttpHeaders httpHeaders) { + public Response listCollectors(@Context HttpHeaders httpHeaders) throws JsonProcessingException { String ifNoneMatch = httpHeaders.getHeaderString("If-None-Match"); Boolean etagCached = false; Response.ResponseBuilder builder = Response.noContent(); - // check if client is up to date with a known valid etag + // check if client is up-to-date with a known valid etag if (ifNoneMatch != null) { EntityTag etag = new EntityTag(ifNoneMatch.replaceAll("\"", "")); - if (etagService.isPresent(etag.toString())) { + if (etagService.collectorsAreCached(etag.toString())) { etagCached = true; builder = Response.notModified(); builder.tag(etag); @@ -157,12 +157,10 @@ public Response listCollectors(@Context HttpHeaders httpHeaders) { CollectorListResponse collectorListResponse = CollectorListResponse.create(result.size(), result); // add new etag to cache - String etagString = collectorsToEtag(collectorListResponse); - - EntityTag collectorsEtag = new EntityTag(etagString); + EntityTag collectorsEtag = etagService.buildEntityTagForResponse(collectorListResponse); builder = Response.ok(collectorListResponse); builder.tag(collectorsEtag); - etagService.put(collectorsEtag.toString()); + etagService.registerCollector(collectorsEtag.toString()); } // set cache control @@ -211,7 +209,7 @@ public Response createCollector(@ApiParam(name = "JSON body", required = true) if (validationResult.failed()) { return Response.status(Response.Status.BAD_REQUEST).entity(validationResult).build(); } - etagService.invalidateAll(); + etagService.invalidateAllCollectors(); return Response.ok().entity(collectorService.save(collector)).build(); } @@ -230,7 +228,7 @@ public Response updateCollector(@ApiParam(name = "id", required = true) if (validationResult.failed()) { return Response.status(Response.Status.BAD_REQUEST).entity(validationResult).build(); } - etagService.invalidateAll(); + etagService.invalidateAllCollectors(); return Response.ok().entity(collectorService.save(collector)).build(); } @@ -248,8 +246,8 @@ public Response copyCollector(@ApiParam(name = "id", required = true) if (validationResult.failed()) { return Response.status(Response.Status.BAD_REQUEST).entity(validationResult).build(); } - etagService.invalidateAll(); collectorService.save(collector); + etagService.invalidateAllCollectors(); return Response.accepted().build(); } @@ -272,7 +270,7 @@ public Response deleteCollector(@ApiParam(name = "id", required = true) if (deleted == 0) { return Response.notModified().build(); } - etagService.invalidateAll(); + etagService.invalidateAllCollectors(); return Response.accepted().build(); } @@ -351,9 +349,4 @@ private boolean validatePath(String path) { return VALID_PATH_PATTERN.matcher(path).matches(); } - private String collectorsToEtag(CollectorListResponse collectors) { - return Hashing.md5() - .hashInt(collectors.hashCode()) // avoid negative values - .toString(); - } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationResource.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationResource.java index 238f7e2a9bb7..808bfe08e0f0 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationResource.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationResource.java @@ -17,8 +17,8 @@ package org.graylog.plugins.sidecar.rest.resources; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.collect.ImmutableMap; -import com.google.common.hash.Hashing; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; @@ -207,15 +207,15 @@ public Response renderConfiguration(@Context HttpHeaders httpHeaders, @ApiParam(name = "sidecarId", required = true) @PathParam("sidecarId") String sidecarId, @ApiParam(name = "configurationId", required = true) - @PathParam("configurationId") String configurationId) throws RenderTemplateException { + @PathParam("configurationId") String configurationId) throws RenderTemplateException, JsonProcessingException { String ifNoneMatch = httpHeaders.getHeaderString("If-None-Match"); - Boolean etagCached = false; + boolean etagCached = false; Response.ResponseBuilder builder = Response.noContent(); - // check if client is up to date with a known valid etag + // check if client is up-to-date with a known valid etag if (ifNoneMatch != null) { EntityTag etag = new EntityTag(ifNoneMatch.replaceAll("\"", "")); - if (etagService.isPresent(etag.toString())) { + if (etagService.configurationsAreCached(etag.toString())) { etagCached = true; builder = Response.notModified(); builder.tag(etag); @@ -236,13 +236,10 @@ public Response renderConfiguration(@Context HttpHeaders httpHeaders, Configuration collectorConfiguration = this.configurationService.renderConfigurationForCollector(sidecar, configuration); // add new etag to cache - String etagString = configurationToEtag(collectorConfiguration); - - EntityTag collectorConfigurationEtag = new EntityTag(etagString); + EntityTag collectorConfigurationEtag = etagService.buildEntityTagForResponse(collectorConfiguration); builder = Response.ok(collectorConfiguration); builder.tag(collectorConfigurationEtag); - etagService.put(collectorConfigurationEtag.toString()); - + etagService.registerConfiguration(collectorConfigurationEtag.toString()); } // set cache control @@ -283,7 +280,12 @@ public Response createConfiguration(@ApiParam(name = "JSON body", required = tru return Response.status(Response.Status.BAD_REQUEST).entity(validationResult).build(); } - return Response.ok().entity(configurationService.save(configuration)).build(); + final Configuration config = configurationService.save(configuration); + if (!config.tags().isEmpty()) { + etagService.invalidateAllRegistrations(); + } + + return Response.ok().entity(config).build(); } @POST @@ -330,7 +332,10 @@ public Response updateConfiguration(@ApiParam(name = "id", required = true) if (validationResult.failed()) { return Response.status(Response.Status.BAD_REQUEST).entity(validationResult).build(); } - etagService.invalidateAll(); + etagService.invalidateAllConfigurations(); + if (! previousConfiguration.tags().equals(updatedConfiguration.tags())) { + etagService.invalidateAllRegistrations(); + } return Response.ok().entity(configurationService.save(updatedConfiguration)).build(); } @@ -351,7 +356,7 @@ public Response deleteConfiguration(@ApiParam(name = "id", required = true) if (deleted == 0) { return Response.notModified().build(); } - etagService.invalidateAll(); + etagService.invalidateAllConfigurations(); return Response.accepted().build(); } @@ -405,12 +410,6 @@ private boolean isConfigurationAssignedToSidecar(String configurationId, Sidecar return assignments.stream().anyMatch(assignment -> assignment.configurationId().equals(configurationId)); } - private String configurationToEtag(Configuration configuration) { - return Hashing.md5() - .hashInt(configuration.hashCode()) // avoid negative values - .toString(); - } - private Configuration configurationFromRequest(String id, Configuration request) { Configuration configuration; if (id == null) { diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationVariableResource.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationVariableResource.java index 04ea7c9df735..a1019ecbb27e 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationVariableResource.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/ConfigurationVariableResource.java @@ -132,7 +132,7 @@ public Response updateConfigurationVariable(@ApiParam(name = "id", required = tr configurationService.replaceVariableNames(previousConfigurationVariable.fullName(), request.fullName()); } final ConfigurationVariable updatedConfigurationVariable = persistConfigurationVariable(id, request); - etagService.invalidateAll(); + etagService.invalidateAllConfigurations(); return Response.ok().entity(updatedConfigurationVariable).build(); } @@ -168,7 +168,7 @@ public Response deleteConfigurationVariable(@ApiParam(name = "id", required = tr if (deleted == 0) { return Response.notModified().build(); } - etagService.invalidateAll(); + etagService.invalidateAllConfigurations(); return Response.accepted().build(); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/SidecarResource.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/SidecarResource.java index 74f5b7b473a0..6cfbbc693991 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/SidecarResource.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/rest/resources/SidecarResource.java @@ -17,6 +17,7 @@ package org.graylog.plugins.sidecar.rest.resources; import com.codahale.metrics.annotation.Timed; +import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.base.Function; import com.google.common.collect.ImmutableMap; import io.swagger.annotations.Api; @@ -42,6 +43,7 @@ import org.graylog.plugins.sidecar.rest.responses.RegistrationResponse; import org.graylog.plugins.sidecar.rest.responses.SidecarListResponse; import org.graylog.plugins.sidecar.services.ActionService; +import org.graylog.plugins.sidecar.services.EtagService; import org.graylog.plugins.sidecar.services.SidecarService; import org.graylog.plugins.sidecar.system.SidecarConfiguration; import org.graylog2.audit.jersey.AuditEvent; @@ -53,12 +55,12 @@ import org.graylog2.search.SearchQueryField; import org.graylog2.search.SearchQueryParser; import org.graylog2.shared.rest.resources.RestResource; -import org.hibernate.validator.constraints.NotEmpty; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import javax.inject.Inject; import javax.validation.Valid; +import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotNull; import javax.ws.rs.BadRequestException; import javax.ws.rs.Consumes; @@ -71,6 +73,7 @@ import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; +import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import java.util.List; @@ -99,6 +102,7 @@ public class SidecarResource extends RestResource implements PluginRestResource private final SidecarService sidecarService; private final ActionService actionService; + private final EtagService etagService; private final ActiveSidecarFilter activeSidecarFilter; private final SearchQueryParser searchQueryParser; private final SidecarStatusMapper sidecarStatusMapper; @@ -108,12 +112,14 @@ public class SidecarResource extends RestResource implements PluginRestResource public SidecarResource(SidecarService sidecarService, ActionService actionService, ClusterConfigService clusterConfigService, - SidecarStatusMapper sidecarStatusMapper) { + SidecarStatusMapper sidecarStatusMapper, + EtagService etagService) { this.sidecarService = sidecarService; this.sidecarConfiguration = clusterConfigService.getOrDefault(SidecarConfiguration.class, SidecarConfiguration.defaultConfiguration()); this.actionService = actionService; this.activeSidecarFilter = new ActiveSidecarFilter(sidecarConfiguration.sidecarInactiveThreshold()); this.sidecarStatusMapper = sidecarStatusMapper; + this.etagService = etagService; this.searchQueryParser = new SearchQueryParser(Sidecar.FIELD_NODE_NAME, SEARCH_FIELD_MAPPING); } @@ -195,39 +201,55 @@ public SidecarSummary get(@ApiParam(name = "sidecarId", required = true) @RequiresPermissions(SidecarRestPermissions.SIDECARS_UPDATE) @NoAuditEvent("this is only a ping from Sidecars, and would overflow the audit log") public Response register(@ApiParam(name = "sidecarId", value = "The id this Sidecar is registering as.", required = true) - @PathParam("sidecarId") @NotEmpty String sidecarId, + @PathParam("sidecarId") @NotEmpty String nodeId, @ApiParam(name = "JSON body", required = true) @Valid @NotNull RegistrationRequest request, - @HeaderParam(value = "X-Graylog-Sidecar-Version") @NotEmpty String sidecarVersion) { - final Sidecar newSidecar; - final Sidecar oldSidecar = sidecarService.findByNodeId(sidecarId); - List assignments = null; + @HeaderParam(value = "If-None-Match") String ifNoneMatch, + @HeaderParam(value = "X-Graylog-Sidecar-Version") @NotEmpty String sidecarVersion) throws JsonProcessingException { + + Sidecar sidecar; + final Sidecar oldSidecar = sidecarService.findByNodeId(nodeId); if (oldSidecar != null) { - assignments = oldSidecar.assignments(); - newSidecar = oldSidecar.toBuilder() + sidecar = oldSidecar.toBuilder() .nodeName(request.nodeName()) .nodeDetails(request.nodeDetails()) .sidecarVersion(sidecarVersion) .lastSeen(DateTime.now(DateTimeZone.UTC)) .build(); } else { - newSidecar = sidecarService.fromRequest(sidecarId, request, sidecarVersion); + sidecar = sidecarService.fromRequest(nodeId, request, sidecarVersion); } - sidecarService.save(newSidecar); - final CollectorActions collectorActions = actionService.findActionBySidecar(sidecarId, true); + // If the sidecar has the recent registration, return with HTTP 304 + if (ifNoneMatch != null) { + EntityTag etag = new EntityTag(ifNoneMatch.replaceAll("\"", "")); + if (etagService.registrationIsCached(sidecar.id(), etag.toString())) { + sidecarService.save(sidecar); + return Response.notModified().tag(etag).build(); + } + } + + final Sidecar updated = sidecarService.updateTaggedConfigurationAssignments(sidecar); + sidecarService.save(updated); + sidecar = updated; + + final CollectorActions collectorActions = actionService.findActionBySidecar(nodeId, true); List collectorAction = null; if (collectorActions != null) { collectorAction = collectorActions.action(); } RegistrationResponse sidecarRegistrationResponse = RegistrationResponse.create( SidecarRegistrationConfiguration.create( - this.sidecarConfiguration.sidecarUpdateInterval().toStandardDuration().getStandardSeconds(), - this.sidecarConfiguration.sidecarSendStatus()), - this.sidecarConfiguration.sidecarConfigurationOverride(), + sidecarConfiguration.sidecarUpdateInterval().toStandardDuration().getStandardSeconds(), + sidecarConfiguration.sidecarSendStatus()), + sidecarConfiguration.sidecarConfigurationOverride(), collectorAction, - assignments); - return Response.accepted(sidecarRegistrationResponse).build(); + sidecar.assignments()); + // add new etag to cache + EntityTag registrationEtag = etagService.buildEntityTagForResponse(sidecarRegistrationResponse); + etagService.addSidecarRegistration(sidecar.id(), registrationEtag.toString()); + + return Response.accepted(sidecarRegistrationResponse).tag(registrationEtag).build(); } @PUT @@ -249,8 +271,9 @@ public Response assignConfiguration(@ApiParam(name = "JSON body", required = tru .flatMap(a -> a.assignments().stream()) .collect(Collectors.toList()); try { - Sidecar sidecar = sidecarService.assignConfiguration(nodeId, nodeRelations); + Sidecar sidecar = sidecarService.applyManualAssignments(nodeId, nodeRelations); sidecarService.save(sidecar); + etagService.invalidateRegistration(sidecar.id()); } catch (org.graylog2.database.NotFoundException e) { throw new NotFoundException(e.getMessage()); } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/ConfigurationService.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/ConfigurationService.java index cccf81573014..da1ef990d41a 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/ConfigurationService.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/ConfigurationService.java @@ -17,6 +17,7 @@ package org.graylog.plugins.sidecar.services; import com.mongodb.BasicDBObject; +import com.mongodb.DBCollection; import freemarker.cache.MultiTemplateLoader; import freemarker.cache.StringTemplateLoader; import freemarker.cache.TemplateLoader; @@ -47,6 +48,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.regex.Pattern; import java.util.stream.Collectors; @@ -60,7 +62,7 @@ public class ConfigurationService extends PaginatedDbService { private static final freemarker.template.Configuration templateConfiguration = new freemarker.template.Configuration(freemarker.template.Configuration.VERSION_2_3_28); private static final StringTemplateLoader stringTemplateLoader = new StringTemplateLoader(); - private ConfigurationVariableService configurationVariableService; + private final ConfigurationVariableService configurationVariableService; private static final String COLLECTION_NAME = "sidecar_configurations"; @@ -70,6 +72,7 @@ public ConfigurationService(MongoConnection mongoConnection, ConfigurationVariableService configurationVariableService) { super(mongoConnection, mapper, Configuration.class, COLLECTION_NAME); MongoDbTemplateLoader mongoDbTemplateLoader = new MongoDbTemplateLoader(db); + DBCollection collection = db.getDB().getCollection(COLLECTION_NAME); MultiTemplateLoader multiTemplateLoader = new MultiTemplateLoader(new TemplateLoader[] { mongoDbTemplateLoader, stringTemplateLoader }); @@ -79,6 +82,10 @@ public ConfigurationService(MongoConnection mongoConnection, templateConfiguration.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); templateConfiguration.setLogTemplateExceptions(false); this.configurationVariableService = configurationVariableService; + + collection.createIndex(new BasicDBObject(Configuration.FIELD_ID, 1)); + collection.createIndex(new BasicDBObject(Configuration.FIELD_COLLECTOR_ID, 1)); + collection.createIndex(new BasicDBObject(Configuration.FIELD_TAGS, 1)); } public Configuration find(String id) { @@ -116,6 +123,10 @@ public List findByConfigurationVariable(ConfigurationVariable con return findByQuery(query); } + public List findByTags(Set tags) { + return findByQuery(DBQuery.in(Configuration.FIELD_TAGS, tags)); + } + public void replaceVariableNames(String oldName, String newName) { final DBQuery.Query query = DBQuery.regex(Configuration.FIELD_TEMPLATE, Pattern.compile(Pattern.quote(oldName))); List configurations = findByQuery(query); @@ -134,15 +145,17 @@ public Configuration save(Configuration configuration) { public Configuration copyConfiguration(String id, String name) { Configuration configuration = find(id); - return Configuration.create(configuration.collectorId(), name, configuration.color(), configuration.template()); + // Tags are not copied on purpose + return Configuration.createWithoutId(configuration.collectorId(), name, configuration.color(), configuration.template(), Set.of()); } public Configuration fromRequest(Configuration request) { - return Configuration.create( + return Configuration.createWithoutId( request.collectorId(), request.name(), request.color(), - request.template()); + request.template(), + request.tags()); } public Configuration fromRequest(String id, Configuration request) { @@ -151,7 +164,8 @@ public Configuration fromRequest(String id, Configuration request) { request.collectorId(), request.name(), request.color(), - request.template()); + request.template(), + request.tags()); } public Configuration renderConfigurationForCollector(Sidecar sidecar, Configuration configuration) throws RenderTemplateException { @@ -165,13 +179,15 @@ public Configuration renderConfigurationForCollector(Sidecar sidecar, Configurat String pathDelim = sidecar.nodeDetails().operatingSystem().equalsIgnoreCase("windows") ? "\\" : "/"; context.put("spoolDir", sidecar.nodeDetails().collectorConfigurationDirectory() + pathDelim + configuration.id()); } + context.put("tags", sidecar.nodeDetails().tags().stream().collect(Collectors.toMap(t -> t, t -> true))); return Configuration.create( configuration.id(), configuration.collectorId(), configuration.name(), configuration.color(), - renderTemplate(configuration.id(), context) + renderTemplate(configuration.id(), context), + configuration.tags() ); } @@ -182,6 +198,7 @@ public String renderPreview(String template) throws RenderTemplateException { context.put("sidecarVersion", ""); context.put("operatingSystem", ""); context.put("spoolDir", ""); + context.put("tags", Map.of()); String previewName = UUID.randomUUID().toString(); stringTemplateLoader.putTemplate(previewName, template); diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagCacheInvalidation.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagCacheInvalidation.java index 9d6123dd1dbe..5d249ff8a464 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagCacheInvalidation.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagCacheInvalidation.java @@ -23,12 +23,15 @@ @AutoValue public abstract class EtagCacheInvalidation { - @JsonProperty("etag") - public abstract String etag(); + @JsonProperty("cache_context") + public abstract EtagService.CacheContext cacheContext(); + + @JsonProperty("cache_key") + public abstract String cacheKey(); @JsonCreator - public static EtagCacheInvalidation etag(@JsonProperty("etag") String etag) { - return new AutoValue_EtagCacheInvalidation(etag); + public static EtagCacheInvalidation create(@JsonProperty("cache_context") EtagService.CacheContext context, @JsonProperty("cache_key") String cacheKey) { + return new AutoValue_EtagCacheInvalidation(context, cacheKey); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagService.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagService.java index 489df2c00bcf..a833c4761f52 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagService.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/EtagService.java @@ -17,21 +17,29 @@ package org.graylog.plugins.sidecar.services; import com.codahale.metrics.MetricRegistry; +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import com.github.joschi.jadconfig.util.Duration; import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; import com.google.common.eventbus.EventBus; import com.google.common.eventbus.Subscribe; +import com.google.common.hash.Hashing; import com.google.common.util.concurrent.AbstractIdleService; import org.graylog.plugins.sidecar.common.SidecarPluginConfiguration; import org.graylog2.events.ClusterEventBus; import org.graylog2.metrics.CacheStatsSet; +import org.graylog2.shared.bindings.providers.ObjectMapperProvider; import org.graylog2.shared.metrics.MetricUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.inject.Inject; import javax.inject.Singleton; +import javax.ws.rs.core.EntityTag; +import java.nio.charset.StandardCharsets; import static com.codahale.metrics.MetricRegistry.name; @@ -39,21 +47,48 @@ public class EtagService extends AbstractIdleService { private static final Logger LOG = LoggerFactory.getLogger(EtagService.class); - private final Cache cache; - private MetricRegistry metricRegistry; - private EventBus eventBus; - private ClusterEventBus clusterEventBus; + private final Cache collectorCache; + private final Cache configurationCache; + private final Cache registrationCache; + private final MetricRegistry metricRegistry; + private final EventBus eventBus; + private final ClusterEventBus clusterEventBus; + private final ObjectMapper objectMapper; + + @JsonAutoDetect + enum CacheContext { + @JsonProperty("collector") + COLLECTOR, + @JsonProperty("configuration") + CONFIGURATION, + @JsonProperty("registration") + REGISTRATION + } @Inject public EtagService(SidecarPluginConfiguration pluginConfiguration, MetricRegistry metricRegistry, EventBus eventBus, - ClusterEventBus clusterEventBus) { + ClusterEventBus clusterEventBus, + ObjectMapperProvider objectMapperProvider) { this.metricRegistry = metricRegistry; this.eventBus = eventBus; this.clusterEventBus = clusterEventBus; + this.objectMapper = objectMapperProvider.get(); Duration cacheTime = pluginConfiguration.getCacheTime(); - cache = CacheBuilder.newBuilder() + collectorCache = CacheBuilder.newBuilder() + .recordStats() + .expireAfterWrite(cacheTime.getQuantity(), cacheTime.getUnit()) + .maximumSize(pluginConfiguration.getCacheMaxSize()) + .build(); + + configurationCache = CacheBuilder.newBuilder() + .recordStats() + .expireAfterWrite(cacheTime.getQuantity(), cacheTime.getUnit()) + .maximumSize(pluginConfiguration.getCacheMaxSize()) + .build(); + + registrationCache = CacheBuilder.newBuilder() .recordStats() .expireAfterWrite(cacheTime.getQuantity(), cacheTime.getUnit()) .maximumSize(pluginConfiguration.getCacheMaxSize()) @@ -62,41 +97,84 @@ public EtagService(SidecarPluginConfiguration pluginConfiguration, @Subscribe public void handleEtagInvalidation(EtagCacheInvalidation event) { - if (event.etag().equals("")) { - LOG.trace("Invalidating all collector configuration etags"); + var cache = switch (event.cacheContext()) { + case COLLECTOR -> collectorCache; + case CONFIGURATION -> configurationCache; + case REGISTRATION -> registrationCache; + }; + + if (event.cacheKey().equals("")) { + LOG.trace("Invalidating {} cache for all keys", event.cacheContext()); cache.invalidateAll(); } else { - LOG.trace("Invalidating collector configuration etag {}", event.etag()); - cache.invalidate(event.etag()); + LOG.trace("Invalidating {} cache for cacheKey {}", event.cacheContext(), event.cacheKey()); + cache.invalidate(event.cacheKey()); } } - public boolean isPresent(String etag) { - return cache.getIfPresent(etag) != null; + public boolean collectorsAreCached(String etag) { + return collectorCache.getIfPresent(etag) != null; + } + + public boolean configurationsAreCached(String etag) { + return configurationCache.getIfPresent(etag) != null; + } + + public boolean registrationIsCached(String sidecarId, String etag) { + return etag.equals(registrationCache.getIfPresent(sidecarId)); + } + + public void registerCollector(String etag) { + collectorCache.put(etag, Boolean.TRUE); + } + + public void registerConfiguration(String etag) { + configurationCache.put(etag, Boolean.TRUE); + } + + public void addSidecarRegistration(String sidecarId, String etag) { + registrationCache.put(sidecarId, etag); + } + + + public void invalidateAllConfigurations() { + configurationCache.invalidateAll(); + clusterEventBus.post(EtagCacheInvalidation.create(CacheContext.CONFIGURATION, "")); + } + + public void invalidateAllCollectors() { + collectorCache.invalidateAll(); + clusterEventBus.post(EtagCacheInvalidation.create(CacheContext.COLLECTOR, "")); } - public void put(String etag) { - cache.put(etag, Boolean.TRUE); + public void invalidateAllRegistrations() { + registrationCache.invalidateAll(); + clusterEventBus.post(EtagCacheInvalidation.create(CacheContext.REGISTRATION, "")); } - public void invalidate(String etag) { - clusterEventBus.post(EtagCacheInvalidation.etag(etag)); + public void invalidateRegistration(String sidecarId) { + registrationCache.invalidate(sidecarId); + clusterEventBus.post(EtagCacheInvalidation.create(CacheContext.REGISTRATION, sidecarId)); } - public void invalidateAll() { - cache.invalidateAll(); - clusterEventBus.post(EtagCacheInvalidation.etag("")); + public EntityTag buildEntityTagForResponse(Object o) throws JsonProcessingException { + final String json = objectMapper.writeValueAsString(o); + return new EntityTag(Hashing.murmur3_128().hashString(json, StandardCharsets.UTF_8).toString()); } @Override protected void startUp() throws Exception { eventBus.register(this); - MetricUtils.safelyRegisterAll(metricRegistry, new CacheStatsSet(name(ConfigurationService.class, "etag-cache"), cache)); + MetricUtils.safelyRegisterAll(metricRegistry, new CacheStatsSet(name(ConfigurationService.class, "etag-cache"), configurationCache)); + MetricUtils.safelyRegisterAll(metricRegistry, new CacheStatsSet(name(CollectorService.class, "etag-cache"), collectorCache)); + MetricUtils.safelyRegisterAll(metricRegistry, new CacheStatsSet(name(SidecarService.class, "etag-cache"), registrationCache)); } @Override protected void shutDown() throws Exception { eventBus.unregister(this); metricRegistry.removeMatching((name, metric) -> name.startsWith(name(ConfigurationService.class, "etag-cache"))); + metricRegistry.removeMatching((name, metric) -> name.startsWith(name(CollectorService.class, "etag-cache"))); + metricRegistry.removeMatching((name, metric) -> name.startsWith(name(SidecarService.class, "etag-cache"))); } } diff --git a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/SidecarService.java b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/SidecarService.java index 865ceadce079..8675cea6994a 100644 --- a/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/SidecarService.java +++ b/graylog2-server/src/main/java/org/graylog/plugins/sidecar/services/SidecarService.java @@ -16,8 +16,10 @@ */ package org.graylog.plugins.sidecar.services; +import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableSet; import com.mongodb.BasicDBObject; +import org.apache.commons.collections4.CollectionUtils; import org.graylog.plugins.sidecar.rest.models.Collector; import org.graylog.plugins.sidecar.rest.models.CollectorStatus; import org.graylog.plugins.sidecar.rest.models.CollectorStatusList; @@ -42,6 +44,8 @@ import javax.inject.Inject; import javax.validation.ConstraintViolation; import javax.validation.Validator; +import java.util.ArrayList; +import java.util.Collection; import java.util.List; import java.util.Set; import java.util.function.Predicate; @@ -75,23 +79,49 @@ public long count() { @Override public Sidecar save(Sidecar sidecar) { - if (sidecar != null) { - final Set> violations = validator.validate(sidecar); - if (violations.isEmpty()) { - return db.findAndModify( - DBQuery.is(Sidecar.FIELD_NODE_ID, sidecar.nodeId()), - new BasicDBObject(), - new BasicDBObject(), - false, - sidecar, - true, - true); - } else { - throw new IllegalArgumentException("Specified object failed validation: " + violations); - } - } else { - throw new IllegalArgumentException("Specified object is not of correct implementation type (" + sidecar.getClass() + ")!"); + Preconditions.checkNotNull(sidecar, "sidecar was null"); + + final Set> violations = validator.validate(sidecar); + if (!violations.isEmpty()) { + throw new IllegalArgumentException("Specified object failed validation: " + violations); } + + return db.findAndModify( + DBQuery.is(Sidecar.FIELD_NODE_ID, sidecar.nodeId()), + new BasicDBObject(), + new BasicDBObject(), + false, + sidecar, + true, + true); + } + + // Create new assignments based on tags and existing manual assignments' + public Sidecar updateTaggedConfigurationAssignments(Sidecar sidecar) { + final Set sidecarTags = sidecar.nodeDetails().tags(); + + // find all configurations that match the tags + // TODO only find configs that match the right collector os + final List taggedConfigs = configurationService.findByTags(sidecarTags); + + final List tagAssigned = taggedConfigs.stream().map(c -> { + // fill in ConfigurationAssignment.assignedFromTags() + // If we only support one tag on a configuration, this can be simplified + final Set matchedTags = c.tags().stream().filter(sidecarTags::contains).collect(Collectors.toSet()); + return ConfigurationAssignment.create(c.collectorId(), c.id(), matchedTags); + }).toList(); + + final List manuallyAssigned = sidecar.assignments().stream().filter(a -> { + // also overwrite manually assigned configs that would now be assigned through tags + if (tagAssigned.stream().anyMatch(tagAssignment -> tagAssignment.configurationId().equals(a.configurationId()))) { + return false; + } + return a.assignedFromTags().isEmpty(); + }).toList(); + + // return a sidecar with updated assignments + final Collection union = CollectionUtils.union(manuallyAssigned, tagAssigned); + return sidecar.toBuilder().assignments(new ArrayList<>(union)).build(); } public List all() { @@ -203,7 +233,7 @@ public Sidecar fromRequest(String nodeId, RegistrationRequest request, String co collectorVersion); } - public Sidecar assignConfiguration(String collectorNodeId, List assignments) throws NotFoundException{ + public Sidecar applyManualAssignments(String collectorNodeId, List assignments) throws NotFoundException{ Sidecar sidecar = findByNodeId(collectorNodeId); if (sidecar == null) { throw new NotFoundException("Couldn't find collector with ID " + collectorNodeId); @@ -222,8 +252,16 @@ public Sidecar assignConfiguration(String collectorNodeId, List taggedAssignments = sidecar.assignments().stream().filter(a -> !a.assignedFromTags().isEmpty()).toList(); + final List configIdsAssignedThroughTags = taggedAssignments.stream().map(ConfigurationAssignment::configurationId).toList(); + + final List filteredAssignments = assignments.stream().filter(a -> !configIdsAssignedThroughTags.contains(a.configurationId())).toList(); + final Collection union = CollectionUtils.union(filteredAssignments, taggedAssignments); + Sidecar toSave = sidecar.toBuilder() - .assignments(assignments) + .assignments(new ArrayList<>(union)) .build(); return save(toSave); } diff --git a/graylog2-server/src/main/java/org/graylog2/contentpacks/facades/SidecarCollectorConfigurationFacade.java b/graylog2-server/src/main/java/org/graylog2/contentpacks/facades/SidecarCollectorConfigurationFacade.java index 91dd72e35350..e8d0c24eb3e7 100644 --- a/graylog2-server/src/main/java/org/graylog2/contentpacks/facades/SidecarCollectorConfigurationFacade.java +++ b/graylog2-server/src/main/java/org/graylog2/contentpacks/facades/SidecarCollectorConfigurationFacade.java @@ -91,11 +91,12 @@ public NativeEntity createNativeEntity(Entity entity, private NativeEntity decode(EntityV1 entity, Map parameters) { final SidecarCollectorConfigurationEntity configurationEntity = objectMapper.convertValue(entity.data(), SidecarCollectorConfigurationEntity.class); - final Configuration configuration = Configuration.create( + final Configuration configuration = Configuration.createWithoutId( configurationEntity.collectorId().asString(parameters), configurationEntity.title().asString(parameters), configurationEntity.color().asString(parameters), - configurationEntity.template().asString(parameters)); + configurationEntity.template().asString(parameters), + Set.of()); final Configuration savedConfiguration = configurationService.save(configuration); return NativeEntity.create(entity.id(), savedConfiguration.id(), TYPE_V1, configuration.name(), savedConfiguration); diff --git a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/ConfigurationServiceTest.java b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/ConfigurationServiceTest.java index b02c69dd3974..d2c3fed73837 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/ConfigurationServiceTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/ConfigurationServiceTest.java @@ -33,6 +33,8 @@ import org.mockito.junit.MockitoJUnit; import org.mockito.junit.MockitoRule; +import java.util.Set; + import static org.junit.Assert.assertEquals; import static org.mockito.Mockito.when; @@ -56,7 +58,7 @@ public class ConfigurationServiceTest { private Configuration buildTestConfig(String template) { - return Configuration.create(FILEBEAT_CONF_ID, "collId", "filebeat", "#ffffff", template); + return Configuration.create(FILEBEAT_CONF_ID, "collId", "filebeat", "#ffffff", template, Set.of()); } @Before diff --git a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/SidecarServiceImplTest.java b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/SidecarServiceImplTest.java index 592a6c2581fe..270250ac6e1f 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/SidecarServiceImplTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/SidecarServiceImplTest.java @@ -36,6 +36,8 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; import javax.validation.Validator; import java.util.List; @@ -50,6 +52,9 @@ @UseModules({ObjectMapperModule.class, ValidatorModule.class, TestPasswordSecretModule.class}) public class SidecarServiceImplTest { private static final String collectionName = "sidecars"; + + @Rule + public final MockitoRule mockitoRule = MockitoJUnit.rule(); @Mock private CollectorService collectorService; diff --git a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/rest/SidecarResourceTest.java b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/rest/SidecarResourceTest.java index a832eb4b7b6f..d189da40dbe4 100644 --- a/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/rest/SidecarResourceTest.java +++ b/graylog2-server/src/test/java/org/graylog/plugins/sidecar/collectors/rest/SidecarResourceTest.java @@ -26,6 +26,7 @@ import org.graylog.plugins.sidecar.rest.requests.RegistrationRequest; import org.graylog.plugins.sidecar.rest.resources.SidecarResource; import org.graylog.plugins.sidecar.services.ActionService; +import org.graylog.plugins.sidecar.services.EtagService; import org.graylog.plugins.sidecar.services.SidecarService; import org.graylog.plugins.sidecar.system.SidecarConfiguration; import org.graylog2.plugin.cluster.ClusterConfigService; @@ -38,6 +39,7 @@ import org.mockito.runners.MockitoJUnitRunner; import javax.ws.rs.NotFoundException; +import javax.ws.rs.core.EntityTag; import javax.ws.rs.core.Response; import java.util.List; @@ -45,6 +47,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Matchers.any; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -66,6 +69,9 @@ public class SidecarResourceTest extends RestResourceBaseTest { @Mock private ClusterConfigService clusterConfigService; + @Mock + private EtagService etagService; + @Mock private SidecarConfiguration sidecarConfiguration; @@ -74,11 +80,13 @@ public void setUp() throws Exception { this.sidecars = getDummyCollectorList(); when(clusterConfigService.getOrDefault(SidecarConfiguration.class, SidecarConfiguration.defaultConfiguration())).thenReturn(sidecarConfiguration); when(sidecarConfiguration.sidecarUpdateInterval()).thenReturn(Period.seconds(30)); + when(etagService.buildEntityTagForResponse(any())).thenReturn(new EntityTag("hash browns")); this.resource = new SidecarResource( sidecarService, actionService, clusterConfigService, - statusMapper); + statusMapper, + etagService); } @Test(expected = NotFoundException.class) @@ -118,20 +126,25 @@ private List getDummyCollectorList() { @Test public void testRegister() throws Exception { + final NodeDetails nodeDetails = NodeDetails.create( + "DummyOS 1.0", + null, + null, + null, + null, + null, + null + ); final RegistrationRequest input = RegistrationRequest.create( "nodeName", - NodeDetails.create( - "DummyOS 1.0", - null, - null, - null, - null, - null, - null - ) + nodeDetails + ); + when(sidecarService.fromRequest(any(), any(RegistrationRequest.class), anyString())).thenReturn( + Sidecar.create("nodeId", "name", nodeDetails, "0.0.1") ); + when(sidecarService.updateTaggedConfigurationAssignments(any(Sidecar.class))).thenAnswer(invocation -> invocation.getArgument(0)); - final Response response = this.resource.register("sidecarId", input, "0.0.1"); + final Response response = this.resource.register("sidecarId", input, null, "0.0.1"); assertThat(response).isSuccess(); } @@ -152,7 +165,7 @@ public void testRegisterInvalidCollectorId() throws Exception { ) ); - final Response response = this.resource.register("", invalid, "0.0.1"); + final Response response = this.resource.register("", invalid, null, "0.0.1"); assertThat(response).isError(); assertThat(response).isStatus(Response.Status.BAD_REQUEST); @@ -174,7 +187,7 @@ public void testRegisterInvalidNodeId() throws Exception { ) ); - final Response response = this.resource.register("sidecarId", invalid, "0.0.1"); + final Response response = this.resource.register("sidecarId", invalid, null, "0.0.1"); assertThat(response).isError(); assertThat(response).isStatus(Response.Status.BAD_REQUEST); @@ -188,7 +201,7 @@ public void testRegisterMissingNodeDetails() throws Exception { null ); - final Response response = this.resource.register("sidecarId", invalid, "0.0.1"); + final Response response = this.resource.register("sidecarId", invalid, null, "0.0.1"); assertThat(response).isError(); assertThat(response).isStatus(Response.Status.BAD_REQUEST); @@ -210,7 +223,7 @@ public void testRegisterMissingOperatingSystem() throws Exception { ) ); - final Response response = this.resource.register("sidecarId", invalid, "0.0.1"); + final Response response = this.resource.register("sidecarId", invalid, null, "0.0.1"); assertThat(response).isError(); assertThat(response).isStatus(Response.Status.BAD_REQUEST); diff --git a/graylog2-web-interface/src/components/sidecars/configuration-forms/ConfigurationForm.jsx b/graylog2-web-interface/src/components/sidecars/configuration-forms/ConfigurationForm.jsx index b0d9f2be683f..55fab55a4a4a 100644 --- a/graylog2-web-interface/src/components/sidecars/configuration-forms/ConfigurationForm.jsx +++ b/graylog2-web-interface/src/components/sidecars/configuration-forms/ConfigurationForm.jsx @@ -65,6 +65,7 @@ const ConfigurationForm = createReactClass({ color: configuration.color, collector_id: configuration.collector_id, template: configuration.template || '', + tags: configuration.tags }, }; },