// 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;
import ai.vespa.metrics.set.Metric;
import com.yahoo.cloud.config.LogforwarderConfig;
import com.yahoo.cloud.config.SentinelConfig;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.NullConfigModelRegistry;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.provision.Hosts;
import com.yahoo.config.model.provision.InMemoryProvisioner;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.admin.monitoring.MetricsConsumer;
import com.yahoo.vespa.model.admin.monitoring.Monitoring;
import org.junit.jupiter.api.Test;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import static com.yahoo.config.model.api.container.ContainerServiceType.LOGSERVER_CONTAINER;
import static com.yahoo.config.model.api.container.ContainerServiceType.METRICS_PROXY_CONTAINER;
import static com.yahoo.config.model.api.container.ContainerServiceType.CONTAINER;
import static org.junit.jupiter.api.Assertions.*;
/**
* @author Ulf Lilleengen
* @author bratseth
*/
public class DedicatedAdminV4Test {
private static final String hosts = ""
+ " "
+ " node0"
+ " "
+ " "
+ " node1"
+ " "
+ " "
+ " node2"
+ " "
+ "";
@Test
void testModelBuilding() throws IOException, SAXException {
String services = "" +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" mydomain" +
" myservice" +
" " +
" " +
"";
VespaModel model = createModel(hosts, services);
assertEquals(3, model.getHosts().size());
assertHostContainsServices(model, "hosts/myhost0", "slobrok", "logd",
METRICS_PROXY_CONTAINER.serviceName);
assertHostContainsServices(model, "hosts/myhost1", "slobrok", "logd",
METRICS_PROXY_CONTAINER.serviceName);
// Note: A logserver container is always added on logserver host
assertHostContainsServices(model, "hosts/myhost2", "logserver", "logd",
METRICS_PROXY_CONTAINER.serviceName, LOGSERVER_CONTAINER.serviceName);
Monitoring monitoring = model.getAdmin().getMonitoring();
assertEquals("vespa.routing", monitoring.getClustername());
assertEquals(60L, (long) monitoring.getIntervalSeconds());
MetricsConsumer consumer = model.getAdmin().getUserMetrics().getConsumers().get("slingstone");
assertNotNull(consumer);
Metric metric = consumer.metrics().get("foobar.count");
assertNotNull(metric);
assertEquals("foobar", metric.outputName);
}
@Test
void testThatThereAre2SlobroksPerContainerCluster() throws IOException, SAXException {
String hosts = ""
+ " "
+ " node0"
+ " "
+ " "
+ " node1"
+ " "
+ " "
+ " node2"
+ " "
+ " "
+ " node3"
+ " "
+ "";
String servicesWith3JdiscClusters = "" +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
"";
VespaModel model = createModel(hosts, servicesWith3JdiscClusters);
assertEquals(4, model.getHosts().size());
// 4 slobroks, 2 per cluster where possible
assertHostContainsServices(model, "hosts/myhost0", "slobrok", "logd", "logserver",
METRICS_PROXY_CONTAINER.serviceName, CONTAINER.serviceName);
assertHostContainsServices(model, "hosts/myhost1", "slobrok", "logd",
METRICS_PROXY_CONTAINER.serviceName, CONTAINER.serviceName);
assertHostContainsServices(model, "hosts/myhost2", "slobrok", "logd",
METRICS_PROXY_CONTAINER.serviceName, CONTAINER.serviceName);
assertHostContainsServices(model, "hosts/myhost3", "slobrok", "logd",
METRICS_PROXY_CONTAINER.serviceName, CONTAINER.serviceName);
}
@Test
void testLogForwarding() throws IOException, SAXException {
String services = "" +
" " +
" " +
" " +
" " +
" " +
" " +
" " +
"";
VespaModel model = createModel(hosts, services);
assertEquals(3, model.getHosts().size());
assertHostContainsServices(model, "hosts/myhost0", "logd", "logforwarder", "slobrok",
METRICS_PROXY_CONTAINER.serviceName);
assertHostContainsServices(model, "hosts/myhost1", "logd", "logforwarder", "slobrok",
METRICS_PROXY_CONTAINER.serviceName);
// Note: A logserver container is always added on logserver host
assertHostContainsServices(model, "hosts/myhost2", "logd", "logforwarder", "logserver",
METRICS_PROXY_CONTAINER.serviceName, LOGSERVER_CONTAINER.serviceName);
Set configIds = model.getConfigIds();
// 1 logforwarder on each host
IntStream.of(0, 1, 2).forEach(i -> assertTrue(configIds.contains("hosts/myhost" + i + "/logforwarder"), configIds.toString()));
// First forwarder
{
LogforwarderConfig.Builder builder = new LogforwarderConfig.Builder();
model.getConfig(builder, "hosts/myhost0/logforwarder");
LogforwarderConfig config = new LogforwarderConfig(builder);
assertEquals("foo:123", config.deploymentServer());
assertEquals("foocli", config.clientName());
assertEquals("/opt/splunkforwarder", config.splunkHome());
assertEquals(900, config.phoneHomeInterval());
assertEquals("some-domain:role.role-name", config.role());
}
// Other host's forwarder
{
LogforwarderConfig.Builder builder = new LogforwarderConfig.Builder();
model.getConfig(builder, "hosts/myhost2/logforwarder");
LogforwarderConfig config = new LogforwarderConfig(builder);
assertEquals("foo:123", config.deploymentServer());
assertEquals("foocli", config.clientName());
assertEquals("/opt/splunkforwarder", config.splunkHome());
assertEquals(900, config.phoneHomeInterval());
assertEquals("some-domain:role.role-name", config.role());
}
}
@Test
void testDedicatedLogserverInHostedVespa() throws IOException, SAXException {
String services = "" +
" " +
" " +
" " +
" " +
" " +
"";
VespaModel model = createModel(hosts, services, new DeployState.Builder()
.zone(new Zone(SystemName.cd, Environment.dev, RegionName.defaultName()))
.properties(new TestProperties().setHostedVespa(true)));
assertEquals(1, model.getHosts().size());
// Should create a logserver container on the same node as logserver
assertHostContainsServices(model, "hosts/myhost0", "slobrok", "logd", "logserver",
METRICS_PROXY_CONTAINER.serviceName, LOGSERVER_CONTAINER.serviceName);
}
private Set serviceNames(VespaModel model, String hostname) {
SentinelConfig config = model.getConfig(SentinelConfig.class, hostname);
return config.service().stream().map(SentinelConfig.Service::name).collect(Collectors.toSet());
}
private void assertHostContainsServices(VespaModel model, String hostname, String... expectedServices) {
Set serviceNames = serviceNames(model, hostname);
assertEquals(expectedServices.length, serviceNames.size());
for (String serviceName : expectedServices) {
assertTrue(serviceNames.contains(serviceName));
}
}
private VespaModel createModel(String hosts, String services) throws IOException, SAXException {
return createModel(hosts, services, new DeployState.Builder());
}
private VespaModel createModel(String hosts, String services, DeployState.Builder deployStateBuilder) throws IOException, SAXException {
ApplicationPackage app = new MockApplicationPackage.Builder()
.withHosts(hosts)
.withServices(services)
.build();
return new VespaModel(new NullConfigModelRegistry(), deployStateBuilder
.applicationPackage(app)
.modelHostProvisioner(new InMemoryProvisioner(Hosts.readFrom(app.getHosts()), true, false))
.build());
}
}