diff --git a/thingsearch/api/src/main/java/org/eclipse/ditto/thingsearch/api/commands/sudo/SudoCountThings.java b/thingsearch/api/src/main/java/org/eclipse/ditto/thingsearch/api/commands/sudo/SudoCountThings.java index 53b27d8f61..3dbc7dd69d 100644 --- a/thingsearch/api/src/main/java/org/eclipse/ditto/thingsearch/api/commands/sudo/SudoCountThings.java +++ b/thingsearch/api/src/main/java/org/eclipse/ditto/thingsearch/api/commands/sudo/SudoCountThings.java @@ -13,24 +13,32 @@ package org.eclipse.ditto.thingsearch.api.commands.sudo; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; import java.util.Objects; import java.util.Optional; +import java.util.Set; import java.util.function.Predicate; +import java.util.stream.Collectors; import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; -import org.eclipse.ditto.json.JsonFactory; -import org.eclipse.ditto.json.JsonField; -import org.eclipse.ditto.json.JsonFieldDefinition; -import org.eclipse.ditto.json.JsonObject; -import org.eclipse.ditto.json.JsonObjectBuilder; import org.eclipse.ditto.base.model.headers.DittoHeaders; import org.eclipse.ditto.base.model.json.FieldType; import org.eclipse.ditto.base.model.json.JsonParsableCommand; import org.eclipse.ditto.base.model.json.JsonSchemaVersion; import org.eclipse.ditto.base.model.signals.commands.AbstractCommand; import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer; +import org.eclipse.ditto.json.JsonArray; +import org.eclipse.ditto.json.JsonCollectors; +import org.eclipse.ditto.json.JsonFactory; +import org.eclipse.ditto.json.JsonField; +import org.eclipse.ditto.json.JsonFieldDefinition; +import org.eclipse.ditto.json.JsonObject; +import org.eclipse.ditto.json.JsonObjectBuilder; +import org.eclipse.ditto.json.JsonValue; import org.eclipse.ditto.utils.jsr305.annotations.AllValuesAreNonnullByDefault; @@ -57,12 +65,25 @@ public final class SudoCountThings extends AbstractCommand JsonFactory.newStringFieldDefinition("filter", FieldType.REGULAR, JsonSchemaVersion.V_2); + static final JsonFieldDefinition JSON_NAMESPACES = + JsonFactory.newJsonArrayFieldDefinition("namespaces", FieldType.REGULAR, + JsonSchemaVersion.V_2); + @Nullable private final String filter; - private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String filter) { + @Nullable + private final Set namespaces; + + private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String filter, + @Nullable final Collection namespaces) { super(TYPE, dittoHeaders); this.filter = filter; + if (namespaces != null) { + this.namespaces = Collections.unmodifiableSet(new HashSet<>(namespaces)); + } else { + this.namespaces = null; + } } /** @@ -74,7 +95,20 @@ private SudoCountThings(final DittoHeaders dittoHeaders, @Nullable final String * @throws NullPointerException if {@code dittoHeaders} is {@code null}. */ public static SudoCountThings of(@Nullable final String filter, final DittoHeaders dittoHeaders) { - return new SudoCountThings(dittoHeaders, filter); + return new SudoCountThings(dittoHeaders, filter, null); + } + + /** + * Returns a new instance of {@code SudoCountThings}. + * + * @param filter the optional filter string + * @param dittoHeaders the headers of the command. + * @return a new command for counting Things. + * @throws NullPointerException if {@code dittoHeaders} is {@code null}. + */ + public static SudoCountThings of(@Nullable final String filter, @Nullable final Collection namespaces, + final DittoHeaders dittoHeaders) { + return new SudoCountThings(dittoHeaders, filter, namespaces); } /** @@ -85,7 +119,7 @@ public static SudoCountThings of(@Nullable final String filter, final DittoHeade * @throws NullPointerException if any argument is {@code null}. */ public static SudoCountThings of(final DittoHeaders dittoHeaders) { - return new SudoCountThings(dittoHeaders, null); + return new SudoCountThings(dittoHeaders, null, null); } /** @@ -119,7 +153,14 @@ public static SudoCountThings fromJson(final JsonObject jsonObject, final DittoH return new CommandJsonDeserializer(TYPE, jsonObject).deserialize(() -> { final String extractedFilter = jsonObject.getValue(JSON_FILTER).orElse(null); - return new SudoCountThings(dittoHeaders, extractedFilter); + final Set extractedNamespaces = jsonObject.getValue(JSON_NAMESPACES) + .map(jsonValues -> jsonValues.stream() + .filter(JsonValue::isString) + .map(JsonValue::asString) + .collect(Collectors.toSet())) + .orElse(null); + + return new SudoCountThings(dittoHeaders, extractedFilter, extractedNamespaces); }); } @@ -132,12 +173,24 @@ public Optional getFilter() { return Optional.ofNullable(filter); } + /** + * Get the optional set of namespaces. + * + * @return the optional set of namespaces. + */ + public Optional> getNamespaces() { + return Optional.ofNullable(namespaces); + } + @Override protected void appendPayload(final JsonObjectBuilder jsonObjectBuilder, final JsonSchemaVersion schemaVersion, final Predicate thePredicate) { final Predicate predicate = schemaVersion.and(thePredicate); getFilter().ifPresent(theFilter -> jsonObjectBuilder.set(JSON_FILTER, theFilter, predicate)); + getNamespaces().ifPresent(presentOptions -> jsonObjectBuilder.set(JSON_NAMESPACES, presentOptions.stream() + .map(JsonValue::of) + .collect(JsonCollectors.valuesToArray()), predicate)); } @Override @@ -147,7 +200,7 @@ public Category getCategory() { @Override public SudoCountThings setDittoHeaders(final DittoHeaders dittoHeaders) { - return of(filter, dittoHeaders); + return of(filter, namespaces, dittoHeaders); } @Override @@ -159,17 +212,21 @@ public boolean equals(@Nullable final Object o) { if (!super.equals(o)) return false; final SudoCountThings that = (SudoCountThings) o; - return Objects.equals(filter, that.filter); + return Objects.equals(filter, that.filter) && + Objects.equals(namespaces, that.namespaces); } @Override public int hashCode() { - return Objects.hash(super.hashCode(), filter); + return Objects.hash(super.hashCode(), filter, namespaces); } @Override public String toString() { - return getClass().getSimpleName() + "[" + "filter='" + filter + "']"; + return getClass().getSimpleName() + "[" + + "filter='" + filter + "'" + + ", namespaces=" + namespaces + + "]"; } } diff --git a/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands/query/CountThings.java b/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands/query/CountThings.java index 15294addf4..3b64ebbd06 100755 --- a/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands/query/CountThings.java +++ b/thingsearch/model/src/main/java/org/eclipse/ditto/thingsearch/model/signals/commands/query/CountThings.java @@ -24,6 +24,12 @@ import javax.annotation.Nullable; import javax.annotation.concurrent.Immutable; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.base.model.json.FieldType; +import org.eclipse.ditto.base.model.json.JsonParsableCommand; +import org.eclipse.ditto.base.model.json.JsonSchemaVersion; +import org.eclipse.ditto.base.model.signals.commands.AbstractCommand; +import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer; import org.eclipse.ditto.json.JsonArray; import org.eclipse.ditto.json.JsonCollectors; import org.eclipse.ditto.json.JsonFactory; @@ -32,12 +38,6 @@ import org.eclipse.ditto.json.JsonObject; import org.eclipse.ditto.json.JsonObjectBuilder; import org.eclipse.ditto.json.JsonValue; -import org.eclipse.ditto.base.model.headers.DittoHeaders; -import org.eclipse.ditto.base.model.json.FieldType; -import org.eclipse.ditto.base.model.json.JsonParsableCommand; -import org.eclipse.ditto.base.model.json.JsonSchemaVersion; -import org.eclipse.ditto.base.model.signals.commands.AbstractCommand; -import org.eclipse.ditto.base.model.signals.commands.CommandJsonDeserializer; import org.eclipse.ditto.thingsearch.model.signals.commands.ThingSearchCommand; /** @@ -87,7 +87,7 @@ private CountThings(final DittoHeaders dittoHeaders, @Nullable final String filt * @return a new command for counting Things. * @throws NullPointerException if any argument is {@code null}. */ - public static CountThings of(@Nullable final String filter, @Nullable final Set namespaces, + public static CountThings of(@Nullable final String filter, @Nullable final Collection namespaces, final DittoHeaders dittoHeaders) { return new CountThings(dittoHeaders, filter, namespaces); diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/CustomMetricConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/CustomMetricConfig.java new file mode 100644 index 0000000000..0c08958c04 --- /dev/null +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/CustomMetricConfig.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.thingsearch.service.common.config; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides the configuration settings for a single custom operator metric. + */ +public interface CustomMetricConfig { + + /** + * Returns whether this specific search operator metric gathering is turned on. + * + * @return true or false. + */ + boolean isEnabled(); + + /** + * Returns the optional scrape interval override for this specific custom metric, how often the metrics should be + * gathered. + * + * @return the optional scrape interval override. + */ + Optional getScrapeInterval(); + + /** + * Returns the namespaces the custom metric should be executed in or an empty list for gathering metrics in all + * namespaces. + * + * @return a list of namespaces. + */ + List getNamespaces(); + + /** + * Returns the filter (RQL statement) to include in the "CountThings" request or an empty string of no filter + * should be applied. + * + * @return the filter RQL statement. + */ + String getFilter(); + + /** + * Return optional tags to report to the custom Gauge metric. + * + * @return optional tags to report. + */ + Map getTags(); + + enum CustomMetricConfigValue implements KnownConfigValue { + + /** + * Whether the metrics should be gathered. + */ + ENABLED("enabled", true), + + /** + * The optional custom scrape interval, how often the metrics should be gathered. + * If this is {@code Duration.ZERO}, then there is no overwrite for the "global" scrape-interval to be applied. + */ + SCRAPE_INTERVAL("scrape-interval", Duration.ZERO), + + /** + * The namespaces the custom metric should be executed in or an empty list for gathering metrics in all + * namespaces. + */ + NAMESPACES("namespaces", List.of()), + + /** + * The filter RQL statement. + */ + FILTER("filter", ""), + + /** + * The optional tags to report to the custom Gauge metric. + */ + TAGS("tags", Map.of()); + + private final String path; + private final Object defaultValue; + + CustomMetricConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + } +} diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultCustomMetricConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultCustomMetricConfig.java new file mode 100644 index 0000000000..2e27e54348 --- /dev/null +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultCustomMetricConfig.java @@ -0,0 +1,124 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.thingsearch.service.common.config; + +import java.time.Duration; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; + +import com.typesafe.config.Config; + +/** + * This class is the default implementation of the CustomMetricConfig. + * It is instantiated for each {@code custom-metrics} entry containing the configuration for the custom metric. + */ +public final class DefaultCustomMetricConfig implements CustomMetricConfig { + + private final String customMetricName; + private final boolean enabled; + private final Duration scrapeInterval; + private final List namespaces; + private final String filter; + private final Map tags; + + private DefaultCustomMetricConfig(final String customMetricName, final ConfigWithFallback configWithFallback) { + this.customMetricName = customMetricName; + enabled = configWithFallback.getBoolean(CustomMetricConfigValue.ENABLED.getConfigPath()); + scrapeInterval = configWithFallback.getDuration(CustomMetricConfigValue.SCRAPE_INTERVAL.getConfigPath()); + namespaces = configWithFallback.getStringList(CustomMetricConfigValue.NAMESPACES.getConfigPath()); + filter = configWithFallback.getString(CustomMetricConfigValue.FILTER.getConfigPath()); + tags = configWithFallback.getObject(CustomMetricConfigValue.TAGS.getConfigPath()).unwrapped() + .entrySet() + .stream() + .collect(Collectors.toUnmodifiableMap(Map.Entry::getKey, e -> String.valueOf(e.getValue()))); + } + + /** + * Returns an instance of {@code DefaultCustomMetricConfig} based on the settings of the specified Config. + * + * @param key the key of the {@code custom-metrics} entry config passed in the {@code config}. + * @param config is supposed to provide the config for the issuer at its current level. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultCustomMetricConfig of(final String key, final Config config) { + return new DefaultCustomMetricConfig(key, + ConfigWithFallback.newInstance(config, CustomMetricConfigValue.values())); + } + + public String getCustomMetricName() { + return customMetricName; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public Optional getScrapeInterval() { + return scrapeInterval.isZero() ? Optional.empty() : Optional.of(scrapeInterval); + } + + @Override + public List getNamespaces() { + return namespaces; + } + + @Override + public String getFilter() { + return filter; + } + + @Override + public Map getTags() { + return tags; + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultCustomMetricConfig that = (DefaultCustomMetricConfig) o; + return enabled == that.enabled && + Objects.equals(scrapeInterval, that.scrapeInterval) && + Objects.equals(namespaces, that.namespaces) && + Objects.equals(filter, that.filter) && + Objects.equals(tags, that.tags); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, scrapeInterval, namespaces, filter, tags); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enabled=" + enabled + + ", scrapeInterval=" + scrapeInterval + + ", namespaces=" + namespaces + + ", filter=" + filter + + ", tags=" + tags + + "]"; + } +} diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultOperatorMetricsConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultOperatorMetricsConfig.java new file mode 100644 index 0000000000..c582f69b7b --- /dev/null +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DefaultOperatorMetricsConfig.java @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.thingsearch.service.common.config; + +import java.time.Duration; +import java.util.Collections; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BinaryOperator; +import java.util.function.Function; +import java.util.function.Supplier; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.ConfigWithFallback; +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; +import com.typesafe.config.ConfigObject; +import com.typesafe.config.ConfigValue; + +/** + * This class is the default implementation for {@link OperatorMetricsConfig}. + */ +@Immutable +public final class DefaultOperatorMetricsConfig implements OperatorMetricsConfig { + + /** + * Path where the operator metrics config values are expected. + */ + static final String CONFIG_PATH = "operator-metrics"; + + private final boolean enabled; + private final Duration scrapeInterval; + private final Map customMetricConfigurations; + + private DefaultOperatorMetricsConfig(final ConfigWithFallback updaterScopedConfig) { + enabled = updaterScopedConfig.getBoolean(OperatorMetricsConfigValue.ENABLED.getConfigPath()); + scrapeInterval = updaterScopedConfig.getNonNegativeDurationOrThrow(OperatorMetricsConfigValue.SCRAPE_INTERVAL); + customMetricConfigurations = loadCustomMetricConfigurations(updaterScopedConfig, + OperatorMetricsConfigValue.CUSTOM_METRICS); + } + + /** + * Returns an instance of DefaultOperatorMetricsConfig based on the settings of the specified Config. + * + * @param config is supposed to provide the settings of the updater config at {@value #CONFIG_PATH}. + * @return the instance. + * @throws org.eclipse.ditto.internal.utils.config.DittoConfigError if {@code config} is invalid. + */ + public static DefaultOperatorMetricsConfig of(final Config config) { + return new DefaultOperatorMetricsConfig( + ConfigWithFallback.newInstance(config, CONFIG_PATH, OperatorMetricsConfigValue.values())); + } + + private static Map loadCustomMetricConfigurations(final ConfigWithFallback config, + final KnownConfigValue configValue) { + + final ConfigObject customMetricsConfig = config.getObject(configValue.getConfigPath()); + + return customMetricsConfig.entrySet().stream().collect(CustomMetricConfigCollector.toMap()); + } + + @Override + public boolean equals(final Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + final DefaultOperatorMetricsConfig that = (DefaultOperatorMetricsConfig) o; + return enabled == that.enabled && + Objects.equals(scrapeInterval, that.scrapeInterval); + } + + @Override + public int hashCode() { + return Objects.hash(enabled, scrapeInterval, customMetricConfigurations); + } + + @Override + public String toString() { + return getClass().getSimpleName() + " [" + + "enabled=" + enabled + + ", scrapeInterval=" + scrapeInterval + + ", customMetricConfigurations=" + customMetricConfigurations + + "]"; + } + + @Override + public boolean isEnabled() { + return enabled; + } + + @Override + public Duration getScrapeInterval() { + return scrapeInterval; + } + + @Override + public Map getCustomMetricConfigurations() { + return customMetricConfigurations; + } + + private static class CustomMetricConfigCollector + implements + Collector, Map, Map> { + + private static CustomMetricConfigCollector toMap() { + return new CustomMetricConfigCollector(); + } + + @Override + public Supplier> supplier() { + return LinkedHashMap::new; + } + + @Override + public BiConsumer, Map.Entry> accumulator() { + return (map, entry) -> map.put(entry.getKey(), + DefaultCustomMetricConfig.of(entry.getKey(), ConfigFactory.empty().withFallback(entry.getValue()))); + } + + @Override + public BinaryOperator> combiner() { + return (left, right) -> Stream.concat(left.entrySet().stream(), right.entrySet().stream()) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue)); + } + + @Override + public Function, Map> finisher() { + return map -> Collections.unmodifiableMap(new LinkedHashMap<>(map)); + } + + @Override + public Set characteristics() { + return Collections.singleton(Characteristics.UNORDERED); + } + } +} diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DittoSearchConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DittoSearchConfig.java index 34e4dffa30..178540fecc 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DittoSearchConfig.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/DittoSearchConfig.java @@ -60,6 +60,7 @@ public final class DittoSearchConfig implements SearchConfig, WithConfigPath { private final MongoDbConfig mongoDbConfig; private final SearchPersistenceConfig queryPersistenceConfig; private final Map simpleFieldMappings; + private final DefaultOperatorMetricsConfig operatorMetricsConfig; private DittoSearchConfig(final ScopedConfig dittoScopedConfig) { dittoServiceConfig = DittoServiceConfig.of(dittoScopedConfig, CONFIG_PATH); @@ -79,6 +80,7 @@ private DittoSearchConfig(final ScopedConfig dittoScopedConfig) { queryPersistenceConfig = DefaultSearchPersistenceConfig.of(queryConfig); simpleFieldMappings = convertToMap(configWithFallback.getConfig(SearchConfigValue.SIMPLE_FIELD_MAPPINGS.getConfigPath())); + operatorMetricsConfig = DefaultOperatorMetricsConfig.of(configWithFallback); } /** @@ -112,6 +114,11 @@ public Map getSimpleFieldMappings() { return simpleFieldMappings; } + @Override + public DefaultOperatorMetricsConfig getOperatorMetricsConfig() { + return operatorMetricsConfig; + } + @Override public ClusterConfig getClusterConfig() { return dittoServiceConfig.getClusterConfig(); @@ -175,13 +182,15 @@ public boolean equals(final Object o) { Objects.equals(persistenceOperationsConfig, that.persistenceOperationsConfig) && Objects.equals(mongoDbConfig, that.mongoDbConfig) && Objects.equals(queryPersistenceConfig, that.queryPersistenceConfig) && - Objects.equals(simpleFieldMappings, that.simpleFieldMappings); + Objects.equals(simpleFieldMappings, that.simpleFieldMappings) && + Objects.equals(operatorMetricsConfig, that.operatorMetricsConfig); } @Override public int hashCode() { return Objects.hash(mongoHintsByNamespace, updaterConfig, dittoServiceConfig, healthCheckConfig, - indexInitializationConfig, persistenceOperationsConfig, mongoDbConfig, queryPersistenceConfig, simpleFieldMappings); + indexInitializationConfig, persistenceOperationsConfig, mongoDbConfig, queryPersistenceConfig, + simpleFieldMappings, operatorMetricsConfig); } @Override @@ -196,6 +205,7 @@ public String toString() { ", mongoDbConfig=" + mongoDbConfig + ", queryPersistenceConfig=" + queryPersistenceConfig + ", simpleFieldMappings=" + simpleFieldMappings + + ", operatorMetricsConfig=" + operatorMetricsConfig + "]"; } diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/OperatorMetricsConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/OperatorMetricsConfig.java new file mode 100644 index 0000000000..f36b429d85 --- /dev/null +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/OperatorMetricsConfig.java @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.thingsearch.service.common.config; + +import java.time.Duration; +import java.util.Collections; +import java.util.Map; + +import javax.annotation.concurrent.Immutable; + +import org.eclipse.ditto.internal.utils.config.KnownConfigValue; + +/** + * Provides the configuration settings for the search operator metrics. + */ +@Immutable +public interface OperatorMetricsConfig { + + /** + * Returns whether search operator metrics gathering is turned on. + * + * @return true or false. + */ + boolean isEnabled(); + + /** + * Returns the default scrape interval, how often the metrics should be gathered. + * + * @return the default scrape interval. + */ + Duration getScrapeInterval(); + + /** + * Returns all registered custom metrics with the key being the metric name to use. + * + * @return the registered custom metrics. + */ + Map getCustomMetricConfigurations(); + + /** + * An enumeration of the known config path expressions and their associated default values for + * OperatorMetricsConfig. + */ + enum OperatorMetricsConfigValue implements KnownConfigValue { + + /** + * Whether the metrics should be gathered. + */ + ENABLED("enabled", true), + + /** + * The default scrape interval, how often the metrics should be gathered. + */ + SCRAPE_INTERVAL("scrape-interval", Duration.ofMinutes(15)), + + /** + * All registered custom metrics with the key being the metric name to use. + */ + CUSTOM_METRICS("custom-metrics", Collections.emptyMap()); + + private final String path; + private final Object defaultValue; + + OperatorMetricsConfigValue(final String thePath, final Object theDefaultValue) { + path = thePath; + defaultValue = theDefaultValue; + } + + @Override + public Object getDefaultValue() { + return defaultValue; + } + + @Override + public String getConfigPath() { + return path; + } + + } + +} diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/SearchConfig.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/SearchConfig.java index 50bc0681cf..b8eaaf0fed 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/SearchConfig.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/common/config/SearchConfig.java @@ -53,10 +53,17 @@ public interface SearchConfig extends ServiceSpecificConfig, WithHealthCheckConf * Returns how simple fields are mapped during query parsing. * * @return the simple field mapping. - * @since 3.0.0 */ Map getSimpleFieldMappings(); + /** + * Returns the operator metrics configuration containing metrics to be exposed via Prometheus based on configured + * search "count" queries. + * + * @return the operator metrics configuration. + */ + OperatorMetricsConfig getOperatorMetricsConfig(); + /** * An enumeration of the known config path expressions and their associated default values for SearchConfig. */ diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/persistence/query/QueryParser.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/persistence/query/QueryParser.java index 7e920ecc6c..14484d5c27 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/persistence/query/QueryParser.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/persistence/query/QueryParser.java @@ -122,7 +122,13 @@ private Criteria parseCriteria(final ThingSearchQueryCommand command) { public CompletionStage parseSudoCountThings(final SudoCountThings sudoCountThings) { final DittoHeaders headers = sudoCountThings.getDittoHeaders(); final String filters = sudoCountThings.getFilter().orElse(null); - final Criteria criteria = queryFilterCriteriaFactory.filterCriteria(filters, headers); + final Set namespaces = sudoCountThings.getNamespaces().orElse(null); + final Criteria criteria; + if (null != namespaces) { + criteria = queryFilterCriteriaFactory.filterCriteriaRestrictedByNamespaces(filters, headers, namespaces); + } else { + criteria = queryFilterCriteriaFactory.filterCriteria(filters, headers); + } return CompletableFuture.completedFuture(queryBuilderFactory.newUnlimitedBuilder(criteria).build()); } diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/OperatorMetricsProviderActor.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/OperatorMetricsProviderActor.java new file mode 100644 index 0000000000..6f06dd77bc --- /dev/null +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/OperatorMetricsProviderActor.java @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2023 Contributors to the Eclipse Foundation + * + * See the NOTICE file(s) distributed with this work for additional + * information regarding copyright ownership. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Public License 2.0 which is available at + * http://www.eclipse.org/legal/epl-2.0 + * + * SPDX-License-Identifier: EPL-2.0 + */ +package org.eclipse.ditto.thingsearch.service.starter.actors; + +import java.time.Duration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ThreadLocalRandom; + +import org.apache.pekko.actor.AbstractActorWithTimers; +import org.apache.pekko.actor.ActorRef; +import org.apache.pekko.actor.Props; +import org.apache.pekko.actor.Status; +import org.apache.pekko.japi.pf.ReceiveBuilder; +import org.apache.pekko.pattern.Patterns; +import org.eclipse.ditto.base.model.exceptions.DittoRuntimeException; +import org.eclipse.ditto.base.model.headers.DittoHeaders; +import org.eclipse.ditto.internal.utils.metrics.instruments.gauge.Gauge; +import org.eclipse.ditto.internal.utils.metrics.instruments.gauge.KamonGauge; +import org.eclipse.ditto.internal.utils.metrics.instruments.tag.Tag; +import org.eclipse.ditto.internal.utils.metrics.instruments.tag.TagSet; +import org.eclipse.ditto.internal.utils.pekko.logging.DittoDiagnosticLoggingAdapter; +import org.eclipse.ditto.internal.utils.pekko.logging.DittoLoggerFactory; +import org.eclipse.ditto.thingsearch.api.commands.sudo.SudoCountThings; +import org.eclipse.ditto.thingsearch.model.signals.commands.query.CountThingsResponse; +import org.eclipse.ditto.thingsearch.service.common.config.CustomMetricConfig; +import org.eclipse.ditto.thingsearch.service.common.config.OperatorMetricsConfig; + +/** + * Actor which is started as singleton for "search" role and is responsible for querying for operator defined + * "custom metrics" (configured via Ditto search service configuration) to expose as {@code Gauge} via Prometheus. + */ +public final class OperatorMetricsProviderActor extends AbstractActorWithTimers { + + /** + * This Actor's actor name. + */ + public static final String ACTOR_NAME = "operatorMetricsProvider"; + + private static final int MIN_INITIAL_DELAY_SECONDS = 30; + private static final int MAX_INITIAL_DELAY_SECONDS = 90; + private static final int DEFAULT_COUNT_TIMEOUT_SECONDS = 60; + + private final DittoDiagnosticLoggingAdapter log = DittoLoggerFactory.getDiagnosticLoggingAdapter(this); + + private final ActorRef searchActor; + private final Map metricsGauges; + + @SuppressWarnings("unused") + private OperatorMetricsProviderActor(final OperatorMetricsConfig operatorMetricsConfig, + final ActorRef searchActor) { + + this.searchActor = searchActor; + metricsGauges = new HashMap<>(); + operatorMetricsConfig.getCustomMetricConfigurations().forEach((metricName, config) -> { + if (config.isEnabled()) { + initializeCustomMetric(operatorMetricsConfig, metricName, config); + } else { + log.info("Initializing custom metric Gauge for metric <{}> is DISABLED", metricName); + } + }); + } + + /** + * Create Props for this actor. + * + * @param operatorMetricsConfig the config to use + * @param searchActor the SearchActor Actor reference + * @return the Props object. + */ + public static Props props(final OperatorMetricsConfig operatorMetricsConfig, final ActorRef searchActor) { + return Props.create(OperatorMetricsProviderActor.class, operatorMetricsConfig, searchActor); + } + + @Override + public Receive createReceive() { + return ReceiveBuilder.create() + .match(GatherMetrics.class, this::handleGatheringMetrics) + .match(Status.Failure.class, f -> log.error(f.cause(), "Got failure: {}", f)) + .matchAny(m -> { + log.warning("Unknown message: {}", m); + unhandled(m); + }) + .build(); + } + + private void initializeCustomMetric(final OperatorMetricsConfig operatorMetricsConfig, final String metricName, + final CustomMetricConfig config) { + // start each custom metric provider with a random initialDelay + final Duration initialDelay = Duration.ofSeconds( + ThreadLocalRandom.current().nextInt(MIN_INITIAL_DELAY_SECONDS, MAX_INITIAL_DELAY_SECONDS) + ); + final Duration scrapeInterval = config.getScrapeInterval() + .orElse(operatorMetricsConfig.getScrapeInterval()); + getTimers().startTimerAtFixedRate( + metricName, createGatherCustomMetric(metricName, config), initialDelay, scrapeInterval); + + final List tags = config.getTags().entrySet().stream() + .map(e -> Tag.of(e.getKey(), e.getValue())) + .toList(); + log.info("Initializing custom metric Gauge for metric <{}> with tags <{}>, initial delay <{}> " + + "and a scrape-interval of <{}>", metricName, tags, initialDelay, scrapeInterval); + final Gauge gauge = KamonGauge.newGauge(metricName) + .tags(TagSet.ofTagCollection(tags)); + metricsGauges.put(metricName, gauge); + } + + private static GatherMetrics createGatherCustomMetric(final String metricName, final CustomMetricConfig config) { + return new GatherMetrics(metricName, config); + } + + private void handleGatheringMetrics(final GatherMetrics gatherMetrics) { + final String metricName = gatherMetrics.metricName(); + final CustomMetricConfig config = gatherMetrics.config(); + final String filter = config.getFilter(); + final List namespaces = config.getNamespaces(); + final DittoHeaders dittoHeaders = DittoHeaders.newBuilder() + .correlationId("gather-metrics_" + metricName + "_" + UUID.randomUUID()) + .build(); + final SudoCountThings sudoCountThings = SudoCountThings.of( + filter.isEmpty() ? null : filter, namespaces.isEmpty() ? null : namespaces, dittoHeaders); + + final long startTs = System.nanoTime(); + log.withCorrelationId(dittoHeaders) + .debug("Asking for count of custom metric <{}>..", metricName); + + Patterns.ask(searchActor, sudoCountThings, Duration.ofSeconds(DEFAULT_COUNT_TIMEOUT_SECONDS)) + .whenComplete((response, throwable) -> { + if (response instanceof CountThingsResponse countThingsResponse) { + log.withCorrelationId(countThingsResponse) + .info("Received sudo CountThingsResponse for custom metric count <{}>: {} - " + + "duration: <{}ms>", + metricName, countThingsResponse.getCount(), + Duration.ofNanos(System.nanoTime() - startTs).toMillis() + ); + metricsGauges.get(metricName).set(countThingsResponse.getCount()); + } else if (response instanceof DittoRuntimeException dre) { + log.withCorrelationId(dittoHeaders).warning( + "Received DittoRuntimeException when gathering count for " + + "custom metric <{}>: {}", metricName, dre.getMessage(), dre + ); + } else { + log.withCorrelationId(dittoHeaders).warning( + "Received unexpected result or throwable when gathering count for " + + "custom metric <{}>: {}", metricName, response, throwable + ); + } + }); + } + + private record GatherMetrics(String metricName, CustomMetricConfig config) {} +} diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchActor.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchActor.java index 8371bd1fd3..53986a3dac 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchActor.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchActor.java @@ -270,7 +270,8 @@ private void count(final CountThings countThings) { private void sudoCount(final SudoCountThings sudoCountThings) { final var sender = getSender(); final ThreadSafeDittoLoggingAdapter l = log.withCorrelationId(sudoCountThings); - l.info("Processing SudoCountThings command with filter: <{}>", sudoCountThings.getFilter()); + l.info("Processing SudoCountThings command with filter: <{}> and namespaces: <{}>", + sudoCountThings.getFilter(), sudoCountThings.getNamespaces()); l.debug("Processing SudoCountThings command: <{}>", sudoCountThings); withRequestCounting( diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchRootActor.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchRootActor.java index 22ccbe1086..4ca5ab0f09 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchRootActor.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchRootActor.java @@ -14,11 +14,17 @@ import static org.eclipse.ditto.thingsearch.service.persistence.PersistenceConstants.BACKGROUND_SYNC_COLLECTION_NAME; +import org.apache.pekko.actor.ActorRef; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.Props; +import org.apache.pekko.event.Logging; +import org.apache.pekko.event.LoggingAdapter; +import org.apache.pekko.stream.SystemMaterializer; import org.eclipse.ditto.base.service.RootChildActorStarter; import org.eclipse.ditto.base.service.actors.DittoRootActor; -import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.internal.utils.cluster.DistPubSubAccess; import org.eclipse.ditto.internal.utils.config.ScopedConfig; +import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.internal.utils.persistence.mongo.DittoMongoClient; import org.eclipse.ditto.internal.utils.persistence.mongo.streaming.MongoTimestampPersistence; import org.eclipse.ditto.rql.query.QueryBuilderFactory; @@ -32,13 +38,6 @@ import org.eclipse.ditto.thingsearch.service.persistence.read.query.MongoQueryBuilderFactory; import org.eclipse.ditto.thingsearch.service.updater.actors.SearchUpdaterRootActor; -import org.apache.pekko.actor.ActorRef; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.Props; -import org.apache.pekko.event.Logging; -import org.apache.pekko.event.LoggingAdapter; -import org.apache.pekko.stream.SystemMaterializer; - /** * Our "Parent" Actor which takes care of supervision of all other Actors in our system. */ @@ -74,7 +73,7 @@ private SearchRootActor(final SearchConfig searchConfig, final ActorRef pubSubMe SystemMaterializer.get(actorSystem).materializer()); final ActorRef searchUpdaterRootActor = startChildActor(SearchUpdaterRootActor.ACTOR_NAME, - SearchUpdaterRootActor.props(searchConfig, pubSubMediator, thingsSearchPersistence, + SearchUpdaterRootActor.props(searchConfig, searchActor, pubSubMediator, thingsSearchPersistence, backgroundSyncPersistence)); final ActorRef healthCheckingActor = initializeHealthCheckActor(searchConfig, searchUpdaterRootActor); diff --git a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/updater/actors/SearchUpdaterRootActor.java b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/updater/actors/SearchUpdaterRootActor.java index 06a47843e7..c9f20244ec 100644 --- a/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/updater/actors/SearchUpdaterRootActor.java +++ b/thingsearch/service/src/main/java/org/eclipse/ditto/thingsearch/service/updater/actors/SearchUpdaterRootActor.java @@ -12,14 +12,22 @@ */ package org.eclipse.ditto.thingsearch.service.updater.actors; +import org.apache.pekko.actor.AbstractActor; +import org.apache.pekko.actor.ActorRef; +import org.apache.pekko.actor.Props; +import org.apache.pekko.actor.Status; +import org.apache.pekko.actor.SupervisorStrategy; +import org.apache.pekko.event.Logging; +import org.apache.pekko.event.LoggingAdapter; +import org.apache.pekko.japi.pf.ReceiveBuilder; import org.eclipse.ditto.base.api.devops.signals.commands.RetrieveStatisticsDetails; import org.eclipse.ditto.base.service.actors.StartChildActor; -import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.internal.utils.cluster.ClusterUtil; import org.eclipse.ditto.internal.utils.cluster.DistPubSubAccess; import org.eclipse.ditto.internal.utils.config.ScopedConfig; import org.eclipse.ditto.internal.utils.health.RetrieveHealth; import org.eclipse.ditto.internal.utils.namespaces.BlockedNamespaces; +import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.internal.utils.persistence.mongo.DittoMongoClient; import org.eclipse.ditto.thingsearch.api.ThingsSearchConstants; import org.eclipse.ditto.thingsearch.service.common.config.SearchConfig; @@ -29,18 +37,7 @@ import org.eclipse.ditto.thingsearch.service.persistence.write.streaming.SearchUpdateMapper; import org.eclipse.ditto.thingsearch.service.persistence.write.streaming.SearchUpdaterStream; import org.eclipse.ditto.thingsearch.service.starter.actors.MongoClientExtension; -import org.eclipse.ditto.thingsearch.service.starter.actors.SearchRootActor; - -import org.apache.pekko.actor.AbstractActor; -import org.apache.pekko.actor.ActorRef; -import org.apache.pekko.actor.ActorRefFactory; -import org.apache.pekko.actor.ActorSelection; -import org.apache.pekko.actor.Props; -import org.apache.pekko.actor.Status; -import org.apache.pekko.actor.SupervisorStrategy; -import org.apache.pekko.event.Logging; -import org.apache.pekko.event.LoggingAdapter; -import org.apache.pekko.japi.pf.ReceiveBuilder; +import org.eclipse.ditto.thingsearch.service.starter.actors.OperatorMetricsProviderActor; /** * Our "Parent" Actor which takes care of supervision of all other Actors in our system. @@ -68,6 +65,7 @@ public final class SearchUpdaterRootActor extends AbstractActor { @SuppressWarnings("unused") private SearchUpdaterRootActor(final SearchConfig searchConfig, + final ActorRef searchActor, final ActorRef pubSubMediator, final MongoThingsSearchPersistence thingsSearchPersistence, final TimestampPersistence backgroundSyncPersistence) { @@ -130,6 +128,11 @@ private SearchUpdaterRootActor(final SearchConfig searchConfig, ClusterUtil.startSingletonProxy(getContext(), CLUSTER_ROLE, startClusterSingletonActor(BackgroundSyncActor.ACTOR_NAME, backgroundSyncActorProps) ); + if (searchConfig.getOperatorMetricsConfig().isEnabled()) { + startClusterSingletonActor(OperatorMetricsProviderActor.ACTOR_NAME, + OperatorMetricsProviderActor.props(searchConfig.getOperatorMetricsConfig(), searchActor) + ); + } startChildActor(ThingsSearchPersistenceOperationsActor.ACTOR_NAME, ThingsSearchPersistenceOperationsActor.props(pubSubMediator, searchUpdaterPersistence, @@ -140,28 +143,19 @@ private SearchUpdaterRootActor(final SearchConfig searchConfig, * Creates Pekko configuration object Props for this SearchUpdaterRootActor. * * @param searchConfig the configuration settings of the Things-Search service. + * @param searchActor the SearchActor Actor reference. * @param pubSubMediator the PubSub mediator Actor. * @param thingsSearchPersistence persistence to access the search index in read-only mode. * @param backgroundSyncPersistence persistence for background synchronization. * @return a Props object to create this actor. */ public static Props props(final SearchConfig searchConfig, + final ActorRef searchActor, final ActorRef pubSubMediator, final MongoThingsSearchPersistence thingsSearchPersistence, final TimestampPersistence backgroundSyncPersistence) { - return Props.create(SearchUpdaterRootActor.class, searchConfig, pubSubMediator, thingsSearchPersistence, - backgroundSyncPersistence); - } - - /** - * Select the ThingsUpdater in the actor system. - * - * @param system the actor system. - * @return actor selection for the ThingsUpdater in the system. - */ - public static ActorSelection getThingsUpdater(final ActorRefFactory system) { - return system.actorSelection( - String.format("user/%s/%s/%s", SearchRootActor.ACTOR_NAME, ACTOR_NAME, ThingsUpdater.ACTOR_NAME)); + return Props.create(SearchUpdaterRootActor.class, searchConfig, searchActor, pubSubMediator, + thingsSearchPersistence, backgroundSyncPersistence); } @Override diff --git a/thingsearch/service/src/main/resources/search-dev.conf b/thingsearch/service/src/main/resources/search-dev.conf index b8eb5a3a26..1f98578de3 100755 --- a/thingsearch/service/src/main/resources/search-dev.conf +++ b/thingsearch/service/src/main/resources/search-dev.conf @@ -6,6 +6,24 @@ ditto { metrics.prometheus.port = 9013 + search { + operator-metrics { + custom-metrics { + my_awesome_things { + scrape-interval = 1m # overwrite scrape interval, run each minute + namespaces = [ + "org.eclipse.ditto.foo" + "org.eclipse.ditto.bar" + ] + # with an empty filter query, counting all existing things + filter = "eq(attributes/awesome,true)" + tags { + category = "bumlux" + } + } + } + } + } } pekko { diff --git a/thingsearch/service/src/main/resources/search.conf b/thingsearch/service/src/main/resources/search.conf index 211f1302ce..a0f665b03c 100755 --- a/thingsearch/service/src/main/resources/search.conf +++ b/thingsearch/service/src/main/resources/search.conf @@ -284,6 +284,42 @@ ditto { readConcern = ${?UPDATER_PERSISTENCE_MONGO_DB_READ_CONCERN} } } + + operator-metrics { + enabled = true + enabled = ${?THINGS_SEARCH_OPERATOR_METRICS_ENABLED} + + # by default, execute "count" metrics once every 15 minutes: + scrape-interval = 15m + scrape-interval = ${?THINGS_SEARCH_OPERATOR_METRICS_SCRAPE_INTERVAL} + + # map of all custom metric providers + custom-metrics { + # built-in query, delivering the total things as metric + total_things { + enabled = true + # for all namespaces + namespaces = [] + # with an empty filter query, counting all existing things + filter = "" + # optionally, provide tags to categorize metrics + tags { + # key = "value" + } + } + + # add new metrics by providing more configuration entries: + # my_awesome_things { + # scrape-interval = 1m # overwrite scrape interval, run each minute + # namespaces [ + # "org.eclipse.ditto.foo" + # "org.eclipse.ditto.bar" + # ] + # # with an empty filter query, counting all existing things + # filter = "eq(attributes/awesome,true)" + # } + } + } } } diff --git a/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchUpdaterRootActorTest.java b/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchUpdaterRootActorTest.java index f2a4f5c5cb..68caff7751 100644 --- a/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchUpdaterRootActorTest.java +++ b/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/actors/SearchUpdaterRootActorTest.java @@ -14,9 +14,11 @@ import java.util.Optional; +import org.apache.pekko.actor.ActorSystem; +import org.apache.pekko.actor.Props; import org.eclipse.ditto.base.service.actors.AbstractDittoRootActorTest; -import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.internal.utils.config.DefaultScopedConfig; +import org.eclipse.ditto.internal.utils.pekko.streaming.TimestampPersistence; import org.eclipse.ditto.thingsearch.service.common.config.DittoSearchConfig; import org.eclipse.ditto.thingsearch.service.common.config.SearchConfig; import org.eclipse.ditto.thingsearch.service.persistence.read.MongoThingsSearchPersistence; @@ -24,9 +26,6 @@ import org.eclipse.ditto.thingsearch.service.updater.actors.SearchUpdaterRootActor; import org.mockito.Mockito; -import org.apache.pekko.actor.ActorSystem; -import org.apache.pekko.actor.Props; - /** * Tests {@link SearchRootActor}. */ @@ -46,7 +45,7 @@ public Optional getRootActorName() { protected Props getRootActorProps(final ActorSystem system) { final SearchConfig config = DittoSearchConfig.of(DefaultScopedConfig.dittoScoped(system.settings().config())); - return SearchUpdaterRootActor.props(config, system.deadLetters(), + return SearchUpdaterRootActor.props(config, system.deadLetters(), system.deadLetters(), Mockito.mock(MongoThingsSearchPersistence.class), Mockito.mock(TimestampPersistence.class)); } } diff --git a/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/config/DittoSearchConfigTest.java b/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/config/DittoSearchConfigTest.java index f1190de974..b054893eb9 100644 --- a/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/config/DittoSearchConfigTest.java +++ b/thingsearch/service/src/test/java/org/eclipse/ditto/thingsearch/service/starter/config/DittoSearchConfigTest.java @@ -24,6 +24,7 @@ import org.eclipse.ditto.internal.utils.persistence.mongo.config.DefaultMongoDbConfig; import org.eclipse.ditto.internal.utils.persistence.mongo.config.ReadConcern; import org.eclipse.ditto.internal.utils.persistence.mongo.config.ReadPreference; +import org.eclipse.ditto.thingsearch.service.common.config.DefaultOperatorMetricsConfig; import org.eclipse.ditto.thingsearch.service.common.config.DefaultSearchPersistenceConfig; import org.eclipse.ditto.thingsearch.service.common.config.DefaultUpdaterConfig; import org.eclipse.ditto.thingsearch.service.common.config.DittoSearchConfig; @@ -43,7 +44,8 @@ public void assertImmutability() { assertInstancesOf(DittoSearchConfig.class, areImmutable(), provided(DefaultHealthCheckConfig.class, DittoServiceConfig.class, DefaultUpdaterConfig.class, - DefaultMongoDbConfig.class, DefaultSearchPersistenceConfig.class) + DefaultMongoDbConfig.class, DefaultSearchPersistenceConfig.class, + DefaultOperatorMetricsConfig.class) .areAlsoImmutable(), assumingFields("simpleFieldMappings").areSafelyCopiedUnmodifiableCollectionsWithImmutableElements()); }