// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.xml;
import com.yahoo.collections.Pair;
import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.model.NullConfigModelRegistry;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.search.config.QrStartConfig;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.container.ContainerCluster;
import org.junit.Test;
import org.w3c.dom.Element;
import org.xml.sax.SAXException;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.logging.Level;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
/**
* @author baldersheim
* @author gjoranv
*/
public class JvmOptionsTest extends ContainerModelBuilderTestBase {
@Test
public void verify_jvm_tag_with_attributes() throws IOException, SAXException {
String servicesXml =
"" +
" " +
" " +
" " +
" " +
" " +
"";
ApplicationPackage applicationPackage = new MockApplicationPackage.Builder().withServices(servicesXml).build();
// Need to create VespaModel to make deploy properties have effect
final TestLogger logger = new TestLogger();
VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder()
.applicationPackage(applicationPackage)
.deployLogger(logger)
.build());
QrStartConfig.Builder qrStartBuilder = new QrStartConfig.Builder();
model.getConfig(qrStartBuilder, "container/container.0");
QrStartConfig qrStartConfig = new QrStartConfig(qrStartBuilder);
assertEquals("-XX:+UseParNewGC", qrStartConfig.jvm().gcopts());
assertEquals(45, qrStartConfig.jvm().heapSizeAsPercentageOfPhysicalMemory());
assertEquals("-XX:SoftRefLRUPolicyMSPerMB=2500", model.getContainerClusters().values().iterator().next().getContainers().get(0).getJvmOptions());
}
@Test
public void honours_jvm_gc_options() {
Element clusterElem = DomBuilderTest.parse(
"",
" ",
" ",
" ",
" ",
"" );
createModel(root, clusterElem);
QrStartConfig.Builder qrStartBuilder = new QrStartConfig.Builder();
root.getConfig(qrStartBuilder, "container/container.0");
QrStartConfig qrStartConfig = new QrStartConfig(qrStartBuilder);
assertEquals("-XX:+UseG1GC", qrStartConfig.jvm().gcopts());
}
private static void verifyIgnoreJvmGCOptions(boolean isHosted) throws IOException, SAXException {
String servicesXml =
"" +
" " +
" " +
" " +
"";
ApplicationPackage applicationPackage = new MockApplicationPackage.Builder().withServices(servicesXml).build();
// Need to create VespaModel to make deploy properties have effect
final TestLogger logger = new TestLogger();
VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder()
.applicationPackage(applicationPackage)
.deployLogger(logger)
.properties(new TestProperties().setHostedVespa(isHosted))
.build());
QrStartConfig.Builder qrStartBuilder = new QrStartConfig.Builder();
model.getConfig(qrStartBuilder, "container/container.0");
QrStartConfig qrStartConfig = new QrStartConfig(qrStartBuilder);
assertEquals("-XX:+UseG1GC", qrStartConfig.jvm().gcopts());
}
@Test
public void ignores_jvmgcoptions_on_conflicting_jvmoptions() throws IOException, SAXException {
verifyIgnoreJvmGCOptions(false);
verifyIgnoreJvmGCOptions(true);
}
private void verifyJvmGCOptions(boolean isHosted, String featureFlagDefault, String override, String expected) throws IOException, SAXException {
String servicesXml =
"" +
" " : ("jvm-gc-options='" + override + "'>")) +
" " +
" " +
"";
ApplicationPackage applicationPackage = new MockApplicationPackage.Builder().withServices(servicesXml).build();
// Need to create VespaModel to make deploy properties have effect
final TestLogger logger = new TestLogger();
VespaModel model = new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder()
.applicationPackage(applicationPackage)
.deployLogger(logger)
.properties(new TestProperties().setJvmGCOptions(featureFlagDefault).setHostedVespa(isHosted))
.build());
QrStartConfig.Builder qrStartBuilder = new QrStartConfig.Builder();
model.getConfig(qrStartBuilder, "container/container.0");
QrStartConfig qrStartConfig = new QrStartConfig(qrStartBuilder);
assertEquals(expected, qrStartConfig.jvm().gcopts());
}
@Test
public void requireThatJvmGCOptionsIsHonoured() throws IOException, SAXException {
verifyJvmGCOptions(false, null, null, ContainerCluster.G1GC);
verifyJvmGCOptions(true, null, null, ContainerCluster.PARALLEL_GC);
verifyJvmGCOptions(true, "", null, ContainerCluster.PARALLEL_GC);
verifyJvmGCOptions(false, "-XX:+UseG1GC", null, "-XX:+UseG1GC");
verifyJvmGCOptions(true, "-XX:+UseG1GC", null, "-XX:+UseG1GC");
verifyJvmGCOptions(false, null, "-XX:+UseG1GC", "-XX:+UseG1GC");
verifyJvmGCOptions(false, "-XX:+UseParallelGC", "-XX:+UseG1GC", "-XX:+UseG1GC");
verifyJvmGCOptions(false, null, "-XX:+UseParallelGC", "-XX:+UseParallelGC");
}
@Test
public void requireThatValidJvmGcOptionsAreNotLogged() throws IOException, SAXException {
// Valid options, should not log anything
verifyLoggingOfJvmGcOptions(true, "-XX:+ParallelGCThreads=8");
verifyLoggingOfJvmGcOptions(true, "-XX:MaxTenuringThreshold=15"); // No + or - after colon
verifyLoggingOfJvmGcOptions(false, "-XX:+UseConcMarkSweepGC");
}
@Test
public void requireThatInvalidJvmGcOptionsFailDeployment() throws IOException, SAXException {
try {
buildModelWithJvmOptions(new TestProperties().setHostedVespa(true),
new TestLogger(),
"gc-options",
"-XX:+ParallelGCThreads=8 foo bar");
fail();
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().startsWith("Invalid or misplaced JVM GC options in services.xml: bar,foo"));
}
}
private void verifyLoggingOfJvmGcOptions(boolean isHosted, String override, String... invalidOptions) throws IOException, SAXException {
verifyLoggingOfJvmOptions(isHosted, "gc-options", override, invalidOptions);
}
private void verifyLoggingOfJvmOptions(boolean isHosted, String optionName, String override, String... invalidOptions) throws IOException, SAXException {
TestLogger logger = new TestLogger();
buildModelWithJvmOptions(isHosted, logger, optionName, override);
List strings = Arrays.asList(invalidOptions.clone());
// Verify that nothing is logged if there are no invalid options
if (strings.isEmpty()) {
assertEquals(logger.msgs.size() > 0 ? logger.msgs.get(0).getSecond() : "", 0, logger.msgs.size());
return;
}
assertTrue("Expected 1 or more log messages for invalid JM options, got none", logger.msgs.size() > 0);
Pair firstOption = logger.msgs.get(0);
assertEquals(Level.WARNING, firstOption.getFirst());
Collections.sort(strings);
assertEquals("Invalid or misplaced JVM" + (optionName.equals("gc-options") ? " GC" : "") +
" options in services.xml: " + String.join(",", strings) + "." +
" See https://docs.vespa.ai/en/reference/services-container.html#jvm"
, firstOption.getSecond());
}
private void buildModelWithJvmOptions(boolean isHosted, TestLogger logger, String optionName, String override) throws IOException, SAXException {
buildModelWithJvmOptions(new TestProperties().setHostedVespa(isHosted), logger, optionName, override);
}
private void buildModelWithJvmOptions(TestProperties properties, TestLogger logger, String optionName, String override) throws IOException, SAXException {
String servicesXml =
"" +
" " +
" " +
" " +
" " +
"";
ApplicationPackage app = new MockApplicationPackage.Builder().withServices(servicesXml).build();
new VespaModel(new NullConfigModelRegistry(), new DeployState.Builder()
.applicationPackage(app)
.deployLogger(logger)
.properties(properties)
.build());
}
@Test
public void requireThatValidJvmOptionsAreNotLogged() throws IOException, SAXException {
// Valid options, should not log anything
verifyLoggingOfJvmOptions(true, "options", "-Xms2G");
verifyLoggingOfJvmOptions(true, "options", "-Xlog:gc");
verifyLoggingOfJvmOptions(true, "options", "-Djava.library.path=/opt/vespa/lib64:/home/y/lib64");
verifyLoggingOfJvmOptions(true, "options", "-XX:-OmitStackTraceInFastThrow");
verifyLoggingOfJvmOptions(false, "options", "-Xms2G");
}
@Test
public void requireThatInvalidJvmOptionsFailDeployment() throws IOException, SAXException {
try {
buildModelWithJvmOptions(new TestProperties().setHostedVespa(true),
new TestLogger(),
"options",
"-Xms2G foo bar");
fail();
} catch (IllegalArgumentException e) {
assertTrue(e.getMessage().contains("Invalid or misplaced JVM options in services.xml: bar,foo"));
}
}
}