diff options
59 files changed, 362 insertions, 245 deletions
diff --git a/client/go/.gitignore b/client/go/.gitignore index baab7c638c6..1d2f5a949a6 100644 --- a/client/go/.gitignore +++ b/client/go/.gitignore @@ -1,7 +1,6 @@ bin/ dist/ share/ -!Makefile !build/ !target/ mytestapp/ diff --git a/client/go/jvm/application_container.go b/client/go/jvm/application_container.go index 924a47f1997..e1ce45f890c 100644 --- a/client/go/jvm/application_container.go +++ b/client/go/jvm/application_container.go @@ -9,6 +9,7 @@ import ( "github.com/vespa-engine/vespa/client/go/defaults" "github.com/vespa-engine/vespa/client/go/envvars" + "github.com/vespa-engine/vespa/client/go/prog" "github.com/vespa-engine/vespa/client/go/trace" "github.com/vespa-engine/vespa/client/go/util" ) @@ -166,3 +167,11 @@ func (a *ApplicationContainer) configureOptions() { opts.AddOption("-Dzookeeper_log_file_prefix=" + zkLogFile) } } + +func (c *ApplicationContainer) exportExtraEnv(ps *prog.Spec) { + if c.ConfigId() != "" { + ps.Setenv(envvars.VESPA_CONFIG_ID, c.ConfigId()) + } else { + util.JustExitMsg("application container requires a config id") + } +} diff --git a/client/go/jvm/container.go b/client/go/jvm/container.go index 384384da137..3a66a2d37c4 100644 --- a/client/go/jvm/container.go +++ b/client/go/jvm/container.go @@ -5,6 +5,7 @@ package jvm import ( "fmt" + "sort" "strings" "github.com/vespa-engine/vespa/client/go/prog" @@ -18,6 +19,7 @@ type Container interface { ArgForMain() string JvmOptions() *Options Exec() + exportExtraEnv(ps *prog.Spec) } type containerBase struct { @@ -38,10 +40,20 @@ func (cb *containerBase) ConfigId() string { return cb.configId } +func keysOfMap(m map[string]string) []string { + keys := make([]string, 0, len(m)) + for k, _ := range m { + keys = append(keys, k) + } + return keys +} + func readableEnv(env map[string]string) string { + keys := keysOfMap(env) + sort.Strings(keys) var buf strings.Builder - for k, v := range env { - fmt.Fprintf(&buf, " %s=%s", k, v) + for _, k := range keys { + fmt.Fprintf(&buf, " %s=%s", k, env[k]) } return buf.String() } @@ -54,7 +66,7 @@ func (cb *containerBase) Exec() { } p := prog.NewSpec(argv) p.ConfigureNumaCtl() - cb.exportEnvSettings(p) + cb.JvmOptions().exportEnvSettings(p) trace.Info("starting container; env:", readableEnv(p.Env)) trace.Info("starting container; exec:", argv) err := p.Run() diff --git a/client/go/jvm/env.go b/client/go/jvm/env.go index 97050bcca7c..a23606e6d0d 100644 --- a/client/go/jvm/env.go +++ b/client/go/jvm/env.go @@ -12,13 +12,16 @@ import ( "github.com/vespa-engine/vespa/client/go/util" ) -func (c *containerBase) exportEnvSettings(ps *prog.Spec) { +func (opts *Options) exportEnvSettings(ps *prog.Spec) { + c := opts.container vespaHome := defaults.VespaHome() vlt := fmt.Sprintf("file:%s/logs/vespa/vespa.log", vespaHome) lcd := fmt.Sprintf("%s/var/db/vespa/logcontrol", vespaHome) + lcf := fmt.Sprintf("%s/%s.logcontrol", lcd, c.ServiceName()) dlp := fmt.Sprintf("%s/lib64", vespaHome) ps.Setenv(envvars.VESPA_LOG_TARGET, vlt) ps.Setenv(envvars.VESPA_LOG_CONTROL_DIR, lcd) + ps.Setenv(envvars.VESPA_LOG_CONTROL_FILE, lcf) ps.Setenv(envvars.VESPA_SERVICE_NAME, c.ServiceName()) ps.Setenv(envvars.LD_LIBRARY_PATH, dlp) ps.Setenv(envvars.MALLOC_ARENA_MAX, "1") @@ -26,8 +29,6 @@ func (c *containerBase) exportEnvSettings(ps *prog.Spec) { ps.Setenv(envvars.JAVAVM_LD_PRELOAD, preload) ps.Setenv(envvars.LD_PRELOAD, preload) } - if c.ConfigId() != "" { - ps.Setenv(envvars.VESPA_CONFIG_ID, c.ConfigId()) - } util.OptionallyReduceTimerFrequency() + c.exportExtraEnv(ps) } diff --git a/client/go/jvm/jdisc_options.go b/client/go/jvm/jdisc_options.go index f218ae7e28f..32cfb840336 100644 --- a/client/go/jvm/jdisc_options.go +++ b/client/go/jvm/jdisc_options.go @@ -20,7 +20,7 @@ func selectedEnv() []byte { var buf bytes.Buffer for _, vv := range os.Environ() { varName := strings.Split(vv, "=")[0] - if strings.Contains(vv, "\n") || strings.Contains(varName, "%%") { + if strings.Contains(vv, "\\u") || strings.Contains(vv, "\n") || strings.Contains(varName, "%%") { continue } buf.WriteString(vv) diff --git a/client/go/jvm/options_test.go b/client/go/jvm/options_test.go index 9ccecf5ae6b..cfb4e90d5e5 100644 --- a/client/go/jvm/options_test.go +++ b/client/go/jvm/options_test.go @@ -5,11 +5,13 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/vespa-engine/vespa/client/go/prog" ) type dummyContainer struct{ containerBase } -func (*dummyContainer) ArgForMain() string { return "arg-for-main" } +func (*dummyContainer) ArgForMain() string { return "arg-for-main" } +func (*dummyContainer) exportExtraEnv(ps *prog.Spec) {} func newDummyContainer() Container { var dc dummyContainer dc.serviceName = "foo" diff --git a/client/go/jvm/standalone_container.go b/client/go/jvm/standalone_container.go index a2181ed7c3e..edaea346889 100644 --- a/client/go/jvm/standalone_container.go +++ b/client/go/jvm/standalone_container.go @@ -8,6 +8,8 @@ import ( "os" "github.com/vespa-engine/vespa/client/go/defaults" + "github.com/vespa-engine/vespa/client/go/envvars" + "github.com/vespa-engine/vespa/client/go/prog" "github.com/vespa-engine/vespa/client/go/trace" "github.com/vespa-engine/vespa/client/go/util" ) @@ -78,3 +80,13 @@ func (a *StandaloneContainer) addJdiscProperties() { opts.AddOption("-Djdisc.cache.path=" + bCacheDir) opts.AddOption("-Djdisc.logger.tag=" + svcName) } + +func (c *StandaloneContainer) exportExtraEnv(ps *prog.Spec) { + vespaHome := defaults.VespaHome() + app := fmt.Sprintf("%s/conf/%s-app", vespaHome, c.ServiceName()) + if util.IsDirectory(app) { + ps.Setenv(envvars.STANDALONE_JDISC_APP_LOCATION, app) + } else { + util.JustExitMsg("standalone container requires an application directory, missing: " + app) + } +} diff --git a/client/go/script-utils/main.go b/client/go/script-utils/main.go index 9d35cb46e1c..97f31957b36 100644 --- a/client/go/script-utils/main.go +++ b/client/go/script-utils/main.go @@ -13,6 +13,7 @@ import ( "github.com/vespa-engine/vespa/client/go/cmd/logfmt" "github.com/vespa-engine/vespa/client/go/jvm" "github.com/vespa-engine/vespa/client/go/script-utils/configserver" + "github.com/vespa-engine/vespa/client/go/script-utils/standalone" "github.com/vespa-engine/vespa/client/go/script-utils/startcbinary" "github.com/vespa-engine/vespa/client/go/util" "github.com/vespa-engine/vespa/client/go/vespa" @@ -38,6 +39,8 @@ func main() { os.Exit(configserver.JustStartConfigserver()) case "vespa-start-container-daemon": os.Exit(jvm.RunApplicationContainer(os.Args[1:])) + case "run-standalone-container": + os.Exit(standalone.StartStandaloneContainer(os.Args[1:])) case "start-c-binary": os.Exit(startcbinary.Run(os.Args[1:])) case "export-env": diff --git a/client/go/script-utils/standalone/start.go b/client/go/script-utils/standalone/start.go new file mode 100644 index 00000000000..7b929730f5a --- /dev/null +++ b/client/go/script-utils/standalone/start.go @@ -0,0 +1,52 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +// Author: arnej + +// for starting standalone jdisc containers +package standalone + +import ( + "os" + + "github.com/vespa-engine/vespa/client/go/jvm" + "github.com/vespa-engine/vespa/client/go/trace" + "github.com/vespa-engine/vespa/client/go/util" + "github.com/vespa-engine/vespa/client/go/vespa" +) + +func commonPreChecks() { + if doTrace := os.Getenv("TRACE_JVM_STARTUP"); doTrace != "" { + trace.AdjustVerbosity(1) + } + if doDebug := os.Getenv("DEBUG_JVM_STARTUP"); doDebug != "" { + trace.AdjustVerbosity(2) + } + veHome := vespa.FindAndVerifyVespaHome() + err := os.Chdir(veHome) + if err != nil { + util.JustExitWith(err) + } + err = vespa.LoadDefaultEnv() + if err != nil { + util.JustExitWith(err) + } +} + +func StartStandaloneContainer(extraArgs []string) int { + commonPreChecks() + util.TuneResourceLimits() + serviceName := os.Getenv("VESPA_SERVICE_NAME") + if serviceName == "" { + util.JustExitMsg("Missing service name, ensure VESPA_SERVICE_NAME is set in the environment") + } + c := jvm.NewStandaloneContainer(serviceName) + jvmOpts := c.JvmOptions() + for _, extra := range extraArgs { + jvmOpts.AddOption(extra) + } + minFallback := jvm.MegaBytesOfMemory(128) + maxFallback := jvm.MegaBytesOfMemory(2048) + jvmOpts.AddDefaultHeapSizeArgs(minFallback, maxFallback) + c.Exec() + // unreachable: + return 1 +} diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/QuotaValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/QuotaValidator.java index 7ea582b99e6..da470f804d9 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/QuotaValidator.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/QuotaValidator.java @@ -86,19 +86,19 @@ public class QuotaValidator extends Validator { private static void throwIfBudgetNegative(double spend, BigDecimal budget, SystemName systemName) { if (budget.doubleValue() < 0) { - throw new IllegalArgumentException(quotaMessage("Please free up some capacity", systemName, spend, budget)); + throw new IllegalArgumentException(quotaMessage("Please free up some capacity.", systemName, spend, budget)); } } private static void throwIfBudgetExceeded(double spend, BigDecimal budget, SystemName systemName) { if (budget.doubleValue() < spend) { - throw new IllegalArgumentException(quotaMessage("Deployment exceeds its quota and has been blocked! Please contact support to update your plan", systemName, spend, budget)); + throw new IllegalArgumentException(quotaMessage("Contact support to upgrade your plan.", systemName, spend, budget)); } } private static String quotaMessage(String message, SystemName system, double spend, BigDecimal budget) { - String quotaDescription = String.format(Locale.ENGLISH, "Quota is $%.2f, but at least $%.2f is required", budget, spend); - return (system == SystemName.Public ? "" : system.value() + ": ") + message + ": " + quotaDescription; + String quotaDescription = String.format(Locale.ENGLISH, "The max resources specified cost $%.2f but your quota is $%.2f", spend, budget); + return (system == SystemName.Public ? "" : system.value() + ": ") + quotaDescription + ": " + message; } } diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/QuotaValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/QuotaValidatorTest.java index ef22f0b2770..1a7b3d62cb7 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/QuotaValidatorTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/QuotaValidatorTest.java @@ -47,7 +47,7 @@ public class QuotaValidatorTest { tester.deploy(null, getServices("testCluster", 10), Environment.prod, null); fail(); } catch (RuntimeException e) { - assertEquals("Deployment exceeds its quota and has been blocked! Please contact support to update your plan: Quota is $1.25, but at least $1.63 is required", e.getMessage()); + assertEquals("The max resources specified cost $1.63 but your quota is $1.25: Contact support to upgrade your plan.", e.getMessage()); } } @@ -58,7 +58,7 @@ public class QuotaValidatorTest { tester.deploy(null, getServices("testCluster", 10), Environment.prod, null); fail(); } catch (RuntimeException e) { - assertEquals("publiccd: Deployment exceeds its quota and has been blocked! Please contact support to update your plan: Quota is $1.00, but at least $1.63 is required", e.getMessage()); + assertEquals("publiccd: The max resources specified cost $1.63 but your quota is $1.00: Contact support to upgrade your plan.", e.getMessage()); } } @@ -69,7 +69,7 @@ public class QuotaValidatorTest { tester.deploy(null, getServices("testCluster", 10), Environment.prod, null); fail(); } catch (RuntimeException e) { - assertEquals("publiccd: Deployment exceeds its quota and has been blocked! Please contact support to update your plan: Quota is $1.25, but at least $1.63 is required", e.getMessage()); + assertEquals("publiccd: The max resources specified cost $1.63 but your quota is $1.25: Contact support to upgrade your plan.", e.getMessage()); } } @@ -82,8 +82,8 @@ public class QuotaValidatorTest { tester.deploy(null, getServices("testCluster", 10), Environment.prod, null); fail(); } catch (RuntimeException e) { - assertEquals("Please free up some capacity: Quota is $--.--, but at least $-.-- is required", - ValidationTester.censorNumbers(e.getMessage())); + assertEquals("The max resources specified cost $-.-- but your quota is $--.--: Please free up some capacity.", + ValidationTester.censorNumbers(e.getMessage())); } } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java index 22c9234b0c5..2dba42ce0d6 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java @@ -31,7 +31,7 @@ public final class ClusterSpec { this.type = type; this.id = id; this.groupId = groupId; - this.vespaVersion = Objects.requireNonNull(vespaVersion); + this.vespaVersion = Objects.requireNonNull(vespaVersion, "vespaVersion cannot be null"); this.exclusive = exclusive; if (type == Type.combined) { if (combinedId.isEmpty()) throw new IllegalArgumentException("combinedId must be set for cluster of type " + type); @@ -84,6 +84,10 @@ public final class ClusterSpec { return new ClusterSpec(type, id, newGroup, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } + public ClusterSpec withExclusivity(boolean exclusive) { + return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); + } + public ClusterSpec exclusive(boolean exclusive) { return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo, stateful); } diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java index d4827f07a80..7b1eb945247 100644 --- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java +++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java @@ -407,7 +407,7 @@ public class NodeResources { public boolean compatibleWith(NodeResources requested) { if ( ! equal(this.vcpu, requested.vcpu)) return false; if ( ! equal(this.memoryGb, requested.memoryGb)) return false; - if (requested.storageType == StorageType.local) { + if (this.storageType == StorageType.local || requested.storageType == StorageType.local) { if ( ! equal(this.diskGb, requested.diskGb)) return false; } else { diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java index 8a541abf4ae..d32d1a4e000 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/modelfactory/ModelsBuilder.java @@ -134,7 +134,7 @@ public abstract class ModelsBuilder<MODELRESULT extends ModelResult> { } else { if (e instanceof IllegalArgumentException) { - var wrapped = new InvalidApplicationException("Error loading " + applicationId, e); + var wrapped = new InvalidApplicationException("Invalid application", e); deployLogger.logApplicationPackage(Level.SEVERE, Exceptions.toMessageString(wrapped)); throw wrapped; } diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java index 8d023cac88a..4928af488e1 100644 --- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java +++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java @@ -148,6 +148,8 @@ public class SessionPreparer { return preparation.result(); } catch (IllegalArgumentException e) { + if (e instanceof InvalidApplicationException) + throw e; throw new InvalidApplicationException("Invalid application package", e); } } diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java index 95f44bf09a1..39b1a588a17 100644 --- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java +++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java @@ -294,7 +294,7 @@ public class HostedDeployTest { DeployTester tester = createTester(hosts, modelFactories, prodZone); // Not OK when failing version is requested. - assertEquals("Invalid application package", + assertEquals("Invalid application", assertThrows(IllegalArgumentException.class, () -> tester.deployApp("src/test/apps/hosted/", wantedVersion.toFullString())) .getMessage()); diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java index 47373413a0d..99b2968a43f 100644 --- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java +++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/ConfigServerException.java @@ -16,6 +16,12 @@ public class ConfigServerException extends RuntimeException { private final ErrorCode code; private final String message; + public ConfigServerException(ErrorCode code, String message) { + super(message); + this.code = code; + this.message = message; + } + public ConfigServerException(ErrorCode code, String message, String context) { super(context + ": " + message); this.code = code; diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java index d6da677cb27..ee1094ac32e 100644 --- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java +++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java @@ -1208,7 +1208,7 @@ public class ApplicationApiTest extends ControllerContainerTest { 400); ConfigServerMock configServer = tester.serviceRegistry().configServerMock(); - configServer.throwOnNextPrepare(new ConfigServerException(ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE, "Failed to prepare application", "Invalid application package")); + configServer.throwOnNextPrepare(new ConfigServerException(ConfigServerException.ErrorCode.INVALID_APPLICATION_PACKAGE, "Deployment failed", "Invalid application package")); // GET non-existent application package tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/package", GET).userIdentity(HOSTED_VESPA_OPERATOR), diff --git a/dist/vespa.spec b/dist/vespa.spec index 2592e769359..4a1946824a6 100644 --- a/dist/vespa.spec +++ b/dist/vespa.spec @@ -94,14 +94,8 @@ BuildRequires: cmake >= 3.11.4-3 BuildRequires: libarchive %endif %define _command_cmake cmake -%if 0%{?_centos_stream} -BuildRequires: (llvm-devel >= 14.0.0 and llvm-devel < 15) -%else -BuildRequires: (llvm-devel >= 13.0.1 and llvm-devel < 14) -%endif -%else -BuildRequires: (llvm-devel >= 13.0.1 and llvm-devel < 14) %endif +BuildRequires: llvm-devel BuildRequires: vespa-boost-devel >= 1.76.0-1 BuildRequires: vespa-openssl-devel >= 1.1.1o-1 %define _use_vespa_openssl 1 @@ -121,11 +115,7 @@ BuildRequires: vespa-lz4-devel >= 1.9.4-1 BuildRequires: vespa-onnxruntime-devel = 1.12.1 BuildRequires: vespa-libzstd-devel >= 1.5.2-1 BuildRequires: protobuf-devel -%if 0%{?_centos_stream} -BuildRequires: (llvm-devel >= 14.0.0 and llvm-devel < 15) -%else -BuildRequires: (llvm-devel >= 13.0.0 and llvm-devel < 14) -%endif +BuildRequires: llvm-devel BuildRequires: boost-devel >= 1.75 BuildRequires: gtest-devel BuildRequires: gmock-devel @@ -145,35 +135,12 @@ BuildRequires: openssl-devel BuildRequires: vespa-lz4-devel >= 1.9.4-1 BuildRequires: vespa-onnxruntime-devel = 1.12.1 BuildRequires: vespa-libzstd-devel >= 1.5.2-1 -%if 0%{?amzn2022} -BuildRequires: protobuf-devel -BuildRequires: llvm-devel >= 14.0.5 -BuildRequires: boost-devel >= 1.75 -BuildRequires: gtest-devel -BuildRequires: gmock-devel -%endif -%if 0%{?fc36} -BuildRequires: protobuf-devel -BuildRequires: llvm-devel >= 14.0.0 -BuildRequires: boost-devel >= 1.76 -BuildRequires: gtest-devel -BuildRequires: gmock-devel -%endif -%if 0%{?fc37} -BuildRequires: protobuf-devel -BuildRequires: llvm-devel >= 15.0.0 -BuildRequires: boost-devel >= 1.78 -BuildRequires: gtest-devel -BuildRequires: gmock-devel -%endif -%if 0%{?fc38} BuildRequires: protobuf-devel -BuildRequires: llvm-devel >= 15.0.0 -BuildRequires: boost-devel >= 1.78 +BuildRequires: llvm-devel +BuildRequires: boost-devel BuildRequires: gtest-devel BuildRequires: gmock-devel %endif -%endif %if 0%{?amzn2022} BuildRequires: vespa-xxhash-devel >= 0.8.1 %define _use_vespa_xxhash 1 @@ -238,7 +205,6 @@ BuildRequires: perf %if 0%{?amzn2022} Requires: vespa-xxhash >= 0.8.1 %else -Requires: xxhash Requires: xxhash-libs >= 0.8.1 %endif Requires: gdb @@ -344,39 +310,16 @@ Requires: vespa-openssl >= 1.1.1o-1 Requires: openssl-libs %endif %if 0%{?el8} -%if 0%{?centos} || 0%{?rocky} || 0%{?oraclelinux} -%if 0%{?_centos_stream} -Requires: (llvm-libs >= 14.0.0 and llvm-libs < 15) -%else -Requires: (llvm-libs >= 13.0.1 and llvm-libs < 14) -%endif -%else -Requires: (llvm-libs >= 13.0.1 and llvm-libs < 14) -%endif +Requires: llvm-libs Requires: vespa-protobuf = 3.21.7 %endif %if 0%{?el9} -%if 0%{?_centos_stream} -Requires: (llvm-libs >= 14.0.0 and llvm-libs < 15) -%else -Requires: (llvm-libs >= 13.0.0 and llvm-libs < 14) -%endif +Requires: llvm-libs Requires: protobuf %endif %if 0%{?fedora} Requires: protobuf -%if 0%{?amzn2022} -Requires: llvm-libs >= 14.0.5 -%endif -%if 0%{?fc36} -Requires: llvm-libs >= 14.0.0 -%endif -%if 0%{?fc37} -Requires: llvm-libs >= 15.0.0 -%endif -%if 0%{?fc38} -Requires: llvm-libs >= 15.0.0 -%endif +Requires: llvm-libs %endif Requires: vespa-onnxruntime = 1.12.1 diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java index 0bf32e534b7..fb21b009a30 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java @@ -7,11 +7,15 @@ import com.yahoo.concurrent.maintenance.JobControl; import com.yahoo.config.provision.ApplicationTransaction; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.DockerImage; +import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeFlavors; import com.yahoo.config.provision.Zone; import com.yahoo.config.provisioning.NodeRepositoryConfig; import com.yahoo.vespa.curator.Curator; import com.yahoo.vespa.flags.FlagSource; +import com.yahoo.vespa.flags.JacksonFlag; +import com.yahoo.vespa.flags.PermanentFlags; +import com.yahoo.vespa.flags.custom.SharedHost; import com.yahoo.vespa.hosted.provision.Node.State; import com.yahoo.vespa.hosted.provision.applications.Applications; import com.yahoo.vespa.hosted.provision.autoscale.MetricsDb; @@ -62,6 +66,7 @@ public class NodeRepository extends AbstractComponent { private final MetricsDb metricsDb; private final Orchestrator orchestrator; private final int spareCount; + private final JacksonFlag<SharedHost> sharedHosts; /** * Creates a node repository from a zookeeper provider. @@ -134,6 +139,7 @@ public class NodeRepository extends AbstractComponent { this.metricsDb = metricsDb; this.orchestrator = orchestrator; this.spareCount = spareCount; + this.sharedHosts = PermanentFlags.SHARED_HOST.bindTo(flagSource()); nodes.rewrite(); } @@ -197,7 +203,8 @@ public class NodeRepository extends AbstractComponent { * perfectly. */ public boolean exclusiveAllocation(ClusterSpec clusterSpec) { - return clusterSpec.isExclusive() || ! zone().cloud().allowHostSharing(); + return clusterSpec.isExclusive() || + ( !zone().cloud().allowHostSharing() && !sharedHosts.value().isEnabled(clusterSpec.type().name())); } /** diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java index 389be5b6652..3d76c8e3f94 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java @@ -164,10 +164,10 @@ public class AllocatableClusterResources { if (! exclusive) { // We decide resources: Add overhead to what we'll request (advertised) to make sure real becomes (at least) cappedNodeResources var advertisedResources = nodeRepository.resourcesCalculator().realToRequest(wantedResources.nodeResources(), exclusive); - advertisedResources = systemLimits.enlargeToLegal(advertisedResources, clusterSpec.type(), exclusive); // Ask for something legal + advertisedResources = systemLimits.enlargeToLegal(advertisedResources, clusterSpec, exclusive); // Ask for something legal advertisedResources = applicationLimits.cap(advertisedResources); // Overrides other conditions, even if it will then fail var realResources = nodeRepository.resourcesCalculator().requestToReal(advertisedResources, exclusive); // What we'll really get - if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) + if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec)) return Optional.empty(); if (anySatisfies(realResources, availableRealHostResources)) return Optional.of(new AllocatableClusterResources(wantedResources.with(realResources), @@ -187,7 +187,7 @@ public class AllocatableClusterResources { // Adjust where we don't need exact match to the flavor if (flavor.resources().storageType() == NodeResources.StorageType.remote) { - double diskGb = systemLimits.enlargeToLegal(cappedWantedResources, clusterSpec.type(), exclusive).diskGb(); + double diskGb = systemLimits.enlargeToLegal(cappedWantedResources, clusterSpec, exclusive).diskGb(); advertisedResources = advertisedResources.withDiskGb(diskGb); realResources = realResources.withDiskGb(diskGb); } @@ -197,7 +197,7 @@ public class AllocatableClusterResources { } if ( ! between(applicationLimits.min().nodeResources(), applicationLimits.max().nodeResources(), advertisedResources)) continue; - if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec.type())) continue; + if ( ! systemLimits.isWithinRealLimits(realResources, clusterSpec)) continue; var candidate = new AllocatableClusterResources(wantedResources.with(realResources), advertisedResources, wantedResources, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java index 689b5a9a950..c816abc060c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java @@ -58,7 +58,8 @@ public class Autoscaler { } private Advice autoscale(Application application, Cluster cluster, NodeList clusterNodes, Limits limits) { - ClusterModel clusterModel = new ClusterModel(application, + ClusterModel clusterModel = new ClusterModel(nodeRepository.zone(), + application, clusterNodes.clusterSpec(), cluster, clusterNodes, diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java index c9167c3a87b..80ff22eae91 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModel.java @@ -2,6 +2,7 @@ package com.yahoo.vespa.hosted.provision.autoscale; import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Zone; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.applications.Application; import com.yahoo.vespa.hosted.provision.applications.Cluster; @@ -39,6 +40,7 @@ public class ClusterModel { // TODO: Measure this, and only take it into account with queries private static final double fixedCpuCostFraction = 0.1; + private final Zone zone; private final Application application; private final ClusterSpec clusterSpec; private final Cluster cluster; @@ -53,12 +55,14 @@ public class ClusterModel { private Double queryFractionOfMax = null; private Double maxQueryGrowthRate = null; - public ClusterModel(Application application, + public ClusterModel(Zone zone, + Application application, ClusterSpec clusterSpec, Cluster cluster, NodeList clusterNodes, MetricsDb metricsDb, Clock clock) { + this.zone = zone; this.application = application; this.clusterSpec = clusterSpec; this.cluster = cluster; @@ -69,14 +73,15 @@ public class ClusterModel { this.nodeTimeseries = new ClusterNodesTimeseries(scalingDuration(), cluster, nodes, metricsDb); } - /** For testing */ - ClusterModel(Application application, + ClusterModel(Zone zone, + Application application, ClusterSpec clusterSpec, Cluster cluster, Clock clock, Duration scalingDuration, ClusterTimeseries clusterTimeseries, ClusterNodesTimeseries nodeTimeseries) { + this.zone = zone; this.application = application; this.clusterSpec = clusterSpec; this.cluster = cluster; @@ -218,13 +223,29 @@ public class ClusterModel { private double idealCpuLoad() { double queryCpuFraction = queryCpuFraction(); - // What's needed to have headroom for growth during scale-up as a fraction of current resources? + // Assumptions: 1) Write load is not organic so we should not grow to handle more. + // (TODO: But allow applications to set their target write rate and size for that) + // 2) Write load does not change in BCP scenarios. + return queryCpuFraction * 1/growthRateHeadroom() * 1/trafficShiftHeadroom() * idealQueryCpuLoad + + (1 - queryCpuFraction) * idealWriteCpuLoad; + } + + /** Returns the headroom for growth during organic traffic growth as a multiple of current resources. */ + private double growthRateHeadroom() { + if ( ! zone.environment().isProduction()) return 1; double growthRateHeadroom = 1 + maxQueryGrowthRate() * scalingDuration().toMinutes(); // Cap headroom at 10% above the historical observed peak if (queryFractionOfMax() != 0) growthRateHeadroom = Math.min(growthRateHeadroom, 1 / queryFractionOfMax() + 0.1); + return growthRateHeadroom; + } - // How much headroom is needed to handle sudden arrival of additional traffic due to another zone going down? + /** + * Returns the headroom is needed to handle sudden arrival of additional traffic due to another zone going down + * as a multiple of current resources. + */ + private double trafficShiftHeadroom() { + if ( ! zone.environment().isProduction()) return 1; double trafficShiftHeadroom; if (application.status().maxReadShare() == 0) // No traffic fraction data trafficShiftHeadroom = 2.0; // assume we currently get half of the global share of traffic @@ -232,13 +253,7 @@ public class ClusterModel { trafficShiftHeadroom = 1/application.status().maxReadShare(); else trafficShiftHeadroom = application.status().maxReadShare() / application.status().currentReadShare(); - trafficShiftHeadroom = Math.min(trafficShiftHeadroom, 1/application.status().maxReadShare()); - - // Assumptions: 1) Write load is not organic so we should not grow to handle more. - // (TODO: But allow applications to set their target write rate and size for that) - // 2) Write load does not change in BCP scenarios. - return queryCpuFraction * 1/growthRateHeadroom * 1/trafficShiftHeadroom * idealQueryCpuLoad + - (1 - queryCpuFraction) * idealWriteCpuLoad; + return Math.min(trafficShiftHeadroom, 1/application.status().maxReadShare()); } /** The estimated fraction of cpu usage which goes to processing queries vs. writes */ @@ -303,14 +318,15 @@ public class ClusterModel { * This is useful in cases where it's possible to continue without the cluser model, * as QuestDb is known to temporarily fail during reading of data. */ - public static Optional<ClusterModel> create(Application application, + public static Optional<ClusterModel> create(Zone zone, + Application application, ClusterSpec clusterSpec, Cluster cluster, NodeList clusterNodes, MetricsDb metricsDb, Clock clock) { try { - return Optional.of(new ClusterModel(application, clusterSpec, cluster, clusterNodes, metricsDb, clock)); + return Optional.of(new ClusterModel(zone, application, clusterSpec, cluster, clusterNodes, metricsDb, clock)); } catch (Exception e) { log.log(Level.WARNING, "Failed creating a cluster model for " + application + " " + cluster, e); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java index 36b32f0b099..cb5d8dd5042 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Limits.java @@ -63,7 +63,7 @@ public class Limits { public Limits fullySpecified(ClusterSpec clusterSpec, NodeRepository nodeRepository, ApplicationId applicationId) { if (this.isEmpty()) throw new IllegalStateException("Unspecified limits can not be made fully specified"); - var defaultResources = new CapacityPolicies(nodeRepository).defaultNodeResources(clusterSpec, applicationId, clusterSpec.isExclusive()); + var defaultResources = new CapacityPolicies(nodeRepository).defaultNodeResources(clusterSpec, applicationId); var specifiedMin = min.nodeResources().isUnspecified() ? min.with(defaultResources) : min; var specifiedMax = max.nodeResources().isUnspecified() ? max.with(defaultResources) : max; return new Limits(specifiedMin, specifiedMax); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsUpgrader.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsUpgrader.java index 258609f043e..120b7f00b38 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsUpgrader.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsUpgrader.java @@ -8,6 +8,7 @@ import com.yahoo.vespa.hosted.provision.Node; import com.yahoo.vespa.hosted.provision.NodeList; import com.yahoo.vespa.hosted.provision.NodeRepository; +import java.time.Duration; import java.time.Instant; /** @@ -43,8 +44,13 @@ public abstract class OsUpgrader { } /** Returns whether node can upgrade at given instant */ - boolean canUpgradeAt(Instant instant, Node node) { - return true; + final boolean canUpgradeAt(Instant instant, Node node) { + return node.history().age(instant).compareTo(gracePeriod()) > 0; + } + + /** The duration this leaves new nodes alone before scheduling any upgrade */ + private Duration gracePeriod() { + return Duration.ofDays(1); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersionChange.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersionChange.java index bdc40742ea4..a21c8aa539b 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersionChange.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/OsVersionChange.java @@ -1,42 +1,36 @@ // Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. package com.yahoo.vespa.hosted.provision.os; -import com.google.common.collect.ImmutableSortedMap; import com.yahoo.component.Version; import com.yahoo.config.provision.NodeType; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; +import java.util.Collections; +import java.util.SortedMap; +import java.util.TreeMap; /** * The OS version change being deployed in a {@link com.yahoo.vespa.hosted.provision.NodeRepository}. * * @author mpolden */ -public record OsVersionChange(Map<NodeType, OsVersionTarget> targets) { +public record OsVersionChange(SortedMap<NodeType, OsVersionTarget> targets) { - public static final OsVersionChange NONE = new OsVersionChange(Map.of()); + public static final OsVersionChange NONE = new OsVersionChange(new TreeMap<>()); - public OsVersionChange(Map<NodeType, OsVersionTarget> targets) { - this.targets = ImmutableSortedMap.copyOf(Objects.requireNonNull(targets)); - } - - /** Version targets in this */ - public Map<NodeType, OsVersionTarget> targets() { - return targets; + public OsVersionChange(SortedMap<NodeType, OsVersionTarget> targets) { + this.targets = Collections.unmodifiableSortedMap(new TreeMap<>(targets)); } /** Returns a copy of this with target for given node type removed */ public OsVersionChange withoutTarget(NodeType nodeType) { - var targets = new HashMap<>(this.targets); + var targets = new TreeMap<>(this.targets); targets.remove(nodeType); return new OsVersionChange(targets); } /** Returns a copy of this with given target added */ public OsVersionChange withTarget(Version version, NodeType nodeType) { - var copy = new HashMap<>(this.targets); + var copy = new TreeMap<>(this.targets); copy.compute(nodeType, (key, prevTarget) -> new OsVersionTarget(nodeType, version)); return new OsVersionChange(copy); } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java index f329c4cb695..f6779d08fd7 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RebuildingOsUpgrader.java @@ -93,8 +93,4 @@ public class RebuildingOsUpgrader extends OsUpgrader { nodeRepository.nodes().upgradeOs(NodeListFilter.from(host), Optional.of(target)); } - private static void illegal(String msg) { - throw new IllegalArgumentException(msg); - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringOsUpgrader.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringOsUpgrader.java index de0ef085bc6..4d98885b72c 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringOsUpgrader.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/os/RetiringOsUpgrader.java @@ -9,7 +9,6 @@ import com.yahoo.vespa.hosted.provision.NodeRepository; import com.yahoo.vespa.hosted.provision.node.Agent; import com.yahoo.vespa.hosted.provision.node.filter.NodeListFilter; -import java.time.Duration; import java.time.Instant; import java.util.Optional; import java.util.logging.Logger; @@ -46,11 +45,6 @@ public class RetiringOsUpgrader extends OsUpgrader { // No action needed in this implementation. } - @Override - public boolean canUpgradeAt(Instant instant, Node node) { - return node.history().age(instant).compareTo(gracePeriod()) > 0; - } - /** Returns nodes that are candidates for upgrade */ private NodeList candidates(Instant instant, OsVersionTarget target, NodeList allNodes) { NodeList activeNodes = allNodes.state(Node.State.active).nodeType(target.nodeType()); @@ -74,9 +68,4 @@ public class RetiringOsUpgrader extends OsUpgrader { nodeRepository.nodes().upgradeOs(NodeListFilter.from(host), Optional.of(target)); } - /** The duration this leaves new nodes alone before scheduling any upgrade */ - private Duration gracePeriod() { - return Duration.ofDays(1); - } - } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializer.java index 1f05e807a69..fd9e5eb34a9 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializer.java @@ -11,7 +11,7 @@ import com.yahoo.vespa.hosted.provision.os.OsVersionTarget; import java.io.IOException; import java.io.UncheckedIOException; -import java.util.HashMap; +import java.util.TreeMap; /** * Serializer for {@link OsVersionChange}. @@ -43,7 +43,7 @@ public class OsVersionChangeSerializer { } public static OsVersionChange fromJson(byte[] data) { - var targets = new HashMap<NodeType, OsVersionTarget>(); + var targets = new TreeMap<NodeType, OsVersionTarget>(); var inspector = SlimeUtils.jsonToSlime(data).get(); inspector.field(TARGETS_FIELD).traverse((ArrayTraverser) (idx, arrayInspector) -> { var version = Version.fromString(arrayInspector.field(VERSION_FIELD).asString()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java index 8d6c6b4bb62..a1400626658 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java @@ -10,10 +10,8 @@ import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.Environment; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.Zone; -import com.yahoo.vespa.flags.JacksonFlag; import com.yahoo.vespa.flags.PermanentFlags; import com.yahoo.vespa.flags.StringFlag; -import com.yahoo.vespa.flags.custom.SharedHost; import com.yahoo.vespa.hosted.provision.NodeRepository; import java.util.Map; import java.util.TreeMap; @@ -30,13 +28,13 @@ import static java.util.Objects.requireNonNull; */ public class CapacityPolicies { + private final NodeRepository nodeRepository; private final Zone zone; - private final JacksonFlag<SharedHost> sharedHosts; private final StringFlag adminClusterNodeArchitecture; public CapacityPolicies(NodeRepository nodeRepository) { + this.nodeRepository = nodeRepository; this.zone = nodeRepository.zone(); - this.sharedHosts = PermanentFlags.SHARED_HOST.bindTo(nodeRepository.flagSource()); this.adminClusterNodeArchitecture = PermanentFlags.ADMIN_CLUSTER_NODE_ARCHITECTURE.bindTo(nodeRepository.flagSource()); } @@ -79,16 +77,15 @@ public class CapacityPolicies { return target; } - public NodeResources defaultNodeResources(ClusterSpec clusterSpec, ApplicationId applicationId, boolean exclusive) { + public NodeResources defaultNodeResources(ClusterSpec clusterSpec, ApplicationId applicationId) { if (clusterSpec.type() == ClusterSpec.Type.admin) { Architecture architecture = adminClusterArchitecture(applicationId); if (clusterSpec.id().value().equals("cluster-controllers")) { - return clusterControllerResources(clusterSpec, exclusive) - .with(architecture); + return clusterControllerResources(clusterSpec).with(architecture); } - return (requiresExclusiveHost(clusterSpec.type(), exclusive) + return (nodeRepository.exclusiveAllocation(clusterSpec) ? versioned(clusterSpec, Map.of(new Version(0), smallestExclusiveResources())) : versioned(clusterSpec, Map.of(new Version(0), smallestSharedResources()))) .with(architecture); @@ -107,8 +104,8 @@ public class CapacityPolicies { } } - private NodeResources clusterControllerResources(ClusterSpec clusterSpec, boolean exclusive) { - if (requiresExclusiveHost(clusterSpec.type(), exclusive)) { + private NodeResources clusterControllerResources(ClusterSpec clusterSpec) { + if (nodeRepository.exclusiveAllocation(clusterSpec)) { return versioned(clusterSpec, Map.of(new Version(0), smallestExclusiveResources())); } return versioned(clusterSpec, Map.of(new Version(0), new NodeResources(0.25, 1.14, 10, 0.3))); @@ -118,11 +115,6 @@ public class CapacityPolicies { return Architecture.valueOf(adminClusterNodeArchitecture.with(APPLICATION_ID, instance.serializedForm()).value()); } - /** Returns whether an exclusive host is required for given cluster type and exclusivity requirement */ - private boolean requiresExclusiveHost(ClusterSpec.Type type, boolean exclusive) { - return ! zone.cloud().allowHostSharing() && (exclusive || !sharedHosts.value().isEnabled(type.name())); - } - /** Returns the resources for the newest version not newer than that requested in the cluster spec. */ static NodeResources versioned(ClusterSpec spec, Map<Version, NodeResources> resources) { return requireNonNull(new TreeMap<>(resources).floorEntry(spec.vespaVersion()), @@ -145,9 +137,10 @@ public class CapacityPolicies { } /** Returns whether the nodes requested can share physical host with other applications */ - public boolean decideExclusivity(Capacity capacity, boolean requestedExclusivity) { - if (capacity.cloudAccount().isPresent()) return true; // Implicit exclusive when using custom cloud account - return requestedExclusivity && (capacity.isRequired() || zone.environment() == Environment.prod); + public ClusterSpec decideExclusivity(Capacity capacity, ClusterSpec requestedCluster) { + if (capacity.cloudAccount().isPresent()) return requestedCluster.withExclusivity(true); // Implicit exclusive + boolean exclusive = requestedCluster.isExclusive() && (capacity.isRequired() || zone.environment() == Environment.prod); + return requestedCluster.withExclusivity(exclusive); } } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index b4c01267d3b..c425c235d11 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java @@ -95,28 +95,29 @@ public class NodeRepositoryProvisioner implements Provisioner { NodeResources resources; NodeSpec nodeSpec; if (requested.type() == NodeType.tenant) { - boolean exclusive = capacityPolicies.decideExclusivity(requested, cluster.isExclusive()); - Capacity actual = capacityPolicies.applyOn(requested, application, exclusive); + cluster = capacityPolicies.decideExclusivity(requested, cluster); + Capacity actual = capacityPolicies.applyOn(requested, application, cluster.isExclusive()); ClusterResources target = decideTargetResources(application, cluster, actual); ensureRedundancy(target.nodes(), cluster, actual.canFail(), application); logIfDownscaled(requested.minResources().nodes(), actual.minResources().nodes(), cluster, logger); groups = target.groups(); - resources = getNodeResources(cluster, target.nodeResources(), application, exclusive); - nodeSpec = NodeSpec.from(target.nodes(), resources, exclusive, actual.canFail(), + resources = getNodeResources(cluster, target.nodeResources(), application); + nodeSpec = NodeSpec.from(target.nodes(), resources, cluster.isExclusive(), actual.canFail(), requested.cloudAccount().orElse(nodeRepository.zone().cloud().account())); } else { groups = 1; // type request with multiple groups is not supported - resources = getNodeResources(cluster, requested.minResources().nodeResources(), application, true); + cluster = cluster.withExclusivity(true); + resources = getNodeResources(cluster, requested.minResources().nodeResources(), application); nodeSpec = NodeSpec.from(requested.type(), nodeRepository.zone().cloud().account()); } return asSortedHosts(preparer.prepare(application, cluster, nodeSpec, groups), resources); } - private NodeResources getNodeResources(ClusterSpec cluster, NodeResources nodeResources, ApplicationId applicationId, boolean exclusive) { + private NodeResources getNodeResources(ClusterSpec cluster, NodeResources nodeResources, ApplicationId applicationId) { return nodeResources.isUnspecified() - ? capacityPolicies.defaultNodeResources(cluster, applicationId, exclusive) + ? capacityPolicies.defaultNodeResources(cluster, applicationId) : nodeResources; } @@ -170,15 +171,14 @@ public class NodeRepositoryProvisioner implements Provisioner { firstDeployment // start at min, preserve current resources otherwise ? new AllocatableClusterResources(initialResourcesFrom(requested, clusterSpec, application.id()), clusterSpec, nodeRepository) : new AllocatableClusterResources(nodes, nodeRepository); - var clusterModel = new ClusterModel(application, clusterSpec, cluster, nodes, nodeRepository.metricsDb(), nodeRepository.clock()); + var clusterModel = new ClusterModel(zone, application, clusterSpec, cluster, nodes, nodeRepository.metricsDb(), nodeRepository.clock()); return within(Limits.of(requested), currentResources, firstDeployment, clusterModel); } private ClusterResources initialResourcesFrom(Capacity requested, ClusterSpec clusterSpec, ApplicationId applicationId) { var initial = requested.minResources(); if (initial.nodeResources().isUnspecified()) - initial = initial.with(capacityPolicies.defaultNodeResources(clusterSpec, applicationId, - capacityPolicies.decideExclusivity(requested, clusterSpec.isExclusive()))); + initial = initial.with(capacityPolicies.defaultNodeResources(clusterSpec, applicationId)); return initial; } @@ -191,8 +191,8 @@ public class NodeRepositoryProvisioner implements Provisioner { if (limits.min().equals(limits.max())) return limits.min(); // Don't change current deployments that are still legal - var currentAsAdvertised = current.advertisedResources(); - if (! firstDeployment && currentAsAdvertised.isWithin(limits.min(), limits.max())) return currentAsAdvertised; + if (! firstDeployment && current.advertisedResources().isWithin(limits.min(), limits.max())) + return current.advertisedResources(); // Otherwise, find an allocation that preserves the current resources as well as possible return allocationOptimizer.findBestAllocation(Load.one(), @@ -260,8 +260,7 @@ public class NodeRepositoryProvisioner implements Provisioner { private IllegalArgumentException newNoAllocationPossible(ClusterSpec spec, Limits limits) { StringBuilder message = new StringBuilder("No allocation possible within ").append(limits); - boolean exclusiveHosts = spec.isExclusive() || ! nodeRepository.zone().cloud().allowHostSharing(); - if (exclusiveHosts) + if (nodeRepository.exclusiveAllocation(spec)) message.append(". Nearest allowed node resources: ").append(findNearestNodeResources(limits)); return new IllegalArgumentException(message.toString()); diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceLimits.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceLimits.java index 81dd852e2a1..66895867623 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceLimits.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeResourceLimits.java @@ -28,10 +28,10 @@ public class NodeResourceLimits { public void ensureWithinAdvertisedLimits(String type, NodeResources requested, ClusterSpec cluster) { if (requested.isUnspecified()) return; - if (requested.vcpu() < minAdvertisedVcpu(cluster.type())) - illegal(type, "vcpu", "", cluster, requested.vcpu(), minAdvertisedVcpu(cluster.type())); - if (requested.memoryGb() < minAdvertisedMemoryGb(cluster.type())) - illegal(type, "memoryGb", "Gb", cluster, requested.memoryGb(), minAdvertisedMemoryGb(cluster.type())); + if (requested.vcpu() < minAdvertisedVcpu(cluster)) + illegal(type, "vcpu", "", cluster, requested.vcpu(), minAdvertisedVcpu(cluster)); + if (requested.memoryGb() < minAdvertisedMemoryGb(cluster)) + illegal(type, "memoryGb", "Gb", cluster, requested.memoryGb(), minAdvertisedMemoryGb(cluster)); if (requested.diskGb() < minAdvertisedDiskGb(requested, cluster.isExclusive())) illegal(type, "diskGb", "Gb", cluster, requested.diskGb(), minAdvertisedDiskGb(requested, cluster.isExclusive())); } @@ -40,36 +40,36 @@ public class NodeResourceLimits { public boolean isWithinRealLimits(NodeCandidate candidateNode, ClusterSpec cluster) { if (candidateNode.type() != NodeType.tenant) return true; // Resource limits only apply to tenant nodes return isWithinRealLimits(nodeRepository.resourcesCalculator().realResourcesOf(candidateNode, nodeRepository), - cluster.type()); + cluster); } /** Returns whether the real resources we'll end up with on a given tenant node are within limits */ - public boolean isWithinRealLimits(NodeResources realResources, ClusterSpec.Type clusterType) { + public boolean isWithinRealLimits(NodeResources realResources, ClusterSpec cluster) { if (realResources.isUnspecified()) return true; - if (realResources.vcpu() < minRealVcpu(clusterType)) return false; - if (realResources.memoryGb() < minRealMemoryGb(clusterType)) return false; + if (realResources.vcpu() < minRealVcpu(cluster)) return false; + if (realResources.memoryGb() < minRealMemoryGb(cluster)) return false; if (realResources.diskGb() < minRealDiskGb()) return false; return true; } - public NodeResources enlargeToLegal(NodeResources requested, ClusterSpec.Type clusterType, boolean exclusive) { + public NodeResources enlargeToLegal(NodeResources requested, ClusterSpec cluster, boolean exclusive) { if (requested.isUnspecified()) return requested; - return requested.withVcpu(Math.max(minAdvertisedVcpu(clusterType), requested.vcpu())) - .withMemoryGb(Math.max(minAdvertisedMemoryGb(clusterType), requested.memoryGb())) + return requested.withVcpu(Math.max(minAdvertisedVcpu(cluster), requested.vcpu())) + .withMemoryGb(Math.max(minAdvertisedMemoryGb(cluster), requested.memoryGb())) .withDiskGb(Math.max(minAdvertisedDiskGb(requested, exclusive), requested.diskGb())); } - private double minAdvertisedVcpu(ClusterSpec.Type clusterType) { - if (zone().environment() == Environment.dev && zone().cloud().allowHostSharing()) return 0.1; - if (clusterType.isContent() && zone().environment().isProduction()) return 1.0; - if (clusterType == ClusterSpec.Type.admin) return 0.1; + private double minAdvertisedVcpu(ClusterSpec cluster) { + if (zone().environment() == Environment.dev && ! nodeRepository.exclusiveAllocation(cluster)) return 0.1; + if (cluster.type().isContent() && zone().environment().isProduction()) return 1.0; + if (cluster.type() == ClusterSpec.Type.admin) return 0.1; return 0.5; } - private double minAdvertisedMemoryGb(ClusterSpec.Type clusterType) { - if (clusterType == ClusterSpec.Type.admin) return 1; + private double minAdvertisedMemoryGb(ClusterSpec cluster) { + if (cluster.type() == ClusterSpec.Type.admin) return 1; return 4; } @@ -85,10 +85,10 @@ public class NodeResourceLimits { return 4; } - private double minRealVcpu(ClusterSpec.Type clusterType) { return minAdvertisedVcpu(clusterType); } + private double minRealVcpu(ClusterSpec cluster) { return minAdvertisedVcpu(cluster); } - private double minRealMemoryGb(ClusterSpec.Type clusterType) { - return minAdvertisedMemoryGb(clusterType) - 1.7; + private double minRealMemoryGb(ClusterSpec cluster) { + return minAdvertisedMemoryGb(cluster) - 1.7; } private double minRealDiskGb() { return 6; } diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java index c5e4654c568..0603eb97cca 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/ApplicationSerializer.java @@ -63,7 +63,13 @@ public class ApplicationSerializer { NodeList nodes = applicationNodes.not().retired().cluster(cluster.id()); if (nodes.isEmpty()) return; ClusterResources currentResources = nodes.toResources(); - Optional<ClusterModel> clusterModel = ClusterModel.create(application, nodes.clusterSpec(), cluster, nodes, metricsDb, nodeRepository.clock()); + Optional<ClusterModel> clusterModel = ClusterModel.create(nodeRepository.zone(), + application, + nodes.clusterSpec(), + cluster, + nodes, + metricsDb, + nodeRepository.clock()); Cursor clusterObject = clustersObject.setObject(cluster.id().value()); clusterObject.setString("type", nodes.clusterSpec().type().name()); Limits limits = Limits.of(cluster).fullySpecified(nodes.clusterSpec(), nodeRepository, application.id()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java index f833348b8dc..d0232f6f62f 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java @@ -272,7 +272,7 @@ public class AutoscalingTest { .build(); NodeResources defaultResources = - new CapacityPolicies(fixture.tester().nodeRepository()).defaultNodeResources(fixture.clusterSpec, fixture.applicationId, false); + new CapacityPolicies(fixture.tester().nodeRepository()).defaultNodeResources(fixture.clusterSpec, fixture.applicationId); fixture.tester().assertResources("Min number of nodes and default resources", 2, 1, defaultResources, @@ -649,4 +649,34 @@ public class AutoscalingTest { fixture.autoscale()); } + @Test + public void test_changing_exclusivity() { + var fixture = AutoscalingTester.fixture() + .awsProdSetup(true) + .cluster(clusterSpec(true)) + .initialResources(Optional.empty()) + .build(); + fixture.tester().assertResources("Initial deployment at minimum", + 2, 1, 2, 4, 10, + fixture.currentResources().advertisedResources()); + + fixture.tester().deploy(fixture.applicationId(), clusterSpec(false), fixture.capacity()); + fixture.tester().assertResources("With non-exclusive nodes, a better solution is " + + "50% more nodes with half the cpu", + 3, 1, 1, 4, 10.2, + fixture.autoscale()); + + fixture.tester().deploy(fixture.applicationId(), clusterSpec(true), fixture.capacity()); + fixture.tester().assertResources("Reverts to the initial resources", + 2, 1, 2, 4, 10, + fixture.currentResources().advertisedResources()); + } + + private ClusterSpec clusterSpec(boolean exclusive) { + return ClusterSpec.request(ClusterSpec.Type.container, + ClusterSpec.Id.from("test")).vespaVersion("8.1.2") + .exclusive(exclusive) + .build(); + } + } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java index 56f23e62f90..00797cd6305 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java @@ -142,7 +142,7 @@ class AutoscalingTester { } public Autoscaler.Advice autoscale(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity) { - capacity = capacityPolicies.applyOn(capacity, applicationId, capacityPolicies.decideExclusivity(capacity, cluster.isExclusive())); + capacity = capacityPolicies.applyOn(capacity, applicationId, capacityPolicies.decideExclusivity(capacity, cluster).isExclusive()); Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId)) .withCluster(cluster.id(), false, capacity); try (Mutex lock = nodeRepository().applications().lock(applicationId)) { @@ -210,17 +210,15 @@ class AutoscalingTester { public static class MockHostResourcesCalculator implements HostResourcesCalculator { private final Zone zone; - private double memoryTax; - public MockHostResourcesCalculator(Zone zone, double memoryTax) { + public MockHostResourcesCalculator(Zone zone) { this.zone = zone; - this.memoryTax = memoryTax; } @Override public NodeResources realResourcesOf(Nodelike node, NodeRepository nodeRepository) { if (zone.cloud().dynamicProvisioning()) - return node.resources().withMemoryGb(node.resources().memoryGb() - memoryTax); + return node.resources().withMemoryGb(node.resources().memoryGb()); else return node.resources(); } @@ -228,19 +226,19 @@ class AutoscalingTester { @Override public NodeResources advertisedResourcesOf(Flavor flavor) { if (zone.cloud().dynamicProvisioning()) - return flavor.resources().withMemoryGb(flavor.resources().memoryGb() + memoryTax); + return flavor.resources().withMemoryGb(flavor.resources().memoryGb()); else return flavor.resources(); } @Override public NodeResources requestToReal(NodeResources resources, boolean exclusive) { - return resources.withMemoryGb(resources.memoryGb() - memoryTax); + return resources.withMemoryGb(resources.memoryGb()); } @Override public NodeResources realToRequest(NodeResources resources, boolean exclusive) { - return resources.withMemoryGb(resources.memoryGb() + memoryTax); + return resources.withMemoryGb(resources.memoryGb()); } @Override diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java index f31ad191637..b38dbfc55ae 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterModelTest.java @@ -6,6 +6,7 @@ import com.yahoo.config.provision.Capacity; import com.yahoo.config.provision.ClusterResources; import com.yahoo.config.provision.ClusterSpec; import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.Zone; import com.yahoo.test.ManualClock; import com.yahoo.vespa.hosted.provision.applications.Application; import com.yahoo.vespa.hosted.provision.applications.Cluster; @@ -73,11 +74,13 @@ public class ClusterModelTest { private ClusterModel clusterModel(Status status, IntFunction<Double> queryRate, IntFunction<Double> writeRate) { ManualClock clock = new ManualClock(); + Zone zone = Zone.defaultZone(); Application application = Application.empty(ApplicationId.from("t1", "a1", "i1")); ClusterSpec clusterSpec = clusterSpec(); Cluster cluster = cluster(resources()); application = application.with(cluster); - return new ClusterModel(application.with(status), + return new ClusterModel(zone, + application.with(status), clusterSpec, cluster, clock, Duration.ofMinutes(10), timeseries(cluster,100, queryRate, writeRate, clock), ClusterNodesTimeseries.empty()); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java index d348df00b78..4828db4dfc8 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java @@ -35,12 +35,14 @@ import java.util.stream.Collectors; public class Fixture { final AutoscalingTester tester; + final Zone zone; final ApplicationId applicationId; final ClusterSpec clusterSpec; final Capacity capacity; final Loader loader; public Fixture(Fixture.Builder builder, Optional<ClusterResources> initialResources, int hostCount) { + zone = builder.zone; applicationId = builder.application; clusterSpec = builder.cluster; capacity = builder.capacity; @@ -60,12 +62,20 @@ public class Fixture { return tester().nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId)); } + public AllocatableClusterResources currentResources() { + return new AllocatableClusterResources(tester.nodeRepository().nodes().list(Node.State.active).owner(applicationId).cluster(clusterId()), + tester.nodeRepository()); + } + public Cluster cluster() { return application().cluster(clusterId()).get(); } + public Capacity capacity() { return capacity; } + public ClusterModel clusterModel() { - return new ClusterModel(application(), + return new ClusterModel(zone, + application(), clusterSpec, cluster(), nodes(), @@ -128,10 +138,10 @@ public class Fixture { List<Flavor> hostFlavors = List.of(new Flavor(new NodeResources(100, 100, 100, 1))); Optional<ClusterResources> initialResources = Optional.of(new ClusterResources(5, 1, new NodeResources(2, 16, 75, 1))); Capacity capacity = Capacity.from(new ClusterResources(2, 1, - new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)), + new NodeResources(1, 4, 10, 1, NodeResources.DiskSpeed.any)), new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any))); - HostResourcesCalculator resourceCalculator = new AutoscalingTester.MockHostResourcesCalculator(zone, 0); + HostResourcesCalculator resourceCalculator = new AutoscalingTester.MockHostResourcesCalculator(zone); int hostCount = 0; public Fixture.Builder zone(Zone zone) { @@ -168,6 +178,11 @@ public class Fixture { return this; } + public Fixture.Builder cluster(ClusterSpec cluster) { + this.cluster = cluster; + return this; + } + public Fixture.Builder awsProdSetup(boolean allowHostSharing) { return this.awsHostFlavors() .awsResourceCalculator() @@ -215,7 +230,7 @@ public class Fixture { return this; } - public Fixture.Builder hostCount(int hostCount) { // TODO: Remove all usage of this + public Fixture.Builder hostCount(int hostCount) { this.hostCount = hostCount; return this; } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java index 4609c0a4023..d148f6d3cc7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/awsnodes/AwsHostResourcesCalculatorImpl.java @@ -12,7 +12,6 @@ import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; import java.util.List; import java.util.Map; import java.util.stream.Collectors; -import java.util.stream.Stream; /** * @author valerijf @@ -45,19 +44,32 @@ public class AwsHostResourcesCalculatorImpl implements HostResourcesCalculator { @Override public NodeResources requestToReal(NodeResources advertisedResources, boolean exclusive) { - double memoryOverhead = flavorsCompatibleWithAdvertised(advertisedResources, exclusive) - .mapToDouble(flavor -> resourcesCalculator.memoryOverhead(flavor, advertisedResources, false)).max().orElse(0); - double diskOverhead = flavorsCompatibleWithAdvertised(advertisedResources, exclusive) - .mapToDouble(flavor -> resourcesCalculator.diskOverhead(flavor, advertisedResources, false, exclusive)).max().orElse(0); + // Only consider exactly matched flavors if any to avoid concluding we have slightly too little resources + // on an exactly matched flavor if we move from exclusive to shared hosts + List<VespaFlavor> consideredFlavors = flavorsCompatibleWithAdvertised(advertisedResources, true); + if (consideredFlavors.isEmpty()) + consideredFlavors = flavorsCompatibleWithAdvertised(advertisedResources, false); + + double memoryOverhead = consideredFlavors.stream() + .mapToDouble(flavor -> resourcesCalculator.memoryOverhead(flavor, advertisedResources, false)) + .max().orElse(0); + double diskOverhead = consideredFlavors.stream() + .mapToDouble(flavor -> resourcesCalculator.diskOverhead(flavor, advertisedResources, false, exclusive)) + .max().orElse(0); return advertisedResources.withMemoryGb(advertisedResources.memoryGb() - memoryOverhead) .withDiskGb(advertisedResources.diskGb() - diskOverhead); } @Override public NodeResources realToRequest(NodeResources realResources, boolean exclusive) { + // Only consider exactly matched flavors if any to avoid concluding we have slightly too little resources + // on an exactly matched flavor if we move from exclusive to shared hosts + List<VespaFlavor> consideredFlavors = flavorsCompatibleWithReal(realResources, true); + if (consideredFlavors.isEmpty()) + consideredFlavors = flavorsCompatibleWithReal(realResources, false); double worstMemoryOverhead = 0; double worstDiskOverhead = 0; - for (VespaFlavor flavor : flavorsCompatibleWithReal(realResources, exclusive)) { + for (VespaFlavor flavor : consideredFlavors) { double memoryOverhead = resourcesCalculator.memoryOverhead(flavor, realResources, true); double diskOverhead = resourcesCalculator.diskOverhead(flavor, realResources, true, exclusive); NodeResources advertised = realResources.withMemoryGb(realResources.memoryGb() + memoryOverhead) @@ -78,20 +90,21 @@ public class AwsHostResourcesCalculatorImpl implements HostResourcesCalculator { } /** Returns the flavors of hosts which are eligible and matches the given advertised resources */ - private Stream<VespaFlavor> flavorsCompatibleWithAdvertised(NodeResources advertisedResources, boolean exclusive) { + private List<VespaFlavor> flavorsCompatibleWithAdvertised(NodeResources advertisedResources, boolean exactOnly) { return flavors.values().stream() - .filter(flavor -> exclusive - ? flavor.advertisedResources().compatibleWith(advertisedResources) - : flavor.advertisedResources().satisfies(advertisedResources)); + .filter(flavor -> exactOnly + ? flavor.advertisedResources().equalsWhereSpecified(advertisedResources) + : flavor.advertisedResources().satisfies(advertisedResources)) + .toList(); } /** Returns the flavors of hosts which are eligible and matches the given real resources */ - private List<VespaFlavor> flavorsCompatibleWithReal(NodeResources realResources, boolean exclusive) { + private List<VespaFlavor> flavorsCompatibleWithReal(NodeResources realResources, boolean exactOnly) { return flavors.values().stream() - .filter(flavor -> exclusive - ? flavor.realResources().compatibleWith(realResources) + .filter(flavor -> exactOnly + ? resourcesCalculator.realResourcesOfChildContainer(flavor.advertisedResources(), flavor).compatibleWith(realResources) : flavor.realResources().satisfies(realResources)) - .collect(Collectors.toList()); + .toList(); } } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java index a0c9e2fc530..be43d39cdeb 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OsUpgradeActivatorTest.java @@ -45,6 +45,7 @@ public class OsUpgradeActivatorTest { var tenantHostApplication = ApplicationId.from("hosted-vespa", "tenant-host", "default"); var tenantHostNodes = tester.makeReadyNodes(3, "default", NodeType.host, 1); tester.prepareAndActivateInfraApplication(tenantHostApplication, NodeType.host, version0); + tester.clock().advance(Duration.ofDays(1).plusSeconds(1)); // Let grace period pass var allNodes = new ArrayList<>(configHostNodes); allNodes.addAll(tenantHostNodes); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java index f9cc41cd8d0..1741dbdb749 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/os/OsVersionsTest.java @@ -175,7 +175,6 @@ public class OsVersionsTest { Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list() .hosts() .not().state(Node.State.deprovisioned); - tester.clock().advance(Duration.ofDays(2)); // Let grace period pass // Target is set and upgrade started var version1 = Version.fromString("7.1"); @@ -219,7 +218,6 @@ public class OsVersionsTest { Supplier<NodeList> hostNodes = () -> tester.nodeRepository().nodes().list() .nodeType(NodeType.host) .not().state(Node.State.deprovisioned); - tester.clock().advance(Duration.ofDays(2)); // Let grace period pass // Target is set and upgrade started var version1 = Version.fromString("7.1"); @@ -541,6 +539,7 @@ public class OsVersionsTest { NodeResources.DiskSpeed.fast, storageType), nodeType, 10); tester.prepareAndActivateInfraApplication(application, nodeType); + tester.clock().advance(Duration.ofDays(1).plusSeconds(1)); // Let grace period pass return nodes.stream() .map(Node::hostname) .flatMap(hostname -> tester.nodeRepository().nodes().node(hostname).stream()) diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializerTest.java index b68df92cf3e..e61e433b2bd 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializerTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/persistence/OsVersionChangeSerializerTest.java @@ -8,6 +8,7 @@ import com.yahoo.vespa.hosted.provision.os.OsVersionTarget; import org.junit.Test; import java.util.Map; +import java.util.TreeMap; import static org.junit.Assert.assertEquals; @@ -18,11 +19,11 @@ public class OsVersionChangeSerializerTest { @Test public void serialization() { - var change = new OsVersionChange(Map.of( + var change = new OsVersionChange(new TreeMap<>(Map.of( NodeType.host, new OsVersionTarget(NodeType.host, Version.fromString("1.2.3")), NodeType.proxyhost, new OsVersionTarget(NodeType.proxyhost, Version.fromString("4.5.6")), NodeType.confighost, new OsVersionTarget(NodeType.confighost, Version.fromString("7.8.9")) - )); + ))); var serialized = OsVersionChangeSerializer.fromJson(OsVersionChangeSerializer.toJson(change)); assertEquals(serialized, change); } diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-os-upgrade-complete.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-os-upgrade-complete.json index 0290aba4118..b5e8a040c30 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-os-upgrade-complete.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-os-upgrade-complete.json @@ -29,8 +29,7 @@ "rebootGeneration": 0, "currentRebootGeneration": 0, "currentOsVersion": "7.5.2", - "wantedOsVersion": "7.5.2", - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json index dc68af4b602..afed3b4e17e 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-2.json @@ -36,7 +36,7 @@ }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": true, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json index 94eb5a6015f..1c366d634cc 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-3.json @@ -36,7 +36,7 @@ }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json index db6ac0040af..98e3920b910 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports-4.json @@ -36,7 +36,7 @@ }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json index bbf1c7b52ea..dbe0222a848 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1-reports.json @@ -36,7 +36,7 @@ }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": true, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1.json index 70eac6a33db..b5c61780c51 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node1.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json index bf12fc18b03..b39aba199b7 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node2.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node3.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node3.json index 67f42d73776..480e8f7f910 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node3.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node3.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node4.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node4.json index ac9019a2d82..163a3d7c244 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node4.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node4.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node5.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node5.json index cff37c43655..c160c5dcdfe 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node5.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/docker-node5.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost1-with-firmware-data.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost1-with-firmware-data.json index 5124560b3bc..6d62c31ce56 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost1-with-firmware-data.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost1-with-firmware-data.json @@ -28,7 +28,7 @@ "requestedResources": { "vcpu":4.0, "memoryGb":32.0, "diskGb":1600.0, "bandwidthGbps":20.0, "diskSpeed":"fast", "storageType":"remote","architecture":"x86_64" }, "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "currentFirmwareCheck": 100, "wantedFirmwareCheck": 123, "failCount": 0, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost6.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost6.json index 4b3a1448d4c..add2fcf87a8 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost6.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/dockerhost6.json @@ -11,7 +11,7 @@ "environment": "BARE_METAL", "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 1, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node8.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node8.json index f6d7a9e201c..3c70371cdc6 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node8.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node8.json @@ -27,7 +27,7 @@ "environment": "BARE_METAL", "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node9.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node9.json index e76a364e1bb..2cb3213add1 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node9.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/node9.json @@ -11,7 +11,7 @@ "environment": "BARE_METAL", "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json index 58b96b51efa..03227335019 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/responses/parent2.json @@ -13,7 +13,7 @@ "environment": "BARE_METAL", "rebootGeneration": 0, "currentRebootGeneration": 0, - "deferOsUpgrade": false, + "deferOsUpgrade": true, "failCount": 0, "wantToRetire": false, "preferToRetire": false, diff --git a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h index 706e05f0156..86bd4f09cd4 100644 --- a/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h +++ b/searchlib/src/vespa/searchlib/queryeval/equiv_blueprint.h @@ -18,7 +18,7 @@ private: public: EquivBlueprint(const FieldSpecBaseList &fields, fef::MatchDataLayout subtree_mdl); - virtual ~EquivBlueprint(); + ~EquivBlueprint() override; // used by create visitor EquivBlueprint& addTerm(Blueprint::UP term, double exactness); diff --git a/searchlib/src/vespa/searchlib/queryeval/field_spec.cpp b/searchlib/src/vespa/searchlib/queryeval/field_spec.cpp index 138b4e6865d..ebda61dd820 100644 --- a/searchlib/src/vespa/searchlib/queryeval/field_spec.cpp +++ b/searchlib/src/vespa/searchlib/queryeval/field_spec.cpp @@ -12,4 +12,8 @@ FieldSpecBase::FieldSpecBase(uint32_t fieldId, fef::TermFieldHandle handle, bool assert(fieldId < 0x1000000); // Can be represented by 24 bits } +FieldSpecBaseList::~FieldSpecBaseList() = default; + +FieldSpecList::~FieldSpecList() = default; + } diff --git a/searchlib/src/vespa/searchlib/queryeval/field_spec.h b/searchlib/src/vespa/searchlib/queryeval/field_spec.h index be151e1fa28..0d66de954ad 100644 --- a/searchlib/src/vespa/searchlib/queryeval/field_spec.h +++ b/searchlib/src/vespa/searchlib/queryeval/field_spec.h @@ -59,6 +59,7 @@ private: List _list; public: + ~FieldSpecBaseList(); using const_iterator = List::const_iterator; FieldSpecBaseList &add(const FieldSpecBase &spec) { _list.push_back(spec); @@ -82,6 +83,7 @@ private: std::vector<FieldSpec> _list; public: + ~FieldSpecList(); FieldSpecList &add(const FieldSpec &spec) { _list.push_back(spec); return *this; |