aboutsummaryrefslogtreecommitdiffstats
path: root/config-model/src/test/java/com/yahoo/vespa/model/application
diff options
context:
space:
mode:
authorbjormel <bjormel@yahooinc.com>2023-10-01 12:23:12 +0000
committerbjormel <bjormel@yahooinc.com>2023-10-01 12:23:12 +0000
commite9058b555d4dfea2f6c872d9a677e8678b569569 (patch)
treefa1b67c6e39712c1e0d9f308b0dd55573b43f913 /config-model/src/test/java/com/yahoo/vespa/model/application
parent0ad931fa86658904fe9212b014d810236b0e00e4 (diff)
parent16030193ec04ee41e98779a3d7ee6a6c1d0d0d6f (diff)
Merge branch 'master' into bjormel/aws-main-controller
Diffstat (limited to 'config-model/src/test/java/com/yahoo/vespa/model/application')
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java130
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java107
2 files changed, 237 insertions, 0 deletions
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java
new file mode 100644
index 00000000000..9b3b659c252
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/JvmHeapSizeValidatorTest.java
@@ -0,0 +1,130 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.ModelReference;
+import com.yahoo.config.application.api.ApplicationFile;
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.application.api.DeployLogger;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.OnnxModelCost;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.provision.InMemoryProvisioner;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.NodeResources;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.net.URI;
+import java.util.concurrent.atomic.AtomicLong;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * @author bjorncs
+ */
+class JvmHeapSizeValidatorTest {
+
+ @Test
+ void fails_on_too_low_jvm_percentage() throws IOException, SAXException {
+ var deployState = createDeployState(8, 7L * 1024 * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ var e = assertThrows(IllegalArgumentException.class, () -> new JvmHeapSizeValidator().validate(model, deployState));
+ String expectedMessage = "Allocated percentage of memory of JVM in cluster 'container' is too low (3% < 15%). Estimated cost of ONNX models is 7.00GB";
+ assertTrue(e.getMessage().contains(expectedMessage), e.getMessage());
+ }
+
+ @Test
+ void fails_on_too_low_heap_size() throws IOException, SAXException {
+ var deployState = createDeployState(2.2, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ var e = assertThrows(IllegalArgumentException.class, () -> new JvmHeapSizeValidator().validate(model, deployState));
+ String expectedMessage = "Allocated memory to JVM in cluster 'container' is too low (0.50GB < 0.60GB). Estimated cost of ONNX models is 1.00GB.";
+ assertTrue(e.getMessage().contains(expectedMessage), e.getMessage());
+ }
+
+ @Test
+ void accepts_adequate_heap_size() throws IOException, SAXException {
+ var deployState = createDeployState(8, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ assertDoesNotThrow(() -> new JvmHeapSizeValidator().validate(model, deployState));
+ }
+
+ @Test
+ void accepts_services_with_explicit_jvm_size() throws IOException, SAXException {
+ String servicesXml =
+ """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <services version='1.0'>
+ <container version='1.0'>
+ <nodes count="2">
+ <jvm allocated-memory='5%'/>
+ <resources vcpu="4" memory="2Gb" disk="125Gb"/>
+ </nodes>
+ <component id="hf-embedder" type="hugging-face-embedder">
+ <transformer-model url="https://my/url/model.onnx"/>
+ <tokenizer-model path="app/tokenizer.json"/>
+ </component>
+ </container>
+ </services>""";
+ var deployState = createDeployState(servicesXml, 2, 1024L * 1024 * 1024);
+ var model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ assertDoesNotThrow(() -> new JvmHeapSizeValidator().validate(model, deployState));
+ }
+
+ private static DeployState createDeployState(String servicesXml, double nodeGb, long modelCostBytes) {
+ return new DeployState.Builder()
+ .applicationPackage(
+ new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build())
+ .modelHostProvisioner(new InMemoryProvisioner(5, new NodeResources(4, nodeGb, 125, 0.3), true))
+ .properties(new TestProperties().setHostedVespa(true).setDynamicHeapSize(true))
+ .onnxModelCost(new ModelCostDummy(modelCostBytes))
+ .build();
+ }
+
+ private static DeployState createDeployState(double nodeGb, long modelCostBytes) {
+ String servicesXml =
+ """
+ <?xml version="1.0" encoding="utf-8" ?>
+ <services version='1.0'>
+ <container version='1.0'>
+ <nodes count="2">
+ <resources vcpu="4" memory="%fGb" disk="125Gb"/>
+ </nodes>
+ <component id="hf-embedder" type="hugging-face-embedder">
+ <transformer-model url="https://my/url/model.onnx"/>
+ <tokenizer-model path="app/tokenizer.json"/>
+ </component>
+ </container>
+ </services>""".formatted(nodeGb);
+ return createDeployState(servicesXml, nodeGb, modelCostBytes);
+ }
+
+ private static class ModelCostDummy implements OnnxModelCost, OnnxModelCost.Calculator {
+ final AtomicLong totalCost = new AtomicLong();
+ final long modelCost;
+
+ ModelCostDummy(long modelCost) { this.modelCost = modelCost; }
+
+ @Override public Calculator newCalculator(ApplicationPackage appPkg, DeployLogger logger) { return this; }
+ @Override public long aggregatedModelCostInBytes() { return totalCost.get(); }
+ @Override public void registerModel(ApplicationFile path) {}
+
+ @SuppressWarnings("removal") @Override public void registerModel(ModelReference ref) {}
+
+ @Override
+ public void registerModel(URI uri) {
+ assertEquals("https://my/url/model.onnx", uri.toString());
+ totalCost.addAndGet(modelCost);
+ }
+ }
+
+} \ No newline at end of file
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java
new file mode 100644
index 00000000000..cef4d8c27dd
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/UrlConfigValidatorTest.java
@@ -0,0 +1,107 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.api.ConfigDefinitionRepo;
+import com.yahoo.config.model.application.provider.MockFileRegistry;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.SystemName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.embedding.BertBaseEmbedderConfig;
+import com.yahoo.vespa.config.ConfigDefinitionKey;
+import com.yahoo.vespa.config.buildergen.ConfigDefinition;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+
+import static com.yahoo.config.provision.Environment.prod;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class UrlConfigValidatorTest {
+
+ @Test
+ void failsWhenContainerNodesNotExclusive() throws IOException, SAXException {
+ runValidatorOnApp(true, SystemName.Public); // Exclusive nodes in public => success
+
+ assertEquals("Found s3:// urls in config for container cluster default. This is only supported in public systems",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(false, SystemName.main))
+ .getMessage());
+
+ assertEquals("Found s3:// urls in config for container cluster default. This is only supported in public systems",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(true, SystemName.main))
+ .getMessage());
+
+ assertEquals("Found s3:// urls in config for container cluster default. Nodes in the cluster need to be 'exclusive',"
+ + " see https://cloud.vespa.ai/en/reference/services#nodes",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(false, SystemName.Public))
+ .getMessage());
+ }
+
+ private static String containerXml(boolean isExclusive) {
+ return """
+ <container version='1.0' id='default'>
+ <component id='transformer' class='ai.vespa.embedding.BertBaseEmbedder' bundle='model-integration'>
+ <config name='embedding.bert-base-embedder'>
+ <transformerModel url='s3://models/minilm-l6-v2/sentence_all_MiniLM_L6_v2.onnx' path='foo'/>
+ <tokenizerVocab url='s3://models/bert-base-uncased.txt'/>
+ </config>
+ </component>
+ <search/>
+ <document-api/>
+ <nodes count='2' exclusive='%s' />
+ </container>
+ """.formatted(Boolean.toString(isExclusive));
+ }
+
+ private static void runValidatorOnApp(boolean isExclusive, SystemName systemName) throws IOException, SAXException {
+ String container = containerXml(isExclusive);
+ String servicesXml = """
+ <services version='1.0'>
+ %s
+ </services>
+ """.formatted(container);
+ ApplicationPackage app = new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build();
+ DeployState deployState = createDeployState(app, systemName);
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ new UrlConfigValidator().validate(model, deployState);
+ }
+
+ private static DeployState createDeployState(ApplicationPackage app, SystemName systemName) {
+ boolean isHosted = true;
+ var builder = new DeployState.Builder()
+ .applicationPackage(app)
+ .zone(new Zone(systemName, prod, RegionName.from("us-east-3")))
+ .properties(new TestProperties().setHostedVespa(isHosted))
+ .fileRegistry(new MockFileRegistry());
+
+ Map<ConfigDefinitionKey, ConfigDefinition> defs = new HashMap<>();
+ defs.put(new ConfigDefinitionKey(BertBaseEmbedderConfig.CONFIG_DEF_NAME, BertBaseEmbedderConfig.CONFIG_DEF_NAMESPACE),
+ new ConfigDefinition(BertBaseEmbedderConfig.CONFIG_DEF_NAME, BertBaseEmbedderConfig.CONFIG_DEF_SCHEMA));
+ builder.configDefinitionRepo(new ConfigDefinitionRepo() {
+ @Override
+ public Map<ConfigDefinitionKey, com.yahoo.vespa.config.buildergen.ConfigDefinition> getConfigDefinitions() {
+ return defs;
+ }
+
+ @Override
+ public com.yahoo.vespa.config.buildergen.ConfigDefinition get(ConfigDefinitionKey key) {
+ return defs.get(key);
+ }
+ });
+ return builder.build();
+ }
+
+}