// Copyright Vespa.ai. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.admin.metricsproxy;
import ai.vespa.metrics.set.Metric;
import ai.vespa.metrics.set.MetricSet;
import ai.vespa.metricsproxy.core.ConsumersConfig;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer;
import org.junit.jupiter.api.Test;
import static ai.vespa.metrics.set.DefaultMetrics.defaultMetricSet;
import static ai.vespa.metrics.set.DefaultVespaMetrics.defaultVespaMetricSet;
import static ai.vespa.metrics.set.NetworkMetrics.networkMetricSet;
import static ai.vespa.metrics.set.SystemMetrics.systemMetricSet;
import static ai.vespa.metrics.set.VespaMetricSet.vespaMetricSet;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.TestMode.hosted;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.TestMode.self_hosted;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.checkMetric;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.consumersConfigFromModel;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.consumersConfigFromXml;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.getCustomConsumer;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.getModel;
import static com.yahoo.vespa.model.admin.metricsproxy.MetricsProxyModelTester.servicesWithAdminOnly;
import static java.util.Collections.singleton;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
/**
* Tests for {@link MetricsProxyContainerCluster} related to metrics consumers.
*
* @author gjoranv
*/
public class MetricsConsumersTest {
private static final int numPublicDefaultMetrics = defaultMetricSet.getMetrics().size();
private static final int numDefaultVespaMetrics = defaultVespaMetricSet.getMetrics().size();
private static final int numVespaMetrics = vespaMetricSet.getMetrics().size();
private static final int numSystemMetrics = systemMetricSet.getMetrics().size();
private static final int numNetworkMetrics = networkMetricSet.getMetrics().size();
private static final int numMetricsForVespaConsumer = numVespaMetrics + numSystemMetrics + numNetworkMetrics;
@Test
void default_public_consumers_is_set_up_for_self_hosted() {
ConsumersConfig config = consumersConfigFromXml(servicesWithAdminOnly(), self_hosted);
assertEquals(4, config.consumer().size());
assertEquals(MetricsConsumer.defaultConsumer.id(), config.consumer(2).name());
int numMetricsForPublicDefaultConsumer = defaultMetricSet.getMetrics().size() + numSystemMetrics;
assertEquals(numMetricsForPublicDefaultConsumer, config.consumer(2).metric().size());
}
@Test
void consumers_are_set_up_for_hosted() {
ConsumersConfig config = consumersConfigFromXml(servicesWithAdminOnly(), hosted);
assertEquals(5, config.consumer().size());
assertEquals(MetricsConsumer.vespa.id(), config.consumer(0).name());
assertEquals(MetricsConsumer.autoscaling.id(), config.consumer(1).name());
assertEquals(MetricsConsumer.defaultConsumer.id(), config.consumer(2).name());
assertEquals(MetricsProxyContainerCluster.NEW_DEFAULT_CONSUMER_ID, config.consumer(3).name());
assertEquals(MetricsConsumer.vespa9.id(), config.consumer(4).name());
}
@Test
void vespa_consumer_is_always_present_and_has_all_vespa_metrics_and_all_system_metrics() {
ConsumersConfig config = consumersConfigFromXml(servicesWithAdminOnly(), self_hosted);
assertEquals(MetricsConsumer.vespa.id(), config.consumer(0).name());
assertEquals(numMetricsForVespaConsumer, config.consumer(0).metric().size());
}
@Test
void vespa_consumer_can_be_amended_via_admin_object() {
VespaModel model = getModel(servicesWithAdminOnly(), self_hosted);
var additionalMetric = new Metric("additional-metric");
model.getAdmin().setAdditionalDefaultMetrics(new MetricSet("amender-metrics", singleton(additionalMetric)));
ConsumersConfig config = consumersConfigFromModel(model);
assertEquals(numMetricsForVespaConsumer + 1, config.consumer(0).metric().size());
ConsumersConfig.Consumer vespaConsumer = config.consumer(0);
assertTrue(checkMetric(vespaConsumer, additionalMetric), "Did not contain additional metric");
}
@Test
void vespa_is_a_reserved_consumer_id() {
assertReservedConsumerId("Vespa");
}
@Test
void default_is_a_reserved_consumer_id() {
assertReservedConsumerId("default");
}
private void assertReservedConsumerId(String consumerId) {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
try {
consumersConfigFromXml(services, self_hosted);
fail();
} catch (IllegalArgumentException e) {
assertEquals("'" + consumerId + "' is not allowed as metrics consumer id (case is ignored.)", e.getMessage());
}
}
@Test
void vespa_consumer_id_is_allowed_for_hosted_infrastructure_applications() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
VespaModel hostedModel = getModel(services, hosted);
ConsumersConfig config = consumersConfigFromModel(hostedModel);
assertEquals(5, config.consumer().size());
// All default metrics are retained
ConsumersConfig.Consumer vespaConsumer = config.consumer(0);
assertEquals(numMetricsForVespaConsumer + 1, vespaConsumer.metric().size());
Metric customMetric1 = new Metric("custom.metric1");
assertTrue(checkMetric(vespaConsumer, customMetric1), "Did not contain metric: " + customMetric1);
}
@Test
void consumer_id_is_case_insensitive() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
try {
consumersConfigFromXml(services, self_hosted);
fail();
} catch (IllegalArgumentException e) {
assertEquals("'a' is used as id for two metrics consumers (case is ignored.)", e.getMessage());
}
}
@Test
void non_existent_metric_set_causes_exception() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
try {
consumersConfigFromXml(services, self_hosted);
fail();
} catch (IllegalArgumentException e) {
assertEquals("No such metric-set: non-existent", e.getMessage());
}
}
@Test
void consumer_with_no_metric_set_has_its_own_metrics_plus_system_metrics_plus_default_vespa_metrics() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
ConsumersConfig.Consumer consumer = getCustomConsumer(services);
assertEquals(numSystemMetrics + numDefaultVespaMetrics + 2, consumer.metric().size());
Metric customMetric1 = new Metric("custom.metric1");
Metric customMetric2 = new Metric("custom.metric2");
assertTrue(checkMetric(consumer, customMetric1), "Did not contain metric: " + customMetric1);
assertTrue(checkMetric(consumer, customMetric2), "Did not contain metric: " + customMetric2);
}
@Test
void consumer_with_default_metric_set_has_all_its_metrics_plus_all_system_metrics_plus_its_own() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
ConsumersConfig.Consumer consumer = getCustomConsumer(services);
assertEquals(numPublicDefaultMetrics + numSystemMetrics + 1, consumer.metric().size());
Metric customMetric = new Metric("custom.metric");
assertTrue(checkMetric(consumer, customMetric), "Did not contain metric: " + customMetric);
}
@Test
void consumer_with_vespa_metric_set_has_all_vespa_metrics_plus_all_system_metrics_plus_its_own() {
String services = String.join("\n",
"",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
" ",
""
);
ConsumersConfig.Consumer consumer = getCustomConsumer(services);
assertEquals(numVespaMetrics + numSystemMetrics + 1, consumer.metric().size());
Metric customMetric = new Metric("my.extra.metric");
assertTrue(checkMetric(consumer, customMetric), "Did not contain metric: " + customMetric);
}
}