diff options
34 files changed, 825 insertions, 820 deletions
diff --git a/.copr/Makefile b/.copr/Makefile index bb37e1c4401..c1d72525053 100644 --- a/.copr/Makefile +++ b/.copr/Makefile @@ -17,7 +17,11 @@ srpm: deps rpmbuild -bs --define "_topdir $(RPMTOPDIR)" $(SPECDIR)/vespa-$(VESPA_VERSION).spec cp -a $(RPMTOPDIR)/SRPMS/* $(outdir) +rpms: srpm + rpmbuild --rebuild $(outdir)/*.src.rpm + cp -a $(RPMTOPDIR)/RPMS/*/*.rpm $(outdir) + clean: -rm -rf $(RPMTOPDIR) -.PHONY: clean deps srpm +.PHONY: clean deps srpm rpms diff --git a/README.md b/README.md index 64bd0897d74..fa4aa5086de 100644 --- a/README.md +++ b/README.md @@ -2,14 +2,14 @@ [![#Vespa](https://vespa.ai/assets/vespa-logo-color.png)](https://vespa.ai) -The open big data serving engine - Store, search, organize and make machine-learned inferences +The open big data serving engine - Store, search, organize and make machine-learned inferences over big data at serving time. -This is the primary repository for Vespa where all development is happening. +This is the primary repository for Vespa where all development is happening. New production releases from this repository's master branch are made each weekday from Monday through Thursday. -* Home page: [https://vespa.ai](https://vespa.ai) -* Documentation: [https://docs.vespa.ai](https://docs.vespa.ai) +* Home page: [https://vespa.ai](https://vespa.ai) +* Documentation: [https://docs.vespa.ai](https://docs.vespa.ai) * Continuous build: [https://factory.vespa.oath.cloud](https://factory.vespa.oath.cloud) * Run applications in the cloud for free: [https://cloud.vespa.ai](https://cloud.vespa.ai) @@ -23,26 +23,26 @@ Vespa build status: [![Vespa Build Status](https://cd.screwdriver.cd/pipelines/6 - [Contribute](#contribute) - [Building](#building) - [License](#license) - + ## Background Use cases such as search, recommendation and personalization need to select a subset of data in a large corpus, evaluate machine-learned models over the selected data, organize and aggregate it and return it, typically in less -than 100 milliseconds, all while the data corpus is continuously changing. +than 100 milliseconds, all while the data corpus is continuously changing. -This is hard to do, especially with large data sets that needs to be distributed over multiple nodes and evaluated in -parallel. Vespa is a platform which performs these operations for you with high availability and performance. -It has been in development for many years and is used on a number of large internet services and apps which serve +This is hard to do, especially with large data sets that needs to be distributed over multiple nodes and evaluated in +parallel. Vespa is a platform which performs these operations for you with high availability and performance. +It has been in development for many years and is used on a number of large internet services and apps which serve hundreds of thousands of queries from Vespa per second. ## Install -Run your own Vespa instance: [https://docs.vespa.ai/en/getting-started.html](https://docs.vespa.ai/en/getting-started.html) +Run your own Vespa instance: [https://docs.vespa.ai/en/getting-started.html](https://docs.vespa.ai/en/getting-started.html) Or deploy your Vespa applications to the cloud service: [https://cloud.vespa.ai](https://cloud.vespa.ai) -## Usage +## Usage - The application created in the getting started guide is fully functional and production ready, but you may want to [add more nodes](https://docs.vespa.ai/en/multinode-systems.html) for redundancy. - See [developing applications](https://docs.vespa.ai/en/developer-guide.html) on adding your own Java components to your Vespa application. @@ -69,11 +69,11 @@ Some suggested improvements with pointers to code are in [TODO.md](TODO.md). ### Development environment -C++ and Java building is supported on CentOS 7. -The Java source can also be built on any platform having Java 11 and Maven installed. +C++ and Java building is supported on CentOS Stream 8. +The Java source can also be built on any platform having Java 17 and Maven installed. Use the following guide to set up a complete development environment using Docker for building Vespa, running unit tests and running system tests: -[Vespa development on CentOS 7](https://github.com/vespa-engine/docker-image-dev#vespa-development-on-centos-7). +[Vespa development on CentOS Stream 8](https://github.com/vespa-engine/docker-image-dev#vespa-development-on-centos-stream-8). ### Build Java modules diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml index 5cbed645539..8f9dbd59019 100644 --- a/cloud-tenant-base-dependencies-enforcer/pom.xml +++ b/cloud-tenant-base-dependencies-enforcer/pom.xml @@ -27,7 +27,7 @@ <httpclient.version>4.5.13</httpclient.version> <httpcore.version>4.4.13</httpcore.version> <junit5.version>5.8.1</junit5.version> <!-- TODO: in parent this is named 'junit.version' --> - <onnxruntime.version>1.8.0</onnxruntime.version> + <onnxruntime.version>1.11.0</onnxruntime.version> <!-- END parent/pom.xml --> diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java index 4d45398de90..c1267568581 100755 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/ContainerCluster.java @@ -70,8 +70,6 @@ import java.util.Objects; import java.util.Optional; import java.util.Set; import java.util.TreeSet; -import java.util.stream.Collectors; -import java.util.stream.Stream; import static com.yahoo.vespa.model.container.component.chain.ProcessingHandler.PROCESSING_HANDLER_CLASS; @@ -130,10 +128,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> public static final BindingPattern VIP_HANDLER_BINDING = SystemBindingPattern.fromHttpPath("/status.html"); - public static final Set<Path> SEARCH_AND_DOCPROC_BUNDLES = Stream.of( - PlatformBundles.SEARCH_AND_DOCPROC_BUNDLE, "container-search-gui", "docprocs", "linguistics-components") - .map(PlatformBundles::absoluteBundlePath).collect(Collectors.toSet()); - private final String name; protected List<CONTAINER> containers = new ArrayList<>(); @@ -272,7 +266,7 @@ public abstract class ContainerCluster<CONTAINER extends Container> * @return the removed component, or null if it was not present */ @SuppressWarnings("unused") // Used from other repositories - public Component removeComponent(ComponentId componentId) { + public Component<?, ?> removeComponent(ComponentId componentId) { return componentGroup.removeComponent(componentId); } @@ -404,18 +398,6 @@ public abstract class ContainerCluster<CONTAINER extends Container> return Collections.unmodifiableCollection(allComponents); } - /* - Add all search/docproc/feed related platform bundles. - This is only required for configured containers as the platform bundle set is not allowed to change between config generations. - For standalone container platform bundles can be added on features enabled as an update of application package requires restart. - */ - public void addAllPlatformBundles() { - ContainerDocumentApi.addVespaClientContainerBundle(this); - addSearchAndDocprocBundles(); - } - - public void addSearchAndDocprocBundles() { SEARCH_AND_DOCPROC_BUNDLES.forEach(this::addPlatformBundle); } - private void recursivelyFindAllComponents(Collection<Component<?, ?>> allComponents, AbstractConfigProducer<?> current) { for (AbstractConfigProducer<?> child: current.getChildren().values()) { if (child instanceof Component) @@ -476,9 +458,22 @@ public abstract class ContainerCluster<CONTAINER extends Container> * Adds the Vespa bundles that are necessary for all container types. */ public void addCommonVespaBundles() { - PlatformBundles.commonVespaBundles().forEach(this::addPlatformBundle); + PlatformBundles.commonVespaBundles.forEach(this::addPlatformBundle); } + /* + Add all search/docproc/feed related platform bundles. + This is only required for application configured containers as the platform bundle set is not allowed to change + between config generations. For standalone container platform bundles can be added on features enabled as an + update of application package requires restart. + */ + public void addAllPlatformBundles() { + ContainerDocumentApi.addVespaClientContainerBundle(this); + addSearchAndDocprocBundles(); + } + + public void addSearchAndDocprocBundles() { PlatformBundles.SEARCH_AND_DOCPROC_BUNDLES.forEach(this::addPlatformBundle); } + /** * Adds a bundle present at a known location at the target container nodes. * Note that the set of platform bundles cannot change during the jdisc container's lifetime. diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java b/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java index 6a1e647e9be..f8691dcde53 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/PlatformBundles.java @@ -6,12 +6,13 @@ import com.yahoo.vespa.defaults.Defaults; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.Collections; -import java.util.LinkedHashSet; -import java.util.List; import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** + * NOTE: Stable ordering of bundles in config is handled by {@link ContainerCluster#addPlatformBundle(Path)} + * * @author gjoranv * @author Ulf Lilleengen */ @@ -31,11 +32,18 @@ public class PlatformBundles { public static final Path LIBRARY_PATH = Paths.get(Defaults.getDefaults().underVespaHome("lib/jars")); public static final String SEARCH_AND_DOCPROC_BUNDLE = BundleInstantiationSpecification.CONTAINER_SEARCH_AND_DOCPROC; - public static Set<Path> commonVespaBundles() { - var bundles = new LinkedHashSet<Path>(); - commonVespaBundles.stream().map(PlatformBundles::absoluteBundlePath).forEach(bundles::add); - return Collections.unmodifiableSet(bundles); - } + // Bundles that must be loaded for all container types. + public static final Set<Path> commonVespaBundles = Stream.of( + "zkfacade", + "zookeeper-server" // TODO: not necessary in metrics-proxy. + ).map(PlatformBundles::absoluteBundlePath).collect(Collectors.toSet()); + + public static final Set<Path> SEARCH_AND_DOCPROC_BUNDLES = Stream.of( + PlatformBundles.SEARCH_AND_DOCPROC_BUNDLE, + "container-search-gui", + "docprocs", + "linguistics-components" + ).map(PlatformBundles::absoluteBundlePath).collect(Collectors.toSet()); public static Path absoluteBundlePath(String fileName) { return absoluteBundlePath(fileName, JarSuffix.JAR_WITH_DEPS); @@ -50,12 +58,6 @@ public class PlatformBundles { return searchAndDocprocComponents.contains(className); } - // Bundles that must be loaded for all container types. - private static final List<String> commonVespaBundles = List.of( - "zkfacade", - "zookeeper-server" // TODO: not necessary in metrics-proxy. - ); - // This is a hack to allow users to declare components from the search-and-docproc bundle without naming the bundle. private static final Set<String> searchAndDocprocComponents = Set.of( "com.yahoo.docproc.AbstractConcreteDocumentFactory", diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java index 8f02476b2cc..423d82687a3 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java @@ -30,7 +30,6 @@ import com.yahoo.config.provision.HostName; import com.yahoo.config.provision.NodeResources; import com.yahoo.config.provision.NodeType; import com.yahoo.config.provision.Zone; -import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.logging.FileConnectionLog; import com.yahoo.osgi.provider.model.ComponentModel; import com.yahoo.schema.OnnxModel; @@ -63,13 +62,13 @@ import com.yahoo.vespa.model.container.PlatformBundles; import com.yahoo.vespa.model.container.SecretStore; import com.yahoo.vespa.model.container.component.AccessLogComponent; import com.yahoo.vespa.model.container.component.BindingPattern; +import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.ConnectionLogComponent; import com.yahoo.vespa.model.container.component.FileStatusHandlerComponent; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.component.SimpleComponent; import com.yahoo.vespa.model.container.component.SystemBindingPattern; import com.yahoo.vespa.model.container.component.UserBindingPattern; -import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; import com.yahoo.vespa.model.container.docproc.ContainerDocproc; import com.yahoo.vespa.model.container.docproc.DocprocChains; import com.yahoo.vespa.model.container.http.AccessControl; @@ -89,6 +88,7 @@ import com.yahoo.vespa.model.container.xml.embedder.EmbedderConfig; import com.yahoo.vespa.model.content.StorageGroup; import org.w3c.dom.Element; import org.w3c.dom.Node; + import java.net.URI; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -886,16 +886,13 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> { } private void addSearchHandler(ApplicationContainerCluster cluster, Element searchElement) { - // Magic spell is needed to receive the chains config :-| - cluster.addComponent(new ProcessingHandler<>( - cluster.getSearch().getChains(), - BundleInstantiationSpecification.fromSearchAndDocproc("com.yahoo.search.searchchain.ExecutionFactory"))); - - cluster.addComponent( - new SearchHandler( - cluster, - serverBindings(searchElement, SearchHandler.DEFAULT_BINDING), - ContainerThreadpool.UserOptions.fromXml(searchElement).orElse(null))); + SearchHandler searchHandler = new SearchHandler(cluster, + serverBindings(searchElement, SearchHandler.DEFAULT_BINDING), + ContainerThreadpool.UserOptions.fromXml(searchElement).orElse(null)); + cluster.addComponent(searchHandler); + + // Add as child to SearchHandler to get the correct chains config. + searchHandler.addComponent(Component.fromClassAndBundle(SearchHandler.EXECUTION_FACTORY_CLASS, PlatformBundles.SEARCH_AND_DOCPROC_BUNDLE)); } private void addGUIHandler(ApplicationContainerCluster cluster) { diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java index 7e1b3be9240..54cd061d2c5 100644 --- a/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java +++ b/config-model/src/main/java/com/yahoo/vespa/model/container/xml/SearchHandler.java @@ -5,7 +5,6 @@ import com.yahoo.container.bundle.BundleInstantiationSpecification; import com.yahoo.container.handler.threadpool.ContainerThreadpoolConfig; import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.ContainerThreadpool; -import com.yahoo.vespa.model.container.PlatformBundles; import com.yahoo.vespa.model.container.component.BindingPattern; import com.yahoo.vespa.model.container.component.SystemBindingPattern; import com.yahoo.vespa.model.container.component.chain.ProcessingHandler; @@ -23,6 +22,8 @@ import static com.yahoo.container.bundle.BundleInstantiationSpecification.fromSe class SearchHandler extends ProcessingHandler<SearchChains> { static final String HANDLER_CLASS = com.yahoo.search.handler.SearchHandler.class.getName(); + static final String EXECUTION_FACTORY_CLASS = com.yahoo.search.searchchain.ExecutionFactory.class.getName(); + static final BundleInstantiationSpecification HANDLER_SPEC = fromSearchAndDocproc(HANDLER_CLASS); static final BindingPattern DEFAULT_BINDING = SystemBindingPattern.fromHttpPath("/search/*"); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java index b634356fcb6..3ff88e22b5d 100755 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/ContainerClusterTest.java @@ -32,6 +32,8 @@ import com.yahoo.vespa.model.container.search.ContainerSearch; import com.yahoo.vespa.model.container.search.searchchain.SearchChains; import org.junit.Test; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; @@ -79,6 +81,26 @@ public class ContainerClusterTest { } @Test + public void search_and_docproc_bundles_are_not_installed_for_plain_application_clusters() { + ApplicationContainerCluster cluster = newContainerCluster(); + + var bundleBuilder = new PlatformBundlesConfig.Builder(); + cluster.getConfig(bundleBuilder); + List<Path> installedBundles = bundleBuilder.build().bundlePaths().stream().map(Paths::get).toList(); + installedBundles.forEach(bundle -> assertFalse(PlatformBundles.SEARCH_AND_DOCPROC_BUNDLES.contains(bundle))); + } + + @Test + public void search_and_docproc_bundles_are_installed_for_application_clusters_with_search() { + ApplicationContainerCluster cluster = newClusterWithSearch(createRoot(false), false, null); + + var bundleBuilder = new PlatformBundlesConfig.Builder(); + cluster.getConfig(bundleBuilder); + List<Path> installedBundles = bundleBuilder.build().bundlePaths().stream().map(Paths::get).toList(); + PlatformBundles.SEARCH_AND_DOCPROC_BUNDLES.forEach(bundle -> assertTrue(installedBundles.contains(bundle))); + } + + @Test public void requireThatWeCanGetTheZoneConfig() { DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(true)) .zone(new Zone(SystemName.cd, Environment.test, RegionName.from("some-region"))) @@ -93,33 +115,11 @@ public class ContainerClusterTest { assertEquals("cd", config.system()); } - private ApplicationContainerCluster createContainerCluster(MockRoot root) { - return createContainerCluster(root, false, null); - } - private ApplicationContainerCluster createContainerCluster(MockRoot root, boolean isCombinedCluster, Integer memoryPercentage) { - ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", root.getDeployState()); - if (isCombinedCluster) - cluster.setHostClusterId("test-content-cluster"); - cluster.setMemoryPercentage(memoryPercentage); - cluster.setSearch(new ContainerSearch(cluster, new SearchChains(cluster, "search-chain"), new ContainerSearch.Options())); - return cluster; - } - private ClusterControllerContainerCluster createClusterControllerCluster(MockRoot root) { - return new ClusterControllerContainerCluster(root, "container0", "container1", root.getDeployState()); - } - private MockRoot createRoot(boolean isHosted) { - DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(isHosted)).build(); - return createRoot(state); - } - private MockRoot createRoot(DeployState deployState) { - return new MockRoot("foo", deployState); - } - private void verifyHeapSizeAsPercentageOfPhysicalMemory(boolean isHosted, boolean isCombinedCluster, Integer explicitMemoryPercentage, int expectedMemoryPercentage) { - ContainerCluster<?> cluster = createContainerCluster(createRoot(isHosted), isCombinedCluster, explicitMemoryPercentage); + ApplicationContainerCluster cluster = newClusterWithSearch(createRoot(isHosted), isCombinedCluster, explicitMemoryPercentage); QrStartConfig.Builder qsB = new QrStartConfig.Builder(); cluster.getConfig(qsB); QrStartConfig qsC= new QrStartConfig(qsB); @@ -155,7 +155,7 @@ public class ContainerClusterTest { private void verifyJvmArgs(boolean isHosted, boolean hasDocProc) { MockRoot root = createRoot(isHosted); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); if (hasDocProc) { cluster.setDocproc(new ContainerDocproc(cluster, null)); } @@ -194,6 +194,17 @@ public class ContainerClusterTest { } @Test + public void search_and_docproc_bundles_are_not_installed_for_cluster_controllers() { + MockRoot root = createRoot(false); + ClusterControllerContainerCluster cluster = createClusterControllerCluster(root); + + var bundleBuilder = new PlatformBundlesConfig.Builder(); + cluster.getConfig(bundleBuilder); + List<Path> installedBundles = bundleBuilder.build().bundlePaths().stream().map(Paths::get).toList(); + installedBundles.forEach(bundle -> assertFalse(PlatformBundles.SEARCH_AND_DOCPROC_BUNDLES.contains(bundle))); + } + + @Test public void testThatLinguisticsIsExcludedForClusterControllerCluster() { MockRoot root = createRoot(false); ClusterControllerContainerCluster cluster = createClusterControllerCluster(root); @@ -220,7 +231,7 @@ public class ContainerClusterTest { public void requireThatJvmOmitStackTraceInFastThrowOptionWorks() { // Empty option if option not set in property MockRoot root = createRoot(new DeployState.Builder().build()); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); ApplicationContainer container = cluster.getContainers().get(0); assertEquals("", container.getJvmOptions()); @@ -228,16 +239,16 @@ public class ContainerClusterTest { String jvmOption = "-XX:-foo"; DeployState deployState = new DeployState.Builder().properties(new TestProperties().setJvmOmitStackTraceInFastThrowOption(jvmOption)).build(); root = createRoot(deployState); - cluster = createContainerCluster(root); + cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); container = cluster.getContainers().get(0); assertEquals(jvmOption, container.getJvmOptions()); } @Test - public void requireThatWeCanHandleNull() { + public void requireThatWeCanHandleNullJvmOptions() { MockRoot root = createRoot(false); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); Container container = cluster.getContainers().get(0); container.setJvmOptions(""); @@ -249,7 +260,7 @@ public class ContainerClusterTest { @Test public void requireThatNonHostedUsesExpectedDefaultThreadpoolConfiguration() { MockRoot root = new MockRoot("foo"); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); root.freezeModelTopology(); @@ -261,7 +272,7 @@ public class ContainerClusterTest { @Test public void container_cluster_has_default_threadpool_provider() { MockRoot root = new MockRoot("foo"); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); root.freezeModelTopology(); @@ -280,7 +291,7 @@ public class ContainerClusterTest { .properties(new TestProperties().setHostedVespa(true)) .applicationPackage(new MockApplicationPackage.Builder().build()) .build()); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); root.freezeModelTopology(); @@ -297,7 +308,7 @@ public class ContainerClusterTest { .properties(new TestProperties().setHostedVespa(true)) .applicationPackage(new MockApplicationPackage.Builder().build()) .build()); - ApplicationContainerCluster cluster = createContainerCluster(root); + ApplicationContainerCluster cluster = newClusterWithSearch(root); addContainer(root, cluster, "c1", "host-c1"); root.freezeModelTopology(); @@ -471,6 +482,31 @@ public class ContainerClusterTest { expectedBundleNames.forEach(b -> assertTrue(installedBundles.stream().filter(p -> p.endsWith(b)).count() > 0)); } + private static ApplicationContainerCluster newClusterWithSearch(MockRoot root) { + return newClusterWithSearch(root, false, null); + } + + private static ApplicationContainerCluster newClusterWithSearch(MockRoot root, boolean isCombinedCluster, Integer memoryPercentage) { + ApplicationContainerCluster cluster = new ApplicationContainerCluster(root, "container0", "container1", root.getDeployState()); + if (isCombinedCluster) + cluster.setHostClusterId("test-content-cluster"); + cluster.setMemoryPercentage(memoryPercentage); + cluster.setSearch(new ContainerSearch(cluster, new SearchChains(cluster, "search-chain"), new ContainerSearch.Options())); + return cluster; + } + + private static ClusterControllerContainerCluster createClusterControllerCluster(MockRoot root) { + return new ClusterControllerContainerCluster(root, "container0", "container1", root.getDeployState()); + } + + private static MockRoot createRoot(boolean isHosted) { + DeployState state = new DeployState.Builder().properties(new TestProperties().setHostedVespa(isHosted)).build(); + return createRoot(state); + } + + private static MockRoot createRoot(DeployState deployState) { + return new MockRoot("foo", deployState); + } private static void addContainer(MockRoot root, ApplicationContainerCluster cluster, String name, String hostName) { addContainerWithHostResource(root, cluster, name, new HostResource(new Host(null, hostName))); diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java index c41373b9f85..e6e580b9baa 100644 --- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java +++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/SearchBuilderTest.java @@ -9,6 +9,7 @@ import com.yahoo.container.jdisc.JdiscBindingsConfig; import com.yahoo.vespa.model.VespaModel; import com.yahoo.vespa.model.container.ApplicationContainerCluster; import com.yahoo.vespa.model.container.ContainerCluster; +import com.yahoo.vespa.model.container.component.Component; import com.yahoo.vespa.model.container.component.Handler; import com.yahoo.vespa.model.container.search.GUIHandler; import com.yahoo.vespa.model.test.utils.ApplicationPackageUtils; @@ -39,13 +40,7 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { @Test public void gui_search_handler_is_always_included_when_search_is_specified() { - Element clusterElem = DomBuilderTest.parse( - "<container id='default' version='1.0'>", - " <search />", - nodesXml, - "</container>"); - - createModel(root, clusterElem); + createBasicSearchModel(); String discBindingsConfig = root.getConfig(JdiscBindingsConfig.class, "default").toString(); assertTrue(discBindingsConfig.contains(GUIHandler.BINDING_PATH)); @@ -190,7 +185,7 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { VespaModel model = getVespaModelWithMusic(hosts, services); - ContainerCluster cluster = model.getContainerClusters().get("container"); + ApplicationContainerCluster cluster = model.getContainerClusters().get("container"); assertFalse(cluster.getSearchChains().localProviders().isEmpty()); } @@ -216,19 +211,13 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { VespaModel model = getVespaModelWithMusic(hosts, services); - ContainerCluster cluster = model.getContainerClusters().get("container"); + ApplicationContainerCluster cluster = model.getContainerClusters().get("container"); assertFalse(cluster.getSearchChains().localProviders().isEmpty()); } @Test public void search_handler_has_dedicated_threadpool() { - Element clusterElem = DomBuilderTest.parse( - "<container id='default' version='1.0'>", - " <search />", - nodesXml, - "</container>"); - - createModel(root, clusterElem); + createBasicSearchModel(); Handler searchHandler = getHandler("default", SearchHandler.HANDLER_CLASS); assertTrue(searchHandler.getInjectedComponentIds().contains("threadpool@search-handler")); @@ -261,10 +250,30 @@ public class SearchBuilderTest extends ContainerModelBuilderTestBase { assertEquals(10, config.queueSize()); } + @Test + public void ExecutionFactory_gets_same_chains_config_as_SearchHandler() { + createBasicSearchModel(); + Component<?,?> executionFactory = ((SearchHandler) getComponent("default",SearchHandler.HANDLER_CLASS)) + .getChildren().get(SearchHandler.EXECUTION_FACTORY_CLASS); + + ChainsConfig executionFactoryChainsConfig = root.getConfig(ChainsConfig.class, executionFactory.getConfigId()); + assertEquals(chainsConfig(), executionFactoryChainsConfig); + } + private VespaModel getVespaModelWithMusic(String hosts, String services) { return new VespaModelCreatorWithMockPkg(hosts, services, ApplicationPackageUtils.generateSchemas("music")).create(); } + private void createBasicSearchModel() { + Element clusterElem = DomBuilderTest.parse( + "<container id='default' version='1.0'>", + " <search />", + nodesXml, + "</container>"); + + createModel(root, clusterElem); + } + private String hostsXml() { return "" + "<hosts> " + diff --git a/dist/vespa-engine.repo b/dist/vespa-engine.repo index 33d439a9572..de436eb16ad 100644 --- a/dist/vespa-engine.repo +++ b/dist/vespa-engine.repo @@ -1,8 +1,10 @@ -[vespa-engine-stable] -name=vespa-engine-stable -baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/epel-7-$basearch/ +[copr:copr.fedorainfracloud.org:group_vespa:vespa] +name=Copr repo for vespa owned by @vespa +baseurl=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/centos-stream-8-$basearch/ type=rpm-md +skip_if_unavailable=True gpgcheck=1 gpgkey=https://download.copr.fedorainfracloud.org/results/@vespa/vespa/pubkey.gpg repo_gpgcheck=0 enabled=1 +enabled_metadata=1 diff --git a/docker/.gitignore b/docker/.gitignore deleted file mode 100644 index 712576eb374..00000000000 --- a/docker/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -*.rpm -logs/ -tmp/ diff --git a/docker/OWNERS b/docker/OWNERS deleted file mode 100644 index 485a8c4be74..00000000000 --- a/docker/OWNERS +++ /dev/null @@ -1 +0,0 @@ -aressem diff --git a/docker/README.md b/docker/README.md deleted file mode 100644 index 18ab6648f3e..00000000000 --- a/docker/README.md +++ /dev/null @@ -1,26 +0,0 @@ -<!-- Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. --> - -# Building Vespa RPM on Docker (OS X and Linux) - -## Installing docker -[Docker installation](https://docs.docker.com/engine/installation/) - -*On Linux, the default storage device is devicemapper with loopback device and max 10GB container size. This size is too small for a full build. Please see [here](http://www.projectatomic.io/blog/2016/03/daemon_option_basedevicesize/) and [here](http://www.projectatomic.io/blog/2015/06/notes-on-fedora-centos-and-docker-storage-drivers/) to overcome this limitation.* - - -## Building Vespa RPM -Execute ```./build-vespa.sh <Vespa version number>``` to build Vespa from this source code. - -The produced rpms will be available in this folder after compilation. -The version number will be compiled into binaries and must be on the form x.y.z, like 7.1.2 - -but has no other meaning than that. - - -## Troubleshooting -- Use ```docker logs CONTAINER``` for output - useful if the commands above fail. - -- If the build fails, start from scratch and build again. Clean local docker if docker image disk full: - - ```docker rm -v $(docker ps -a -q -f status=exited)``` - - ```docker rmi $(docker images -f "dangling=true" -q)``` - -- _Directory renamed before its status could be extracted_ can be caused by [1219](https://github.com/docker/for-mac/issues/1219) - workaround (from the issue): "It may be an overlay storage driver issue - you can add ```{"storage-driver":"aufs"}``` in the advanced daemon preferences pane and see if that makes a difference." diff --git a/docker/build-vespa.sh b/docker/build-vespa.sh deleted file mode 100755 index f7b106b7800..00000000000 --- a/docker/build-vespa.sh +++ /dev/null @@ -1,18 +0,0 @@ -#!/bin/bash -# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -set -e - -if [ $# -ne 1 ]; then - echo "Usage: $0 <vespa version>" - exit 1 -fi - -DIR=$(cd $(dirname "${BASH_SOURCE[0]}") && pwd) -cd $DIR - -VESPA_VERSION=$1 -DOCKER_IMAGE="centos:7" - -docker pull ${DOCKER_IMAGE} - -docker run --rm -v $(pwd)/..:/vespa -w /vespa --entrypoint /vespa/docker/build/build-vespa-internal.sh "$DOCKER_IMAGE" "$VESPA_VERSION" "$(id -u)" "$(id -g)" diff --git a/docker/build/build-vespa-internal.sh b/docker/build/build-vespa-internal.sh deleted file mode 100755 index dada151a29f..00000000000 --- a/docker/build/build-vespa-internal.sh +++ /dev/null @@ -1,45 +0,0 @@ -#!/bin/bash -# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. -set -e - -if [ $# -ne 3 ]; then - echo "Usage: $0 <vespa version> <caller uid> <caller gid>" - echo "This script should not be called manually." - exit 1 -fi -VESPA_VERSION=$1 -CALLER_UID=$2 -CALLER_GID=$3 - -if ! [ -x ./dist.sh ]; then - echo ". is not a vespa-engine/vespa root directory" - exit 1 -fi - -./dist.sh ${VESPA_VERSION} - -yum -y install epel-release -yum -y install centos-release-scl - -if ! yum-config-manager --add-repo https://copr.fedorainfracloud.org/coprs/g/vespa/vespa/repo/epel-7/group_vespa-vespa-epel-7.repo; then - cat << 'EOF' > /etc/yum.repos.d/vespa-release.repo -[vespa-release] -name=Vespa releases -baseurl=https://verizonmedia.jfrog.io/artifactory/vespa/centos/$releasever/release/$basearch -gpgcheck=0 -enabled=1 -EOF -fi - -yum-builddep -y \ - --setopt="base-source.skip_if_unavailable=true" \ - --setopt="updates-source.skip_if_unavailable=true" \ - --setopt="extras-source.skip_if_unavailable=true" \ - --setopt="epel-source.skip_if_unavailable=true" \ - --setopt="centos-sclo-rh-source.skip_if_unavailable=true" \ - --setopt="centos-sclo-sclo-source.skip_if_unavailable=true" \ - ~/rpmbuild/SPECS/vespa-${VESPA_VERSION}.spec - -rpmbuild -bb ~/rpmbuild/SPECS/vespa-${VESPA_VERSION}.spec -chown ${CALLER_UID}:${CALLER_GID} ~/rpmbuild/RPMS/x86_64/*.rpm -mv ~/rpmbuild/RPMS/x86_64/*.rpm docker diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java index 7fa369da9c6..5bebd346bdb 100644 --- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java +++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocationOptimizer.java @@ -57,8 +57,8 @@ public class AllocationOptimizer { // Adjust for redundancy: Node in group if groups = 1, an extra group if multiple groups // TODO: Make the best choice based on size and redundancy setting instead - int nodesAdjustedForRedundancy = target.adjustForRedundancy() ? (groups == 1 ? nodes - 1 : nodes - groupSize) : nodes; - int groupsAdjustedForRedundancy = target.adjustForRedundancy() ? (groups == 1 ? 1 : groups - 1) : groups; + int nodesAdjustedForRedundancy = target.adjustForRedundancy() && nodes > 1 ? (groups == 1 ? nodes - 1 : nodes - groupSize) : nodes; + int groupsAdjustedForRedundancy = target.adjustForRedundancy() && nodes > 1 ? (groups == 1 ? 1 : groups - 1) : groups; ClusterResources next = new ClusterResources(nodes, groups, @@ -67,7 +67,6 @@ public class AllocationOptimizer { limits, target, current, clusterModel)); var allocatableResources = AllocatableClusterResources.from(next, current.clusterSpec(), limits, hosts, nodeRepository); - if (allocatableResources.isEmpty()) continue; if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get())) bestAllocation = allocatableResources; @@ -96,6 +95,7 @@ public class AllocationOptimizer { // The fixed cost portion of cpu does not scale with changes to the node count double queryCpuPerGroup = fixedCpuCostFraction * target.resources().vcpu() + (1 - fixedCpuCostFraction) * target.resources().vcpu() * current.groupSize() / groupSize; + double queryCpu = queryCpuPerGroup * current.groups() / groups; double writeCpu = target.resources().vcpu() * current.groupSize() / groupSize; cpu = clusterModel.queryCpuFraction() * queryCpu + (1 - clusterModel.queryCpuFraction()) * writeCpu; @@ -107,7 +107,6 @@ public class AllocationOptimizer { memory = target.resources().memoryGb(); disk = target.resources().diskGb(); } - // Combine the scaled resource values computed here // with the currently configured non-scaled values, given in the limits, if any NodeResources nonScaled = limits.isEmpty() || limits.min().nodeResources().isUnspecified() 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 ed1fbcd3ff8..ac072639cfe 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 @@ -61,8 +61,8 @@ public class Autoscaler { private Advice autoscale(Application application, Cluster cluster, NodeList clusterNodes, Limits limits) { ClusterModel clusterModel = new ClusterModel(application, - cluster, clusterNodes.clusterSpec(), + cluster, clusterNodes, nodeRepository.metricsDb(), nodeRepository.clock()); 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 34e31671491..5b1ee6cc496 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 @@ -48,8 +48,8 @@ public class ClusterModel { private Double maxQueryGrowthRate = null; public ClusterModel(Application application, - Cluster cluster, ClusterSpec clusterSpec, + Cluster cluster, NodeList clusterNodes, MetricsDb metricsDb, Clock clock) { @@ -169,7 +169,6 @@ public class ClusterModel { // Assume we have missed timely recording completion if it is longer than 4 days totalDuration = totalDuration.plus(maximum(Duration.ofDays(4), event.duration().get())); } - if (completedEventCount == 0) { // Use defaults if (clusterSpec.isStateful()) return Duration.ofHours(12); return Duration.ofMinutes(10); @@ -210,13 +209,13 @@ public class ClusterModel { * as QuestDb is known to temporarily fail during reading of data. */ public static Optional<ClusterModel> create(Application application, - Cluster cluster, ClusterSpec clusterSpec, + Cluster cluster, NodeList clusterNodes, MetricsDb metricsDb, Clock clock) { try { - return Optional.of(new ClusterModel(application, cluster, clusterSpec, clusterNodes, metricsDb, clock)); + return Optional.of(new ClusterModel(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/provisioning/NodeRepositoryProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java index 64865c15529..4ffe04d748c 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 @@ -171,7 +171,7 @@ 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.asList(), nodeRepository); - var clusterModel = new ClusterModel(application, cluster, clusterSpec, nodes, nodeRepository.metricsDb(), nodeRepository.clock()); + var clusterModel = new ClusterModel(application, clusterSpec, cluster, nodes, nodeRepository.metricsDb(), nodeRepository.clock()); return within(Limits.of(requested), currentResources, firstDeployment, clusterModel); } 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 f0b8e77ee56..1c10de8498a 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 @@ -62,7 +62,7 @@ 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, cluster, nodes.clusterSpec(), nodes, metricsDb, nodeRepository.clock()); + Optional<ClusterModel> clusterModel = ClusterModel.create(application, nodes.clusterSpec(), cluster, nodes, metricsDb, nodeRepository.clock()); Cursor clusterObject = clustersObject.setObject(cluster.id().value()); clusterObject.setString("type", nodes.clusterSpec().type().name()); toSlime(cluster.minResources(), clusterObject.setObject("min")); diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java index 667dffef2a6..bb441c6621c 100644 --- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java @@ -37,8 +37,8 @@ public class AutoscalingIntegrationTest { new MockHttpClient(tester.clock())); Autoscaler autoscaler = new Autoscaler(tester.nodeRepository()); - ApplicationId application1 = tester.applicationId("test1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "test"); + ApplicationId application1 = AutoscalingTester.applicationId("test1"); + ClusterSpec cluster1 = AutoscalingTester.clusterSpec(ClusterSpec.Type.container, "test"); Set<String> hostnames = tester.deploy(application1, cluster1, 2, 1, nodes) .stream().map(HostSpec::hostname) .collect(Collectors.toSet()); 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 d3142b3cc7f..a2eb5fba9f9 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 @@ -36,260 +36,196 @@ public class AutoscalingTest { @Test public void test_autoscaling_single_content_group() { - NodeResources hostResources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, - new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)); - ClusterResources max = new ClusterResources(20, 1, - new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); + var fixture = AutoscalingTester.fixture().build(); - // deploy - tester.deploy(application1, cluster1, 5, 1, hostResources); + fixture.tester().clock().advance(Duration.ofDays(1)); + assertTrue("No measurements -> No change", fixture.autoscale().isEmpty()); - tester.clock().advance(Duration.ofDays(1)); - assertTrue("No measurements -> No change", tester.autoscale(application1, cluster1, capacity).isEmpty()); + fixture.applyCpuLoad(0.7f, 59); + assertTrue("Too few measurements -> No change", fixture.autoscale().isEmpty()); - tester.addCpuMeasurements(0.25f, 1f, 59, application1); - assertTrue("Too few measurements -> No change", tester.autoscale(application1, cluster1, capacity).isEmpty()); + fixture.tester().clock().advance(Duration.ofDays(1)); + fixture.applyCpuLoad(0.7f, 120); + ClusterResources scaledResources = fixture.tester().assertResources("Scaling up since resource usage is too high", + 9, 1, 2.8, 5.0, 50.0, + fixture.autoscale()); - tester.clock().advance(Duration.ofDays(1)); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - ClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high", - 15, 1, 1.2, 28.6, 28.6, - tester.autoscale(application1, cluster1, capacity)); + fixture.deploy(Capacity.from(scaledResources)); + assertTrue("Cluster in flux -> No further change", fixture.autoscale().isEmpty()); - tester.deploy(application1, cluster1, scaledResources); - assertTrue("Cluster in flux -> No further change", tester.autoscale(application1, cluster1, capacity).isEmpty()); + fixture.deactivateRetired(Capacity.from(scaledResources)); - tester.deactivateRetired(application1, cluster1, scaledResources); - - tester.clock().advance(Duration.ofDays(2)); - tester.addCpuMeasurements(0.8f, 1f, 3, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyCpuLoad(0.8f, 3); assertTrue("Load change is large, but insufficient measurements for new config -> No change", - tester.autoscale(application1, cluster1, capacity).isEmpty()); + fixture.autoscale().isEmpty()); - tester.addCpuMeasurements(0.19f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - assertEquals("Load change is small -> No change", Optional.empty(), tester.autoscale(application1, cluster1, capacity).target()); + fixture.applyCpuLoad(0.19f, 100); + assertEquals("Load change is small -> No change", Optional.empty(), fixture.autoscale().target()); - tester.addCpuMeasurements(0.1f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling down to minimum since usage has gone down significantly", - 7, 1, 1.0, 66.7, 66.7, - tester.autoscale(application1, cluster1, capacity)); + fixture.applyCpuLoad(0.1f, 120); + fixture.tester().assertResources("Scaling cpu down since usage has gone down significantly", + 9, 1, 1.0, 5.0, 50.0, + fixture.autoscale()); + } + + /** Using too many resources for a short period is proof we should scale up regardless of the time that takes. */ + @Test + public void test_autoscaling_up_is_fast_TODO() { + var fixture = AutoscalingTester.fixture().build(); + fixture.tester().clock().advance(Duration.ofDays(1)); // TODO: Remove the need for this + fixture.applyLoad(1.0, 1.0, 1.0, 120); // TODO: Make this low + fixture.tester().assertResources("Scaling up since resource usage is too high", + 10, 1, 9.4, 8.5, 92.6, + fixture.autoscale()); } /** We prefer fewer nodes for container clusters as (we assume) they all use the same disk and memory */ @Test public void test_autoscaling_single_container_group() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 5, 1, resources); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - ClusterResources scaledResources = tester.assertResources("Scaling up since cpu usage is too high", - 7, 1, 2.5, 80.0, 50.5, - tester.autoscale(application1, cluster1, capacity)); - - tester.deploy(application1, cluster1, scaledResources); - tester.deactivateRetired(application1, cluster1, scaledResources); - - tester.addCpuMeasurements(0.1f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling down since cpu usage has gone down", - 4, 1, 2.5, 68.6, 27.4, - tester.autoscale(application1, cluster1, capacity)); + var fixture = AutoscalingTester.fixture().clusterType(ClusterSpec.Type.container).build(); + fixture.applyCpuLoad(0.25f, 120); + ClusterResources scaledResources = fixture.tester().assertResources("Scaling up since cpu usage is too high", + 5, 1, 3.8, 8.0, 50.5, + fixture.autoscale()); + fixture.deploy(Capacity.from(scaledResources)); + fixture.applyCpuLoad(0.1f, 120); + fixture.tester().assertResources("Scaling down since cpu usage has gone down", + 4, 1, 2.5, 6.4, 25.5, + fixture.autoscale()); } @Test public void autoscaling_handles_disk_setting_changes() { - NodeResources hostResources = new NodeResources(3, 100, 100, 1, NodeResources.DiskSpeed.slow); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - // deploy with slow - tester.deploy(application1, cluster1, 5, 1, hostResources); - assertTrue(tester.nodeRepository().nodes().list().owner(application1).stream() + var resources = new NodeResources(3, 100, 100, 1, NodeResources.DiskSpeed.slow); + var fixture = AutoscalingTester.fixture() + .hostResources(resources) + .initialResources(Optional.of(new ClusterResources(5, 1, resources))) + .capacity(Capacity.from(new ClusterResources(5, 1, resources))) + .build(); + + assertTrue(fixture.tester().nodeRepository().nodes().list().owner(fixture.application).stream() .allMatch(n -> n.allocation().get().requestedResources().diskSpeed() == NodeResources.DiskSpeed.slow)); - tester.clock().advance(Duration.ofDays(2)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.addCpuMeasurements(0.25f, 1f, 120, application1); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyCpuLoad(0.25, 120); + // Changing min and max from slow to any ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)); ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)); var capacity = Capacity.from(min, max); - ClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high", - 14, 1, 1.4, 30.8, 30.8, - tester.autoscale(application1, cluster1, capacity)); - assertEquals("Disk speed from min/max is used", + ClusterResources scaledResources = fixture.tester().assertResources("Scaling up", + 14, 1, 1.4, 30.8, 30.8, + fixture.autoscale(capacity)); + assertEquals("Disk speed from new capacity is used", NodeResources.DiskSpeed.any, scaledResources.nodeResources().diskSpeed()); - tester.deploy(application1, cluster1, scaledResources); - assertTrue(tester.nodeRepository().nodes().list().owner(application1).stream() - .allMatch(n -> n.allocation().get().requestedResources().diskSpeed() == NodeResources.DiskSpeed.any)); + fixture.deploy(Capacity.from(scaledResources)); + assertTrue(fixture.nodes().stream() + .allMatch(n -> n.allocation().get().requestedResources().diskSpeed() == NodeResources.DiskSpeed.any)); } @Test public void autoscaling_target_preserves_any() { - NodeResources hostResources = new NodeResources(3, 100, 100, 1); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - // Initial deployment NodeResources resources = new NodeResources(1, 10, 10, 1); - var min = new ClusterResources( 2, 1, resources.with(NodeResources.DiskSpeed.any)); - var max = new ClusterResources( 10, 1, resources.with(NodeResources.DiskSpeed.any)); - var capacity = Capacity.from(min, max); - tester.deploy(application1, cluster1, Capacity.from(min, max)); + var capacity = Capacity.from(new ClusterResources( 2, 1, resources.with(NodeResources.DiskSpeed.any)), + new ClusterResources( 10, 1, resources.with(NodeResources.DiskSpeed.any))); + var fixture = AutoscalingTester.fixture() + .capacity(capacity) + .initialResources(Optional.empty()) + .build(); // Redeployment without target: Uses current resource numbers with *requested* non-numbers (i.e disk-speed any) - assertTrue(tester.nodeRepository().applications().get(application1).get().cluster(cluster1.id()).get().targetResources().isEmpty()); - tester.deploy(application1, cluster1, Capacity.from(min, max)); - assertEquals(NodeResources.DiskSpeed.any, - tester.nodeRepository().nodes().list().owner(application1).cluster(cluster1.id()).first().get() - .allocation().get().requestedResources().diskSpeed()); + assertTrue(fixture.tester().nodeRepository().applications().get(fixture.application).get().cluster(fixture.cluster.id()).get().targetResources().isEmpty()); + fixture.deploy(); + assertEquals(NodeResources.DiskSpeed.any, fixture.nodes().first().get().allocation().get().requestedResources().diskSpeed()); // Autoscaling: Uses disk-speed any as well - tester.clock().advance(Duration.ofDays(2)); - tester.addCpuMeasurements(0.8f, 1f, 120, application1); - Autoscaler.Advice advice = tester.autoscale(application1, cluster1, capacity); - assertEquals(NodeResources.DiskSpeed.any, advice.target().get().nodeResources().diskSpeed()); - - + fixture.deactivateRetired(capacity); + fixture.tester().clock().advance(Duration.ofDays(1)); + fixture.applyCpuLoad(0.8, 120); + assertEquals(NodeResources.DiskSpeed.any, fixture.autoscale(capacity).target().get().nodeResources().diskSpeed()); } @Test public void autoscaling_respects_upper_limit() { - NodeResources hostResources = new NodeResources(6, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 5, 1, - new NodeResources(1.9, 70, 70, 1)); - tester.addMeasurements(0.25f, 0.95f, 0.95f, 0, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up to limit since resource usage is too high", - 6, 1, 2.4, 78.0, 70.0, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(5, 1, new NodeResources(1.9, 70, 70, 1)); + var max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)).build(); + + fixture.tester().clock().advance(Duration.ofDays(1)); + fixture.applyLoad(0.25, 0.95, 0.95, 120); + fixture.tester().assertResources("Scaling up to limit since resource usage is too high", + 6, 1, 2.4, 78.0, 79.0, + fixture.autoscale()); } @Test public void autoscaling_respects_lower_limit() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 4, 1, new NodeResources(1.8, 7.4, 8.5, 1)); - ClusterResources max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + var min = new ClusterResources( 4, 1, new NodeResources(1.8, 7.4, 8.5, 1)); + var max = new ClusterResources( 6, 1, new NodeResources(2.4, 78, 79, 1)); + var fixture = AutoscalingTester.fixture().capacity(Capacity.from(min, max)).build(); // deploy - tester.deploy(application1, cluster1, 5, 1, resources); - tester.addMeasurements(0.05f, 0.05f, 0.05f, 0, 120, application1); - tester.assertResources("Scaling down to limit since resource usage is low", - 4, 1, 1.8, 7.7, 10.0, - tester.autoscale(application1, cluster1, capacity)); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(0.05f, 0.05f, 0.05f, 120); + fixture.tester().assertResources("Scaling down to limit since resource usage is low", + 4, 1, 1.8, 7.4, 13.9, + fixture.autoscale()); } @Test public void autoscaling_with_unspecified_resources_use_defaults() { - NodeResources hostResources = new NodeResources(6, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, NodeResources.unspecified()); - ClusterResources max = new ClusterResources( 6, 1, NodeResources.unspecified()); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + var min = new ClusterResources( 2, 1, NodeResources.unspecified()); + var max = new ClusterResources( 6, 1, NodeResources.unspecified()); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.empty()) + .capacity(Capacity.from(min, max)) + .build(); NodeResources defaultResources = - new CapacityPolicies(tester.nodeRepository()).defaultNodeResources(cluster1, application1, false); - - // deploy - tester.deploy(application1, cluster1, Capacity.from(min, max)); - tester.assertResources("Min number of nodes and default resources", - 2, 1, defaultResources, - tester.nodeRepository().nodes().list().owner(application1).toResources()); - tester.addMeasurements(0.25f, 0.95f, 0.95f, 0, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up to limit since resource usage is too high", - 4, 1, - defaultResources.vcpu(), defaultResources.memoryGb(), defaultResources.diskGb(), - tester.autoscale(application1, cluster1, capacity)); + new CapacityPolicies(fixture.tester().nodeRepository()).defaultNodeResources(fixture.cluster, fixture.application, false); + + fixture.tester().assertResources("Min number of nodes and default resources", + 2, 1, defaultResources, + fixture.nodes().toResources()); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(0.25, 0.95, 0.95, 120); + fixture.tester().assertResources("Scaling up", + 5, 1, + defaultResources.vcpu(), defaultResources.memoryGb(), defaultResources.diskGb(), + fixture.autoscale()); } @Test public void autoscaling_respects_group_limit() { - NodeResources hostResources = new NodeResources(30.0, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(18, 6, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 5, 5, new NodeResources(3.0, 10, 10, 1)); - tester.addCpuMeasurements( 0.3f, 1f, 240, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up since resource usage is too high", - 6, 6, 3.6, 8.0, 10.0, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(5, 5, new NodeResources(3.0, 10, 10, 1)); + var max = new ClusterResources(18, 6, new NodeResources(100, 1000, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyCpuLoad(0.3, 240); + fixture.tester().assertResources("Scaling up", + 6, 6, 3.8, 8.0, 10.0, + fixture.autoscale()); } @Test public void test_autoscaling_limits_when_min_equals_max() { - NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = min; - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + var fixture = AutoscalingTester.fixture().capacity(Capacity.from(min, min)).build(); // deploy - tester.deploy(application1, cluster1, 5, 1, resources); - tester.clock().advance(Duration.ofDays(1)); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - assertTrue(tester.autoscale(application1, cluster1, capacity).isEmpty()); + fixture.tester().clock().advance(Duration.ofDays(1)); + fixture.applyCpuLoad(0.25, 120); + assertTrue(fixture.autoscale().isEmpty()); } @Test @@ -308,8 +244,8 @@ public class AutoscalingTest { tester.provisioning().makeReadyNodes(5, remoteFlavor.name(), NodeType.host, 8); tester.provisioning().activateTenantHosts(); - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + ApplicationId application1 = AutoscalingTester.applicationId("application1"); + ClusterSpec cluster1 = AutoscalingTester.clusterSpec(ClusterSpec.Type.container, "cluster1"); // deploy tester.deploy(application1, cluster1, 3, 1, min.nodeResources()); @@ -326,239 +262,150 @@ public class AutoscalingTest { @Test public void suggestions_ignores_limits() { - NodeResources resources = new NodeResources(3, 100, 100, 1); ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = min; - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 5, 1, resources); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up since resource usage is too high", - 7, 1, 2.5, 80.0, 50.5, - tester.suggest(application1, cluster1.id(), min, max)); + var fixture = AutoscalingTester.fixture().capacity(Capacity.from(min, min)).build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyCpuLoad(1.0, 120); + fixture.tester().assertResources("Suggesting above capacity limit", + 8, 1, 9.3, 5.7, 57.1, + fixture.tester().suggest(fixture.application, fixture.cluster.id(), min, min)); } @Test public void not_using_out_of_service_measurements() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources(2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(5, 1, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 2, 1, resources); - tester.addMeasurements(0.5f, 0.6f, 0.7f, 1, false, true, 120, application1); - assertTrue("Not scaling up since nodes were measured while cluster was unstable", - tester.autoscale(application1, cluster1, capacity).isEmpty()); + var fixture = AutoscalingTester.fixture().build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(0.9, 0.6, 0.7, 1, false, true, 120); + assertTrue("Not scaling up since nodes were measured while cluster was out of service", + fixture.autoscale().isEmpty()); } @Test public void not_using_unstable_measurements() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources(2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(5, 1, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 2, 1, resources); - tester.addMeasurements(0.5f, 0.6f, 0.7f, 1, true, false, 120, application1); - assertTrue("Not scaling up since nodes were measured while cluster was unstable", - tester.autoscale(application1, cluster1, capacity).isEmpty()); + var fixture = AutoscalingTester.fixture().build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(0.9, 0.6, 0.7, 1, true, false, 120); + assertTrue("Not scaling up since nodes were measured while cluster was out of service", + fixture.autoscale().isEmpty()); } @Test public void test_autoscaling_group_size_1() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(20, 20, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 5, 5, resources); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up since resource usage is too high", - 7, 7, 2.5, 80.0, 50.5, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(5, 5, new NodeResources(3, 100, 100, 1)); + var max = new ClusterResources(20, 20, new NodeResources(10, 1000, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyCpuLoad(0.9, 120); + fixture.tester().assertResources("Scaling the number of groups, but nothing requires us to stay with 1 node per group", + 10, 5, 7.7, 40.0, 40.0, + fixture.autoscale()); } @Test public void test_autoscaling_groupsize_by_cpu_read_dominated() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 6, 2, resources); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 10, - t -> t == 0 ? 20.0 : 10.0, - t -> 1.0); - tester.assertResources("Scaling up since resource usage is too high, changing to 1 group is cheaper", - 8, 1, 2.6, 83.3, 52.6, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(6, 2, new NodeResources(3, 100, 100, 1)); + var max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + Duration timePassed = fixture.addCpuMeasurements(0.25, 120); + fixture.tester().clock().advance(timePassed.negated()); + fixture.addLoadMeasurements(10, t -> t == 0 ? 20.0 : 10.0, t -> 1.0); + fixture.tester().assertResources("Scaling up since resource usage is too high, changing to 1 group is cheaper", + 10, 1, 2.3, 27.8, 27.8, + fixture.autoscale()); } /** Same as above but mostly write traffic, which favors smaller groups */ @Test public void test_autoscaling_groupsize_by_cpu_write_dominated() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 6, 2, resources); - tester.addCpuMeasurements(0.25f, 1f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 10, - t -> t == 0 ? 20.0 : 10.0, - t -> 100.0); - tester.assertResources("Scaling down since resource usage is too high, changing to 1 group is cheaper", - 4, 1, 2.1, 83.3, 52.6, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 3, 1, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(6, 2, new NodeResources(3, 100, 100, 1)); + var max = new ClusterResources(21, 7, new NodeResources(100, 1000, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + Duration timePassed = fixture.addCpuMeasurements(0.25, 120); + fixture.tester().clock().advance(timePassed.negated()); + fixture.addLoadMeasurements(10, t -> t == 0 ? 20.0 : 10.0, t -> 100.0); + fixture.tester().assertResources("Scaling down since resource usage is too high, changing to 1 group is cheaper", + 6, 1, 1.0, 50.0, 50.0, + fixture.autoscale()); } @Test public void test_autoscaling_group_size() { - NodeResources hostResources = new NodeResources(100, 1000, 1000, 100); - ClusterResources min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(30, 30, new NodeResources(100, 100, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 6, 2, new NodeResources(10, 100, 100, 1)); - tester.clock().advance(Duration.ofDays(1)); - tester.addMemMeasurements(1.0f, 1f, 1000, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Increase group size to reduce memory load", - 8, 2, 12.4, 96.2, 62.5, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 2, 2, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(6, 2, new NodeResources(10, 100, 100, 1)); + var max = new ClusterResources(30, 30, new NodeResources(100, 100, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(1)); + fixture.applyMemLoad(1.0, 1000); + fixture.tester().assertResources("Increase group size to reduce memory load", + 8, 2, 6.5, 96.2, 62.5, + fixture.autoscale()); } @Test public void autoscaling_avoids_illegal_configurations() { - NodeResources hostResources = new NodeResources(6, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - // deploy - tester.deploy(application1, cluster1, 6, 1, hostResources.withVcpu(hostResources.vcpu() / 2)); - tester.clock().advance(Duration.ofDays(2)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.addMemMeasurements(0.02f, 0.95f, 120, application1); - tester.assertResources("Scaling down", - 6, 1, 2.9, 4.0, 95.0, - tester.autoscale(application1, cluster1, capacity)); + var min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); + var now = new ClusterResources(6, 1, new NodeResources(3, 100, 100, 1)); + var max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); + var fixture = AutoscalingTester.fixture() + .initialResources(Optional.of(now)) + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyMemLoad(0.02, 120); + fixture.tester().assertResources("Scaling down", + 6, 1, 3.1, 4.0, 100.0, + fixture.autoscale()); } @Test public void scaling_down_only_after_delay() { - NodeResources hostResources = new NodeResources(6, 100, 100, 1); - ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1)); - ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1)); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(hostResources); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - tester.deploy(application1, cluster1, 6, 1, hostResources.withVcpu(hostResources.vcpu() / 2)); - - // No autoscaling as it is too soon to scale down after initial deploy (counting as a scaling event) - tester.addMemMeasurements(0.02f, 0.95f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - assertTrue(tester.autoscale(application1, cluster1, capacity).target().isEmpty()); - - // Trying the same later causes autoscaling - tester.clock().advance(Duration.ofDays(2)); - tester.addMemMeasurements(0.02f, 0.95f, 120, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling down", - 6, 1, 1.4, 4.0, 95.0, - tester.autoscale(application1, cluster1, capacity)); + var fixture = AutoscalingTester.fixture().build(); + fixture.applyMemLoad(0.02, 120); + assertTrue("Too soon after initial deployment", fixture.autoscale().target().isEmpty()); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyMemLoad(0.02, 120); + fixture.tester().assertResources("Scaling down since enough time has passed", + 6, 1, 1.2, 4.0, 80.0, + fixture.autoscale()); } @Test public void test_autoscaling_considers_real_resources() { - NodeResources hostResources = new NodeResources(60, 100, 1000, 10); - ClusterResources min = new ClusterResources(2, 1, new NodeResources( 2, 20, 200, 1)); - ClusterResources max = new ClusterResources(4, 1, new NodeResources(60, 100, 1000, 1)); - var capacity = Capacity.from(min, max); - { // No memory tax - AutoscalingTester tester = new AutoscalingTester(new Zone(Environment.prod, RegionName.from("us-east")), - hostResources, - new OnlySubtractingWhenForecastingCalculator(0)); - - ApplicationId application1 = tester.applicationId("app1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - tester.deploy(application1, cluster1, min); - tester.addMeasurements(1.0f, 1.0f, 0.7f, 0, 1000, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up", - 4, 1, 6.7, 20.5, 200, - tester.autoscale(application1, cluster1, capacity)); + var fixture = AutoscalingTester.fixture() + .resourceCalculator(new OnlySubtractingWhenForecastingCalculator(0)) + .build(); + fixture.applyLoad(1.0, 1.0, 0.7, 1000); + fixture.tester().assertResources("Scaling up", + 9, 1, 5.0, 9.6, 72.9, + fixture.autoscale()); } - { // 15 Gb memory tax - AutoscalingTester tester = new AutoscalingTester(new Zone(Environment.prod, RegionName.from("us-east")), - hostResources, - new OnlySubtractingWhenForecastingCalculator(15)); - - ApplicationId application1 = tester.applicationId("app1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); - - tester.deploy(application1, cluster1, min); - tester.addMeasurements(1.0f, 1.0f, 0.7f, 0, 1000, application1); - tester.clock().advance(Duration.ofMinutes(-10 * 5)); - tester.addQueryRateMeasurements(application1, cluster1.id(), 10, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.assertResources("Scaling up", - 4, 1, 6.7, 35.5, 200, - tester.autoscale(application1, cluster1, capacity)); + { + var fixture = AutoscalingTester.fixture() + .resourceCalculator(new OnlySubtractingWhenForecastingCalculator(3)) + .build(); + fixture.applyLoad(1.0, 1.0, 0.7, 1000); + fixture.tester().assertResources("With 3Gb memory tax, we scale up memory more", + 7, 1, 6.4, 15.8, 97.2, + fixture.autoscale()); } } @@ -579,8 +426,8 @@ public class AutoscalingTest { Environment.prod, RegionName.from("us-east")), flavors); - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.content, "cluster1"); + ApplicationId application1 = AutoscalingTester.applicationId("application1"); + ClusterSpec cluster1 = AutoscalingTester.clusterSpec(ClusterSpec.Type.content, "cluster1"); // deploy (Why 103 Gb memory? See AutoscalingTester.MockHostResourcesCalculator tester.deploy(application1, cluster1, 5, 1, new NodeResources(3, 103, 100, 1)); @@ -605,196 +452,165 @@ public class AutoscalingTest { @Test public void test_autoscaling_considers_read_share() { - NodeResources resources = new NodeResources(3, 100, 100, 1); - ClusterResources min = new ClusterResources( 1, 1, resources); - ClusterResources max = new ClusterResources(10, 1, resources); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(resources.withVcpu(resources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - tester.deploy(application1, cluster1, 5, 1, resources); - tester.addQueryRateMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0); // Query traffic only - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addCpuMeasurements(0.25f, 1f, 100, application1); + var min = new ClusterResources( 1, 1, new NodeResources(3, 100, 100, 1)); + var max = new ClusterResources(10, 1, new NodeResources(3, 100, 100, 1)); + var fixture = AutoscalingTester.fixture() + .capacity(Capacity.from(min, max)) + .build(); + fixture.tester.clock().advance(Duration.ofDays(1)); + fixture.applyCpuLoad(0.25, 120); // (no read share stored) - tester.assertResources("Advice to scale up since we set aside for bcp by default", - 7, 1, 3, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.storeReadShare(0.25, 0.5, application1); - tester.assertResources("Half of global share is the same as the default assumption used above", - 7, 1, 3, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.storeReadShare(0.5, 0.5, application1); - tester.assertResources("Advice to scale down since we don't need room for bcp", - 4, 1, 3, 100, 100, - tester.autoscale(application1, cluster1, capacity)); + fixture.tester().assertResources("Advice to scale up since we set aside for bcp by default", + 7, 1, 3, 100, 100, + fixture.autoscale()); + + fixture.storeReadShare(0.25, 0.5); + fixture.tester().assertResources("Half of global share is the same as the default assumption used above", + 7, 1, 3, 100, 100, + fixture.autoscale()); + + fixture.storeReadShare(0.5, 0.5); + fixture.tester().assertResources("Advice to scale down since we don't need room for bcp", + 6, 1, 3, 100, 100, + fixture.autoscale()); } @Test public void test_autoscaling_considers_growth_rate() { - NodeResources minResources = new NodeResources( 1, 100, 100, 1); - NodeResources midResources = new NodeResources( 5, 100, 100, 1); - NodeResources maxResources = new NodeResources(10, 100, 100, 1); - ClusterResources min = new ClusterResources(5, 1, minResources); - ClusterResources max = new ClusterResources(5, 1, maxResources); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(maxResources.withVcpu(maxResources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - tester.deploy(application1, cluster1, 5, 1, midResources); - Duration timeAdded = tester.addQueryRateMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0); - tester.clock().advance(timeAdded.negated()); - tester.addCpuMeasurements(0.25f, 1f, 200, application1); - - // (no query rate data) - tester.assertResources("Scale up since we assume we need 2x cpu for growth when no data scaling time data", - 5, 1, 6.3, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.setScalingDuration(application1, cluster1.id(), Duration.ofMinutes(5)); - timeAdded = tester.addQueryRateMeasurements(application1, cluster1.id(), - 100, - t -> 10.0 + (t < 50 ? t : 100 - t)); - tester.clock().advance(timeAdded.negated()); - tester.addCpuMeasurements(0.25f, 1f, 200, application1); - tester.assertResources("Scale down since observed growth is slower than scaling time", - 5, 1, 3.4, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.clearQueryRateMeasurements(application1, cluster1.id()); - - tester.setScalingDuration(application1, cluster1.id(), Duration.ofMinutes(60)); - timeAdded = tester.addQueryRateMeasurements(application1, cluster1.id(), - 100, - t -> 10.0 + (t < 50 ? t * t * t : 125000 - (t - 49) * (t - 49) * (t - 49))); - tester.clock().advance(timeAdded.negated()); - tester.addCpuMeasurements(0.25f, 1f, 200, application1); - tester.assertResources("Scale up since observed growth is faster than scaling time", - 5, 1, 10.0, 100, 100, - tester.autoscale(application1, cluster1, capacity)); + var fixture = AutoscalingTester.fixture().build(); + + fixture.tester().clock().advance(Duration.ofDays(2)); + Duration timeAdded = fixture.addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 0.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.25, 200); + + fixture.tester().assertResources("Scale up since we assume we need 2x cpu for growth when no data scaling time data", + 9, 1, 2.1, 5, 50, + fixture.autoscale()); + + fixture.setScalingDuration(Duration.ofMinutes(5)); + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, t -> 10.0 + (t < 50 ? t : 100 - t), t -> 0.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.25, 200); + fixture.tester().assertResources("Scale down since observed growth is slower than scaling time", + 9, 1, 1.8, 5, 50, + fixture.autoscale()); + + fixture.setScalingDuration(Duration.ofMinutes(60)); + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, + t -> 10.0 + (t < 50 ? t * t * t : 125000 - (t - 49) * (t - 49) * (t - 49)), + t -> 0.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.25, 200); + fixture.tester().assertResources("Scale up since observed growth is faster than scaling time", + 9, 1, 2.1, 5, 50, + fixture.autoscale()); } @Test public void test_autoscaling_considers_query_vs_write_rate() { - NodeResources minResources = new NodeResources( 1, 100, 100, 1); - NodeResources midResources = new NodeResources( 5, 100, 100, 1); - NodeResources maxResources = new NodeResources(10, 100, 100, 1); - ClusterResources min = new ClusterResources(5, 1, minResources); - ClusterResources max = new ClusterResources(5, 1, maxResources); - var capacity = Capacity.from(min, max); - AutoscalingTester tester = new AutoscalingTester(maxResources.withVcpu(maxResources.vcpu() * 2)); - - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); + var fixture = AutoscalingTester.fixture().build(); - tester.deploy(application1, cluster1, 5, 1, midResources); - tester.addCpuMeasurements(0.4f, 1f, 120, application1); + fixture.addCpuMeasurements(0.4, 220); // Why twice the query rate at time = 0? // This makes headroom for queries doubling, which we want to observe the effect of here - tester.addCpuMeasurements(0.4f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t -> 10.0); - tester.assertResources("Query and write load is equal -> scale up somewhat", - 5, 1, 7.3, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.addCpuMeasurements(0.4f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 80.0 : 40.0, t -> 10.0); - tester.assertResources("Query load is 4x write load -> scale up more", - 5, 1, 9.5, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.addCpuMeasurements(0.3f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t -> 100.0); - tester.assertResources("Write load is 10x query load -> scale down", - 5, 1, 2.9, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.addCpuMeasurements(0.4f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> t == 0 ? 20.0 : 10.0, t-> 0.0); - tester.assertResources("Query only -> largest possible", - 5, 1, 10.0, 100, 100, - tester.autoscale(application1, cluster1, capacity)); - - tester.addCpuMeasurements(0.4f, 1f, 100, application1); - tester.clock().advance(Duration.ofMinutes(-100 * 5)); - tester.addLoadMeasurements(application1, cluster1.id(), 100, t -> 0.0, t -> 10.0); - tester.assertResources("Write only -> smallest possible", - 5, 1, 2.1, 100, 100, - tester.autoscale(application1, cluster1, capacity)); + fixture.tester().clock().advance(Duration.ofDays(2)); + var timeAdded = fixture.addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 10.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.4, 200); + fixture.tester.assertResources("Query and write load is equal -> scale up somewhat", + 9, 1, 2.4, 5, 50, + fixture.autoscale()); + + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, t -> t == 0 ? 80.0 : 40.0, t -> 10.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.4, 200); + // TODO: Ackhually, we scale down here - why? + fixture.tester().assertResources("Query load is 4x write load -> scale up more", + 9, 1, 2.1, 5, 50, + fixture.autoscale()); + + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t -> 100.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.4, 200); + fixture.tester().assertResources("Write load is 10x query load -> scale down", + 9, 1, 1.1, 5, 50, + fixture.autoscale()); + + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, t -> t == 0 ? 20.0 : 10.0, t-> 0.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.4, 200); + fixture.tester().assertResources("Query only -> largest possible", + 8, 1, 4.9, 5.7, 57.1, + fixture.autoscale()); + + fixture.tester().clock().advance(Duration.ofDays(2)); + timeAdded = fixture.addLoadMeasurements(100, t -> 0.0, t -> 10.0); + fixture.tester.clock().advance(timeAdded.negated()); + fixture.addCpuMeasurements(0.4, 200); + fixture.tester().assertResources("Write only -> smallest possible", + 6, 1, 1.0, 8, 80, + fixture.autoscale()); } @Test public void test_autoscaling_in_dev() { - NodeResources resources = new NodeResources(1, 4, 50, 1); - ClusterResources min = new ClusterResources( 1, 1, resources); - ClusterResources max = new ClusterResources(3, 1, resources); - Capacity capacity = Capacity.from(min, max, false, true); - - AutoscalingTester tester = new AutoscalingTester(Environment.dev, resources.withVcpu(resources.vcpu() * 2)); - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - tester.deploy(application1, cluster1, capacity); - tester.addQueryRateMeasurements(application1, cluster1.id(), - 500, t -> 100.0); - tester.addCpuMeasurements(1.0f, 1f, 10, application1); + var fixture = AutoscalingTester.fixture() + .zone(new Zone(Environment.dev, RegionName.from("us-east"))) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(1.0, 1.0, 1.0, 200); assertTrue("Not attempting to scale up because policies dictate we'll only get one node", - tester.autoscale(application1, cluster1, capacity).target().isEmpty()); + fixture.autoscale().target().isEmpty()); } /** Same setup as test_autoscaling_in_dev(), just with required = true */ @Test public void test_autoscaling_in_dev_with_required_resources() { - NodeResources resources = new NodeResources(1, 4, 50, 1); - ClusterResources min = new ClusterResources( 1, 1, resources); - ClusterResources max = new ClusterResources(3, 1, resources); - Capacity capacity = Capacity.from(min, max, true, true); - - AutoscalingTester tester = new AutoscalingTester(Environment.dev, resources.withVcpu(resources.vcpu() * 2)); - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - tester.deploy(application1, cluster1, capacity); - tester.addQueryRateMeasurements(application1, cluster1.id(), - 500, t -> 100.0); - tester.addCpuMeasurements(1.0f, 1f, 10, application1); - tester.assertResources("We scale up even in dev because resources are required", - 3, 1, 1.0, 4, 50, - tester.autoscale(application1, cluster1, capacity)); + var requiredCapacity = + Capacity.from(new ClusterResources(2, 1, + new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)), + new ClusterResources(20, 1, + new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any)), + true, + true); + + var fixture = AutoscalingTester.fixture() + .capacity(requiredCapacity) + .zone(new Zone(Environment.dev, RegionName.from("us-east"))) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(1.0, 1.0, 1.0, 200); + fixture.tester().assertResources("We scale even in dev because resources are required", + 3, 1, 1.0, 7.7, 83.3, + fixture.autoscale()); } @Test public void test_autoscaling_in_dev_with_required_unspecified_resources() { - NodeResources resources = NodeResources.unspecified(); - ClusterResources min = new ClusterResources( 1, 1, resources); - ClusterResources max = new ClusterResources(3, 1, resources); - Capacity capacity = Capacity.from(min, max, true, true); - - AutoscalingTester tester = new AutoscalingTester(Environment.dev, - new NodeResources(10, 16, 100, 2)); - ApplicationId application1 = tester.applicationId("application1"); - ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1"); - - tester.deploy(application1, cluster1, capacity); - tester.addQueryRateMeasurements(application1, cluster1.id(), - 500, t -> 100.0); - tester.addCpuMeasurements(1.0f, 1f, 10, application1); - tester.assertResources("We scale up even in dev because resources are required", - 3, 1, 1.5, 8, 50, - tester.autoscale(application1, cluster1, capacity)); + var requiredCapacity = + Capacity.from(new ClusterResources(1, 1, NodeResources.unspecified()), + new ClusterResources(3, 1, NodeResources.unspecified()), + true, + true); + + var fixture = AutoscalingTester.fixture() + .capacity(requiredCapacity) + .zone(new Zone(Environment.dev, RegionName.from("us-east"))) + .build(); + fixture.tester().clock().advance(Duration.ofDays(2)); + fixture.applyLoad(1.0, 1.0, 1.0, 200); + fixture.tester().assertResources("We scale even in dev because resources are required", + 3, 1, 1.5, 8, 50, + fixture.autoscale()); } /** 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 f860b3e5d81..2a4dbe32ab5 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 @@ -45,7 +45,7 @@ class AutoscalingTester { private final ProvisioningTester provisioningTester; private final Autoscaler autoscaler; - private final MockHostResourcesCalculator hostResourcesCalculator; + private final HostResourcesCalculator hostResourcesCalculator; private final CapacityPolicies capacityPolicies; /** Creates an autoscaling tester with a single host type ready */ @@ -57,14 +57,6 @@ class AutoscalingTester { this(new Zone(environment, RegionName.from("us-east")), hostResources, null); } - public AutoscalingTester(Zone zone, NodeResources hostResources) { - this(zone, hostResources, null); - } - - public AutoscalingTester(Zone zone, NodeResources hostResources, int hostCount) { - this(zone, hostResources, null, hostCount); - } - public AutoscalingTester(Zone zone, NodeResources hostResources, HostResourcesCalculator resourcesCalculator) { this(zone, hostResources, resourcesCalculator, 20); } @@ -76,7 +68,7 @@ class AutoscalingTester { } public AutoscalingTester(Zone zone, List<Flavor> flavors) { - this(zone, flavors, new MockHostResourcesCalculator(zone)); + this(zone, flavors, new MockHostResourcesCalculator(zone, 3)); } private AutoscalingTester(Zone zone, List<Flavor> flavors, HostResourcesCalculator resourcesCalculator) { @@ -86,18 +78,20 @@ class AutoscalingTester { .hostProvisioner(zone.getCloud().dynamicProvisioning() ? new MockHostProvisioner(flavors) : null) .build(); - hostResourcesCalculator = new MockHostResourcesCalculator(zone); + hostResourcesCalculator = resourcesCalculator; autoscaler = new Autoscaler(nodeRepository()); capacityPolicies = new CapacityPolicies(provisioningTester.nodeRepository()); } + public static Fixture.Builder fixture() { return new Fixture.Builder(); } + public ProvisioningTester provisioning() { return provisioningTester; } - public ApplicationId applicationId(String applicationName) { + public static ApplicationId applicationId(String applicationName) { return ApplicationId.from("tenant1", applicationName, "instance1"); } - public ClusterSpec clusterSpec(ClusterSpec.Type type, String clusterId) { + public static ClusterSpec clusterSpec(ClusterSpec.Type type, String clusterId) { return ClusterSpec.request(type, ClusterSpec.Id.from(clusterId)).vespaVersion("7").build(); } @@ -128,13 +122,27 @@ class AutoscalingTester { } public void deactivateRetired(ApplicationId application, ClusterSpec cluster, ClusterResources resources) { - try (Mutex lock = nodeRepository().nodes().lock(application)){ + deactivateRetired(application, cluster, Capacity.from(resources)); + } + + public void deactivateRetired(ApplicationId application, ClusterSpec cluster, Capacity capacity) { + try (Mutex lock = nodeRepository().nodes().lock(application)) { for (Node node : nodeRepository().nodes().list(Node.State.active).owner(application)) { if (node.allocation().get().membership().retired()) nodeRepository().nodes().write(node.with(node.allocation().get().removable(true, true)), lock); } } - deploy(application, cluster, resources); + deploy(application, cluster, capacity); + } + + public ClusterModel clusterModel(ApplicationId applicationId, ClusterSpec clusterSpec) { + var application = nodeRepository().applications().get(applicationId).get(); + return new ClusterModel(application, + clusterSpec, + application.cluster(clusterSpec.id()).get(), + nodeRepository().nodes().list(Node.State.active).cluster(clusterSpec.id()), + nodeRepository().metricsDb(), + nodeRepository().clock()); } /** @@ -147,10 +155,11 @@ class AutoscalingTester { * @param count the number of measurements * @param applicationId the application we're adding measurements for all nodes of */ - public void addCpuMeasurements(float value, float otherResourcesLoad, - int count, ApplicationId applicationId) { + public Duration addCpuMeasurements(float value, float otherResourcesLoad, + int count, ApplicationId applicationId) { NodeList nodes = nodeRepository().nodes().list(Node.State.active).owner(applicationId); float oneExtraNodeFactor = (float)(nodes.size() - 1.0) / (nodes.size()); + Instant initialTime = clock().instant(); for (int i = 0; i < count; i++) { clock().advance(Duration.ofSeconds(150)); for (Node node : nodes) { @@ -166,6 +175,7 @@ class AutoscalingTester { 0.0)))); } } + return Duration.between(initialTime, clock().instant()); } /** @@ -236,8 +246,8 @@ class AutoscalingTester { } } - public void addMeasurements(float cpu, float memory, float disk, int generation, int count, ApplicationId applicationId) { - addMeasurements(cpu, memory, disk, generation, true, true, count, applicationId); + public void addMeasurements(float cpu, float memory, float disk, int count, ApplicationId applicationId) { + addMeasurements(cpu, memory, disk, 0, true, true, count, applicationId); } public void addMeasurements(float cpu, float memory, float disk, int generation, boolean inService, boolean stable, @@ -285,11 +295,12 @@ class AutoscalingTester { } /** Creates the given number of measurements, spaced 5 minutes between, using the given function */ - public void addLoadMeasurements(ApplicationId application, + public Duration addLoadMeasurements(ApplicationId application, ClusterSpec.Id cluster, int measurements, IntFunction<Double> queryRate, IntFunction<Double> writeRate) { + Instant initialTime = clock().instant(); for (int i = 0; i < measurements; i++) { nodeMetricsDb().addClusterMetrics(application, Map.of(cluster, new ClusterMetricSnapshot(clock().instant(), @@ -297,6 +308,7 @@ class AutoscalingTester { writeRate.apply(i)))); clock().advance(Duration.ofMinutes(5)); } + return Duration.between(initialTime, clock().instant()); } /** Creates the given number of measurements, spaced 5 minutes between, using the given function */ @@ -304,21 +316,25 @@ class AutoscalingTester { ClusterSpec.Id cluster, int measurements, IntFunction<Double> queryRate) { + return addQueryRateMeasurements(application, cluster, measurements, Duration.ofMinutes(5), queryRate); + } + + public Duration addQueryRateMeasurements(ApplicationId application, + ClusterSpec.Id cluster, + int measurements, + Duration samplingInterval, + IntFunction<Double> queryRate) { Instant initialTime = clock().instant(); for (int i = 0; i < measurements; i++) { nodeMetricsDb().addClusterMetrics(application, Map.of(cluster, new ClusterMetricSnapshot(clock().instant(), queryRate.apply(i), 0.0))); - clock().advance(Duration.ofMinutes(5)); + clock().advance(samplingInterval); } return Duration.between(initialTime, clock().instant()); } - public void clearQueryRateMeasurements(ApplicationId application, ClusterSpec.Id cluster) { - ((MemoryMetricsDb)nodeMetricsDb()).clearClusterMetrics(application, cluster); - } - public Autoscaler.Advice autoscale(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity) { capacity = capacityPolicies.applyOn(capacity, applicationId, capacityPolicies.decideExclusivity(capacity, cluster.isExclusive())); Application application = nodeRepository().applications().get(applicationId).orElse(Application.empty(applicationId)) @@ -387,15 +403,17 @@ class AutoscalingTester { public static class MockHostResourcesCalculator implements HostResourcesCalculator { private final Zone zone; + private double memoryTax = 0; - public MockHostResourcesCalculator(Zone zone) { + public MockHostResourcesCalculator(Zone zone, double memoryTax) { this.zone = zone; + this.memoryTax = memoryTax; } @Override public NodeResources realResourcesOf(Nodelike node, NodeRepository nodeRepository) { if (zone.getCloud().dynamicProvisioning()) - return node.resources().withMemoryGb(node.resources().memoryGb() - 3); + return node.resources().withMemoryGb(node.resources().memoryGb() - memoryTax); else return node.resources(); } @@ -403,19 +421,19 @@ class AutoscalingTester { @Override public NodeResources advertisedResourcesOf(Flavor flavor) { if (zone.getCloud().dynamicProvisioning()) - return flavor.resources().withMemoryGb(flavor.resources().memoryGb() + 3); + return flavor.resources().withMemoryGb(flavor.resources().memoryGb() + memoryTax); else return flavor.resources(); } @Override public NodeResources requestToReal(NodeResources resources, boolean exclusive) { - return resources.withMemoryGb(resources.memoryGb() - 3); + return resources.withMemoryGb(resources.memoryGb() - memoryTax); } @Override public NodeResources realToRequest(NodeResources resources, boolean exclusive) { - return resources.withMemoryGb(resources.memoryGb() + 3); + return resources.withMemoryGb(resources.memoryGb() + memoryTax); } @Override 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 new file mode 100644 index 00000000000..896897f45c1 --- /dev/null +++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/Fixture.java @@ -0,0 +1,164 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package com.yahoo.vespa.hosted.provision.autoscale; + +import com.yahoo.config.provision.ApplicationId; +import com.yahoo.config.provision.Capacity; +import com.yahoo.config.provision.ClusterResources; +import com.yahoo.config.provision.ClusterSpec; +import com.yahoo.config.provision.Environment; +import com.yahoo.config.provision.NodeResources; +import com.yahoo.config.provision.RegionName; +import com.yahoo.config.provision.Zone; +import com.yahoo.vespa.hosted.provision.NodeList; +import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator; + +import java.time.Duration; +import java.util.Optional; +import java.util.function.IntFunction; + +/** + * Fixture for autoscaling tests. + * + * @author bratseth + */ +public class Fixture { + + final AutoscalingTester tester; + final ApplicationId application; + final ClusterSpec cluster; + final Capacity capacity; + + public Fixture(Fixture.Builder builder, Optional<ClusterResources> initialResources) { + application = builder.application; + cluster = builder.cluster; + capacity = builder.capacity; + tester = new AutoscalingTester(builder.zone, builder.hostResources, builder.resourceCalculator); + var deployCapacity = initialResources.isPresent() ? Capacity.from(initialResources.get()) : capacity; + tester.deploy(builder.application, builder.cluster, deployCapacity); + } + + public AutoscalingTester tester() { return tester; } + + /** Autoscale within the deployed capacity of this. */ + public Autoscaler.Advice autoscale() { + return autoscale(capacity); + } + + /** Autoscale within the given capacity. */ + public Autoscaler.Advice autoscale(Capacity capacity) { + return tester().autoscale(application, cluster, capacity); + } + + /** Redeploy with the deployed capacity of this. */ + public void deploy() { + deploy(capacity); + } + + /** Redeploy with the given capacity. */ + public void deploy(Capacity capacity) { + tester().deploy(application, cluster, capacity); + } + + /** Returns the nodes allocated to the fixture application cluster */ + public NodeList nodes() { + return tester().nodeRepository().nodes().list().owner(application).cluster(cluster.id()); + } + + public void deactivateRetired(Capacity capacity) { + tester().deactivateRetired(application, cluster, capacity); + } + + public void setScalingDuration(Duration duration) { + tester().setScalingDuration(application, cluster.id(), duration); + } + + public Duration addCpuMeasurements(double cpuLoad, int measurements) { + return tester().addCpuMeasurements((float)cpuLoad, 1.0f, measurements, application); + } + + public Duration addLoadMeasurements(int measurements, IntFunction<Double> queryRate, IntFunction<Double> writeRate) { + return tester().addLoadMeasurements(application, cluster.id(), measurements, queryRate, writeRate); + } + + public void applyCpuLoad(double cpuLoad, int measurements) { + Duration samplingInterval = Duration.ofSeconds(150L); // in addCpuMeasurements + tester().addCpuMeasurements((float)cpuLoad, 1.0f, measurements, application); + tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); + tester().addQueryRateMeasurements(application, cluster.id(), measurements, samplingInterval, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + } + + public void applyMemLoad(double memLoad, int measurements) { + Duration samplingInterval = Duration.ofSeconds(150L); // in addCpuMeasurements + tester().addMemMeasurements((float)memLoad, 1.0f, measurements, application); + tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); + tester().addQueryRateMeasurements(application, cluster.id(), measurements, samplingInterval, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + } + + public void applyLoad(double cpuLoad, double memoryLoad, double diskLoad, int measurements) { + Duration samplingInterval = Duration.ofSeconds(150L); // in addCpuMeasurements + tester().addMeasurements((float)cpuLoad, (float)memoryLoad, (float)diskLoad, measurements, application); + tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); + tester().addQueryRateMeasurements(application, cluster.id(), measurements, samplingInterval, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + } + + public void applyLoad(double cpuLoad, double memoryLoad, double diskLoad, int generation, boolean inService, boolean stable, int measurements) { + Duration samplingInterval = Duration.ofSeconds(150L); // in addCpuMeasurements + tester().addMeasurements((float)cpuLoad, (float)memoryLoad, (float)diskLoad, generation, inService, stable, measurements, application); + tester().clock().advance(samplingInterval.negated().multipliedBy(measurements)); + tester().addQueryRateMeasurements(application, cluster.id(), measurements, samplingInterval, t -> t == 0 ? 20.0 : 10.0); // Query traffic only + } + + public void storeReadShare(double currentReadShare, double maxReadShare) { + tester().storeReadShare(currentReadShare, maxReadShare, application); + } + + public static class Builder { + + NodeResources hostResources = new NodeResources(100, 100, 100, 1); + Optional<ClusterResources> initialResources = Optional.of(new ClusterResources(5, 1, new NodeResources(3, 10, 100, 1))); + Capacity capacity = Capacity.from(new ClusterResources(2, 1, + new NodeResources(1, 1, 1, 1, NodeResources.DiskSpeed.any)), + new ClusterResources(20, 1, + new NodeResources(100, 1000, 1000, 1, NodeResources.DiskSpeed.any))); + ApplicationId application = AutoscalingTester.applicationId("application1"); + ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("cluster1")).vespaVersion("7").build(); + Zone zone = new Zone(Environment.prod, RegionName.from("us-east")); + HostResourcesCalculator resourceCalculator = new AutoscalingTester.MockHostResourcesCalculator(zone, 0); + + public Fixture.Builder zone(Zone zone) { + this.zone = zone; + return this; + } + + public Fixture.Builder clusterType(ClusterSpec.Type type) { + cluster = ClusterSpec.request(type, cluster.id()).vespaVersion("7").build(); + return this; + } + + public Fixture.Builder hostResources(NodeResources hostResources) { + this.hostResources = hostResources; + return this; + } + + public Fixture.Builder initialResources(Optional<ClusterResources> initialResources) { + this.initialResources = initialResources; + return this; + } + + public Fixture.Builder capacity(Capacity capacity) { + this.capacity = capacity; + return this; + } + + public Fixture.Builder resourceCalculator(HostResourcesCalculator resourceCalculator) { + this.resourceCalculator = resourceCalculator; + return this; + } + + public Fixture build() { + return new Fixture(this, initialResources); + } + + } + +} diff --git a/parent/pom.xml b/parent/pom.xml index 1536ef9cb53..05060c57ec9 100644 --- a/parent/pom.xml +++ b/parent/pom.xml @@ -1032,7 +1032,7 @@ <maven-site-plugin.version>3.9.1</maven-site-plugin.version> <maven-source-plugin.version>3.2.1</maven-source-plugin.version> <mockito.version>4.0.0</mockito.version> - <onnxruntime.version>1.8.0</onnxruntime.version> <!-- WARNING: sync cloud-tenant-base-dependencies-enforcer/pom.xml --> + <onnxruntime.version>1.11.0</onnxruntime.version> <!-- WARNING: sync cloud-tenant-base-dependencies-enforcer/pom.xml --> <org.lz4.version>1.8.0</org.lz4.version> <prometheus.client.version>0.6.0</prometheus.client.version> <protobuf.version>3.19.2</protobuf.version> diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp index 37d04f6bcb9..ecdc679e4bc 100644 --- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp +++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.cpp @@ -164,13 +164,15 @@ BucketDB::isActiveBucket(const BucketId &bucketId) const return (itr != _map.end()) && itr->second.isActive(); } -void -BucketDB::getBuckets(BucketId::List &buckets) const +document::BucketId::List +BucketDB::getBuckets() const { + BucketId::List buckets; buckets.reserve(_map.size()); for (const auto & entry : _map) { buckets.push_back(entry.first); } + return buckets; } bool @@ -261,8 +263,8 @@ BucketDB::populateActiveBuckets(BucketId::List buckets) BucketState activeState; activeState.setActive(true); for (const BucketId & bucketId : toAdd) { - InsertResult ins(_map.emplace(bucketId, activeState)); - assert(ins.second); + auto [itr, inserted] = _map.emplace(bucketId, activeState); + assert(inserted); } return fixupBuckets; } diff --git a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h index 6fe46d97f46..da475f2969a 100644 --- a/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h +++ b/searchcore/src/vespa/searchcore/proton/bucketdb/bucketdb.h @@ -20,9 +20,7 @@ public: typedef storage::spi::BucketChecksum BucketChecksum; typedef bucketdb::BucketState BucketState; typedef std::map<BucketId, BucketState> Map; - typedef Map::iterator MapIterator; typedef Map::const_iterator ConstMapIterator; - typedef std::pair<MapIterator, bool> InsertResult; private: Map _map; @@ -58,7 +56,7 @@ public: storage::spi::BucketInfo cachedGetBucketInfo(const BucketId &bucketId) const; BucketState cachedGet(const BucketId &bucketId) const; bool hasBucket(const BucketId &bucketId) const; - void getBuckets(BucketId::List & buckets) const; + BucketId::List getBuckets() const; bool empty() const; void setBucketState(const BucketId &bucketId, bool active); void createBucket(const BucketId &bucketId); @@ -69,7 +67,6 @@ public: ConstMapIterator begin() const { return _map.begin(); } ConstMapIterator end() const { return _map.end(); } ConstMapIterator lowerBound(const BucketId &bucket) const { return _map.lower_bound(bucket); } - ConstMapIterator upperBound(const BucketId &bucket) const { return _map.upper_bound(bucket); } size_t size() const { return _map.size(); } bool isActiveBucket(const BucketId &bucketId) const; BucketState *getBucketStatePtr(const BucketId &bucket); diff --git a/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp b/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp index d9201ce5b9e..080d182d1fa 100644 --- a/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp +++ b/searchcore/src/vespa/searchcore/proton/server/buckethandler.cpp @@ -90,8 +90,7 @@ BucketHandler::handleListBuckets(IBucketIdListResultHandler &resultHandler) // Called by SPI thread. // BucketDBOwner ensures synchronization between SPI thread and // master write thread in document database. - BucketIdListResult::List buckets; - _ready->getBucketDB().takeGuard()->getBuckets(buckets); + BucketIdListResult::List buckets = _ready->getBucketDB().takeGuard()->getBuckets(); resultHandler.handle(BucketIdListResult(std::move(buckets))); } diff --git a/standalone-container/pom.xml b/standalone-container/pom.xml index 557fb1493f1..3776743cbd8 100644 --- a/standalone-container/pom.xml +++ b/standalone-container/pom.xml @@ -93,6 +93,7 @@ <discApplicationClass>com.yahoo.container.standalone.StandaloneContainerApplication</discApplicationClass> <buildLegacyVespaPlatformBundle>true</buildLegacyVespaPlatformBundle> <discPreInstallBundle> + <!-- Standalone container has an embedded config model, so model related bundles cannot be retrieved via config --> configdefinitions-jar-with-dependencies.jar, config-provisioning-jar-with-dependencies.jar, config-bundle-jar-with-dependencies.jar, diff --git a/storage/src/vespa/storage/distributor/externaloperationhandler.cpp b/storage/src/vespa/storage/distributor/externaloperationhandler.cpp index 3f3924df229..9ce8d871fc3 100644 --- a/storage/src/vespa/storage/distributor/externaloperationhandler.cpp +++ b/storage/src/vespa/storage/distributor/externaloperationhandler.cpp @@ -97,9 +97,9 @@ ExternalOperationHandler::~ExternalOperationHandler() = default; bool ExternalOperationHandler::handleMessage(const std::shared_ptr<api::StorageMessage>& msg, Operation::SP& op) { - _op = Operation::SP(); + _op.reset(); bool retVal = msg->callHandler(*this, msg); - op = _op; + op = std::move(_op); // Don't maintain any strong refs in _op after we've passed it on. return retVal; } diff --git a/vespa-feed-client/pom.xml b/vespa-feed-client/pom.xml index 536637bdce2..8b7b82573c4 100644 --- a/vespa-feed-client/pom.xml +++ b/vespa-feed-client/pom.xml @@ -53,10 +53,33 @@ <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> - <configuration> - <release>${vespaClients.jdk.releaseVersion}</release> - <showDeprecation>true</showDeprecation> - </configuration> + <executions> + <execution> + <id>compile-java-${vespaClients.jdk.releaseVersion}</id> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <release>${vespaClients.jdk.releaseVersion}</release> + <showDeprecation>true</showDeprecation> + </configuration> + </execution> + <execution> + <id>compile-java-9</id> + <phase>compile</phase> + <goals> + <goal>compile</goal> + </goals> + <configuration> + <release>9</release> + <compileSourceRoots> + <compileSourceRoot>${project.basedir}/src/main/java9</compileSourceRoot> + </compileSourceRoots> + <outputDirectory>${project.build.outputDirectory}/META-INF/versions/9</outputDirectory> + <showDeprecation>true</showDeprecation> + </configuration> + </execution> + </executions> </plugin> <plugin> <groupId>org.codehaus.mojo</groupId> diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java index 62cd56f21ce..decb5021f8f 100644 --- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java @@ -15,7 +15,6 @@ import org.apache.hc.core5.http.message.BasicHeader; import org.apache.hc.core5.http2.config.H2Config; import org.apache.hc.core5.net.URIAuthority; import org.apache.hc.core5.reactor.IOReactorConfig; -import org.apache.hc.core5.reactor.ssl.TlsDetails; import org.apache.hc.core5.util.Timeout; import javax.net.ssl.SSLContext; @@ -131,10 +130,9 @@ class ApacheCluster implements Cluster { throw new IllegalStateException("No adequate SSL cipher suites supported by the JVM"); ClientTlsStrategyBuilder tlsStrategyBuilder = ClientTlsStrategyBuilder.create() - .setTlsDetailsFactory(sslEngine -> - new TlsDetails(sslEngine.getSession(), sslEngine.getApplicationProtocol())) - .setCiphers(allowedCiphers) - .setSslContext(sslContext); + .setTlsDetailsFactory(TlsDetailsFactory::create) + .setCiphers(allowedCiphers) + .setSslContext(sslContext); if (builder.hostnameVerifier != null) tlsStrategyBuilder.setHostnameVerifier(builder.hostnameVerifier); diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/TlsDetailsFactory.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/TlsDetailsFactory.java new file mode 100644 index 00000000000..5183ce61761 --- /dev/null +++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/TlsDetailsFactory.java @@ -0,0 +1,16 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.feed.client.impl; + +import org.apache.hc.core5.reactor.ssl.TlsDetails; + +import javax.net.ssl.SSLEngine; + +/** + * @author bjorncs + */ +public class TlsDetailsFactory { + private TlsDetailsFactory() {} + + public static TlsDetails create(SSLEngine e) { return new TlsDetails(e.getSession(), "h2"); /*h2 == HTTP2*/ } +} + diff --git a/vespa-feed-client/src/main/java9/ai/vespa/feed/client/impl/TlsDetailsFactory.java b/vespa-feed-client/src/main/java9/ai/vespa/feed/client/impl/TlsDetailsFactory.java new file mode 100644 index 00000000000..f9903d9943d --- /dev/null +++ b/vespa-feed-client/src/main/java9/ai/vespa/feed/client/impl/TlsDetailsFactory.java @@ -0,0 +1,20 @@ +// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root. +package ai.vespa.feed.client.impl; + +import org.apache.hc.core5.reactor.ssl.TlsDetails; + +import javax.net.ssl.SSLEngine; + +/** + * {@link SSLEngine#getApplicationProtocol()} is not available on all JDK8 versions + * (https://bugs.openjdk.org/browse/JDK-8051498) + * + * @author bjorncs + */ +public class TlsDetailsFactory { + private TlsDetailsFactory() {} + + public static TlsDetails create(SSLEngine e) { + return new TlsDetails(e.getSession(), e.getApplicationProtocol()); + } +} |