aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLester Solbakken <lesters@users.noreply.github.com>2020-04-02 13:06:54 +0200
committerGitHub <noreply@github.com>2020-04-02 13:06:54 +0200
commitcd15c0e9021bce99839fcccb40d9406054de6c08 (patch)
tree2d7c5a1418dcce1878de837d38272e0fadb33920
parentc339a407bdcdd3a33e03628d593830dd66091072 (diff)
parent448f5a09f05e0d17d58f9433d8fe78403f0ff78d (diff)
Merge branch 'master' into partial_update_bag_as_map_and_tensor
-rw-r--r--build_settings.cmake22
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperTestServer.java5
-rwxr-xr-xconfig-lib/src/main/java/com/yahoo/config/FileReference.java12
-rw-r--r--config-model-api/abi-spec.json516
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ConfigServerSpec.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/HostProvisioner.java6
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java8
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java6
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java61
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java10
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/Index.java21
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java3
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java25
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java28
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java8
-rw-r--r--config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java5
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java44
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java7
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java144
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/Container.java10
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java12
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java48
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java12
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java13
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java57
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java16
-rw-r--r--config-model/src/main/javacc/SDParser.jj2
-rw-r--r--config-model/src/main/resources/schema/common.rnc6
-rw-r--r--config-model/src/main/resources/schema/containercluster.rnc2
-rw-r--r--config-model/src/main/resources/schema/content.rnc8
-rw-r--r--config-model/src/test/derived/hnsw_index/test.sd2
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java98
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/derived/NearestNeighborTestCase.java3
-rw-r--r--config-model/src/test/java/com/yahoo/searchdefinition/document/HnswIndexParamsTestCase.java14
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java6
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomSearchTuningBuilderTest.java3
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java62
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java28
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java28
-rw-r--r--config-model/src/test/schema-test-files/services-hosted.xml9
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java81
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java2
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterResources.java78
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java18
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java12
-rw-r--r--config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java5
-rw-r--r--config-provisioning/src/test/java/com/yahoo/config/provision/CapacityTest.java48
-rw-r--r--configdefinitions/src/vespa/lb-services.def3
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java14
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java28
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterMetricsRetriever.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java8
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java9
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java7
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java5
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java1
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java24
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java9
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java6
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java80
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java6
-rw-r--r--container-accesslogging/src/test/java/com/yahoo/container/logging/LogFileHandlerTestCase.java68
-rw-r--r--container-core/abi-spec.json1
-rw-r--r--container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java11
-rw-r--r--container-core/src/main/java/com/yahoo/container/handler/VipStatus.java24
-rw-r--r--container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java68
-rw-r--r--container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java5
-rw-r--r--container-messagebus/src/main/resources/configdefinitions/container-mbus.def17
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java15
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/Hasher.java1
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java72
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java5
-rw-r--r--container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java14
-rw-r--r--container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java13
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/Select.java9
-rw-r--r--container-search/src/test/java/com/yahoo/search/dispatch/searchcluster/SearchClusterTest.java16
-rw-r--r--container-search/src/test/java/com/yahoo/select/SelectTestCase.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java9
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java7
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java3
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java4
-rw-r--r--controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java30
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java2
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java53
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/root.json3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user-which-exists.json5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json5
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java5
-rw-r--r--dist/vespa.spec333
-rw-r--r--document/src/tests/documentselectparsertest.cpp82
-rw-r--r--document/src/vespa/document/select/CMakeLists.txt1
-rw-r--r--document/src/vespa/document/select/branch.cpp6
-rw-r--r--document/src/vespa/document/select/branch.h9
-rw-r--r--document/src/vespa/document/select/compare.cpp2
-rw-r--r--document/src/vespa/document/select/grammar/lexer.ll7
-rw-r--r--document/src/vespa/document/select/node.h29
-rw-r--r--document/src/vespa/document/select/parser.cpp14
-rw-r--r--document/src/vespa/document/select/parser_limits.cpp13
-rw-r--r--document/src/vespa/document/select/parser_limits.h19
-rw-r--r--document/src/vespa/document/select/valuenode.h26
-rw-r--r--document/src/vespa/document/select/valuenodes.cpp39
-rw-r--r--document/src/vespa/document/select/valuenodes.h10
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-CreateVisitorMessage.datbin193 -> 193 bytes
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java3
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java24
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java1
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java2
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java19
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/DeploymentLog.java1
-rw-r--r--hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java2
-rw-r--r--jdisc_http_service/abi-spec.json42
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java34
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java2
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java31
-rw-r--r--jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java52
-rw-r--r--jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def12
-rw-r--r--jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.server.def5
-rw-r--r--jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java4
-rw-r--r--jrt/src/com/yahoo/jrt/Acceptor.java2
-rw-r--r--jrt/src/com/yahoo/jrt/Connection.java9
-rw-r--r--jrt/src/com/yahoo/jrt/Transport.java20
-rw-r--r--messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java7
-rwxr-xr-xmessagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java21
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetwork.cpp5
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp1
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcnetworkparams.h8
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservice.cpp13
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp2
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcserviceaddress.h7
-rw-r--r--messagebus/src/vespa/messagebus/network/rpcservicepool.cpp11
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctarget.cpp14
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctarget.h14
-rw-r--r--messagebus/src/vespa/messagebus/network/rpctargetpool.cpp11
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java17
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriter.java8
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java49
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java18
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java11
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriterTest.java14
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java42
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java6
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java59
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Applications.java29
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java66
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java61
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Autoscaler.java87
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterResources.java66
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcher.java17
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java59
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java52
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java19
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java32
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImages.java8
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImpl.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeRepositoryProvisioner.java106
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingIntegrationTest.java11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTest.java123
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/AutoscalingTester.java25
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java14
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java113
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java18
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceTester.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java31
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java7
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java21
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java3
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java15
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java11
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java5
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java9
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java41
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java6
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java49
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java57
-rw-r--r--searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp8
-rw-r--r--searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp1
-rw-r--r--searchcore/src/tests/proton/index/indexmanager_test.cpp1
-rw-r--r--searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp24
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.cpp7
-rw-r--r--searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.h3
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/documentdb.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/executor_thread_service.cpp7
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/executor_thread_service.h10
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp51
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h33
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/i_proton_configurer_owner.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.cpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/proton_configurer.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/thread_service_observer.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h1
-rw-r--r--searchlib/CMakeLists.txt2
-rw-r--r--searchlib/src/tests/attribute/enum_attribute_compaction/enum_attribute_compaction_test.cpp4
-rw-r--r--searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp11
-rw-r--r--searchlib/src/tests/attribute/enumstore/enumstore_test.cpp4
-rw-r--r--searchlib/src/tests/attribute/save_target/CMakeLists.txt9
-rw-r--r--searchlib/src/tests/attribute/save_target/attribute_save_target_test.cpp148
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/CMakeLists.txt3
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp160
-rwxr-xr-xsearchlib/src/tests/attribute/tensorattribute/tensorattribute_test.sh5
-rw-r--r--searchlib/src/tests/common/sequencedtaskexecutor/CMakeLists.txt8
-rw-r--r--searchlib/src/tests/common/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp251
-rw-r--r--searchlib/src/tests/common/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp76
-rw-r--r--searchlib/src/tests/features/prod_features.cpp10
-rw-r--r--searchlib/src/tests/memoryindex/field_index/field_index_test.cpp2
-rw-r--r--searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt9
-rw-r--r--searchlib/src/tests/tensor/hnsw_saver/hnsw_save_load_test.cpp150
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.cpp52
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.h23
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.cpp45
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.h24
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.cpp73
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.h11
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attrvector.cpp5
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attrvector.hpp5
-rw-r--r--searchlib/src/vespa/searchlib/attribute/flagattribute.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/iattributesavetarget.h13
-rw-r--r--searchlib/src/vespa/searchlib/attribute/load_utils.cpp75
-rw-r--r--searchlib/src/vespa/searchlib/attribute/load_utils.h35
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp9
-rw-r--r--searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp11
-rw-r--r--searchlib/src/vespa/searchlib/attribute/readerbase.cpp52
-rw-r--r--searchlib/src/vespa/searchlib/attribute/readerbase.h3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp13
-rw-r--r--searchlib/src/vespa/searchlib/attribute/stringbase.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/common/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.cpp324
-rw-r--r--searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.h126
-rw-r--r--searchlib/src/vespa/searchlib/common/foregroundtaskexecutor.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/features/dotproductfeature.cpp23
-rw-r--r--searchlib/src/vespa/searchlib/tensor/CMakeLists.txt4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp17
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.cpp59
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.h27
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_graph.cpp72
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_graph.h76
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp227
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.h52
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.cpp47
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.h35
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.cpp57
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.h37
-rw-r--r--searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h20
-rw-r--r--searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.h33
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/duper/InfraApplication.java1
-rw-r--r--service-monitor/src/main/java/com/yahoo/vespa/service/monitor/InfraApplicationApi.java2
-rw-r--r--staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.h13
-rw-r--r--staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.hpp9
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp45
-rw-r--r--staging_vespalib/src/vespa/vespalib/util/singleexecutor.h5
-rwxr-xr-xstandalone-container/src/main/sh/standalone-container.sh160
-rw-r--r--storage/src/tests/distributor/btree_bucket_database_test.cpp4
-rw-r--r--storage/src/tests/distributor/mapbucketdatabasetest.cpp4
-rw-r--r--storage/src/vespa/storage/config/stor-communicationmanager.def2
-rw-r--r--storage/src/vespa/storage/persistence/persistencethread.cpp15
-rw-r--r--storage/src/vespa/storage/storageserver/communicationmanager.cpp1
-rw-r--r--storageapi/src/tests/mbusprot/storageprotocoltest.cpp11
-rw-r--r--tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java10
-rw-r--r--vespa-hadoop/src/main/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperation.java34
-rw-r--r--vespa-hadoop/src/test/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperationTest.java21
-rw-r--r--vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/DeployMojo.java19
-rw-r--r--vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/FeederParams.java14
-rw-r--r--vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/SimpleFeeder.java12
-rwxr-xr-xvespa_feed_perf/src/main/sh/vespa-feed-perf2
-rw-r--r--vespa_feed_perf/src/test/java/com/yahoo/vespa/feed/perf/FeederParamsTest.java11
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java1
-rw-r--r--vespajlib/src/main/java/com/yahoo/system/ProcessExecuter.java12
-rw-r--r--vespalib/src/tests/datastore/unique_store/unique_store_test.cpp2
-rw-r--r--vespalib/src/tests/executor/executor_test.cpp26
-rw-r--r--vespalib/src/tests/executor/threadstackexecutor_test.cpp18
-rw-r--r--vespalib/src/tests/stllike/hash_test.cpp2
-rw-r--r--vespalib/src/tests/stllike/lookup_benchmark.cpp1
-rw-r--r--vespalib/src/vespa/vespalib/gtest/gtest.h12
-rw-r--r--vespalib/src/vespa/vespalib/stllike/hash_fun.cpp18
-rw-r--r--vespalib/src/vespa/vespalib/util/executor_stats.h58
-rw-r--r--vespalib/src/vespa/vespalib/util/signalhandler.cpp16
-rw-r--r--vespalib/src/vespa/vespalib/util/threadexecutor.h1
-rw-r--r--vespalib/src/vespa/vespalib/util/threadstackexecutorbase.cpp5
-rw-r--r--vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h2
-rw-r--r--vespalog/abi-spec.json20
-rw-r--r--vespalog/src/main/java/com/yahoo/log/LevelController.java26
-rw-r--r--vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java8
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java8
-rw-r--r--vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java2
-rw-r--r--vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java65
-rw-r--r--vespalog/src/vespa/log/llparser.cpp3
-rw-r--r--vsm/src/tests/textutil/textutil.cpp6
333 files changed, 5628 insertions, 2661 deletions
diff --git a/build_settings.cmake b/build_settings.cmake
index 53fcbbad9a2..2edf48103cf 100644
--- a/build_settings.cmake
+++ b/build_settings.cmake
@@ -3,6 +3,14 @@
include(${CMAKE_CURRENT_LIST_DIR}/vtag.cmake)
+if (VESPA_USE_SANITIZER)
+ if (VESPA_USE_SANITIZER STREQUAL "address" OR VESPA_USE_SANITIZER STREQUAL "thread")
+ message("-- Instrumenting code using ${VESPA_USE_SANITIZER} sanitizer")
+ else()
+ message(FATAL_ERROR "Unsupported sanitizer option '${VESPA_USE_SANITIZER}'. Supported: 'address' or 'thread'")
+ endif()
+endif()
+
# Build options
# Whether to build unit tests as part of the 'all' target
set(EXCLUDE_TESTS_FROM_ALL FALSE CACHE BOOL "If TRUE, do not build tests as part of the 'all' target")
@@ -17,7 +25,13 @@ set(RUN_BENCHMARKS FALSE CACHE BOOL "If TRUE, benchmarks are run together with t
set(AUTORUN_UNIT_TESTS FALSE CACHE BOOL "If TRUE, tests will be run immediately after linking the test executable")
# Warnings
-set(C_WARN_OPTS "-Winline -Wuninitialized -Werror -Wall -W -Wchar-subscripts -Wcomment -Wformat -Wparentheses -Wreturn-type -Wswitch -Wtrigraphs -Wunused -Wshadow -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings")
+set(C_WARN_OPTS "-Wuninitialized -Werror -Wall -W -Wchar-subscripts -Wcomment -Wformat -Wparentheses -Wreturn-type -Wswitch -Wtrigraphs -Wunused -Wshadow -Wpointer-arith -Wcast-qual -Wcast-align -Wwrite-strings")
+if (VESPA_USE_SANITIZER)
+ # Instrumenting code changes binary size, which triggers inlining warnings that
+ # don't happen during normal, non-instrumented compilation.
+else()
+ set(C_WARN_OPTS "-Winline ${C_WARN_OPTS}")
+endif()
# Warnings that are specific to C++ compilation
# Note: this is not a union of C_WARN_OPTS, since CMAKE_CXX_FLAGS already includes CMAKE_C_FLAGS, which in turn includes C_WARN_OPTS transitively
@@ -46,7 +60,11 @@ else()
endif()
# C and C++ compiler flags
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -fno-omit-frame-pointer ${C_WARN_OPTS} -fPIC ${VESPA_CXX_ABI_FLAGS} -DBOOST_DISABLE_ASSERTS ${VESPA_CPU_ARCH_FLAGS} ${EXTRA_C_FLAGS}")
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -g -O3 -fno-omit-frame-pointer ${C_WARN_OPTS} -fPIC ${VESPA_CXX_ABI_FLAGS} -DXXH_INLINE_ALL -DBOOST_DISABLE_ASSERTS ${VESPA_CPU_ARCH_FLAGS} ${EXTRA_C_FLAGS}")
+# AddressSanitizer/ThreadSanitizer work for both GCC and Clang
+if (VESPA_USE_SANITIZER)
+ set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fsanitize=${VESPA_USE_SANITIZER}")
+endif()
if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${CMAKE_C_FLAGS} ${CXX_SPECIFIC_WARN_OPTS} -std=c++1z -fdiagnostics-color=auto ${EXTRA_CXX_FLAGS}")
else()
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperTestServer.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperTestServer.java
index 34c8fafa702..73b4163d542 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperTestServer.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/ZooKeeperTestServer.java
@@ -8,6 +8,7 @@ import org.apache.zookeeper.server.ZooKeeperServer;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
+import java.time.Duration;
/**
* This class sets up a zookeeper server, such that we can test fleetcontroller zookeeper parts without stubbing in the client.
@@ -15,7 +16,7 @@ import java.net.InetSocketAddress;
public class ZooKeeperTestServer {
private File zooKeeperDir;
private ZooKeeperServer server;
- private static final int tickTime = 100;
+ private static final Duration tickTime = Duration.ofMillis(2000);
private NIOServerCnxnFactory factory;
private static final String DIR_PREFIX = "test_fltctrl_zk";
private static final String DIR_POSTFIX = "sdir";
@@ -31,7 +32,7 @@ public class ZooKeeperTestServer {
throw new IllegalStateException("Failed to create directory " + zooKeeperDir);
}
zooKeeperDir.deleteOnExit();
- server = new ZooKeeperServer(zooKeeperDir, zooKeeperDir, tickTime);
+ server = new ZooKeeperServer(zooKeeperDir, zooKeeperDir, (int)tickTime.toMillis());
final int maxcc = 10000; // max number of connections from the same client
factory = new NIOServerCnxnFactory();
factory.configure(new InetSocketAddress(port), maxcc); // Use any port
diff --git a/config-lib/src/main/java/com/yahoo/config/FileReference.java b/config-lib/src/main/java/com/yahoo/config/FileReference.java
index 3b95c2fbd4c..ee99ebfa2b7 100755
--- a/config-lib/src/main/java/com/yahoo/config/FileReference.java
+++ b/config-lib/src/main/java/com/yahoo/config/FileReference.java
@@ -28,14 +28,16 @@ public final class FileReference {
}
@Override
- public int hashCode() {
- return value.hashCode();
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ FileReference that = (FileReference) o;
+ return value.equals(that.value);
}
@Override
- public boolean equals(Object other) {
- return other instanceof FileReference &&
- value.equals(((FileReference)other).value);
+ public int hashCode() {
+ return Objects.hash(value);
}
@Override
diff --git a/config-model-api/abi-spec.json b/config-model-api/abi-spec.json
index 0c061dd8222..4ccf34d30b0 100644
--- a/config-model-api/abi-spec.json
+++ b/config-model-api/abi-spec.json
@@ -606,521 +606,5 @@
"public static final com.yahoo.config.application.api.ValidationOverrides empty",
"public static final com.yahoo.config.application.api.ValidationOverrides all"
]
- },
- "com.yahoo.config.model.api.ApplicationInfo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(com.yahoo.config.provision.ApplicationId, long, com.yahoo.config.model.api.Model)",
- "public com.yahoo.config.provision.ApplicationId getApplicationId()",
- "public long getGeneration()",
- "public com.yahoo.config.model.api.Model getModel()",
- "public java.lang.String toString()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigChangeAction$Type": {
- "superClass": "java.lang.Enum",
- "interfaces": [],
- "attributes": [
- "public",
- "final",
- "enum"
- ],
- "methods": [
- "public static com.yahoo.config.model.api.ConfigChangeAction$Type[] values()",
- "public static com.yahoo.config.model.api.ConfigChangeAction$Type valueOf(java.lang.String)",
- "public java.lang.String toString()"
- ],
- "fields": [
- "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type RESTART",
- "public static final enum com.yahoo.config.model.api.ConfigChangeAction$Type REFEED"
- ]
- },
- "com.yahoo.config.model.api.ConfigChangeAction": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
- "public abstract java.lang.String getMessage()",
- "public abstract java.util.List getServices()",
- "public abstract boolean allowed()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigChangeRefeedAction": {
- "superClass": "java.lang.Object",
- "interfaces": [
- "com.yahoo.config.model.api.ConfigChangeAction"
- ],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
- "public java.lang.String name()",
- "public abstract java.lang.String getDocumentType()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigChangeRestartAction": {
- "superClass": "java.lang.Object",
- "interfaces": [
- "com.yahoo.config.model.api.ConfigChangeAction"
- ],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public com.yahoo.config.model.api.ConfigChangeAction$Type getType()",
- "public boolean allowed()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigDefinitionRepo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract java.util.Map getConfigDefinitions()",
- "public abstract com.yahoo.vespa.config.buildergen.ConfigDefinition get(com.yahoo.vespa.config.ConfigDefinitionKey)"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigDefinitionStore": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.vespa.config.ConfigDefinition getConfigDefinition(com.yahoo.vespa.config.ConfigDefinitionKey)"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigModelPlugin": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [],
- "fields": []
- },
- "com.yahoo.config.model.api.ConfigServerSpec": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract java.lang.String getHostName()",
- "public abstract int getConfigServerPort()",
- "public int getHttpPort()",
- "public abstract int getZooKeeperPort()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ContainerEndpoint": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.util.List)",
- "public java.lang.String clusterId()",
- "public java.util.List names()",
- "public boolean equals(java.lang.Object)",
- "public int hashCode()",
- "public java.lang.String toString()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.EndpointCertificateMetadata": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.lang.String, int)",
- "public java.lang.String keyName()",
- "public java.lang.String certName()",
- "public int version()",
- "public java.lang.String toString()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.EndpointCertificateSecrets": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.lang.String)",
- "public java.lang.String certificate()",
- "public java.lang.String key()",
- "public boolean isMissing()"
- ],
- "fields": [
- "public static final com.yahoo.config.model.api.EndpointCertificateSecrets MISSING"
- ]
- },
- "com.yahoo.config.model.api.FileDistribution": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract void startDownload(java.lang.String, int, java.util.Set)",
- "public abstract java.io.File getFileReferencesDir()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.HostInfo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.util.Collection)",
- "public java.lang.String getHostname()",
- "public java.util.Collection getServices()",
- "public boolean equals(java.lang.Object)",
- "public int hashCode()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.HostProvisioner": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.config.provision.HostSpec allocateHost(java.lang.String)",
- "public abstract java.util.List prepare(com.yahoo.config.provision.ClusterSpec, com.yahoo.config.provision.Capacity, int, com.yahoo.config.provision.ProvisionLogger)"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.Model": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.vespa.config.ConfigPayload getConfig(com.yahoo.vespa.config.ConfigKey, com.yahoo.vespa.config.buildergen.ConfigDefinition)",
- "public abstract java.util.Set allConfigsProduced()",
- "public abstract java.util.Collection getHosts()",
- "public abstract java.util.Set allConfigIds()",
- "public abstract void distributeFiles(com.yahoo.config.model.api.FileDistribution)",
- "public abstract java.util.Set fileReferences()",
- "public abstract com.yahoo.config.provision.AllocatedHosts allocatedHosts()",
- "public boolean allowModelVersionMismatch(java.time.Instant)",
- "public boolean skipOldConfigModels(java.time.Instant)",
- "public com.yahoo.component.Version version()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ModelContext$Properties": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract boolean multitenant()",
- "public abstract com.yahoo.config.provision.ApplicationId applicationId()",
- "public abstract java.util.List configServerSpecs()",
- "public abstract com.yahoo.config.provision.HostName loadBalancerName()",
- "public abstract java.net.URI ztsUrl()",
- "public abstract java.lang.String athenzDnsSuffix()",
- "public abstract boolean hostedVespa()",
- "public abstract com.yahoo.config.provision.Zone zone()",
- "public abstract java.util.Set endpoints()",
- "public abstract boolean isBootstrap()",
- "public abstract boolean isFirstTimeDeployment()",
- "public boolean useDedicatedNodeForLogserver()",
- "public abstract boolean useAdaptiveDispatch()",
- "public java.util.Optional tlsSecrets()",
- "public java.util.Optional endpointCertificateSecrets()",
- "public abstract double defaultTermwiseLimit()",
- "public abstract boolean useBucketSpaceMetric()",
- "public boolean useNewAthenzFilter()",
- "public boolean usePhraseSegmenting()",
- "public java.lang.String proxyProtocol()",
- "public java.util.Optional athenzDomain()",
- "public boolean useDedicatedNodesWhenUnspecified()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ModelContext": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.config.application.api.ApplicationPackage applicationPackage()",
- "public abstract java.util.Optional previousModel()",
- "public abstract java.util.Optional permanentApplicationPackage()",
- "public abstract java.util.Optional hostProvisioner()",
- "public abstract com.yahoo.config.application.api.DeployLogger deployLogger()",
- "public abstract com.yahoo.config.model.api.ConfigDefinitionRepo configDefinitionRepo()",
- "public abstract com.yahoo.config.application.api.FileRegistry getFileRegistry()",
- "public abstract com.yahoo.config.model.api.ModelContext$Properties properties()",
- "public java.util.Optional appDir()",
- "public java.util.Optional wantedDockerImageRepository()",
- "public abstract com.yahoo.component.Version modelVespaVersion()",
- "public abstract com.yahoo.component.Version wantedNodeVespaVersion()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ModelCreateResult": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(com.yahoo.config.model.api.Model, java.util.List)",
- "public com.yahoo.config.model.api.Model getModel()",
- "public java.util.List getConfigChangeActions()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ModelFactory": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.component.Version version()",
- "public abstract com.yahoo.config.model.api.Model createModel(com.yahoo.config.model.api.ModelContext)",
- "public abstract com.yahoo.config.model.api.ModelCreateResult createAndValidateModel(com.yahoo.config.model.api.ModelContext, com.yahoo.config.model.api.ValidationParameters)"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ModelState": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract com.yahoo.config.model.api.Model getModel()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.PortInfo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(int, java.util.Collection)",
- "public int getPort()",
- "public java.util.Collection getTags()",
- "public boolean equals(java.lang.Object)",
- "public int hashCode()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.ServiceInfo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.lang.String, java.util.Collection, java.util.Map, java.lang.String, java.lang.String)",
- "public java.lang.String getServiceName()",
- "public java.lang.String getConfigId()",
- "public java.lang.String getServiceType()",
- "public java.util.Optional getProperty(java.lang.String)",
- "public java.util.Collection getPorts()",
- "public java.lang.String getHostName()",
- "public boolean equals(java.lang.Object)",
- "public int hashCode()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.SuperModel": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>()",
- "public void <init>(java.util.Map, boolean)",
- "public java.util.Map getModelsPerTenant()",
- "public java.util.Map getModels()",
- "public boolean isComplete()",
- "public java.util.List getAllApplicationInfos()",
- "public java.util.Optional getApplicationInfo(com.yahoo.config.provision.ApplicationId)",
- "public com.yahoo.config.model.api.SuperModel cloneAndSetApplication(com.yahoo.config.model.api.ApplicationInfo)",
- "public com.yahoo.config.model.api.SuperModel cloneAndRemoveApplication(com.yahoo.config.provision.ApplicationId)",
- "public com.yahoo.config.model.api.SuperModel cloneAsComplete()",
- "public java.util.Set getApplicationIds()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.SuperModelListener": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract void applicationActivated(com.yahoo.config.model.api.SuperModel, com.yahoo.config.model.api.ApplicationInfo)",
- "public abstract void applicationRemoved(com.yahoo.config.model.api.SuperModel, com.yahoo.config.provision.ApplicationId)",
- "public abstract void notifyOfCompleteness(com.yahoo.config.model.api.SuperModel)"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.SuperModelProvider": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public",
- "interface",
- "abstract"
- ],
- "methods": [
- "public abstract void registerListener(com.yahoo.config.model.api.SuperModelListener)",
- "public abstract com.yahoo.config.model.api.SuperModel getSuperModel()"
- ],
- "fields": []
- },
- "com.yahoo.config.model.api.TlsSecrets": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.lang.String, java.lang.String)",
- "public void <init>(com.yahoo.config.model.api.EndpointCertificateSecrets)",
- "public java.lang.String certificate()",
- "public java.lang.String key()",
- "public boolean isMissing()"
- ],
- "fields": [
- "public static final com.yahoo.config.model.api.TlsSecrets MISSING"
- ]
- },
- "com.yahoo.config.model.api.ValidationParameters$CheckRouting": {
- "superClass": "java.lang.Enum",
- "interfaces": [],
- "attributes": [
- "public",
- "final",
- "enum"
- ],
- "methods": [
- "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting[] values()",
- "public static com.yahoo.config.model.api.ValidationParameters$CheckRouting valueOf(java.lang.String)"
- ],
- "fields": [
- "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting TRUE",
- "public static final enum com.yahoo.config.model.api.ValidationParameters$CheckRouting FALSE"
- ]
- },
- "com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange": {
- "superClass": "java.lang.Enum",
- "interfaces": [],
- "attributes": [
- "public",
- "final",
- "enum"
- ],
- "methods": [
- "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange[] values()",
- "public static com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange valueOf(java.lang.String)"
- ],
- "fields": [
- "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange TRUE",
- "public static final enum com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange FALSE"
- ]
- },
- "com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors": {
- "superClass": "java.lang.Enum",
- "interfaces": [],
- "attributes": [
- "public",
- "final",
- "enum"
- ],
- "methods": [
- "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors[] values()",
- "public static com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors valueOf(java.lang.String)"
- ],
- "fields": [
- "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors TRUE",
- "public static final enum com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors FALSE"
- ]
- },
- "com.yahoo.config.model.api.ValidationParameters": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>()",
- "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors)",
- "public void <init>(com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
- "public void <init>(com.yahoo.config.model.api.ValidationParameters$IgnoreValidationErrors, com.yahoo.config.model.api.ValidationParameters$FailOnIncompatibleChange, com.yahoo.config.model.api.ValidationParameters$CheckRouting)",
- "public boolean ignoreValidationErrors()",
- "public boolean failOnIncompatibleChanges()",
- "public boolean checkRouting()"
- ],
- "fields": []
}
} \ No newline at end of file
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ConfigServerSpec.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ConfigServerSpec.java
index e63e4ce3af6..96e76e46cda 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ConfigServerSpec.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ConfigServerSpec.java
@@ -10,8 +10,6 @@ public interface ConfigServerSpec {
String getHostName();
int getConfigServerPort();
- // TODO: Remove when latest model version in use is 7.47
- default int getHttpPort() { return getConfigServerPort() + 1; }
int getZooKeeperPort();
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/HostProvisioner.java b/config-model-api/src/main/java/com/yahoo/config/model/api/HostProvisioner.java
index bf58000dd36..4edf3c455d0 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/HostProvisioner.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/HostProvisioner.java
@@ -18,15 +18,17 @@ public interface HostProvisioner {
// TODO: Remove
HostSpec allocateHost(String alias);
+ @Deprecated // TODO: Remove after April 2020
+ List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger);
+
/**
* Prepares allocation of a set of hosts with a given type, common id and the amount.
*
* @param cluster the cluster to allocate nodes to
* @param capacity the capacity describing the capacity requested
- * @param groups the number of groups to divide the nodes into
* @param logger a logger to which messages to the deployer may be written
* @return the specification of the allocated hosts
*/
- List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger);
+ List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger);
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
index a3478026520..689e2524dde 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/package-info.java
@@ -1,6 +1,5 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
@ExportPackage
-@PublicApi // Not really "public", only annotated as such to enable the ABI checker plugin
package com.yahoo.config.model.api;
import com.yahoo.osgi.annotation.ExportPackage;
diff --git a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
index e37b0b07746..896c6ea9a7f 100644
--- a/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
+++ b/config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java
@@ -43,7 +43,6 @@ public class TestProperties implements ModelContext.Properties {
private double defaultTermwiseLimit = 1.0;
private Optional<EndpointCertificateSecrets> endpointCertificateSecrets = Optional.empty();
private boolean useNewAthenzFilter = false;
- private boolean useDedicatedNodesWhenUnspecified = false;
private AthenzDomain athenzDomain;
@Override public boolean multitenant() { return multitenant; }
@@ -65,7 +64,7 @@ public class TestProperties implements ModelContext.Properties {
@Override public double defaultTermwiseLimit() { return defaultTermwiseLimit; }
@Override public boolean useBucketSpaceMetric() { return true; }
@Override public boolean useNewAthenzFilter() { return useNewAthenzFilter; }
- @Override public boolean useDedicatedNodesWhenUnspecified() { return useDedicatedNodesWhenUnspecified; }
+ @Override public boolean useDedicatedNodesWhenUnspecified() { return true; }
@Override public Optional<AthenzDomain> athenzDomain() { return Optional.ofNullable(athenzDomain); }
public TestProperties setDefaultTermwiseLimit(double limit) {
@@ -118,11 +117,6 @@ public class TestProperties implements ModelContext.Properties {
return this;
}
- public TestProperties setUseDedicatedNodesWhenUnspecified(boolean useDedicatedNodesWhenUnspecified) {
- this.useDedicatedNodesWhenUnspecified = useDedicatedNodesWhenUnspecified;
- return this;
- }
-
public TestProperties setAthenzDomain(AthenzDomain domain) {
this.athenzDomain = domain;
return this;
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java
index f909f3864da..201b69c1aae 100644
--- a/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java
+++ b/config-model/src/main/java/com/yahoo/config/model/provision/HostsXmlProvisioner.java
@@ -45,10 +45,16 @@ public class HostsXmlProvisioner implements HostProvisioner {
}
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ClusterSpec cluster, Capacity quantity, int groups, ProvisionLogger logger) {
throw new UnsupportedOperationException("Prepare on an XML host provisioner is not supported");
}
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity quantity, ProvisionLogger logger) {
+ throw new UnsupportedOperationException("Prepare on an XML host provisioner is not supported");
+ }
+
private HostSpec host2HostSpec(Host host) {
return new HostSpec(host.hostname(), host.aliases());
}
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
index 6047b6a9818..298517b85f6 100644
--- a/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
+++ b/config-model/src/main/java/com/yahoo/config/model/provision/InMemoryProvisioner.java
@@ -6,6 +6,7 @@ import com.yahoo.collections.Pair;
import com.yahoo.config.model.api.HostProvisioner;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostSpec;
@@ -57,6 +58,8 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Use this index as start index for all clusters */
private final int startIndexForClusters;
+ private final boolean useMaxResources;
+
/** Creates this with a number of nodes with resources 1, 3, 9, 1 */
public InMemoryProvisioner(int nodeCount) {
this(nodeCount, defaultResources);
@@ -64,27 +67,29 @@ public class InMemoryProvisioner implements HostProvisioner {
/** Creates this with a number of nodes with given resources */
public InMemoryProvisioner(int nodeCount, NodeResources resources) {
- this(Map.of(resources, createHostInstances(nodeCount)), true, 0);
+ this(Map.of(resources, createHostInstances(nodeCount)), true, false, 0);
}
/** Creates this with a set of host names of the flavor 'default' */
public InMemoryProvisioner(boolean failOnOutOfCapacity, String... hosts) {
- this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, 0);
+ this(Map.of(defaultResources, toHostInstances(hosts)), failOnOutOfCapacity, false, 0);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, 0, retiredHostNames);
+ this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, 0, retiredHostNames);
}
/** Creates this with a set of hosts of the flavor 'default' */
public InMemoryProvisioner(Hosts hosts, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) {
- this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, startIndexForClusters, retiredHostNames);
+ this(Map.of(defaultResources, hosts.asCollection()), failOnOutOfCapacity, false, startIndexForClusters, retiredHostNames);
}
public InMemoryProvisioner(Map<NodeResources, Collection<Host>> hosts, boolean failOnOutOfCapacity,
+ boolean useMaxResources,
int startIndexForClusters, String ... retiredHostNames) {
this.failOnOutOfCapacity = failOnOutOfCapacity;
+ this.useMaxResources = useMaxResources;
for (Map.Entry<NodeResources, Collection<Host>> hostsWithResources : hosts.entrySet())
for (Host host : hostsWithResources.getValue())
freeNodes.put(hostsWithResources.getKey(), host);
@@ -113,34 +118,43 @@ public class InMemoryProvisioner implements HostProvisioner {
}
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ClusterSpec cluster, Capacity requestedCapacity, int groups, ProvisionLogger logger) {
- if (cluster.group().isPresent() && groups > 1)
+ return prepare(cluster, requestedCapacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity requested, ProvisionLogger logger) {
+ if (useMaxResources)
+ return prepare(cluster, requested.maxResources(), requested.isRequired(), requested.canFail());
+ else
+ return prepare(cluster, requested.minResources(), requested.isRequired(), requested.canFail());
+ }
+
+ public List<HostSpec> prepare(ClusterSpec cluster, ClusterResources requested, boolean required, boolean canFail) {
+ if (cluster.group().isPresent() && requested.groups() > 1)
throw new IllegalArgumentException("Cannot both be specifying a group and ask for groups to be created");
- if (requestedCapacity.nodeCount() % groups != 0)
- throw new IllegalArgumentException("Requested " + requestedCapacity.nodeCount() + " nodes in " +
- groups + " groups, but the node count is not divisible into this number of groups");
- int capacity = failOnOutOfCapacity || requestedCapacity.isRequired()
- ? requestedCapacity.nodeCount()
- : Math.min(requestedCapacity.nodeCount(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
- if (groups > capacity)
- groups = capacity;
+ int capacity = failOnOutOfCapacity || required
+ ? requested.nodes()
+ : Math.min(requested.nodes(), freeNodes.get(defaultResources).size() + totalAllocatedTo(cluster));
+ int groups = requested.groups() > capacity ? capacity : requested.groups();
List<HostSpec> allocation = new ArrayList<>();
if (groups == 1) {
allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(0))),
- requestedCapacity.nodeResources(),
+ requested.nodeResources(),
capacity,
startIndexForClusters,
- requestedCapacity.canFail()));
+ canFail));
}
else {
for (int i = 0; i < groups; i++) {
allocation.addAll(allocateHostGroup(cluster.with(Optional.of(ClusterSpec.Group.from(i))),
- requestedCapacity.nodeResources(),
+ requested.nodeResources(),
capacity / groups,
allocation.size(),
- requestedCapacity.canFail()));
+ canFail));
}
}
for (ListIterator<HostSpec> i = allocation.listIterator(); i.hasNext(); ) {
@@ -162,7 +176,7 @@ public class InMemoryProvisioner implements HostProvisioner {
host.dockerImageRepo());
}
- private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, Optional<NodeResources> requestedResources,
+ private List<HostSpec> allocateHostGroup(ClusterSpec clusterGroup, NodeResources requestedResources,
int nodesInGroup, int startIndex, boolean canFail) {
List<HostSpec> allocation = allocations.getOrDefault(clusterGroup, new ArrayList<>());
allocations.put(clusterGroup, allocation);
@@ -170,8 +184,8 @@ public class InMemoryProvisioner implements HostProvisioner {
// Check if the current allocations are compatible with the new request
for (int i = allocation.size() - 1; i >= 0; i--) {
Optional<NodeResources> currentResources = allocation.get(0).flavor().map(Flavor::resources);
- if (currentResources.isEmpty() || requestedResources.isEmpty()) continue;
- if (!currentResources.get().compatibleWith(requestedResources.get())) {
+ if (currentResources.isEmpty() || requestedResources == NodeResources.unspecified) continue;
+ if (!currentResources.get().compatibleWith(requestedResources)) {
HostSpec removed = allocation.remove(i);
freeNodes.put(currentResources.get(), new Host(removed.hostname())); // Return the node back to free pool
}
@@ -182,7 +196,7 @@ public class InMemoryProvisioner implements HostProvisioner {
// Find the smallest host that can fit the requested requested
Optional<NodeResources> hostResources = freeNodes.keySet().stream()
.sorted(new MemoryDiskCpu())
- .filter(resources -> requestedResources.isEmpty() || resources.satisfies(requestedResources.get()))
+ .filter(resources -> requestedResources == NodeResources.unspecified || resources.satisfies(requestedResources))
.findFirst();
if (hostResources.isEmpty()) {
if (canFail)
@@ -195,8 +209,9 @@ public class InMemoryProvisioner implements HostProvisioner {
if (freeNodes.get(hostResources.get()).isEmpty()) freeNodes.removeAll(hostResources.get());
ClusterMembership membership = ClusterMembership.from(clusterGroup, nextIndex++);
allocation.add(new HostSpec(newHost.hostname(), newHost.aliases(),
- hostResources.map(Flavor::new), Optional.of(membership),
- newHost.version(), Optional.empty(), requestedResources));
+ hostResources.map(Flavor::new), Optional.of(membership),
+ newHost.version(), Optional.empty(),
+ requestedResources == NodeResources.unspecified ? Optional.empty() : Optional.of(requestedResources)));
}
nextIndexInCluster.put(new Pair<>(clusterGroup.type(), clusterGroup.id()), nextIndex);
diff --git a/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java b/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java
index 180a16f3c8f..8945223447f 100644
--- a/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java
+++ b/config-model/src/main/java/com/yahoo/config/model/provision/SingleNodeProvisioner.java
@@ -30,6 +30,7 @@ public class SingleNodeProvisioner implements HostProvisioner {
host = new Host(HostName.getLocalhost());
this.hostSpec = new HostSpec(host.hostname(), host.aliases());
}
+
public SingleNodeProvisioner(Flavor flavor) {
host = new Host(HostName.getLocalhost());
this.hostSpec = new HostSpec(host.hostname(), host.aliases(), flavor);
@@ -41,7 +42,14 @@ public class SingleNodeProvisioner implements HostProvisioner {
}
@Override
- public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) { // TODO: This should fail if capacity requested is more than 1
+ @Deprecated // TODO: Remove after April 2020
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
+ return prepare(cluster, capacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ // TODO: This should fail if capacity requested is more than 1
List<HostSpec> hosts = new ArrayList<>();
hosts.add(new HostSpec(host.hostname(), host.aliases(), ClusterMembership.from(cluster, counter++)));
return hosts;
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
index 90f061d933d..aba6cf9a233 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/Index.java
@@ -10,11 +10,11 @@ import java.io.Serializable;
import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedHashSet;
+import java.util.Locale;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
-
/**
* An index definition in a search definition.
* Two indices are equal if they have the same name and the same settings, except
@@ -24,6 +24,8 @@ import java.util.Set;
*/
public class Index implements Cloneable, Serializable {
+ public static enum DistanceMetric { EUCLIDEAN, ANGULAR, GEODEGREES }
+
public enum Type {
VESPA("vespa");
@@ -61,7 +63,9 @@ public class Index implements Cloneable, Serializable {
/** The boolean index definition, if set */
private BooleanIndexDefinition boolIndex;
- private Optional<HnswIndexParams> hnswIndexParams;
+ private Optional<HnswIndexParams> hnswIndexParams = Optional.empty();
+
+ private Optional<DistanceMetric> distanceMetric = Optional.empty();
/** Whether the posting lists of this index field should have interleaved features (num occs, field length) in document id stream. */
private boolean interleavedFeatures = false;
@@ -134,12 +138,13 @@ public class Index implements Cloneable, Serializable {
stemming == index.stemming &&
type == index.type &&
Objects.equals(boolIndex, index.boolIndex) &&
+ Objects.equals(distanceMetric, index.distanceMetric) &&
Objects.equals(hnswIndexParams, index.hnswIndexParams);
}
@Override
public int hashCode() {
- return Objects.hash(name, rankType, prefix, aliases, stemming, normalized, type, boolIndex, hnswIndexParams, interleavedFeatures);
+ return Objects.hash(name, rankType, prefix, aliases, stemming, normalized, type, boolIndex, distanceMetric, hnswIndexParams, interleavedFeatures);
}
public String toString() {
@@ -187,6 +192,16 @@ public class Index implements Cloneable, Serializable {
boolIndex = def;
}
+ public Optional<DistanceMetric> getDistanceMetric() {
+ return distanceMetric;
+ }
+
+ public void setDistanceMetric(String value) {
+ String upper = value.toUpperCase(Locale.ENGLISH);
+ DistanceMetric dm = DistanceMetric.valueOf(upper);
+ distanceMetric = Optional.of(dm);
+ }
+
public Optional<HnswIndexParams> getHnswIndexParams() {
return hnswIndexParams;
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
index 5b87fdcf5f6..8b5f7658475 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/derived/AttributeFields.java
@@ -240,13 +240,14 @@ public class AttributeFields extends Derived implements AttributesConfig.Produce
aaB.tensortype(attribute.tensorType().get().toString());
}
aaB.imported(imported);
+ var dma = attribute.distanceMetric();
if (attribute.hnswIndexParams().isPresent()) {
var ib = new AttributesConfig.Attribute.Index.Builder();
var params = attribute.hnswIndexParams().get();
ib.hnsw.enabled(true);
ib.hnsw.maxlinkspernode(params.maxLinksPerNode());
ib.hnsw.neighborstoexploreatinsert(params.neighborsToExploreAtInsert());
- var dm = AttributesConfig.Attribute.Index.Hnsw.Distancemetric.Enum.valueOf(params.distanceMetric().toString());
+ var dm = AttributesConfig.Attribute.Index.Hnsw.Distancemetric.Enum.valueOf(dma.toString());
ib.hnsw.distancemetric(dm);
aaB.index(ib);
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java
index 9ed5e4ca2de..1661a80f238 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/Attribute.java
@@ -24,6 +24,7 @@ import com.yahoo.document.datatypes.Float16FieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.TensorFieldValue;
import com.yahoo.tensor.TensorType;
+import static com.yahoo.searchdefinition.Index.DistanceMetric;
import java.io.Serializable;
import java.util.LinkedHashSet;
@@ -66,7 +67,9 @@ public final class Attribute implements Cloneable, Serializable {
/** This is set if the type of this is REFERENCE */
private final Optional<StructuredDataType> referenceDocumentType;
- private Optional<HnswIndexParams> hnswIndexParams;
+ private Optional<DistanceMetric> distanceMetric = Optional.empty();
+
+ private Optional<HnswIndexParams> hnswIndexParams = Optional.empty();
private boolean isPosition = false;
private final Sorting sorting = new Sorting();
@@ -152,7 +155,6 @@ public final class Attribute implements Cloneable, Serializable {
setCollectionType(collectionType);
this.tensorType = tensorType;
this.referenceDocumentType = referenceDocumentType;
- this.hnswIndexParams = Optional.empty();
}
public Attribute convertToArray() {
@@ -197,6 +199,11 @@ public final class Attribute implements Cloneable, Serializable {
public double densePostingListThreshold() { return densePostingListThreshold; }
public Optional<TensorType> tensorType() { return tensorType; }
public Optional<StructuredDataType> referenceDocumentType() { return referenceDocumentType; }
+
+ public static final DistanceMetric DEFAULT_DISTANCE_METRIC = DistanceMetric.EUCLIDEAN;
+ public DistanceMetric distanceMetric() {
+ return distanceMetric.orElse(DEFAULT_DISTANCE_METRIC);
+ }
public Optional<HnswIndexParams> hnswIndexParams() { return hnswIndexParams; }
public Sorting getSorting() { return sorting; }
@@ -221,6 +228,7 @@ public final class Attribute implements Cloneable, Serializable {
public void setUpperBound(long upperBound) { this.upperBound = upperBound; }
public void setDensePostingListThreshold(double threshold) { this.densePostingListThreshold = threshold; }
public void setTensorType(TensorType tensorType) { this.tensorType = Optional.of(tensorType); }
+ public void setDistanceMetric(Optional<DistanceMetric> dm) { this.distanceMetric = dm; }
public void setHnswIndexParams(HnswIndexParams params) { this.hnswIndexParams = Optional.of(params); }
public String getName() { return name; }
@@ -354,8 +362,8 @@ public final class Attribute implements Cloneable, Serializable {
/** Returns whether these attributes describes the same entity, even if they have different names */
public boolean isCompatible(Attribute other) {
- if ( ! this.type.equals(other.type)) return false;
- if ( ! this.collectionType.equals(other.collectionType)) return false;
+ if (! this.type.equals(other.type)) return false;
+ if (! this.collectionType.equals(other.collectionType)) return false;
if (this.isPrefetch() != other.isPrefetch()) return false;
if (this.removeIfZero != other.removeIfZero) return false;
if (this.createIfNonExistent != other.createIfNonExistent) return false;
@@ -364,10 +372,11 @@ public final class Attribute implements Cloneable, Serializable {
// if (this.noSearch != other.noSearch) return false; No backend consequences so compatible for now
if (this.fastSearch != other.fastSearch) return false;
if (this.huge != other.huge) return false;
- if ( ! this.sorting.equals(other.sorting)) return false;
- if (!this.tensorType.equals(other.tensorType)) return false;
- if (!this.referenceDocumentType.equals(other.referenceDocumentType)) return false;
- if (!this.hnswIndexParams.equals(other.hnswIndexParams)) return false;
+ if (! this.sorting.equals(other.sorting)) return false;
+ if (! Objects.equals(tensorType, other.tensorType)) return false;
+ if (! Objects.equals(referenceDocumentType, other.referenceDocumentType)) return false;
+ if (! Objects.equals(distanceMetric, other.distanceMetric)) return false;
+ if (! Objects.equals(hnswIndexParams, other.hnswIndexParams)) return false;
return true;
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java b/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java
index 01434be8785..2f084d3e513 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/document/HnswIndexParams.java
@@ -13,18 +13,13 @@ public class HnswIndexParams {
public static final int DEFAULT_MAX_LINKS_PER_NODE = 16;
public static final int DEFAULT_NEIGHBORS_TO_EXPLORE_AT_INSERT = 200;
- public static final DistanceMetric DEFAULT_DISTANCE_METRIC = DistanceMetric.EUCLIDEAN;
private final Optional<Integer> maxLinksPerNode;
private final Optional<Integer> neighborsToExploreAtInsert;
- private final Optional<DistanceMetric> distanceMetric;
-
- public static enum DistanceMetric { EUCLIDEAN, ANGULAR, GEODEGREES }
public static class Builder {
private Optional<Integer> maxLinksPerNode = Optional.empty();
private Optional<Integer> neighborsToExploreAtInsert = Optional.empty();
- private Optional<DistanceMetric> distanceMetric = Optional.empty();
public void setMaxLinksPerNode(int value) {
maxLinksPerNode = Optional.of(value);
@@ -32,38 +27,31 @@ public class HnswIndexParams {
public void setNeighborsToExploreAtInsert(int value) {
neighborsToExploreAtInsert = Optional.of(value);
}
- public void setDistanceMetric(String value) {
- String upper = value.toUpperCase(Locale.ENGLISH);
- DistanceMetric dm = DistanceMetric.valueOf(upper);
- distanceMetric = Optional.of(dm);
- }
public HnswIndexParams build() {
- return new HnswIndexParams(maxLinksPerNode, neighborsToExploreAtInsert, distanceMetric);
+ return new HnswIndexParams(maxLinksPerNode, neighborsToExploreAtInsert);
}
}
public HnswIndexParams() {
this.maxLinksPerNode = Optional.empty();
this.neighborsToExploreAtInsert = Optional.empty();
- this.distanceMetric = Optional.empty();
}
public HnswIndexParams(Optional<Integer> maxLinksPerNode,
- Optional<Integer> neighborsToExploreAtInsert,
- Optional<DistanceMetric> distanceMetric) {
+ Optional<Integer> neighborsToExploreAtInsert) {
this.maxLinksPerNode = maxLinksPerNode;
this.neighborsToExploreAtInsert = neighborsToExploreAtInsert;
- this.distanceMetric = distanceMetric;
}
/**
* Creates a new instance where values from the given parameter instance are used where they are present,
* otherwise we use values from this.
*/
- public HnswIndexParams overrideFrom(HnswIndexParams rhs) {
+ public HnswIndexParams overrideFrom(Optional<HnswIndexParams> other) {
+ if (! other.isPresent()) return this;
+ HnswIndexParams rhs = other.get();
return new HnswIndexParams(rhs.maxLinksPerNode.or(() -> maxLinksPerNode),
- rhs.neighborsToExploreAtInsert.or(() -> neighborsToExploreAtInsert),
- rhs.distanceMetric.or(() -> distanceMetric));
+ rhs.neighborsToExploreAtInsert.or(() -> neighborsToExploreAtInsert));
}
public int maxLinksPerNode() {
@@ -73,8 +61,4 @@ public class HnswIndexParams {
public int neighborsToExploreAtInsert() {
return neighborsToExploreAtInsert.orElse(DEFAULT_NEIGHBORS_TO_EXPLORE_AT_INSERT);
}
-
- public DistanceMetric distanceMetric() {
- return distanceMetric.orElse(DEFAULT_DISTANCE_METRIC);
- }
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
index 7f9da28b9ca..0c1f443dee3 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/fieldoperation/IndexOperation.java
@@ -32,6 +32,7 @@ public class IndexOperation implements FieldOperation {
private OptionalDouble densePostingListThreshold = OptionalDouble.empty();
private Optional<Boolean> enableBm25 = Optional.empty();
+ private Optional<String> distanceMetric = Optional.empty();
private Optional<HnswIndexParams.Builder> hnswIndexParams = Optional.empty();
public String getIndexName() {
@@ -94,6 +95,9 @@ public class IndexOperation implements FieldOperation {
if (enableBm25.isPresent()) {
index.setInterleavedFeatures(enableBm25.get());
}
+ if (distanceMetric.isPresent()) {
+ index.setDistanceMetric(distanceMetric.get());
+ }
if (hnswIndexParams.isPresent()) {
index.setHnswIndexParams(hnswIndexParams.get().build());
}
@@ -127,6 +131,10 @@ public class IndexOperation implements FieldOperation {
enableBm25 = Optional.of(value);
}
+ public void setDistanceMetric(String value) {
+ this.distanceMetric = Optional.of(value);
+ }
+
public void setHnswIndexParams(HnswIndexParams.Builder params) {
this.hnswIndexParams = Optional.of(params);
}
diff --git a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java
index 2790f2ddf6e..c97ee2bd935 100644
--- a/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java
+++ b/config-model/src/main/java/com/yahoo/searchdefinition/processing/TensorFieldProcessor.java
@@ -81,8 +81,9 @@ public class TensorFieldProcessor extends Processor {
var index = field.getIndex(field.getName());
// TODO: Calculate default params based on tensor dimension size
var params = new HnswIndexParams();
- if (index != null && index.getHnswIndexParams().isPresent()) {
- params = params.overrideFrom(index.getHnswIndexParams().get());
+ if (index != null) {
+ params = params.overrideFrom(index.getHnswIndexParams());
+ field.getAttribute().setDistanceMetric(index.getDistanceMetric());
}
field.getAttribute().setHnswIndexParams(params);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
index 3765e683b18..557a61ec211 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/HostSystem.java
@@ -111,8 +111,8 @@ public class HostSystem extends AbstractConfigProducer<Host> {
}
}
- public Map<HostResource, ClusterMembership> allocateHosts(ClusterSpec cluster, Capacity capacity, int groups, DeployLogger logger) {
- List<HostSpec> allocatedHosts = provisioner.prepare(cluster, capacity, groups, new ProvisionDeployLogger(logger));
+ public Map<HostResource, ClusterMembership> allocateHosts(ClusterSpec cluster, Capacity capacity, DeployLogger logger) {
+ List<HostSpec> allocatedHosts = provisioner.prepare(cluster, capacity, new ProvisionDeployLogger(logger));
// TODO: Even if HostResource owns a set of memberships, we need to return a map because the caller needs the current membership.
Map<HostResource, ClusterMembership> retAllocatedHosts = new LinkedHashMap<>();
for (HostSpec spec : allocatedHosts) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
index 001e6a9a407..09128e741b9 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/monitoring/VespaMetricSet.java
@@ -301,6 +301,14 @@ public class VespaMetricSet {
return metrics;
}
+ private static void addSearchNodeExecutorMetrics(Set<Metric> metrics, String prefix) {
+ metrics.add(new Metric(prefix + ".queuesize.max"));
+ metrics.add(new Metric(prefix + ".queuesize.sum"));
+ metrics.add(new Metric(prefix + ".queuesize.count"));
+ metrics.add(new Metric(prefix + ".maxpending.last")); // TODO: Remove in Vespa 8
+ metrics.add(new Metric(prefix + ".accepted.rate"));
+ }
+
private static Set<Metric> getSearchNodeMetrics() {
Set<Metric> metrics = new LinkedHashSet<>();
@@ -345,18 +353,12 @@ public class VespaMetricSet {
metrics.add(new Metric("content.proton.search_protocol.docsum.requested_documents.count"));
// Executors shared between all document dbs
- metrics.add(new Metric("content.proton.executor.proton.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.proton.accepted.rate"));
- metrics.add(new Metric("content.proton.executor.flush.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.flush.accepted.rate"));
- metrics.add(new Metric("content.proton.executor.match.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.match.accepted.rate"));
- metrics.add(new Metric("content.proton.executor.docsum.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.docsum.accepted.rate"));
- metrics.add(new Metric("content.proton.executor.shared.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.shared.accepted.rate"));
- metrics.add(new Metric("content.proton.executor.warmup.maxpending.last"));
- metrics.add(new Metric("content.proton.executor.warmup.accepted.rate"));
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.proton");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.flush");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.match");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.docsum");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.shared");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.executor.warmup");
// jobs
metrics.add(new Metric("content.proton.documentdb.job.total.average"));
@@ -370,18 +372,12 @@ public class VespaMetricSet {
metrics.add(new Metric("content.proton.documentdb.job.removed_documents_prune.average"));
// Threading service (per document db)
- metrics.add(new Metric("content.proton.documentdb.threading_service.master.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.master.accepted.rate"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index.accepted.rate"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.summary.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.summary.accepted.rate"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_inverter.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_inverter.accepted.rate"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_writer.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.index_field_writer.accepted.rate"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.attribute_field_writer.maxpending.last"));
- metrics.add(new Metric("content.proton.documentdb.threading_service.attribute_field_writer.accepted.rate"));
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.master");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.summary");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index_field_inverter");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.index_field_writer");
+ addSearchNodeExecutorMetrics(metrics, "content.proton.documentdb.threading_service.attribute_field_writer");
// lid space
metrics.add(new Metric("content.proton.documentdb.ready.lid_space.lid_bloat_factor.average"));
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java
index b4246171277..b6f7ab4ff62 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/RankSetupValidator.java
@@ -138,7 +138,7 @@ public class RankSetupValidator extends Validator {
private boolean execValidate(String configId, SearchCluster sc, String sdName, DeployLogger deployLogger) {
String job = String.format("%s %s", binaryName, configId);
- ProcessExecuter executer = new ProcessExecuter();
+ ProcessExecuter executer = new ProcessExecuter(true);
try {
Pair<Integer, String> ret = executer.exec(job);
if (ret.getFirst() != 0) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
index 5629956e8b9..804a5442608 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java
@@ -65,12 +65,12 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
createSlobroks(deployLogger, admin, allocateHosts(admin.hostSystem(), "slobroks", nodesSpecification));
}
else {
- createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.count(), 2));
+ createSlobroks(deployLogger, admin, pickContainerHostsForSlobrok(nodesSpecification.minResources().nodes(), 2));
}
}
private void assignLogserver(DeployState deployState, NodesSpecification nodesSpecification, Admin admin) {
- if (nodesSpecification.count() > 1) throw new IllegalArgumentException("You can only request a single log server");
+ if (nodesSpecification.minResources().nodes() > 1) throw new IllegalArgumentException("You can only request a single log server");
if (deployState.getProperties().applicationId().instance().isTester()) return; // No logserver is needed on tester applications
if (nodesSpecification.isDedicated()) {
Collection<HostResource> hosts = allocateHosts(admin.hostSystem(), "logserver", nodesSpecification);
@@ -79,7 +79,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
Logserver logserver = createLogserver(deployState.getDeployLogger(), admin, hosts);
createContainerOnLogserverHost(deployState, admin, logserver.getHostResource());
} else if (containerModels.iterator().hasNext()) {
- List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.count(), false);
+ List<HostResource> hosts = sortedContainerHostsFrom(containerModels.iterator().next(), nodesSpecification.minResources().nodes(), false);
if (hosts.isEmpty()) return; // No log server can be created (and none is needed)
createLogserver(deployState.getDeployLogger(), admin, hosts);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
index d34a11abdf4..80c95ad6b59 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/ModelElement.java
@@ -172,7 +172,12 @@ public class ModelElement {
/** Returns the content of the attribute with the given name, or null if none */
public String stringAttribute(String name) {
- if ( ! xml.hasAttribute(name)) return null;
+ return stringAttribute(name, null);
+ }
+
+ /** Returns the content of the attribute with the given name, or the default value if none */
+ public String stringAttribute(String name, String defaultValue) {
+ if ( ! xml.hasAttribute(name)) return defaultValue;
return xml.getAttribute(name);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java
index 384d891003e..6a52ff4f051 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/NodesSpecification.java
@@ -1,11 +1,13 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.builder.xml.dom;
+import com.yahoo.collections.Pair;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.ConfigModelContext;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.text.XML;
@@ -18,6 +20,8 @@ import org.w3c.dom.Node;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.Function;
+import java.util.regex.Pattern;
/**
* A common utility class to represent a requirement for nodes during model building.
@@ -27,11 +31,9 @@ import java.util.Optional;
*/
public class NodesSpecification {
- private final boolean dedicated;
-
- private final int count;
+ private final ClusterResources min, max;
- private final int groups;
+ private final boolean dedicated;
/** The Vespa version we want the nodes to run */
private Version version;
@@ -46,43 +48,51 @@ public class NodesSpecification {
private final boolean exclusive;
- /** The resources each node should have, or empty to use the default */
- private final Optional<NodeResources> resources;
-
/** The repo part of a docker image (without tag), optional */
private final Optional<String> dockerImageRepo;
/** The ID of the cluster referencing this node specification, if any */
private final Optional<String> combinedId;
- private NodesSpecification(boolean dedicated, int count, int groups, Version version,
+ private NodesSpecification(ClusterResources min,
+ ClusterResources max,
+ boolean dedicated, Version version,
boolean required, boolean canFail, boolean exclusive,
- Optional<NodeResources> resources, Optional<String> dockerImageRepo,
+ Optional<String> dockerImageRepo,
Optional<String> combinedId) {
+ this.min = min;
+ this.max = max;
this.dedicated = dedicated;
- this.count = count;
- this.groups = groups;
this.version = version;
this.required = required;
this.canFail = canFail;
this.exclusive = exclusive;
- this.resources = resources;
this.dockerImageRepo = dockerImageRepo;
this.combinedId = combinedId;
}
- private NodesSpecification(boolean dedicated, boolean canFail, Version version, ModelElement nodesElement,
- Optional<String> combinedId, Optional<String> dockerImageRepo) {
- this(dedicated,
- nodesElement.integerAttribute("count", 1),
- nodesElement.integerAttribute("groups", 1),
- version,
- nodesElement.booleanAttribute("required", false),
- canFail,
- nodesElement.booleanAttribute("exclusive", false),
- getResources(nodesElement),
- dockerImageToUse(nodesElement, dockerImageRepo),
- combinedId);
+ private static NodesSpecification create(boolean dedicated, boolean canFail, Version version,
+ ModelElement nodesElement, Optional<String> dockerImageRepo) {
+ var resolvedElement = resolveElement(nodesElement);
+ var combinedId = findCombinedId(nodesElement, resolvedElement);
+ var resources = toResources(resolvedElement);
+ return new NodesSpecification(resources.getFirst(),
+ resources.getSecond(),
+ dedicated,
+ version,
+ resolvedElement.booleanAttribute("required", false),
+ canFail,
+ resolvedElement.booleanAttribute("exclusive", false),
+ dockerImageToUse(resolvedElement, dockerImageRepo),
+ combinedId);
+ }
+
+ private static Pair<ClusterResources, ClusterResources> toResources(ModelElement nodesElement) {
+ Pair<Integer, Integer> nodes = toRange(nodesElement.stringAttribute("count"), 1, Integer::parseInt);
+ Pair<Integer, Integer> groups = toRange(nodesElement.stringAttribute("groups"), 1, Integer::parseInt);
+ var min = new ClusterResources(nodes.getFirst(), groups.getFirst(), nodeResources(nodesElement).getFirst());
+ var max = new ClusterResources(nodes.getSecond(), groups.getSecond(), nodeResources(nodesElement).getSecond());
+ return new Pair<>(min, max);
}
/** Returns the ID of the cluster referencing this node specification, if any */
@@ -95,13 +105,6 @@ public class NodesSpecification {
return containerIdReferencing(nodesElement);
}
- private static NodesSpecification create(boolean dedicated, boolean canFail, Version version,
- ModelElement nodesElement, Optional<String> dockerImage) {
- var resolvedElement = resolveElement(nodesElement);
- var combinedId = findCombinedId(nodesElement, resolvedElement);
- return new NodesSpecification(dedicated, canFail, version, resolvedElement, combinedId, dockerImage);
- }
-
/** Returns a requirement for dedicated nodes taken from the given <code>nodes</code> element */
public static NodesSpecification from(ModelElement nodesElement, ConfigModelContext context) {
return create(true,
@@ -144,32 +147,33 @@ public class NodesSpecification {
* Returns a requirement from <code>count</code> non-dedicated nodes in one group
*/
public static NodesSpecification nonDedicated(int count, ConfigModelContext context) {
- return new NodesSpecification(false,
- count,
- 1,
+ return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified),
+ new ClusterResources(count, 1, NodeResources.unspecified),
+ false,
context.getDeployState().getWantedNodeVespaVersion(),
false,
! context.getDeployState().getProperties().isBootstrap(),
false,
- Optional.empty(),
context.getDeployState().getWantedDockerImageRepo(),
Optional.empty());
}
/** Returns a requirement from <code>count</code> dedicated nodes in one group */
public static NodesSpecification dedicated(int count, ConfigModelContext context) {
- return new NodesSpecification(true,
- count,
- 1,
+ return new NodesSpecification(new ClusterResources(count, 1, NodeResources.unspecified),
+ new ClusterResources(count, 1, NodeResources.unspecified),
+ true,
context.getDeployState().getWantedNodeVespaVersion(),
false,
! context.getDeployState().getProperties().isBootstrap(),
false,
- Optional.empty(),
context.getDeployState().getWantedDockerImageRepo(),
Optional.empty());
}
+ public ClusterResources minResources() { return min; }
+ public ClusterResources maxResources() { return max; }
+
/**
* Returns whether this requires dedicated nodes.
* Otherwise the model encountering this request should reuse nodes requested for other purposes whenever possible.
@@ -183,12 +187,6 @@ public class NodesSpecification {
*/
public boolean isExclusive() { return exclusive; }
- /** Returns the number of nodes required */
- public int count() { return count; }
-
- /** Returns the number of host groups this specifies. Default is 1 */
- public int groups() { return groups; }
-
public Map<HostResource, ClusterMembership> provision(HostSystem hostSystem,
ClusterSpec.Type clusterType,
ClusterSpec.Id clusterId,
@@ -201,29 +199,36 @@ public class NodesSpecification {
.combinedId(combinedId.map(ClusterSpec.Id::from))
.dockerImageRepo(dockerImageRepo)
.build();
- return hostSystem.allocateHosts(cluster, Capacity.fromCount(count, resources, required, canFail), groups, logger);
+ return hostSystem.allocateHosts(cluster, Capacity.from(min, max, required, canFail), logger);
}
- private static Optional<NodeResources> getResources(ModelElement nodesElement) {
+ private static Pair<NodeResources, NodeResources> nodeResources(ModelElement nodesElement) {
ModelElement resources = nodesElement.child("resources");
if (resources != null) {
- return Optional.of(new NodeResources(resources.requiredDoubleAttribute("vcpu"),
- parseGbAmount(resources.requiredStringAttribute("memory"), "B"),
- parseGbAmount(resources.requiredStringAttribute("disk"), "B"),
- Optional.ofNullable(resources.stringAttribute("bandwidth"))
- .map(b -> parseGbAmount(b, "BPS"))
- .orElse(0.3),
- parseOptionalDiskSpeed(resources.stringAttribute("disk-speed")),
- parseOptionalStorageType(resources.stringAttribute("storage-type"))));
+ return nodeResourcesFromResorcesElement(resources);
}
else if (nodesElement.stringAttribute("flavor") != null) { // legacy fallback
- return Optional.of(NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor")));
+ var flavorResources = NodeResources.fromLegacyName(nodesElement.stringAttribute("flavor"));
+ return new Pair<>(flavorResources, flavorResources);
}
- else { // Get the default
- return Optional.empty();
+ else {
+ return new Pair<>(NodeResources.unspecified, NodeResources.unspecified);
}
}
+ private static Pair<NodeResources, NodeResources> nodeResourcesFromResorcesElement(ModelElement element) {
+ Pair<Double, Double> vcpu = toRange(element.requiredStringAttribute("vcpu"), .0, Double::parseDouble);
+ Pair<Double, Double> memory = toRange(element.requiredStringAttribute("memory"), .0, s -> parseGbAmount(s, "B"));
+ Pair<Double, Double> disk = toRange(element.requiredStringAttribute("disk"), .0, s -> parseGbAmount(s, "B"));
+ Pair<Double, Double> bandwith = toRange(element.stringAttribute("bandwith"), .3, s -> parseGbAmount(s, "BPS"));
+ NodeResources.DiskSpeed diskSpeed = parseOptionalDiskSpeed(element.stringAttribute("disk-speed"));
+ NodeResources.StorageType storageType = parseOptionalStorageType(element.stringAttribute("storage-type"));
+
+ var min = new NodeResources(vcpu.getFirst(), memory.getFirst(), disk.getFirst(), bandwith.getFirst(), diskSpeed, storageType);
+ var max = new NodeResources(vcpu.getSecond(), memory.getSecond(), disk.getSecond(), bandwith.getSecond(), diskSpeed, storageType);
+ return new Pair<>(min, max);
+ }
+
private static double parseGbAmount(String byteAmount, String unit) {
byteAmount = byteAmount.strip();
byteAmount = byteAmount.toUpperCase();
@@ -358,11 +363,28 @@ public class NodesSpecification {
return dockerImageFromElement == null ? dockerImage : Optional.of(dockerImageFromElement);
}
+ /** Parses a value ("value") or value range ("[min-value, max-value]") */
+ private static <T> Pair<T, T> toRange(String s, T defaultValue, Function<String, T> valueParser) {
+ try {
+ if (s == null) return new Pair<>(defaultValue, defaultValue);
+ s = s.trim();
+ if (s.startsWith("[") && s.endsWith("]")) {
+ String[] numbers = s.substring(1, s.length() - 1).split(",");
+ if (numbers.length != 2) throw new IllegalArgumentException();
+ return new Pair<>(valueParser.apply(numbers[0].trim()), valueParser.apply(numbers[1].trim()));
+ } else {
+ return new Pair<>(valueParser.apply(s), valueParser.apply(s));
+ }
+ }
+ catch (IllegalArgumentException e) {
+ throw new IllegalArgumentException("Expected a number or range on the form [min, max], but got '" + s + "'", e);
+ }
+ }
+
@Override
public String toString() {
- return "specification of " + count + (dedicated ? " dedicated " : " ") + "nodes" +
- (resources.map(nodeResources -> " with resources " + nodeResources).orElse("")) +
- (groups > 1 ? " in " + groups + " groups" : "");
+ return "specification of " + (dedicated ? "dedicated " : "") +
+ (min.equals(max) ? min : "min " + min + " max " + max);
}
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java
index 97b78e1b9b1..c9caca1831f 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/VespaDomBuilder.java
@@ -141,8 +141,7 @@ public class VespaDomBuilder extends VespaModelBuilder {
}
private void initializeService(AbstractService t, DeployState deployState,
- HostSystem hostSystem, Element producerSpec)
- {
+ HostSystem hostSystem, Element producerSpec) {
initializeProducer(t, deployState, producerSpec);
if (producerSpec != null) {
if (producerSpec.hasAttribute(JVM_OPTIONS)) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
index 7e2d6680827..31c8724d634 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/Container.java
@@ -140,7 +140,7 @@ public abstract class Container extends AbstractService implements
if (http == null) {
return defaultHttpServer;
} else {
- return http.getHttpServer();
+ return http.getHttpServer().orElse(null);
}
}
@@ -228,10 +228,10 @@ public abstract class Container extends AbstractService implements
// XXX unused - remove:
from.allocatePort("http/1");
portsMeta.on(offset++).tag("http").tag("external");
- } else if (getHttp().getHttpServer() == null) {
+ } else if (getHttp().getHttpServer().isEmpty()) {
// no http server ports
} else {
- for (ConnectorFactory connectorFactory : getHttp().getHttpServer().getConnectorFactories()) {
+ for (ConnectorFactory connectorFactory : getHttp().getHttpServer().get().getConnectorFactories()) {
int port = getPort(connectorFactory);
String name = "http/" + connectorFactory.getName();
from.requirePort(port, name);
@@ -280,7 +280,7 @@ public abstract class Container extends AbstractService implements
final Http http = getHttp();
if (http != null) {
// TODO: allow the user to specify health port manually
- if (http.getHttpServer() == null) {
+ if (http.getHttpServer().isEmpty()) {
return -1;
} else {
return getRelativePort(0);
@@ -303,7 +303,7 @@ public abstract class Container extends AbstractService implements
.slobrokId(serviceSlobrokId())).
filedistributor(filedistributorConfig());
if (clusterName != null) {
- builder.discriminator(clusterName+"."+name);
+ builder.discriminator(clusterName + "." + name);
} else {
builder.discriminator(name);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java b/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java
index ee61b34987a..3d9a1b2e665 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/component/FileStatusHandlerComponent.java
@@ -10,6 +10,7 @@ import com.yahoo.osgi.provider.model.ComponentModel;
* @author Tony Vaagenes
*/
public class FileStatusHandlerComponent extends Handler implements VipStatusConfig.Producer {
+
public static final String CLASS = "com.yahoo.container.handler.VipStatusHandler";
private final String fileName;
@@ -26,4 +27,5 @@ public class FileStatusHandlerComponent extends Handler implements VipStatusConf
builder.accessdisk(true).
statusfile(fileName);
}
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
index f3758def2b1..470b82496a3 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/configserver/ConfigserverCluster.java
@@ -9,6 +9,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.StatisticsConfig;
+import com.yahoo.container.core.VipStatusConfig;
import com.yahoo.container.jdisc.config.HealthMonitorConfig;
import com.yahoo.net.HostName;
import com.yahoo.vespa.defaults.Defaults;
@@ -29,7 +30,8 @@ public class ConfigserverCluster extends AbstractConfigProducer
ZookeeperServerConfig.Producer,
ConfigserverConfig.Producer,
StatisticsConfig.Producer,
- HealthMonitorConfig.Producer {
+ HealthMonitorConfig.Producer,
+ VipStatusConfig.Producer {
private final CloudConfigOptions options;
private ContainerCluster containerCluster;
@@ -116,8 +118,8 @@ public class ConfigserverCluster extends AbstractConfigProducer
}
builder.serverId(HostName.getLocalhost());
- if (!containerCluster.getHttp().getHttpServer().getConnectorFactories().isEmpty()) {
- builder.httpport(containerCluster.getHttp().getHttpServer().getConnectorFactories().get(0).getListenPort());
+ if (!containerCluster.getHttp().getHttpServer().get().getConnectorFactories().isEmpty()) {
+ builder.httpport(containerCluster.getHttp().getHttpServer().get().getConnectorFactories().get(0).getListenPort());
}
if (options.useVespaVersionInRequest().isPresent()) {
builder.useVespaVersionInRequest(options.useVespaVersionInRequest().get());
@@ -178,4 +180,8 @@ public class ConfigserverCluster extends AbstractConfigProducer
builder.snapshot_interval(60.0);
}
+ @Override
+ public void getConfig(VipStatusConfig.Builder builder) {
+ builder.initiallyInRotation(false);
+ }
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java
index 400ddf80cf9..0fcf7b2d06c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/Http.java
@@ -8,46 +8,39 @@ import com.yahoo.vespa.model.container.component.chain.Chain;
import com.yahoo.vespa.model.container.component.chain.ChainedComponent;
import java.util.Collection;
-import java.util.Collections;
import java.util.List;
import java.util.Optional;
+import java.util.concurrent.CopyOnWriteArrayList;
/**
* Represents the http servers and filters of a container cluster.
*
* @author Tony Vaagenes
+ * @author bjorncs
*/
public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> implements ServerConfig.Producer {
- private FilterChains filterChains;
- private JettyHttpServer httpServer;
- private List<Binding> bindings;
- private final Optional<AccessControl> accessControl;
+ private final FilterChains filterChains;
+ private final List<Binding> bindings = new CopyOnWriteArrayList<>();
+ private volatile JettyHttpServer httpServer;
+ private volatile AccessControl accessControl;
- public Http(List<Binding> bindings) {
- this(bindings, null);
+ public Http(FilterChains chains) {
+ super("http");
+ this.filterChains = chains;
}
- public Http(List<Binding> bindings, AccessControl accessControl) {
- super( "http");
- this.bindings = Collections.unmodifiableList(bindings);
- this.accessControl = Optional.ofNullable(accessControl);
- }
-
- public void setFilterChains(FilterChains filterChains) {
- this.filterChains = filterChains;
- }
-
- public void setBindings(List<Binding> bindings) {
- this.bindings = Collections.unmodifiableList(bindings);
+ public void setAccessControl(AccessControl accessControl) {
+ if (this.accessControl != null) throw new IllegalStateException("Access control already assigned");
+ this.accessControl = accessControl;
}
public FilterChains getFilterChains() {
return filterChains;
}
- public JettyHttpServer getHttpServer() {
- return httpServer;
+ public Optional<JettyHttpServer> getHttpServer() {
+ return Optional.ofNullable(httpServer);
}
public void setHttpServer(JettyHttpServer newServer) {
@@ -76,25 +69,21 @@ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> impl
}
public Optional<AccessControl> getAccessControl() {
- return accessControl;
+ return Optional.ofNullable(accessControl);
}
@Override
public void getConfig(ServerConfig.Builder builder) {
for (Binding binding : bindings) {
builder.filter(new ServerConfig.Filter.Builder()
- .id(binding.filterId().stringValue())
- .binding(binding.binding()));
+ .id(binding.filterId().stringValue())
+ .binding(binding.binding()));
}
}
@Override
public void validate() {
- validate(bindings);
- }
-
- void validate(Collection<Binding> bindings) {
- if (bindings.isEmpty()) return;
+ if (((Collection<Binding>) bindings).isEmpty()) return;
if (filterChains == null)
throw new IllegalArgumentException("Null FilterChains are not allowed when there are filter bindings");
@@ -107,5 +96,4 @@ public class Http extends AbstractConfigProducer<AbstractConfigProducer<?>> impl
throw new RuntimeException("Can't find filter " + binding.filterId() + " for binding " + binding.binding());
}
}
-
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
index f61618c789b..fb8e9dffbbb 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/ssl/HostedSslConnectorFactory.java
@@ -7,6 +7,7 @@ import com.yahoo.jdisc.http.ConnectorConfig.Ssl.ClientAuth;
import com.yahoo.vespa.model.container.component.SimpleComponent;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
+import java.time.Duration;
import java.util.List;
/**
@@ -67,10 +68,13 @@ public class HostedSslConnectorFactory extends ConnectorFactory {
@Override
public void getConfig(ConnectorConfig.Builder connectorBuilder) {
super.getConfig(connectorBuilder);
- connectorBuilder.tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder()
- .pathWhitelist(INSECURE_WHITELISTED_PATHS)
- .enable(enforceClientAuth));
- connectorBuilder.proxyProtocol(configureProxyProtocol());
+ connectorBuilder
+ .tlsClientAuthEnforcer(new ConnectorConfig.TlsClientAuthEnforcer.Builder()
+ .pathWhitelist(INSECURE_WHITELISTED_PATHS)
+ .enable(enforceClientAuth))
+ .proxyProtocol(configureProxyProtocol())
+ .idleTimeout(Duration.ofMinutes(3).toSeconds())
+ .maxConnectionLife(Duration.ofMinutes(10).toSeconds());
}
private ConnectorConfig.ProxyProtocol.Builder configureProxyProtocol() {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java
index 61a588fb716..bfde9b9add1 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/http/xml/HttpBuilder.java
@@ -54,11 +54,10 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http>
filterChains = new FilterChainsBuilder().newChainsInstance(ancestor);
}
- Http http = new Http(bindings, accessControl);
- http.setFilterChains(filterChains);
-
- buildHttpServers(deployState, ancestor, http, spec);
-
+ Http http = new Http(filterChains);
+ http.getBindings().addAll(bindings);
+ http.setAccessControl(accessControl);
+ http.setHttpServer(new JettyHttpServerBuilder().build(deployState, ancestor, spec));
return http;
}
@@ -131,10 +130,6 @@ public class HttpBuilder extends VespaDomBuilder.DomConfigProducerBuilder<Http>
return result;
}
- private void buildHttpServers(DeployState deployState, AbstractConfigProducer ancestor, Http http, Element spec) {
- http.setHttpServer(new JettyHttpServerBuilder().build(deployState, ancestor, spec));
- }
-
static int readPort(ModelElement spec, boolean isHosted, DeployLogger logger) {
Integer port = spec.integerAttribute("port");
if (port == null)
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 c1f793e255d..06707786136 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
@@ -23,9 +23,11 @@ import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.SystemName;
import com.yahoo.config.provision.Zone;
@@ -324,13 +326,14 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
cluster.setHttp(buildHttp(deployState, cluster, httpElement));
}
if (isHostedTenantApplication(context)) {
- addHostedImplicitHttpIfNotPresent(deployState, cluster);
+ addHostedImplicitHttpIfNotPresent(cluster);
+ addHostedImplicitAccessControlIfNotPresent(deployState, cluster);
addAdditionalHostedConnector(deployState, cluster);
}
}
private void addAdditionalHostedConnector(DeployState deployState, ApplicationContainerCluster cluster) {
- JettyHttpServer server = cluster.getHttp().getHttpServer();
+ JettyHttpServer server = cluster.getHttp().getHttpServer().get();
String serverName = server.getComponentId().getName();
String proxyProtocol = deployState.getProperties().proxyProtocol();
@@ -356,39 +359,31 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
return deployState.isHosted() && context.getApplicationType() == ApplicationType.DEFAULT && !isTesterApplication;
}
- private static void addHostedImplicitHttpIfNotPresent(DeployState deployState, ApplicationContainerCluster cluster) {
+ private static void addHostedImplicitHttpIfNotPresent(ApplicationContainerCluster cluster) {
if(cluster.getHttp() == null) {
- Http http = deployState.getProperties().athenzDomain()
- .map(tenantDomain -> createHostedImplicitHttpWithAccessControl(deployState, tenantDomain, cluster))
- .orElseGet(() -> createHostedImplicitHttpWithoutAccessControl(cluster));
- cluster.setHttp(http);
+ cluster.setHttp(new Http(new FilterChains(cluster)));
}
- if(cluster.getHttp().getHttpServer() == null) {
+ if(cluster.getHttp().getHttpServer().isEmpty()) {
JettyHttpServer defaultHttpServer = new JettyHttpServer(new ComponentId("DefaultHttpServer"));
cluster.getHttp().setHttpServer(defaultHttpServer);
defaultHttpServer.addConnector(new ConnectorFactory("SearchServer", Defaults.getDefaults().vespaWebServicePort()));
}
}
- private static Http createHostedImplicitHttpWithAccessControl(
- DeployState deployState, AthenzDomain tenantDomain, ApplicationContainerCluster cluster) {
+ private void addHostedImplicitAccessControlIfNotPresent(DeployState deployState, ApplicationContainerCluster cluster) {
+ Http http = cluster.getHttp();
+ if (http.getAccessControl().isPresent()) return; // access control added explicitly
+ AthenzDomain tenantDomain = deployState.getProperties().athenzDomain().orElse(null);
+ if (tenantDomain == null) return; // tenant domain not present, cannot add access control. this should eventually be a failure.
AccessControl accessControl =
new AccessControl.Builder(tenantDomain.value(), deployState.getDeployLogger())
.setHandlers(cluster)
.readEnabled(false)
.writeEnabled(false)
.build();
- Http http = new Http(accessControl.getBindings(), accessControl);
- FilterChains filterChains = new FilterChains(cluster);
- filterChains.add(new Chain<>(FilterChains.emptyChainSpec(ACCESS_CONTROL_CHAIN_ID)));
- http.setFilterChains(filterChains);
- return http;
- }
-
- private static Http createHostedImplicitHttpWithoutAccessControl(ApplicationContainerCluster cluster) {
- Http http = new Http(Collections.emptyList());
- http.setFilterChains(new FilterChains(cluster));
- return http;
+ http.getFilterChains().add(new Chain<>(FilterChains.emptyChainSpec(ACCESS_CONTROL_CHAIN_ID)));
+ http.setAccessControl(accessControl);
+ http.getBindings().addAll(accessControl.getBindings());
}
private Http buildHttp(DeployState deployState, ApplicationContainerCluster cluster, Element httpElement) {
@@ -672,11 +667,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.vespaVersion(deployState.getWantedNodeVespaVersion())
.dockerImageRepo(deployState.getWantedDockerImageRepo())
.build();
- Capacity capacity = Capacity.fromCount(1,
- Optional.empty(),
- false,
- ! deployState.getProperties().isBootstrap());
- HostResource host = hostSystem.allocateHosts(clusterSpec, capacity, 1, log).keySet().iterator().next();
+ Capacity capacity = Capacity.from(new ClusterResources(1, 1, NodeResources.unspecified),
+ false,
+ ! deployState.getProperties().isBootstrap());
+ HostResource host = hostSystem.allocateHosts(clusterSpec, capacity, log).keySet().iterator().next();
return singleHostContainerCluster(cluster, host, context);
}
}
@@ -687,11 +681,10 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.dockerImageRepo(deployState.getWantedDockerImageRepo())
.build();
int nodeCount = deployState.zone().environment().isProduction() ? 2 : 1;
- Capacity capacity = Capacity.fromCount(nodeCount,
- Optional.empty(),
- false,
- !deployState.getProperties().isBootstrap());
- var hosts = hostSystem.allocateHosts(clusterSpec, capacity, 1, log);
+ Capacity capacity = Capacity.from(new ClusterResources(nodeCount, 1, NodeResources.unspecified),
+ false,
+ !deployState.getProperties().isBootstrap());
+ var hosts = hostSystem.allocateHosts(clusterSpec, capacity, log);
return createNodesFromHosts(log, hosts, cluster);
}
return singleHostContainerCluster(cluster, hostSystem.getHost(Container.SINGLENODE_CONTAINER_SERVICESPEC), context);
@@ -721,7 +714,7 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
.build();
Map<HostResource, ClusterMembership> hosts =
cluster.getRoot().hostSystem().allocateHosts(clusterSpec,
- Capacity.fromRequiredNodeType(type), 1, log);
+ Capacity.fromRequiredNodeType(type), log);
return createNodesFromHosts(context.getDeployLogger(), hosts, cluster);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
index 066fef727c5..6dd3e619ec2 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java
@@ -286,7 +286,7 @@ public class ContentCluster extends AbstractConfigProducer implements
.orElse(NodesSpecification.nonDedicated(3, context));
Collection<HostResource> hosts = nodesSpecification.isDedicated() ?
getControllerHosts(nodesSpecification, admin, clusterName, context) :
- drawControllerHosts(nodesSpecification.count(), rootGroup, containers);
+ drawControllerHosts(nodesSpecification.minResources().nodes(), rootGroup, containers);
clusterControllers = createClusterControllers(new ClusterControllerCluster(contentCluster, "standalone"),
hosts,
clusterName,
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java b/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java
index f5a91297e9e..f93bf6fc872 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/search/Tuning.java
@@ -241,7 +241,12 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ
public Integer level = null;
public void getConfig(ProtonConfig.Summary.Cache.Compression.Builder compression) {
- if (type != null) compression.type(ProtonConfig.Summary.Cache.Compression.Type.Enum.valueOf(type.name));
+ if (type != null) compression.type(ProtonConfig.Summary.Cache.Compression.Type.Enum.valueOf(type.name));
+ if (level != null) compression.level(level);
+ }
+
+ public void getConfig(ProtonConfig.Summary.Log.Compact.Compression.Builder compression) {
+ if (type != null) compression.type(ProtonConfig.Summary.Log.Compact.Compression.Type.Enum.valueOf(type.name));
if (level != null) compression.level(level);
}
@@ -281,6 +286,12 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ
}
}
+ public void getConfig(ProtonConfig.Summary.Log.Compact.Builder compact) {
+ if (compression != null) {
+ compression.getConfig(compact.compression);
+ }
+ }
+
public void getConfig(ProtonConfig.Summary.Log.Chunk.Builder chunk) {
if (outputInt) {
if (maxSize!=null) chunk.maxbytes(maxSize.intValue());
@@ -288,7 +299,7 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ
throw new IllegalStateException("Fix this, chunk does not have long types");
}
if (compression != null) {
- compression.getConfig(chunk.compression);
+ compression.getConfig(chunk.compression);
}
}
}
@@ -303,6 +314,7 @@ public class Tuning extends AbstractConfigProducer implements ProtonConfig.Produ
if (minFileSizeFactor!=null) log.minfilesizefactor(minFileSizeFactor);
if (chunk != null) {
chunk.getConfig(log.chunk);
+ chunk.getConfig(log.compact);
}
}
}
diff --git a/config-model/src/main/javacc/SDParser.jj b/config-model/src/main/javacc/SDParser.jj
index cca56c209c8..3560cf2cd84 100644
--- a/config-model/src/main/javacc/SDParser.jj
+++ b/config-model/src/main/javacc/SDParser.jj
@@ -1816,6 +1816,7 @@ Object indexBody(IndexOperation index) :
| <UPPERBOUND> <COLON> num = consumeLong() { index.setUpperBound(num); }
| <DENSEPOSTINGLISTTHRESHOLD> <COLON> threshold = consumeFloat() { index.setDensePostingListThreshold(threshold); }
| <ENABLE_BM25> { index.setEnableBm25(true); }
+ | <DISTANCEMETRIC> <COLON> str = identifierWithDash() { index.setDistanceMetric(str); }
| hnswIndex(index) { }
)
{ return null; }
@@ -1841,7 +1842,6 @@ void hnswIndexBody(HnswIndexParams.Builder params) :
}
{
( <MAXLINKSPERNODE> <COLON> num = integer() { params.setMaxLinksPerNode(num); }
- | <DISTANCEMETRIC> <COLON> str = identifierWithDash() { params.setDistanceMetric(str); }
| <NEIGHBORSTOEXPLOREATINSERT> <COLON> num = integer() { params.setNeighborsToExploreAtInsert(num); } )
}
diff --git a/config-model/src/main/resources/schema/common.rnc b/config-model/src/main/resources/schema/common.rnc
index c47983adc12..878faabfec1 100644
--- a/config-model/src/main/resources/schema/common.rnc
+++ b/config-model/src/main/resources/schema/common.rnc
@@ -17,14 +17,14 @@ anyElement = element * {
JavaId = xsd:string { pattern = "([a-zA-Z_$][a-zA-Z\d_$]*\.)*[a-zA-Z_$][a-zA-Z\d_$]*" }
Nodes = element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute docker-image { xsd:string }? &
Resources?
}
Resources = element resources {
- attribute vcpu { xsd:double { minExclusive = "0.0" } } &
+ attribute vcpu { xsd:double { minExclusive = "0.0" } | xsd:string } &
attribute memory { xsd:string } &
attribute disk { xsd:string } &
attribute disk-speed { xsd:string }? &
@@ -32,7 +32,7 @@ Resources = element resources {
}
OptionalDedicatedNodes = element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute docker-image { xsd:string }? &
diff --git a/config-model/src/main/resources/schema/containercluster.rnc b/config-model/src/main/resources/schema/containercluster.rnc
index 726fa849c00..3c8b60fb84b 100644
--- a/config-model/src/main/resources/schema/containercluster.rnc
+++ b/config-model/src/main/resources/schema/containercluster.rnc
@@ -239,7 +239,7 @@ NodesOfContainerCluster = element nodes {
attribute type { xsd:string }
|
(
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute exclusive { xsd:boolean }? &
diff --git a/config-model/src/main/resources/schema/content.rnc b/config-model/src/main/resources/schema/content.rnc
index ee451185415..b1821680b14 100644
--- a/config-model/src/main/resources/schema/content.rnc
+++ b/config-model/src/main/resources/schema/content.rnc
@@ -221,11 +221,11 @@ ContentNodes = element nodes {
attribute vespamalloc-debug-stacktrace { xsd:string }? &
(
(
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute docker-image { xsd:string }? &
- attribute groups { xsd:positiveInteger }?
+ attribute groups { xsd:positiveInteger | xsd:string }?
)
|
ContentNode +
@@ -266,12 +266,12 @@ Group = element group {
|
(
element nodes {
- attribute count { xsd:positiveInteger } &
+ attribute count { xsd:positiveInteger | xsd:string } &
attribute flavor { xsd:string }? &
attribute required { xsd:boolean }? &
attribute exclusive { xsd:boolean }? &
attribute docker-image { xsd:string }? &
- attribute groups { xsd:positiveInteger }?
+ attribute groups { xsd:positiveInteger | xsd:string }?
}
)
|
diff --git a/config-model/src/test/derived/hnsw_index/test.sd b/config-model/src/test/derived/hnsw_index/test.sd
index 3b954e74fc5..207ed764a87 100644
--- a/config-model/src/test/derived/hnsw_index/test.sd
+++ b/config-model/src/test/derived/hnsw_index/test.sd
@@ -3,9 +3,9 @@ search test {
field t1 type tensor(x[128]) {
indexing: attribute | index
index {
+ distance-metric: angular
hnsw {
max-links-per-node: 32
- distance-metric: angular
neighbors-to-explore-at-insert: 300
}
}
diff --git a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
index 1670ac23ba4..7208d8c5fc1 100644
--- a/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
+++ b/config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java
@@ -1205,6 +1205,62 @@ public class ModelProvisioningTest {
}
@Test
+ public void testRequestingRangesMin() {
+ String services =
+ "<?xml version='1.0' encoding='utf-8' ?>" +
+ "<services>" +
+ " <container version='1.0' id='container'>" +
+ " <nodes count='[4, 6]'>" +
+ " <resources vcpu='[11.5, 13.5]' memory='[10Gb, 100Gb]' disk='[30Gb, 1Tb]'/>" +
+ " </nodes>" +
+ " </container>" +
+ " <content version='1.0' id='foo'>" +
+ " <documents>" +
+ " <document type='type1' mode='index'/>" +
+ " </documents>" +
+ " <nodes count='[6, 20]' groups='[3,4]'>" +
+ " <resources vcpu='8' memory='200Gb' disk='1Pb'/>" +
+ " </nodes>" +
+ " </content>" +
+ "</services>";
+
+ int totalHosts = 10;
+ VespaModelTester tester = new VespaModelTester();
+ tester.addHosts(new NodeResources(11.5, 10, 30, 0.3), 6);
+ tester.addHosts(new NodeResources(85, 200, 1000_000_000, 0.3), 20);
+ VespaModel model = tester.createModel(services, true);
+ assertEquals(totalHosts, model.getRoot().hostSystem().getHosts().size());
+ }
+
+ @Test
+ public void testRequestingRangesMax() {
+ String services =
+ "<?xml version='1.0' encoding='utf-8' ?>" +
+ "<services>" +
+ " <container version='1.0' id='container'>" +
+ " <nodes count='[4, 6]'>" +
+ " <resources vcpu='[11.5, 13.5]' memory='[10Gb, 100Gb]' disk='[30Gb, 1Tb]'/>" +
+ " </nodes>" +
+ " </container>" +
+ " <content version='1.0' id='foo'>" +
+ " <documents>" +
+ " <document type='type1' mode='index'/>" +
+ " </documents>" +
+ " <nodes count='[6, 20]' groups='[3,4]'>" +
+ " <resources vcpu='8' memory='200Gb' disk='1Pb'/>" +
+ " </nodes>" +
+ " </content>" +
+ "</services>";
+
+ int totalHosts = 26;
+ VespaModelTester tester = new VespaModelTester();
+ tester.addHosts(new NodeResources(13.5, 100, 1000, 0.3), 6);
+ tester.addHosts(new NodeResources(85, 200, 1000_000_000, 0.3), 20);
+ VespaModel model = tester.createModel(services, true, true);
+ assertEquals(totalHosts, model.getRoot().hostSystem().getHosts().size());
+ }
+
+ @Test
public void testContainerOnly() {
String services =
"<?xml version='1.0' encoding='utf-8' ?>\n" +
@@ -1308,10 +1364,11 @@ public class ModelProvisioningTest {
" </http>" +
"</container>";
VespaModelTester tester = new VespaModelTester();
- tester.addHosts(1);
+ tester.addHosts(2);
VespaModel model = tester.createModel(services, true);
- assertEquals(1, model.getHosts().size());
+ assertEquals(2, model.getHosts().size());
assertEquals(1, model.getContainerClusters().size());
+ assertEquals(2, model.getContainerClusters().get("foo").getContainers().size());
}
@Test
@@ -1374,7 +1431,7 @@ public class ModelProvisioningTest {
}
@Test
- public void testNoNodeTagMeans1Node() {
+ public void testNoNodeTagMeansTwoNodes() {
String services =
"<?xml version='1.0' encoding='utf-8' ?>\n" +
"<services>" +
@@ -1389,31 +1446,6 @@ public class ModelProvisioningTest {
" </content>" +
"</services>";
VespaModelTester tester = new VespaModelTester();
- tester.addHosts(1);
- VespaModel model = tester.createModel(services, true);
- assertEquals(1, model.getRoot().hostSystem().getHosts().size());
- assertEquals(1, model.getAdmin().getSlobroks().size());
- assertEquals(1, model.getContainerClusters().get("foo").getContainers().size());
- assertEquals(1, model.getContentClusters().get("bar").getRootGroup().countNodes());
- }
-
- @Test
- public void testNoNodeTagMeansTwoNodesInContainerClusterWithFeatureFlag() {
- String services =
- "<?xml version='1.0' encoding='utf-8' ?>\n" +
- "<services>" +
- " <container id='foo' version='1.0'>" +
- " <search/>" +
- " <document-api/>" +
- " </container>" +
- " <content version='1.0' id='bar'>" +
- " <documents>" +
- " <document type='type1' mode='index'/>" +
- " </documents>" +
- " </content>" +
- "</services>";
- VespaModelTester tester = new VespaModelTester();
- tester.setUseDedicatedNodesWhenUnspecified(true);
tester.addHosts(3);
VespaModel model = tester.createModel(services, true);
assertEquals(3, model.getRoot().hostSystem().getHosts().size());
@@ -1423,7 +1455,7 @@ public class ModelProvisioningTest {
}
@Test
- public void testNoNodeTagMeans1NodeNoContent() {
+ public void testNoNodeTagMeansTwoNodesNoContent() {
String services =
"<?xml version='1.0' encoding='utf-8' ?>\n" +
"<services>" +
@@ -1433,11 +1465,11 @@ public class ModelProvisioningTest {
" </container>" +
"</services>";
VespaModelTester tester = new VespaModelTester();
- tester.addHosts(1);
+ tester.addHosts(2);
VespaModel model = tester.createModel(services, true);
- assertEquals(1, model.getRoot().hostSystem().getHosts().size());
- assertEquals(1, model.getAdmin().getSlobroks().size());
- assertEquals(1, model.getContainerClusters().get("foo").getContainers().size());
+ assertEquals(2, model.getRoot().hostSystem().getHosts().size());
+ assertEquals(2, model.getAdmin().getSlobroks().size());
+ assertEquals(2, model.getContainerClusters().get("foo").getContainers().size());
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/derived/NearestNeighborTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/derived/NearestNeighborTestCase.java
index ead4e586d9f..9f57b22fd58 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/derived/NearestNeighborTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/derived/NearestNeighborTestCase.java
@@ -31,6 +31,9 @@ public class NearestNeighborTestCase extends AbstractExportingTestCase {
} catch (QueryException e) {
// success
assertEquals("Invalid request parameter", e.getMessage());
+ } catch (RuntimeException e) {
+ e.printStackTrace();
+ throw e;
}
}
diff --git a/config-model/src/test/java/com/yahoo/searchdefinition/document/HnswIndexParamsTestCase.java b/config-model/src/test/java/com/yahoo/searchdefinition/document/HnswIndexParamsTestCase.java
index d687590faf2..e3dcc925e5e 100644
--- a/config-model/src/test/java/com/yahoo/searchdefinition/document/HnswIndexParamsTestCase.java
+++ b/config-model/src/test/java/com/yahoo/searchdefinition/document/HnswIndexParamsTestCase.java
@@ -2,13 +2,13 @@
package com.yahoo.searchdefinition.document;
+import java.util.Optional;
import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
-import static com.yahoo.searchdefinition.document.HnswIndexParams.DistanceMetric;
public class HnswIndexParamsTestCase {
@@ -18,35 +18,27 @@ public class HnswIndexParamsTestCase {
var builder = new HnswIndexParams.Builder();
builder.setMaxLinksPerNode(7);
var one = builder.build();
- builder.setDistanceMetric("angular");
- var two = builder.build();
builder.setNeighborsToExploreAtInsert(42);
var three = builder.build();
builder.setMaxLinksPerNode(17);
- builder.setDistanceMetric("geodegrees");
builder.setNeighborsToExploreAtInsert(500);
var four = builder.build();
assertThat(empty.maxLinksPerNode(), is(16));
- assertThat(empty.distanceMetric(), is(DistanceMetric.EUCLIDEAN));
assertThat(empty.neighborsToExploreAtInsert(), is(200));
assertThat(one.maxLinksPerNode(), is(7));
- assertThat(two.distanceMetric(), is(DistanceMetric.ANGULAR));
assertThat(three.neighborsToExploreAtInsert(), is(42));
assertThat(four.maxLinksPerNode(), is(17));
- assertThat(four.distanceMetric(), is(DistanceMetric.GEODEGREES));
assertThat(four.neighborsToExploreAtInsert(), is(500));
- var five = four.overrideFrom(empty);
+ var five = four.overrideFrom(Optional.of(empty));
assertThat(five.maxLinksPerNode(), is(17));
- assertThat(five.distanceMetric(), is(DistanceMetric.GEODEGREES));
assertThat(five.neighborsToExploreAtInsert(), is(500));
- var six = four.overrideFrom(two);
+ var six = four.overrideFrom(Optional.of(one));
assertThat(six.maxLinksPerNode(), is(7));
- assertThat(six.distanceMetric(), is(DistanceMetric.ANGULAR));
assertThat(six.neighborsToExploreAtInsert(), is(500));
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java b/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java
index 4b7f727ff63..cf1ae637cf9 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/VespaModelFactoryTest.java
@@ -109,7 +109,13 @@ public class VespaModelFactoryTest {
}
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
+ return prepare(cluster, capacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
return List.of(new HostSpec(hostName,
List.of(),
ClusterMembership.from(ClusterSpec.request(ClusterSpec.Type.container, new ClusterSpec.Id(routingClusterName)).vespaVersion("6.42").build(),
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomSearchTuningBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomSearchTuningBuilderTest.java
index 060fff5bf66..73dd1ca3f3b 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomSearchTuningBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/builder/xml/dom/DomSearchTuningBuilderTest.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.model.builder.xml.dom;
import com.yahoo.collections.CollectionUtil;
import com.yahoo.vespa.config.search.core.ProtonConfig;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
-import com.yahoo.vespa.model.content.DispatchTuning;
import com.yahoo.vespa.model.search.Tuning;
import org.junit.Test;
import org.w3c.dom.Element;
@@ -228,6 +227,8 @@ public class DomSearchTuningBuilderTest extends DomBuilderTest {
assertEquals(cfg.summary().log().chunk().maxbytes(), 256);
assertEquals(cfg.summary().log().chunk().compression().type(), ProtonConfig.Summary.Log.Chunk.Compression.Type.LZ4);
assertEquals(cfg.summary().log().chunk().compression().level(), 5);
+ assertEquals(cfg.summary().log().compact().compression().type(), ProtonConfig.Summary.Log.Compact.Compression.Type.LZ4);
+ assertEquals(cfg.summary().log().compact().compression().level(), 5);
}
@Test
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
index b1e63628852..28e23ce3222 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/AccessControlTest.java
@@ -3,8 +3,13 @@ package com.yahoo.vespa.model.container.xml;
import com.google.common.collect.ImmutableSet;
import com.yahoo.collections.CollectionUtil;
+import com.yahoo.component.ComponentId;
import com.yahoo.config.model.builder.xml.test.DomBuilderTest;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.container.jdisc.state.StateHandler;
+import com.yahoo.vespa.model.container.ApplicationContainer;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.http.AccessControl;
import com.yahoo.vespa.model.container.http.Http;
@@ -16,14 +21,19 @@ import org.w3c.dom.Element;
import java.util.Collection;
import java.util.HashSet;
+import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
import static com.yahoo.config.model.test.TestUtil.joinLines;
+import static com.yahoo.vespa.defaults.Defaults.getDefaults;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.Matchers.equalTo;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
/**
@@ -229,6 +239,58 @@ public class AccessControlTest extends ContainerModelBuilderTestBase {
}
+
+ @Test
+ public void access_control_is_implicitly_added_for_hosted_apps() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ nodesXml,
+ "</container>" );
+ AthenzDomain tenantDomain = AthenzDomain.from("my-tenant-domain");
+ DeployState state = new DeployState.Builder().properties(
+ new TestProperties()
+ .setAthenzDomain(tenantDomain)
+ .setHostedVespa(true))
+ .build();
+ createModel(root, state, null, clusterElem);
+ Optional<AccessControl> maybeAccessControl =
+ ((ApplicationContainer) root.getProducer("container/container.0")).getHttp().getAccessControl();
+ assertThat(maybeAccessControl.isPresent(), is(true));
+ AccessControl accessControl = maybeAccessControl.get();
+ assertThat(accessControl.writeEnabled, is(false));
+ assertThat(accessControl.readEnabled, is(false));
+ assertThat(accessControl.domain, equalTo(tenantDomain.value()));
+ }
+
+ @Test
+ public void access_control_is_implicitly_added_for_hosted_apps_with_existing_http_element() {
+ Element clusterElem = DomBuilderTest.parse(
+ "<container version='1.0'>",
+ " <http>",
+ " <server port='" + getDefaults().vespaWebServicePort() + "' id='main' />",
+ " <filtering>",
+ " <filter id='outer' />",
+ " <request-chain id='myChain'>",
+ " <filter id='inner' />",
+ " </request-chain>",
+ " </filtering>",
+ " </http>",
+ nodesXml,
+ "</container>" );
+ AthenzDomain tenantDomain = AthenzDomain.from("my-tenant-domain");
+ DeployState state = new DeployState.Builder().properties(
+ new TestProperties()
+ .setAthenzDomain(tenantDomain)
+ .setHostedVespa(true))
+ .build();
+ createModel(root, state, null, clusterElem);
+ Http http = ((ApplicationContainer) root.getProducer("container/container.0")).getHttp();
+ assertThat(http.getAccessControl().isPresent(), is(true));
+ assertThat(http.getFilterChains().hasChain(AccessControl.ACCESS_CONTROL_CHAIN_ID), is(true));
+ assertThat(http.getFilterChains().hasChain(ComponentId.fromString("myChain")), is(true));
+ }
+
+
private String httpWithExcludedBinding(String excludedBinding) {
return joinLines(
" <http>",
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
index 73db6e35428..dcd1c46e52f 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilderTest.java
@@ -14,7 +14,6 @@ import com.yahoo.config.model.provision.InMemoryProvisioner;
import com.yahoo.config.model.provision.SingleNodeProvisioner;
import com.yahoo.config.model.test.MockApplicationPackage;
import com.yahoo.config.model.test.MockRoot;
-import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.RegionName;
@@ -44,7 +43,6 @@ import com.yahoo.vespa.model.container.ApplicationContainer;
import com.yahoo.vespa.model.container.ContainerCluster;
import com.yahoo.vespa.model.container.SecretStore;
import com.yahoo.vespa.model.container.component.Component;
-import com.yahoo.vespa.model.container.http.AccessControl;
import com.yahoo.vespa.model.container.http.ConnectorFactory;
import com.yahoo.vespa.model.content.utils.ContentClusterUtils;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithFilePkg;
@@ -626,7 +624,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
.setMultitenant(true)
.setHostedVespa(true))
.build());
- assertEquals(1, model.hostSystem().getHosts().size());
+ assertEquals(2, model.hostSystem().getHosts().size());
}
@Test(expected = IllegalArgumentException.class)
@@ -811,7 +809,7 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
ApplicationContainer container = (ApplicationContainer)root.getProducer("container/container.0");
// Verify that there are two connectors
- List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().getConnectorFactories();
+ List<ConnectorFactory> connectorFactories = container.getHttp().getHttpServer().get().getConnectorFactories();
assertEquals(2, connectorFactories.size());
List<Integer> ports = connectorFactories.stream()
.map(ConnectorFactory::getListenPort)
@@ -835,28 +833,6 @@ public class ContainerModelBuilderTest extends ContainerModelBuilderTestBase {
assertThat(connectorConfig.ssl().caCertificate(), isEmptyString());
}
- @Test
- public void access_control_is_implicitly_added_for_hosted_apps() {
- Element clusterElem = DomBuilderTest.parse(
- "<container version='1.0'>",
- nodesXml,
- "</container>" );
- AthenzDomain tenantDomain = AthenzDomain.from("my-tenant-domain");
- DeployState state = new DeployState.Builder().properties(
- new TestProperties()
- .setAthenzDomain(tenantDomain)
- .setHostedVespa(true))
- .build();
- createModel(root, state, null, clusterElem);
- Optional<AccessControl> maybeAccessControl =
- ((ApplicationContainer) root.getProducer("container/container.0")).getHttp().getAccessControl();
- assertThat(maybeAccessControl.isPresent(), is(true));
- AccessControl accessControl = maybeAccessControl.get();
- assertThat(accessControl.writeEnabled, is(false));
- assertThat(accessControl.readEnabled, is(false));
- assertThat(accessControl.domain, equalTo(tenantDomain.value()));
- }
-
private Element generateContainerElementWithRenderer(String rendererId) {
return DomBuilderTest.parse(
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
index fd837c6dea3..cdfd9fab194 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java
@@ -48,7 +48,6 @@ public class VespaModelTester {
private Map<NodeResources, Collection<Host>> hostsByResources = new HashMap<>();
private ApplicationId applicationId = ApplicationId.defaultId();
private boolean useDedicatedNodeForLogserver = false;
- private boolean useDedicatedNodesWhenUnspecified = false;
public VespaModelTester() {
this(new NullConfigModelRegistry());
@@ -98,10 +97,6 @@ public class VespaModelTester {
this.useDedicatedNodeForLogserver = useDedicatedNodeForLogserver;
}
- public void setUseDedicatedNodesWhenUnspecified(boolean useDedicatedNodesWhenUnspecified) {
- this.useDedicatedNodesWhenUnspecified = useDedicatedNodesWhenUnspecified;
- }
-
/** Creates a model which uses 0 as start index and fails on out of capacity */
public VespaModel createModel(String services, String ... retiredHostNames) {
return createModel(Zone.defaultZone(), services, true, retiredHostNames);
@@ -109,41 +104,48 @@ public class VespaModelTester {
/** Creates a model which uses 0 as start index */
public VespaModel createModel(String services, boolean failOnOutOfCapacity, String ... retiredHostNames) {
- return createModel(Zone.defaultZone(), services, failOnOutOfCapacity, 0, retiredHostNames);
+ return createModel(Zone.defaultZone(), services, failOnOutOfCapacity, false, 0, retiredHostNames);
+ }
+
+ /** Creates a model which uses 0 as start index */
+ public VespaModel createModel(String services, boolean failOnOutOfCapacity, boolean useMaxResources, String ... retiredHostNames) {
+ return createModel(Zone.defaultZone(), services, failOnOutOfCapacity, useMaxResources, 0, retiredHostNames);
}
/** Creates a model which uses 0 as start index */
public VespaModel createModel(String services, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) {
- return createModel(Zone.defaultZone(), services, failOnOutOfCapacity, startIndexForClusters, retiredHostNames);
+ return createModel(Zone.defaultZone(), services, failOnOutOfCapacity, false, startIndexForClusters, retiredHostNames);
}
/** Creates a model which uses 0 as start index */
public VespaModel createModel(Zone zone, String services, boolean failOnOutOfCapacity, String ... retiredHostNames) {
- return createModel(zone, services, failOnOutOfCapacity, 0, retiredHostNames);
+ return createModel(zone, services, failOnOutOfCapacity, false, 0, retiredHostNames);
}
/**
* Creates a model using the hosts already added to this
*
* @param services the services xml string
+ * @param useMaxResources false to use the minmal resources (when given a range), true to use max
* @param failOnOutOfCapacity whether we should get an exception when not enough hosts of the requested flavor
* is available or if we should just silently receive a smaller allocation
* @return the resulting model
*/
- public VespaModel createModel(Zone zone, String services, boolean failOnOutOfCapacity, int startIndexForClusters, String ... retiredHostNames) {
+ public VespaModel createModel(Zone zone, String services, boolean failOnOutOfCapacity, boolean useMaxResources,
+ int startIndexForClusters, String ... retiredHostNames) {
VespaModelCreatorWithMockPkg modelCreatorWithMockPkg = new VespaModelCreatorWithMockPkg(null, services, ApplicationPackageUtils.generateSearchDefinition("type1"));
ApplicationPackage appPkg = modelCreatorWithMockPkg.appPkg;
- HostProvisioner provisioner = hosted ?
- new InMemoryProvisioner(hostsByResources, failOnOutOfCapacity, startIndexForClusters, retiredHostNames) :
+ HostProvisioner provisioner = hosted ?
+ new InMemoryProvisioner(hostsByResources, failOnOutOfCapacity, useMaxResources,
+ startIndexForClusters, retiredHostNames) :
new SingleNodeProvisioner();
TestProperties properties = new TestProperties()
.setMultitenant(true)
.setHostedVespa(hosted)
.setApplicationId(applicationId)
- .setUseDedicatedNodeForLogserver(useDedicatedNodeForLogserver)
- .setUseDedicatedNodesWhenUnspecified(useDedicatedNodesWhenUnspecified);
+ .setUseDedicatedNodeForLogserver(useDedicatedNodeForLogserver);
DeployState deployState = new DeployState.Builder()
.applicationPackage(appPkg)
diff --git a/config-model/src/test/schema-test-files/services-hosted.xml b/config-model/src/test/schema-test-files/services-hosted.xml
index 07839239c81..71a07926240 100644
--- a/config-model/src/test/schema-test-files/services-hosted.xml
+++ b/config-model/src/test/schema-test-files/services-hosted.xml
@@ -7,7 +7,7 @@
</admin>
<container id="container1" version="1.0">
- <nodes count="5" required="true">
+ <nodes count="[5,7]" required="true">
<resources vcpu="1.2" memory="10Gb" disk="0.3 TB"/>
</nodes>
</container>
@@ -27,4 +27,11 @@
</nodes>
</content>
+ <content id="ml" version="1.0">
+ <redundancy>2</redundancy>
+ <nodes count="[10,20]" flavor="large" groups="[1,3]">
+ <resources vcpu="[3.0, 4]" memory="[32000.0Mb, 33Gb]" disk="[300 Gb, 1Tb]"/>
+ </nodes>
+ </content>
+
</services>
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
index 59d6ec8feb8..48b4e9d91bc 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Capacity.java
@@ -11,26 +11,37 @@ import java.util.Optional;
*/
public final class Capacity {
- private final int nodeCount;
+ /** Resources should stay between these values, inclusive */
+ private final ClusterResources min, max;
private final boolean required;
private final boolean canFail;
- private final Optional<NodeResources> nodeResources;
-
private final NodeType type;
- private Capacity(int nodeCount, Optional<NodeResources> nodeResources, boolean required, boolean canFail, NodeType type) {
- this.nodeCount = nodeCount;
+ private Capacity(ClusterResources min, ClusterResources max, boolean required, boolean canFail, NodeType type) {
+ if (max.smallerThan(min))
+ throw new IllegalArgumentException("The max capacity must be larger than the min capacity, but got min " +
+ min + " and max " + max);
+ this.min = min;
+ this.max = max;
this.required = required;
this.canFail = canFail;
- this.nodeResources = nodeResources;
this.type = type;
}
/** Returns the number of nodes requested */
- public int nodeCount() { return nodeCount; }
+ @Deprecated // TODO: Remove after April 2020
+ public int nodeCount() { return min.nodes(); }
+
+ /** Returns the number of nodes requested (across all groups), or 0 if not specified */
+ @Deprecated // TODO: Remove after April 2020
+ public int nodes() { return min.nodes(); }
+
+ /** Returns the number of groups requested, or 0 if not specified */
+ @Deprecated // TODO: Remove after April 2020
+ public int groups() { return min.groups(); }
/**
* The node flavor requested, or empty if no legacy flavor name has been used.
@@ -38,14 +49,21 @@ public final class Capacity {
*
* @deprecated use nodeResources instead
*/
- @Deprecated
+ @Deprecated // TODO: Remove after March 2020
public Optional<String> flavor() {
if (nodeResources().isEmpty()) return Optional.empty();
- return nodeResources.map(n -> n.toString());
+ return Optional.of(min.nodeResources().toString());
}
/** Returns the resources requested for each node, or empty to leave this decision to provisioning */
- public Optional<NodeResources> nodeResources() { return nodeResources; }
+ @Deprecated // TODO: Remove after March 2020
+ public Optional<NodeResources> nodeResources() {
+ if (min.nodeResources() == NodeResources.unspecified) return Optional.empty();
+ return Optional.of(min.nodeResources());
+ }
+
+ public ClusterResources minResources() { return min; }
+ public ClusterResources maxResources() { return max; }
/** Returns whether the requested number of nodes must be met exactly for a request for this to succeed */
public boolean isRequired() { return required; }
@@ -64,32 +82,53 @@ public final class Capacity {
*/
public NodeType type() { return type; }
+ public Capacity withGroups(int groups) {
+ return new Capacity(min.withGroups(groups), max.withGroups(groups), required, canFail, type);
+ }
+
@Override
public String toString() {
- return nodeCount + " nodes " + (nodeResources.isPresent() ? nodeResources.get() : "with default resources" );
+ return (required ? "required " : "") +
+ (min.equals(max) ? min : "between " + min + " and " + max);
}
- /** Creates this from a desired node count: The request may be satisfied with a smaller number of nodes. */
- public static Capacity fromNodeCount(int capacity) {
- return fromCount(capacity, Optional.empty(), false, true);
+ /** Create a non-required, failable capacity request */
+ public static Capacity from(ClusterResources resources) {
+ return from(resources, false, true);
+ }
+
+ public static Capacity from(ClusterResources resources, boolean required, boolean canFail) {
+ return from(resources, required, canFail, NodeType.tenant);
+ }
+
+ public static Capacity from(ClusterResources min, ClusterResources max, boolean required, boolean canFail) {
+ return new Capacity(min, max, required, canFail, NodeType.tenant);
}
/** Create a non-required, failable capacity request */
- public static Capacity fromCount(int nodeCount, NodeResources resources) {
- return fromCount(nodeCount, resources, false, true);
+ @Deprecated // TODO: Remove after April 2020
+ public static Capacity fromCount(int nodes, NodeResources resources) {
+ return fromCount(nodes, resources, false, true);
}
- public static Capacity fromCount(int nodeCount, NodeResources resources, boolean required, boolean canFail) {
- return new Capacity(nodeCount, Optional.of(resources), required, canFail, NodeType.tenant);
+ @Deprecated // TODO: Remove after April 2020
+ public static Capacity fromCount(int nodes, NodeResources resources, boolean required, boolean canFail) {
+ return fromCount(nodes, Optional.of(resources), required, canFail);
}
- public static Capacity fromCount(int nodeCount, Optional<NodeResources> resources, boolean required, boolean canFail) {
- return new Capacity(nodeCount, resources, required, canFail, NodeType.tenant);
+ @Deprecated // TODO: Remove after April 2020
+ public static Capacity fromCount(int nodes, Optional<NodeResources> resources, boolean required, boolean canFail) {
+ return from(new ClusterResources(nodes, 0, resources.orElse(NodeResources.unspecified)),
+ required, canFail, NodeType.tenant);
}
/** Creates this from a node type */
public static Capacity fromRequiredNodeType(NodeType type) {
- return new Capacity(0, Optional.empty(), true, false, type);
+ return from(new ClusterResources(0, 0, NodeResources.unspecified), true, false, type);
+ }
+
+ private static Capacity from(ClusterResources resources, boolean required, boolean canFail, NodeType type) {
+ return new Capacity(resources, resources, required, canFail, type);
}
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java
index b777a13af7c..178bbac9e64 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterMembership.java
@@ -110,8 +110,6 @@ public class ClusterMembership {
@Override
public String toString() { return stringValue(); }
- // TODO: Remove when when 7.195 is oldest model version in use
- @Deprecated
public static ClusterMembership from(String stringValue, Version vespaVersion) {
return new ClusterMembership(stringValue, vespaVersion, Optional.empty());
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterResources.java
new file mode 100644
index 00000000000..a4ed22c5266
--- /dev/null
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterResources.java
@@ -0,0 +1,78 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import java.util.Objects;
+
+/**
+ * The resources of a cluster
+ *
+ * @author bratseth
+ */
+public class ClusterResources {
+
+ /** The node count in the cluster */
+ private final int nodes;
+
+ /** The number of node groups in the cluster */
+ private final int groups;
+
+ /** The resources of each node in the cluster */
+ private final NodeResources nodeResources;
+
+ public ClusterResources(int nodes, int groups, NodeResources nodeResources) {
+ if (nodes > 0 && groups > 0 && nodes % groups != 0)
+ throw new IllegalArgumentException("The number of nodes (" + nodes +
+ ") must be divisible by the number of groups (" + groups + ")");
+ this.nodes = nodes;
+ this.groups = groups;
+ this.nodeResources = Objects.requireNonNull(nodeResources);
+ }
+
+ /** Returns the total number of allocated nodes (over all groups) */
+ public int nodes() { return nodes; }
+ public int groups() { return groups; }
+ public NodeResources nodeResources() { return nodeResources; }
+
+ public ClusterResources with(NodeResources resources) { return new ClusterResources(nodes, groups, resources); }
+ public ClusterResources withGroups(int groups) { return new ClusterResources(nodes, groups, nodeResources); }
+
+ /** Returns true if this is smaller than the given resources in any dimension */
+ public boolean smallerThan(ClusterResources other) {
+ if (this.nodes < other.nodes) return true;
+ if (this.groups < other.groups) return true;
+ if ( ! this.nodeResources.justNumbers().satisfies(other.nodeResources.justNumbers())) return true;
+ return false;
+ }
+
+ /** Returns true if this is within the given limits (inclusive) */
+ public boolean isWithin(ClusterResources min, ClusterResources max) {
+ if (this.smallerThan(min)) return false;
+ if (max.smallerThan(this)) return false;
+ return true;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (o == this) return true;
+ if ( ! (o instanceof ClusterResources)) return false;
+
+ ClusterResources other = (ClusterResources)o;
+ if (other.nodes != this.nodes) return false;
+ if (other.groups != this.groups) return false;
+ if ( ! other.nodeResources.equals(this.nodeResources)) return false;
+ return true;
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(nodes, groups, nodeResources);
+ }
+
+ @Override
+ public String toString() {
+ return nodes + " nodes" +
+ (groups > 1 ? " (in " + groups + " groups)" : "") +
+ " with " + nodeResources;
+ }
+
+}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
index 66a2ff411fe..147066b82e7 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/ClusterSpec.java
@@ -77,27 +77,13 @@ public final class ClusterSpec {
return new ClusterSpec(type, id, groupId, vespaVersion, exclusive, combinedId, dockerImageRepo);
}
- // TODO: Remove when when 7.195 is oldest model version in use
- // TODO: Add @Deprecated when internal repo has been updated to not use this method
- // @Deprecated
- public static ClusterSpec request(Type type, Id id, Version vespaVersion, boolean exclusive,
- Optional<Id> combinedId) {
- return request(type, id, vespaVersion, exclusive, combinedId, Optional.empty());
- }
-
+ // TODO: Remove when when 7.200 is oldest model version in use
public static ClusterSpec request(Type type, Id id, Version vespaVersion, boolean exclusive,
Optional<Id> combinedId, Optional<String> dockerImageRepo) {
return new ClusterSpec(type, id, Optional.empty(), vespaVersion, exclusive, combinedId, dockerImageRepo);
}
- // TODO: Remove when when 7.195 is oldest model version in use
- // TODO: Add @Deprecated when internal repo has been updated to not use this method
- // @Deprecated
- public static ClusterSpec from(Type type, Id id, Group groupId, Version vespaVersion, boolean exclusive,
- Optional<Id> combinedId) {
- return from(type, id, groupId, vespaVersion, exclusive, combinedId, Optional.empty());
- }
-
+ // TODO: Remove when when 7.200 is oldest model version in use
public static ClusterSpec from(Type type, Id id, Group groupId, Version vespaVersion, boolean exclusive,
Optional<Id> combinedId, Optional<String> dockerImageRepo) {
return new ClusterSpec(type, id, Optional.of(groupId), vespaVersion, exclusive, combinedId, dockerImageRepo);
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
index 6399352a6ec..5fc05a87a7d 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/NodeResources.java
@@ -10,6 +10,8 @@ import java.util.Objects;
*/
public class NodeResources {
+ public static final NodeResources unspecified = new NodeResources(0, 0, 0, 0);
+
public enum DiskSpeed {
fast, // Has/requires SSD disk or similar speed
@@ -112,26 +114,32 @@ public class NodeResources {
public StorageType storageType() { return storageType; }
public NodeResources withVcpu(double vcpu) {
+ if (vcpu == this.vcpu) return this;
return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
public NodeResources withMemoryGb(double memoryGb) {
+ if (memoryGb == this.memoryGb) return this;
return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
public NodeResources withDiskGb(double diskGb) {
+ if (diskGb == this.diskGb) return this;
return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
public NodeResources withBandwidthGbps(double bandwidthGbps) {
+ if (bandwidthGbps == this.bandwidthGbps) return this;
return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
- public NodeResources with(DiskSpeed speed) {
- return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, speed, storageType);
+ public NodeResources with(DiskSpeed diskSpeed) {
+ if (diskSpeed == this.diskSpeed) return this;
+ return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
public NodeResources with(StorageType storageType) {
+ if (storageType == this.storageType) return this;
return new NodeResources(vcpu, memoryGb, diskGb, bandwidthGbps, diskSpeed, storageType);
}
diff --git a/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java
index 6be1d49ebd3..e308d631442 100644
--- a/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java
+++ b/config-provisioning/src/main/java/com/yahoo/config/provision/Provisioner.java
@@ -19,10 +19,12 @@ public interface Provisioner {
* @param applicationId the application requesting hosts
* @param cluster the specification of the cluster to allocate nodes for
* @param capacity the capacity requested
- * @param groups the number of node groups to divide the requested capacity into
* @param logger a logger which receives messages which are returned to the requestor
* @return the specification of the hosts allocated
*/
+ List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, ProvisionLogger logger);
+
+ @Deprecated // TODO: Remove after April 2020
List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger);
/**
@@ -40,7 +42,6 @@ public interface Provisioner {
* @param transaction Transaction with operations to commit together with any operations done within the provisioner.
* @param application the application to remove
*/
- @SuppressWarnings("deprecation")
void remove(NestedTransaction transaction, ApplicationId application);
/**
diff --git a/config-provisioning/src/test/java/com/yahoo/config/provision/CapacityTest.java b/config-provisioning/src/test/java/com/yahoo/config/provision/CapacityTest.java
new file mode 100644
index 00000000000..326ed7317f6
--- /dev/null
+++ b/config-provisioning/src/test/java/com/yahoo/config/provision/CapacityTest.java
@@ -0,0 +1,48 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.config.provision;
+
+import org.junit.Test;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+
+/**
+ * @author bratseth
+ */
+public class CapacityTest {
+
+ @Test
+ public void testCapacityValidation() {
+ // Equal min and max is allowed
+ Capacity.from(new ClusterResources(4, 2, new NodeResources(1,2,3,4)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)),
+ false, true);
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(1,2,3,4)),
+ new ClusterResources(2, 2, new NodeResources(1,2,3,4)));
+ assertValidationFailure(new ClusterResources(4, 4, new NodeResources(1,2,3,4)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)));
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(2,2,3,4)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)));
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(1,3,3,4)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)));
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(1,2,4,4)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)));
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(1,2,3,5)),
+ new ClusterResources(4, 2, new NodeResources(1,2,3,4)));
+ // It's enough than one dimension is smaller also when the others are larger
+ assertValidationFailure(new ClusterResources(4, 2, new NodeResources(1,2,3,4)),
+ new ClusterResources(8, 4, new NodeResources(2,1,6,8)));
+ }
+
+ private void assertValidationFailure(ClusterResources min, ClusterResources max) {
+ try {
+ Capacity.from(min, max, false, true);
+ fail("Expected exception with min " + min + " and max " + max);
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("The max capacity must be larger than the min capacity, but got min " + min + " and max " + max,
+ e.getMessage());
+ }
+ }
+
+}
diff --git a/configdefinitions/src/vespa/lb-services.def b/configdefinitions/src/vespa/lb-services.def
index 33c568061fe..cc496a99c20 100644
--- a/configdefinitions/src/vespa/lb-services.def
+++ b/configdefinitions/src/vespa/lb-services.def
@@ -4,6 +4,9 @@
namespace=cloud.config
+# Enable proxy-protocol for nginx upstreams
+nginxUpstreamProxyProtocol bool default=false
+
# Active rotation given as flag 'active' for a prod region in deployment.xml
# Default true for now (since code in config-model to set it is not ready yet), should have no default value
tenants{}.applications{}.activeRotation bool default=true
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
index b10726cd73b..95c8733b540 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java
@@ -314,10 +314,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
tenant.getLocalSessionRepo().addSession(newSession);
return Optional.of(Deployment.unprepared(newSession, this, hostProvisioner, tenant, timeout, clock,
- false /* don't validate as this is already deployed */,
- newSession.getDockerImageRepository(),
- newSession.getVespaVersion(),
- bootstrap));
+ false /* don't validate as this is already deployed */, bootstrap));
}
@Override
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
index 439408fc01c..63b1df3d634 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/ConfigServerBootstrap.java
@@ -62,7 +62,6 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable
private final StateMonitor stateMonitor;
private final VipStatus vipStatus;
private final ConfigserverConfig configserverConfig;
- private final SuperModelManager superModelManager;
private final Duration maxDurationOfRedeployment;
private final Duration sleepTimeWhenRedeployingFails;
private final RedeployingApplicationsFails exitIfRedeployingApplicationsFails;
@@ -71,32 +70,29 @@ public class ConfigServerBootstrap extends AbstractComponent implements Runnable
@SuppressWarnings("unused")
@Inject
public ConfigServerBootstrap(ApplicationRepository applicationRepository, RpcServer server,
- VersionState versionState, StateMonitor stateMonitor, VipStatus vipStatus,
- SuperModelManager superModelManager) {
+ VersionState versionState, StateMonitor stateMonitor, VipStatus vipStatus) {
this(applicationRepository, server, versionState, stateMonitor, vipStatus, BOOTSTRAP_IN_CONSTRUCTOR, EXIT_JVM,
applicationRepository.configserverConfig().hostedVespa()
? VipStatusMode.VIP_STATUS_FILE
- : VipStatusMode.VIP_STATUS_PROGRAMMATICALLY,
- superModelManager);
+ : VipStatusMode.VIP_STATUS_PROGRAMMATICALLY);
}
// For testing only
ConfigServerBootstrap(ApplicationRepository applicationRepository, RpcServer server, VersionState versionState,
- StateMonitor stateMonitor, VipStatus vipStatus, Mode mode, VipStatusMode vipStatusMode) {
- this(applicationRepository, server, versionState, stateMonitor, vipStatus, mode, CONTINUE, vipStatusMode, null);
+ StateMonitor stateMonitor, VipStatus vipStatus, Mode mode, VipStatusMode vipStatusMode) {
+ this(applicationRepository, server, versionState, stateMonitor, vipStatus, mode, CONTINUE, vipStatusMode);
}
private ConfigServerBootstrap(ApplicationRepository applicationRepository, RpcServer server,
VersionState versionState, StateMonitor stateMonitor, VipStatus vipStatus,
Mode mode, RedeployingApplicationsFails exitIfRedeployingApplicationsFails,
- VipStatusMode vipStatusMode, SuperModelManager superModelManager) {
+ VipStatusMode vipStatusMode) {
this.applicationRepository = applicationRepository;
this.server = server;
this.versionState = versionState;
this.stateMonitor = stateMonitor;
this.vipStatus = vipStatus;
this.configserverConfig = applicationRepository.configserverConfig();
- this.superModelManager = superModelManager;
this.maxDurationOfRedeployment = Duration.ofSeconds(configserverConfig.maxDurationOfBootstrap());
this.sleepTimeWhenRedeployingFails = Duration.ofSeconds(configserverConfig.sleepTimeWhenRedeployingFails());
this.exitIfRedeployingApplicationsFails = exitIfRedeployingApplicationsFails;
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
index 9a46ce099f8..b3c5b38b9ba 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/Deployment.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.config.server.deploy;
import com.yahoo.component.Version;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.log.LogLevel;
@@ -56,6 +57,9 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
/** True if this deployment is done to bootstrap the config server */
private final boolean isBootstrap;
+ /** The (optional) Athenz domain this application should use */
+ private final Optional<AthenzDomain> athenzDomain;
+
private boolean prepared = false;
/** Whether this model should be validated (only takes effect if prepared=false) */
@@ -64,9 +68,8 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
private boolean ignoreSessionStaleFailure = false;
private Deployment(LocalSession session, ApplicationRepository applicationRepository,
- Optional<Provisioner> hostProvisioner, Tenant tenant,
- Duration timeout, Clock clock, boolean prepared, boolean validate,
- Optional<String> dockerImageRepository, Version version, boolean isBootstrap) {
+ Optional<Provisioner> hostProvisioner, Tenant tenant, Duration timeout,
+ Clock clock, boolean prepared, boolean validate, boolean isBootstrap) {
this.session = session;
this.applicationRepository = applicationRepository;
this.hostProvisioner = hostProvisioner;
@@ -75,26 +78,24 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
this.clock = clock;
this.prepared = prepared;
this.validate = validate;
- this.dockerImageRepository = dockerImageRepository;
- this.version = version;
+ this.dockerImageRepository = session.getDockerImageRepository();
+ this.version = session.getVespaVersion();
this.isBootstrap = isBootstrap;
+ this.athenzDomain = session.getAthenzDomain();
}
public static Deployment unprepared(LocalSession session, ApplicationRepository applicationRepository,
Optional<Provisioner> hostProvisioner, Tenant tenant,
- Duration timeout, Clock clock, boolean validate,
- Optional<String> dockerImageRepository, Version version,
- boolean isBootstrap) {
- return new Deployment(session, applicationRepository, hostProvisioner, tenant,
- timeout, clock, false, validate, dockerImageRepository, version, isBootstrap);
+ Duration timeout, Clock clock, boolean validate, boolean isBootstrap) {
+ return new Deployment(session, applicationRepository, hostProvisioner, tenant, timeout, clock, false,
+ validate, isBootstrap);
}
public static Deployment prepared(LocalSession session, ApplicationRepository applicationRepository,
Optional<Provisioner> hostProvisioner, Tenant tenant,
Duration timeout, Clock clock, boolean isBootstrap) {
return new Deployment(session, applicationRepository, hostProvisioner, tenant,
- timeout, clock, true, true, session.getDockerImageRepository(),
- session.getVespaVersion(), isBootstrap);
+ timeout, clock, true, true, isBootstrap);
}
public void setIgnoreSessionStaleFailure(boolean ignoreSessionStaleFailure) {
@@ -114,6 +115,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
.vespaVersion(version.toString())
.isBootstrap(isBootstrap);
dockerImageRepository.ifPresent(params::dockerImageRepository);
+ athenzDomain.ifPresent(params::athenzDomain);
session.prepare(logger, params.build(), Optional.empty(), tenant.getPath(), clock.instant());
this.prepared = true;
}
@@ -147,7 +149,7 @@ public class Deployment implements com.yahoo.config.provision.Deployment {
log.log(LogLevel.INFO, session.logPre() + "Session " + session.getSessionId() +
" activated successfully using " +
- (hostProvisioner.isPresent() ? hostProvisioner.get() : "no host provisioner") +
+ (hostProvisioner.isPresent() ? hostProvisioner.get().getClass().getSimpleName() : "no host provisioner") +
". Config generation " + session.getMetaData().getGeneration() +
". File references used: " + applicationRepository.getFileReferences(applicationId));
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterMetricsRetriever.java b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterMetricsRetriever.java
index 197b90e322c..c971fdd7b13 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterMetricsRetriever.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/metrics/ClusterMetricsRetriever.java
@@ -102,7 +102,7 @@ public class ClusterMetricsRetriever {
return slime;
} catch (IOException e) {
// Usually caused by applications being deleted during metric retrieval
- log.warning("Was unable to fetch metrics from " + hostURI + " : " + Exceptions.toMessageString(e));
+ log.info("Was unable to fetch metrics from " + hostURI + " : " + Exceptions.toMessageString(e));
return new Slime();
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java
index 6366576e163..4fe2ec129d3 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/model/LbServicesProducer.java
@@ -9,7 +9,9 @@ import com.yahoo.config.model.api.ServiceInfo;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
+import com.yahoo.vespa.flags.BooleanFlag;
import com.yahoo.vespa.flags.FlagSource;
+import com.yahoo.vespa.flags.Flags;
import java.util.Collections;
import java.util.Comparator;
@@ -32,14 +34,17 @@ public class LbServicesProducer implements LbServicesConfig.Producer {
private final Map<TenantName, Set<ApplicationInfo>> models;
private final Zone zone;
+ private final BooleanFlag nginxUpstreamProxyProtocol;
public LbServicesProducer(Map<TenantName, Set<ApplicationInfo>> models, Zone zone, FlagSource flagSource) {
this.models = models;
this.zone = zone;
+ this.nginxUpstreamProxyProtocol = Flags.NGINX_UPSTREAM_PROXY_PROTOCOL.bindTo(flagSource);
}
@Override
public void getConfig(LbServicesConfig.Builder builder) {
+ builder.nginxUpstreamProxyProtocol(nginxUpstreamProxyProtocol.value());
models.keySet().stream()
.sorted()
.forEach(tenant -> {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java
index ee4cc4a3043..daacdf9fd50 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/ProvisionerAdapter.java
@@ -36,8 +36,14 @@ public class ProvisionerAdapter implements HostProvisioner {
}
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
- return provisioner.prepare(applicationId, cluster, capacity, groups, logger);
+ return provisioner.prepare(applicationId, cluster, capacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ return provisioner.prepare(applicationId, cluster, capacity, logger);
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java
index 26d322665f0..8ddfb1e8b09 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/provision/StaticProvisioner.java
@@ -33,8 +33,15 @@ public class StaticProvisioner implements HostProvisioner {
throw new UnsupportedOperationException("Allocating a single host from provisioning info is not supported");
}
+
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
+ return prepare(cluster, capacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
List<HostSpec> hostsAlreadyAllocatedToCluster =
allocatedHosts.getHosts().stream()
.filter(host -> host.membership().isPresent() && matches(host.membership().get().cluster(), cluster))
@@ -42,7 +49,7 @@ public class StaticProvisioner implements HostProvisioner {
if ( ! hostsAlreadyAllocatedToCluster.isEmpty())
return hostsAlreadyAllocatedToCluster;
else
- return fallback.prepare(cluster, capacity, groups, logger);
+ return fallback.prepare(cluster, capacity, logger);
}
private boolean matches(ClusterSpec nodeCluster, ClusterSpec requestedCluster) {
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java
index e9d1f39f788..825ae0d8d92 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/LocalSession.java
@@ -7,6 +7,7 @@ import com.yahoo.config.application.api.ApplicationPackage;
import com.yahoo.config.application.api.ApplicationMetaData;
import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.provision.AllocatedHosts;
+import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.transaction.AbstractTransaction;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.transaction.Transaction;
@@ -142,6 +143,10 @@ public class LocalSession extends Session implements Comparable<LocalSession> {
zooKeeperClient.writeDockerImageRepository(dockerImageRepository);
}
+ public void setAthenzDomain(Optional<AthenzDomain> athenzDomain) {
+ zooKeeperClient.writeAthenzDomain(athenzDomain);
+ }
+
public enum Mode {
READ, WRITE
}
@@ -156,6 +161,8 @@ public class LocalSession extends Session implements Comparable<LocalSession> {
public Version getVespaVersion() { return zooKeeperClient.readVespaVersion(); }
+ public Optional<AthenzDomain> getAthenzDomain() { return zooKeeperClient.readAthenzDomain(); }
+
public AllocatedHosts getAllocatedHosts() {
return zooKeeperClient.getAllocatedHosts();
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
index 6a671648b27..f7ed801ddbd 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/PrepareParams.java
@@ -163,6 +163,11 @@ public final class PrepareParams {
return this;
}
+ public Builder athenzDomain(AthenzDomain athenzDomain) {
+ this.athenzDomain = Optional.of(athenzDomain);
+ return this;
+ }
+
public PrepareParams build() {
return new PrepareParams(applicationId, timeoutBudget, ignoreValidationErrors, dryRun,
verbose, isBootstrap, vespaVersion, containerEndpoints, tlsSecretsKeyName,
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java
index 87dead8eed1..06a3dfa8777 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionFactoryImpl.java
@@ -129,9 +129,11 @@ public class SessionFactoryImpl implements SessionFactory, LocalSessionLoader {
long activeSessionId = getActiveSessionId(existingApplicationId);
logger.log(LogLevel.DEBUG, "Create from existing application id " + existingApplicationId + ", active session id is " + activeSessionId);
LocalSession session = create(existingApp, existingApplicationId, activeSessionId, internalRedeploy, timeoutBudget);
+ // Note: Needs to be kept in sync with calls in SessionPreparer.writeStateToZooKeeper()
session.setApplicationId(existingApplicationId);
session.setVespaVersion(existingSession.getVespaVersion());
session.setDockerImageRepository(existingSession.getDockerImageRepository());
+ session.setAthenzDomain(existingSession.getAthenzDomain());
return session;
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
index b88fdc90316..6c77964a58b 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/session/SessionPreparer.java
@@ -300,6 +300,7 @@ public class SessionPreparer {
ZooKeeperDeployer zkDeployer = zooKeeperClient.createDeployer(deployLogger);
try {
zkDeployer.deploy(applicationPackage, fileRegistryMap, allocatedHosts);
+ // Note: When changing the below you need to also change similar calls in SessionFactoryImpl.createSessionFromExisting()
zooKeeperClient.writeApplicationId(applicationId);
zooKeeperClient.writeVespaVersion(vespaVersion);
zooKeeperClient.writeDockerImageRepository(dockerImageRepository);
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
index 2a1254d0d8d..654d811a31f 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/ConfigServerBootstrapTest.java
@@ -12,6 +12,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.component.Version;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.QrSearchersConfig;
+import com.yahoo.container.core.VipStatusConfig;
import com.yahoo.container.handler.ClustersStatus;
import com.yahoo.container.handler.VipStatus;
import com.yahoo.container.jdisc.config.HealthMonitorConfig;
@@ -235,6 +236,7 @@ public class ConfigServerBootstrapTest {
private VipStatus createVipStatus(StateMonitor stateMonitor) {
return new VipStatus(new QrSearchersConfig.Builder().build(),
+ new VipStatusConfig.Builder().build(),
new ClustersStatus(),
stateMonitor);
}
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
index 85f07da0325..84987bce32e 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/DeployTester.java
@@ -221,26 +221,26 @@ public class DeployTester {
* Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet.
*/
public PrepareResult deployApp(String applicationPath, String vespaVersion, String dockerImageRepository) {
- return deployApp(applicationPath, vespaVersion, Instant.now(), dockerImageRepository);
+ PrepareParams.Builder paramsBuilder = new PrepareParams.Builder();
+ if (vespaVersion != null)
+ paramsBuilder.vespaVersion(vespaVersion);
+
+ return deployApp(applicationPath, Instant.now(), paramsBuilder.dockerImageRepository(dockerImageRepository));
}
/**
* Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet.
*/
public PrepareResult deployApp(String applicationPath, String vespaVersion, Instant now) {
- return deployApp(applicationPath, vespaVersion, now, null);
+ return deployApp(applicationPath, now, new PrepareParams.Builder().vespaVersion(vespaVersion));
}
/**
* Do the initial "deploy" with the existing API-less code as the deploy API doesn't support first deploys yet.
*/
- public PrepareResult deployApp(String applicationPath, String vespaVersion, Instant now, String dockerImageRepository) {
- PrepareParams.Builder paramsBuilder = new PrepareParams.Builder()
- .applicationId(applicationId)
- .dockerImageRepository(dockerImageRepository)
+ public PrepareResult deployApp(String applicationPath, Instant now, PrepareParams.Builder paramsBuilder) {
+ paramsBuilder.applicationId(applicationId)
.timeoutBudget(new TimeoutBudget(clock, Duration.ofSeconds(60)));
- if (vespaVersion != null)
- paramsBuilder.vespaVersion(vespaVersion);
return applicationRepository.deploy(new File(applicationPath), paramsBuilder.build(), false, now);
}
@@ -299,8 +299,14 @@ public class DeployTester {
}
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
- return hostProvisioner.prepare(cluster, capacity, groups, logger);
+ return hostProvisioner.prepare(cluster, capacity.withGroups(groups), logger);
+ }
+
+ @Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ return hostProvisioner.prepare(cluster, capacity, logger);
}
@Override
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
index 5498d4b0315..7e700b78bf7 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/deploy/HostedDeployTest.java
@@ -24,6 +24,7 @@ import com.yahoo.vespa.config.server.configchange.RestartActions;
import com.yahoo.vespa.config.server.http.InvalidApplicationException;
import com.yahoo.vespa.config.server.http.v2.PrepareResult;
import com.yahoo.vespa.config.server.model.TestModelFactory;
+import com.yahoo.vespa.config.server.session.PrepareParams;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -87,17 +88,21 @@ public class HostedDeployTest {
}
@Test
- public void testDeployWithWantedDockerImageRepository() throws IOException {
+ public void testReDeployWithWantedDockerImageRepositoryAndAthenzDomain() throws IOException {
CountingModelFactory modelFactory = createHostedModelFactory(Version.fromString("4.5.6"), Clock.systemUTC());
DeployTester tester = new DeployTester(List.of(modelFactory), createConfigserverConfig());
String dockerImageRepository = "docker.foo.com:4443/bar/baz";
- tester.deployApp("src/test/apps/hosted/", "4.5.6", dockerImageRepository);
+ tester.deployApp("src/test/apps/hosted/", Instant.now(), new PrepareParams.Builder()
+ .vespaVersion("4.5.6")
+ .dockerImageRepository(dockerImageRepository)
+ .athenzDomain("foo"));
Optional<com.yahoo.config.provision.Deployment> deployment = tester.redeployFromLocalActive(tester.applicationId());
assertTrue(deployment.isPresent());
deployment.get().activate();
assertEquals("4.5.6", ((Deployment) deployment.get()).session().getVespaVersion().toString());
assertEquals(dockerImageRepository, ((Deployment) deployment.get()).session().getDockerImageRepository().get());
+ assertEquals("foo", ((Deployment) deployment.get()).session().getAthenzDomain().get().value());
}
@Test
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
index 70bba674778..5b0bb7885d8 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/SessionHandlerTest.java
@@ -237,11 +237,17 @@ public class SessionHandlerTest {
public Collection<HostSpec> lastHosts;
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
throw new UnsupportedOperationException();
}
@Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) {
activated = true;
lastApplicationId = application;
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
index 67677822317..70f66cf8fde 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandlerTest.java
@@ -28,12 +28,12 @@ import com.yahoo.vespa.config.server.session.PrepareParams;
import com.yahoo.vespa.config.server.tenant.Tenant;
import com.yahoo.vespa.config.server.tenant.TenantBuilder;
import com.yahoo.vespa.config.server.tenant.TenantRepository;
+import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import javax.ws.rs.client.Client;
import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
@@ -42,6 +42,7 @@ import java.nio.charset.StandardCharsets;
import java.time.Clock;
import static com.yahoo.config.model.api.container.ContainerServiceType.CLUSTERCONTROLLER_CONTAINER;
+import static com.yahoo.vespa.config.server.http.SessionHandlerTest.getRenderedString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
@@ -58,11 +59,16 @@ public class ApplicationHandlerTest {
private static File testApp = new File("src/test/apps/app");
- private ListApplicationsHandler listApplicationsHandler;
private final static TenantName mytenantName = TenantName.from("mytenant");
private final static TenantName foobar = TenantName.from("foobar");
- private final static ApplicationId applicationId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build();
-
+ private final static ApplicationId myTenantApplicationId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(mytenantName).build();
+ private final static ApplicationId applicationId = new ApplicationId.Builder().applicationName(ApplicationName.defaultName()).tenant(TenantName.defaultName()).build();
+ private final static MockTesterClient testerClient = new MockTesterClient();
+ private final static NullMetric metric = new NullMetric();
+ private final static ConfigserverConfig configserverConfig = new ConfigserverConfig(new ConfigserverConfig.Builder());
+ private static final MockLogRetriever logRetriever = new MockLogRetriever();
+
+ private TestComponentRegistry componentRegistry;
private TenantRepository tenantRepository;
private ApplicationRepository applicationRepository;
private SessionHandlerTest.MockProvisioner provisioner;
@@ -71,27 +77,30 @@ public class ApplicationHandlerTest {
@Before
public void setup() {
- TestComponentRegistry componentRegistry = new TestComponentRegistry.Builder().build();
+ componentRegistry = new TestComponentRegistry.Builder().build();
tenantRepository = new TenantRepository(componentRegistry, false);
- tenantRepository.addTenant(TenantBuilder.create(componentRegistry, mytenantName));
- tenantRepository.addTenant(TenantBuilder.create(componentRegistry, foobar));
provisioner = new SessionHandlerTest.MockProvisioner();
orchestrator = new OrchestratorMock();
applicationRepository = new ApplicationRepository(tenantRepository,
provisioner,
orchestrator,
- new ConfigserverConfig(new ConfigserverConfig.Builder()),
- new MockLogRetriever(),
+ configserverConfig,
+ logRetriever,
Clock.systemUTC(),
- new MockTesterClient(),
- new NullMetric());
- listApplicationsHandler = new ListApplicationsHandler(ListApplicationsHandler.testOnlyContext(),
- tenantRepository,
- Zone.defaultZone());
+ testerClient,
+ metric);
+ }
+
+ @After
+ public void shutdown() {
+ tenantRepository.close();
}
@Test
public void testDelete() throws Exception {
+ tenantRepository.addTenant(TenantBuilder.create(componentRegistry, foobar));
+ tenantRepository.addTenant(TenantBuilder.create(componentRegistry, mytenantName));
+
{
applicationRepository.deploy(testApp, prepareParams(applicationId));
Tenant mytenant = tenantRepository.getTenant(applicationId.tenant());
@@ -132,7 +141,7 @@ public class ApplicationHandlerTest {
@Test
public void testDeleteNonExistent() throws Exception {
- deleteAndAssertResponse(applicationId,
+ deleteAndAssertResponse(myTenantApplicationId,
Zone.defaultZone(),
Response.Status.NOT_FOUND,
HttpErrorResponse.errorCodes.NOT_FOUND,
@@ -180,10 +189,10 @@ public class ApplicationHandlerTest {
InfraDeployerProvider.empty(),
new ConfigConvergenceChecker(stateApiFactory),
mockHttpProxy,
- new ConfigserverConfig(new ConfigserverConfig.Builder()),
- new OrchestratorMock(),
- new MockTesterClient(),
- new NullMetric());
+ configserverConfig,
+ orchestrator,
+ testerClient,
+ metric);
ApplicationHandler mockHandler = createApplicationHandler(applicationRepository);
when(mockHttpProxy.get(any(), eq(host), eq(CLUSTERCONTROLLER_CONTAINER.serviceName),eq("clustercontroller-status/v1/clusterName1")))
.thenReturn(new StaticResponse(200, "text/html", "<html>...</html>"));
@@ -204,16 +213,15 @@ public class ApplicationHandlerTest {
HttpResponse response = fileDistributionStatus(applicationId, zone);
assertEquals(200, response.getStatus());
- SessionHandlerTest.getRenderedString(response);
assertEquals("{\"hosts\":[{\"hostname\":\"mytesthost\",\"status\":\"UNKNOWN\",\"message\":\"error: Connection error(104)\",\"fileReferences\":[]}],\"status\":\"UNKNOWN\"}",
- SessionHandlerTest.getRenderedString(response));
+ getRenderedString(response));
// 404 for unknown application
- ApplicationId unknown = new ApplicationId.Builder().applicationName("unknown").tenant(mytenantName).build();
+ ApplicationId unknown = new ApplicationId.Builder().applicationName("unknown").tenant("default").build();
HttpResponse responseForUnknown = fileDistributionStatus(unknown, zone);
assertEquals(404, responseForUnknown.getStatus());
- assertEquals("{\"error-code\":\"NOT_FOUND\",\"message\":\"No such application id: 'mytenant.unknown'\"}",
- SessionHandlerTest.getRenderedString(responseForUnknown));
+ assertEquals("{\"error-code\":\"NOT_FOUND\",\"message\":\"No such application id: 'default.unknown'\"}",
+ getRenderedString(responseForUnknown));
}
@Test
@@ -225,9 +233,7 @@ public class ApplicationHandlerTest {
HttpResponse response = mockHandler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
assertEquals(200, response.getStatus());
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- response.render(baos);
- assertEquals("log line", baos.toString());
+ assertEquals("log line", getRenderedString(response));
}
@Test
@@ -235,13 +241,9 @@ public class ApplicationHandlerTest {
applicationRepository.deploy(testApp, prepareParams(applicationId));
String url = toUrlPath(applicationId, Zone.defaultZone(), true) + "/tester/status";
ApplicationHandler mockHandler = createApplicationHandler();
-
HttpResponse response = mockHandler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
assertEquals(200, response.getStatus());
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- response.render(baos);
- assertEquals("OK", baos.toString());
+ assertEquals("OK", getRenderedString(response));
}
@Test
@@ -252,10 +254,7 @@ public class ApplicationHandlerTest {
HttpResponse response = mockHandler.handle(HttpRequest.createTestRequest(url, com.yahoo.jdisc.http.HttpRequest.Method.GET));
assertEquals(200, response.getStatus());
-
- ByteArrayOutputStream baos = new ByteArrayOutputStream();
- response.render(baos);
- assertEquals("log", baos.toString());
+ assertEquals("log", getRenderedString(response));
}
@Test
@@ -345,10 +344,13 @@ public class ApplicationHandlerTest {
"/environment/" + zone.environment().value() +
"/region/" + zone.region().value() +
"/instance/" + applicationId.instance().value() + "\"]";
+ ListApplicationsHandler listApplicationsHandler = new ListApplicationsHandler(ListApplicationsHandler.testOnlyContext(),
+ tenantRepository,
+ Zone.defaultZone());
ListApplicationsHandlerTest.assertResponse(listApplicationsHandler, "http://myhost:14000/application/v2/tenant/" + tenantName + "/application/",
- Response.Status.OK,
- expected,
- com.yahoo.jdisc.http.HttpRequest.Method.GET);
+ Response.Status.OK,
+ expected,
+ com.yahoo.jdisc.http.HttpRequest.Method.GET);
}
private void restart(ApplicationId application, Zone zone) throws IOException {
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
index 40115170b69..a3b0f3ec44a 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/session/SessionPreparerTest.java
@@ -342,11 +342,17 @@ public class SessionPreparerTest {
private static class FailWithTransientExceptionProvisioner implements Provisioner {
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
throw new LoadBalancerServiceException("Unable to create load balancer", new Exception("some internal exception"));
}
@Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ throw new LoadBalancerServiceException("Unable to create load balancer", new Exception("some internal exception"));
+ }
+
+ @Override
public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) { }
@Override
diff --git a/container-accesslogging/src/test/java/com/yahoo/container/logging/LogFileHandlerTestCase.java b/container-accesslogging/src/test/java/com/yahoo/container/logging/LogFileHandlerTestCase.java
index d7361eec488..bc7257b1ca9 100644
--- a/container-accesslogging/src/test/java/com/yahoo/container/logging/LogFileHandlerTestCase.java
+++ b/container-accesslogging/src/test/java/com/yahoo/container/logging/LogFileHandlerTestCase.java
@@ -10,6 +10,8 @@ import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
+import java.nio.file.Files;
+import java.nio.file.Paths;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
@@ -103,52 +105,46 @@ public class LogFileHandlerTestCase {
h.shutdown();
}
- @Test
+ @Test(timeout = /*5 minutes*/300_000)
public void testSymlink() throws IOException, InterruptedException {
File root = temporaryFolder.newFolder("testlogforsymlinkchecking");
- LogFileHandler h = new LogFileHandler();
- h.setFilePattern(root.getAbsolutePath() + "/logfilehandlertest.%Y%m%d%H%M%S%s");
- h.setFormatter(new Formatter() {
+ LogFileHandler handler = new LogFileHandler();
+ handler.setFilePattern(root.getAbsolutePath() + "/logfilehandlertest.%Y%m%d%H%M%S%s");
+ handler.setFormatter(new Formatter() {
public String format(LogRecord r) {
DateFormat df = new SimpleDateFormat("yyyy.MM.dd:HH:mm:ss.SSS");
String timeStamp = df.format(new Date(r.getMillis()));
return ("["+timeStamp+"]" + " " + formatMessage(r) + "\n");
}
} );
- h.setSymlinkName("symlink");
- LogRecord lr = new LogRecord(Level.INFO, "test");
- h.publish(lr);
- String f1 = h.getFileName();
- String f2 = null;
- while (f1 == null) {
- Thread.sleep(1);
- f1 = h.getFileName();
- }
- h.rotateNow();
- Thread.sleep(1);
- f2 = h.getFileName();
- while (f1.equals(f2)) {
+ handler.setSymlinkName("symlink");
+
+ handler.publish(new LogRecord(Level.INFO, "test"));
+ String firstFile;
+ do {
+ Thread.sleep(1);
+ firstFile = handler.getFileName();
+ } while (firstFile == null);
+ handler.rotateNow();
+ String secondFileName;
+ do {
Thread.sleep(1);
- f2 = h.getFileName();
- }
- lr = new LogRecord(Level.INFO, "string which is way longer than the word test");
- h.publish(lr);
- h.waitDrained();
- File f = new File(f1);
- long first = f.length();
- f = new File(f2);
- long second = f.length();
- final long secondLength = 72;
- for (int n = 0; n < 20 && second != secondLength; ++n) {
+ secondFileName = handler.getFileName();
+ } while (firstFile.equals(secondFileName));
+
+ handler.publish(new LogRecord(Level.INFO, "string which is way longer than the word test"));
+ handler.waitDrained();
+ assertThat(Files.size(Paths.get(firstFile))).isEqualTo(31);
+ final long expectedSecondFileLength = 72;
+ long secondFileLength;
+ do {
Thread.sleep(1);
- second = f.length();
- }
- f = new File(root, "symlink");
- long link = f.length();
- assertThat(secondLength).isEqualTo(link);
- assertThat(31).isEqualTo(first);
- assertThat(secondLength).isEqualTo(second);
- h.shutdown();
+ secondFileLength = Files.size(Paths.get(secondFileName));
+ } while (secondFileLength != expectedSecondFileLength);
+
+ long symlinkFileLength = Files.size(root.toPath().resolve("symlink"));
+ assertThat(symlinkFileLength).isEqualTo(expectedSecondFileLength);
+ handler.shutdown();
}
@Test
diff --git a/container-core/abi-spec.json b/container-core/abi-spec.json
index ce567176679..6d683c53984 100644
--- a/container-core/abi-spec.json
+++ b/container-core/abi-spec.json
@@ -250,6 +250,7 @@
"public void <init>(com.yahoo.container.QrSearchersConfig)",
"public void <init>(com.yahoo.container.handler.ClustersStatus)",
"public void <init>(com.yahoo.container.QrSearchersConfig, com.yahoo.container.handler.ClustersStatus)",
+ "public void <init>(com.yahoo.container.QrSearchersConfig, com.yahoo.container.core.VipStatusConfig, com.yahoo.container.handler.ClustersStatus, com.yahoo.container.jdisc.state.StateMonitor)",
"public void <init>(com.yahoo.container.QrSearchersConfig, com.yahoo.container.handler.ClustersStatus, com.yahoo.container.jdisc.state.StateMonitor)",
"public void <init>(com.yahoo.container.QrSearchersConfig, com.yahoo.container.core.VipStatusConfig, com.yahoo.container.handler.ClustersStatus)",
"public void setInRotation(java.lang.Boolean)",
diff --git a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
index 4c3d76436dd..ca11ad387ee 100644
--- a/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
+++ b/container-core/src/main/java/com/yahoo/container/core/config/BundleLoader.java
@@ -148,10 +148,9 @@ public class BundleLoader {
/**
* Returns the bundles that are not assumed to be retained by the new application generation.
- * and cleans up the map of active file references. Note that at this point we don't yet know
- * the full set of new bundles, because of the potential pre-install directives in the new bundles.
- * However, only "disk bundles" (file:) can be listed in the pre-install directive, so we know
- * about all the obsolete application bundles.
+ * Note that at this point we don't yet know the full set of new bundles, because of the potential
+ * pre-install directives in the new bundles. However, only "disk bundles" (file:) can be listed
+ * in the pre-install directive, so we know about all the obsolete application bundles.
*/
private Set<Bundle> getObsoleteBundles(List<FileReference> newReferences) {
Set<Bundle> bundlesToRemove = new HashSet<>(osgi.getCurrentBundles());
@@ -165,6 +164,9 @@ public class BundleLoader {
return bundlesToRemove;
}
+ /**
+ * Cleans up the map of active file references
+ */
private void removeInactiveFileReferences(List<FileReference> newReferences) {
// Clean up the map of active bundles
Set<FileReference> fileReferencesToRemove = getObsoleteFileReferences(newReferences);
@@ -184,6 +186,7 @@ public class BundleLoader {
// The bundle at index 0 for each file reference always corresponds to the bundle at the file reference location
Set<Bundle> allowedDuplicates = obsoleteReferences.stream()
+ .filter(reference -> ! isDiskBundle(reference))
.map(reference -> reference2Bundles.get(reference).get(0))
.collect(Collectors.toSet());
diff --git a/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java b/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java
index f712690efc5..0bf86e8f440 100644
--- a/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java
+++ b/container-core/src/main/java/com/yahoo/container/handler/VipStatus.java
@@ -24,6 +24,8 @@ public class VipStatus {
/** If this is non-null, its value decides whether this container is in rotation */
private Boolean rotationOverride = null;
+ private final boolean initiallyInRotation;
+
/** The current state of this */
private boolean currentlyInRotation;
@@ -44,20 +46,29 @@ public class VipStatus {
this(new QrSearchersConfig.Builder().build(), clustersStatus);
}
+ /** For testing */
public VipStatus(QrSearchersConfig dispatchers, ClustersStatus clustersStatus) {
- this(dispatchers, clustersStatus, new StateMonitor());
+ this(dispatchers, new VipStatusConfig.Builder().build(), clustersStatus, new StateMonitor());
}
@Inject
- public VipStatus(QrSearchersConfig dispatchers, ClustersStatus clustersStatus, StateMonitor healthState) {
+ public VipStatus(QrSearchersConfig dispatchers,
+ VipStatusConfig vipStatusConfig,
+ ClustersStatus clustersStatus,
+ StateMonitor healthState) {
this.clustersStatus = clustersStatus;
this.healthState = healthState;
+ initiallyInRotation = vipStatusConfig.initiallyInRotation();
healthState.status(StateMonitor.Status.initializing);
clustersStatus.setContainerHasClusters(! dispatchers.searchcluster().isEmpty());
updateCurrentlyInRotation();
}
- /** @deprecated don't pass VipStatusConfig */
+ @Deprecated // TODO: Remove on Vespa 8
+ public VipStatus(QrSearchersConfig dispatchers, ClustersStatus clustersStatus, StateMonitor healthState) {
+ this(dispatchers, new VipStatusConfig.Builder().build(), clustersStatus, healthState);
+ }
+
@Deprecated // TODO: Remove on Vespa 8
public VipStatus(QrSearchersConfig dispatchers, VipStatusConfig ignored, ClustersStatus clustersStatus) {
this(dispatchers, clustersStatus);
@@ -107,7 +118,12 @@ public class VipStatus {
} else {
if (healthState.status() == StateMonitor.Status.up) {
currentlyInRotation = clustersStatus.containerShouldReceiveTraffic(ClustersStatus.Require.ONE);
- } else {
+ }
+ else if (healthState.status() == StateMonitor.Status.initializing) {
+ currentlyInRotation = clustersStatus.containerShouldReceiveTraffic(ClustersStatus.Require.ALL)
+ && initiallyInRotation;
+ }
+ else {
currentlyInRotation = clustersStatus.containerShouldReceiveTraffic(ClustersStatus.Require.ALL);
}
}
diff --git a/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java
index d3479936544..e13debcddda 100644
--- a/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java
+++ b/container-core/src/test/java/com/yahoo/container/handler/VipStatusTestCase.java
@@ -4,6 +4,7 @@ package com.yahoo.container.handler;
import static org.junit.Assert.*;
import com.yahoo.container.QrSearchersConfig;
+import com.yahoo.container.core.VipStatusConfig;
import com.yahoo.container.jdisc.state.StateMonitor;
import com.yahoo.jdisc.core.SystemTimer;
import org.junit.Test;
@@ -14,39 +15,45 @@ import org.junit.Test;
* @author steinar
*/
public class VipStatusTestCase {
- private static final String [] clusters = {"cluster1", "cluster2", "cluster3"};
- private static QrSearchersConfig getSearchersCfg() {
+ private static QrSearchersConfig getSearchersConfig(String[] clusters) {
var b = new QrSearchersConfig.Builder();
- var searchClusterB = new QrSearchersConfig.Searchcluster.Builder();
- for (String cluster : clusters) {
- searchClusterB.name(cluster);
+ if (clusters.length > 0) {
+ var searchClusterB = new QrSearchersConfig.Searchcluster.Builder();
+ for (String cluster : clusters) {
+ searchClusterB.name(cluster);
+ }
+ b.searchcluster(searchClusterB);
}
- b.searchcluster(searchClusterB);
return b.build();
}
- private static VipStatus getVipStatus(StateMonitor.Status startState) {
- return new VipStatus(getSearchersCfg(), new ClustersStatus(), new StateMonitor(1000, startState, new SystemTimer(), runnable -> {
- Thread thread = new Thread(runnable, "StateMonitor");
- thread.setDaemon(true);
- return thread;
- }));
+
+ private static VipStatus getVipStatus(String[] clusters, StateMonitor.Status startState, boolean initiallyInRotation) {
+ return new VipStatus(getSearchersConfig(clusters),
+ new VipStatusConfig.Builder().initiallyInRotation(initiallyInRotation).build(),
+ new ClustersStatus(),
+ new StateMonitor(1000, startState, new SystemTimer(), runnable -> {
+ Thread thread = new Thread(runnable, "StateMonitor");
+ thread.setDaemon(true);
+ return thread;
+ }));
}
- private static void removeAll(VipStatus v) {
+ private static void remove(String[] clusters, VipStatus v) {
for (String s : clusters) {
v.removeFromRotation(s);
}
}
- private static void addAll(VipStatus v) {
+
+ private static void add(String[] clusters, VipStatus v) {
for (String s : clusters) {
v.addToRotation(s);
}
}
- private static void verifyUpOrDown(StateMonitor.Status status) {
- VipStatus v = getVipStatus(status);
- removeAll(v);
+ private static void verifyUpOrDown(String[] clusters, StateMonitor.Status status) {
+ VipStatus v = getVipStatus(clusters, status, true);
+ remove(clusters, v);
// initial state
assertFalse(v.isInRotation());
v.addToRotation(clusters[0]);
@@ -59,15 +66,18 @@ public class VipStatusTestCase {
@Test
public void testInitializingOrDownRequireAllUp() {
- verifyUpOrDown(StateMonitor.Status.initializing);
- verifyUpOrDown(StateMonitor.Status.down);
+ String[] clusters = {"cluster1", "cluster2", "cluster3"};
+ verifyUpOrDown(clusters, StateMonitor.Status.initializing);
+ verifyUpOrDown(clusters, StateMonitor.Status.down);
}
@Test
public void testUpRequireAllDown() {
- VipStatus v = getVipStatus(StateMonitor.Status.initializing);
+ String[] clusters = {"cluster1", "cluster2", "cluster3"};
+
+ VipStatus v = getVipStatus(clusters, StateMonitor.Status.initializing, true);
assertFalse(v.isInRotation());
- addAll(v);
+ add(clusters, v);
assertTrue(v.isInRotation());
v.removeFromRotation(clusters[0]);
@@ -89,4 +99,18 @@ public class VipStatusTestCase {
assertTrue(v.isInRotation());
}
-}
+ @Test
+ public void testNoClustersConfiguringInitiallyInRotationFalse() {
+ String[] clusters = {};
+ VipStatus v = getVipStatus(clusters, StateMonitor.Status.initializing, false);
+ assertFalse(v.isInRotation());
+ }
+
+ @Test
+ public void testNoClustersConfiguringInitiallyInRotationTrue() {
+ String[] clusters = {};
+ VipStatus v = getVipStatus(clusters, StateMonitor.Status.initializing, true);
+ assertTrue(v.isInRotation());
+ }
+
+} \ No newline at end of file
diff --git a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
index 113d99f77f9..7193433ccf7 100644
--- a/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
+++ b/container-messagebus/src/main/java/com/yahoo/container/jdisc/messagebus/SessionCache.java
@@ -120,7 +120,10 @@ public final class SessionCache extends AbstractComponent {
RPCNetworkParams netParams = new RPCNetworkParams()
.setSlobrokConfigId(slobrokConfigId)
.setIdentity(new Identity(identity))
- .setListenPort(mbusConfig.port());
+ .setListenPort(mbusConfig.port())
+ .setNumTargetsPerSpec(mbusConfig.numconnectionspertarget())
+ .setNumNetworkThreads(mbusConfig.numthreads())
+ .setOptimization(RPCNetworkParams.Optimization.valueOf(mbusConfig.optimize_for().name()));
return SharedMessageBus.newInstance(mbusParams, netParams);
}
diff --git a/container-messagebus/src/main/resources/configdefinitions/container-mbus.def b/container-messagebus/src/main/resources/configdefinitions/container-mbus.def
index b18bec66959..9aef2b32a66 100644
--- a/container-messagebus/src/main/resources/configdefinitions/container-mbus.def
+++ b/container-messagebus/src/main/resources/configdefinitions/container-mbus.def
@@ -2,9 +2,24 @@
namespace=container.jdisc
#settings for message bus in container
-enabled bool default=false
+
+# Which network port is used
port int default=0
+
+# Number of connections per target
+numconnectionspertarget int default=1
+
+# Number network threads
+numthreads int default=2
+
+# Optimize for latency, or throughput.
+optimize_for enum {LATENCY, THROUGHPUT} default=LATENCY
+
+# Everying below is deprecated and will go away very soon.
+# Dynamic throttling is used, and works better than anything else.
maxpendingcount int default=2048
+
+enabled bool default=false
#maxpendingsize is set in megabytes!
maxpendingsize int default=100
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
index 3d244312b2f..0686a4bdb43 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/SimpleParser.java
@@ -126,7 +126,6 @@ abstract class SimpleParser extends StructuredParser {
if (topLevelItem != null && topLevelItem != not) {
// => neutral rank items becomes implicit positives
- System.out.println("Extracting positive item from " + topLevelItem);
not.addPositiveItem(getItemAsPositiveItem(topLevelItem, not));
return not;
} else { // Only negatives - ignore them
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
index 2d05168731a..645c6446ef1 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/ClusterSearcher.java
@@ -220,14 +220,6 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
if (result == null)
result = new Result(query, ErrorMessage.createBackendCommunicationError("No result returned in " + this +
" from " + connection + " for " + query));
-
- if (result.hits().getError() != null) {
- log(LogLevel.FINE, "FAILED: ", query);
- } else if ( ! result.isCached()) {
- log(LogLevel.FINE, "WORKING: ", query);
- } else {
- log(LogLevel.FINE, "CACHE HIT: ", query);
- }
return result;
}
@@ -263,13 +255,6 @@ public abstract class ClusterSearcher<T> extends PingableSearcher implements Nod
result.hits().addError(ErrorMessage.createBackendCommunicationError("Error filling " + result + " from " + connection + ": " +
Exceptions.toMessageString(e)));
}
- if (result.hits().getError() != null) {
- log(LogLevel.FINE, "FAILED: ", result.getQuery());
- } else if ( ! result.isCached()) {
- log(LogLevel.FINE, "WORKING: ", result.getQuery());
- } else {
- log(LogLevel.FINE, "CACHE HIT: " + result.getQuery());
- }
}
/**
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java b/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
index 46752b0bedb..6c83e1c64e3 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/Hasher.java
@@ -11,6 +11,7 @@ package com.yahoo.search.cluster;
public class Hasher<T> {
public static class NodeFactor<T> {
+
private final T node;
/**
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java b/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
index 21e5fe3bc7f..a2fb982e3c5 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/MonitorConfiguration.java
@@ -8,62 +8,43 @@ package com.yahoo.search.cluster;
*/
public class MonitorConfiguration {
- /**
- * The interval in ms between consecutive checks of the monitored
- * nodes
- */
+ /** The interval in ms between consecutive checks of the monitored nodes */
private long checkInterval=1000;
- /**
- * The number of milliseconds to attempt to complete a request
- * before giving up
- */
+ /** The number of milliseconds to attempt to complete a request before giving up */
private final long requestTimeout = 980;
- /**
- * The number of milliseconds a node is allowed to fail before we
- * mark it as not working
- */
- private long failLimit=5000;
+ /** The number of milliseconds a node is allowed to fail before we mark it as not working */
+ private long failLimit = 5000;
- /**
- * Sets the interval between each ping of idle or failing nodes
- * Default is 1000ms
- */
- public void setCheckInterval(long intervalMs) {
- this.checkInterval=intervalMs;
- }
+ /** Sets the interval between each ping of idle or failing nodes. Default is 1000 ms. */
+ public void setCheckInterval(long intervalMs) { this.checkInterval = intervalMs; }
- /**
- * Returns the interval between each ping of idle or failing nodes
- * Default is 1000ms
- */
- public long getCheckInterval() {
- return checkInterval;
- }
+ /** Returns the interval between each ping of idle or failing nodes. Default is 1000 ms. */
+ public long getCheckInterval() { return checkInterval; }
/**
- * Sets the number of times a failed node must respond before it is put
- * back in service. Default is 3.
- * @deprecated Will go away in Vespa 8
+ * Sets the number of times a failed node must respond before it is put back in service. Default is 3.
+ *
+ * @deprecated will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setResponseAfterFailLimit(int responseAfterFailLimit) { }
/**
- * Sets the number of ms a node (failing or working) is allowed to
- * stay idle before it is pinged. Default is 3000
+ * Sets the number of ms a node (failing or working) is allowed to stay idle before it is pinged. Default is 3000.
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setIdleLimit(int idleLimit) { }
/**
- * Gets the number of ms a node (failing or working)
- * is allowed to stay idle before it is pinged. Default is 3000
+ * Gets the number of ms a node (failing or working) is allowed to stay idle before it is pinged. Default is 3000.
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public long getIdleLimit() {
return 3000;
}
@@ -91,25 +72,26 @@ public class MonitorConfiguration {
* in quarantine. Once in quarantine it won't be put back in
* productuion before quarantineTime has expired even if it is
* working. Default is 3
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setFailQuarantineLimit(int failQuarantineLimit) { }
/**
- * The number of ms an unstable node is quarantined. Default is
- * 100*60*60
+ * The number of ms an unstable node is quarantined. Default is 100*60*60
+ *
* @deprecated Will go away in Vespa 8
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
public void setQuarantineTime(long quarantineTime) { }
public String toString() {
return "monitor configuration [" +
- "checkInterval: " + checkInterval +
- " requestTimeout " + requestTimeout +
- " failLimit " + failLimit +
- "]";
+ "checkInterval: " + checkInterval +
+ " requestTimeout " + requestTimeout +
+ " failLimit " + failLimit +
+ "]";
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java b/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
index 481f1e1b5a5..836c71089c1 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/NodeManager.java
@@ -7,7 +7,7 @@ import java.util.concurrent.Executor;
* Must be implemented by a node collection which wants
* it's node state monitored by a ClusterMonitor
*
- * @author bratseth
+ * @author bratseth
*/
public interface NodeManager<T> {
@@ -20,9 +20,10 @@ public interface NodeManager<T> {
/**
* Called when a node should be pinged.
* This *must* lead to either a call to NodeMonitor.failed or NodeMonitor.responded
+ *
* @deprecated Use ping(ClusterMonitor clusterMonitor, T node, Executor executor) instead.
*/
- @Deprecated
+ @Deprecated // TODO: Remove on Vespa 8
default void ping(T node, Executor executor) {
throw new IllegalStateException("If you have not overrriden ping(ClusterMonitor<T> clusterMonitor, T node, Executor executor), you should at least have overriden this method.");
}
diff --git a/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java b/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
index ccf3e863ff3..d17f6bfbaa8 100644
--- a/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
+++ b/container-search/src/main/java/com/yahoo/search/cluster/TrafficNodeMonitor.java
@@ -52,8 +52,8 @@ public class TrafficNodeMonitor<T> extends BaseNodeMonitor<T> {
* Called when a response is received from this node.
*/
public void responded() {
- respondedAt=now();
- succeededAt=respondedAt;
+ respondedAt = now();
+ succeededAt = respondedAt;
setWorking(true,"Responds correctly");
}
@@ -69,20 +69,20 @@ public class TrafficNodeMonitor<T> extends BaseNodeMonitor<T> {
atStartUp = false;
if (this.isWorking == working) return; // Old news
- if (explanation==null) {
- explanation="";
+ if (explanation == null) {
+ explanation = "";
} else {
- explanation=": " + explanation;
+ explanation = ": " + explanation;
}
if (working) {
log.info("Putting " + node + " in service" + explanation);
} else {
log.warning("Taking " + node + " out of service" + explanation);
- failedAt=now();
+ failedAt = now();
}
- this.isWorking=working;
+ this.isWorking = working;
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
index 7619cb34b77..7862648ba51 100644
--- a/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
+++ b/container-search/src/main/java/com/yahoo/search/dispatch/searchcluster/SearchCluster.java
@@ -78,10 +78,10 @@ public class SearchCluster implements NodeManager<Node> {
this.nodesByHost = nodesByHostBuilder.build();
this.localCorpusDispatchTarget = findLocalCorpusDispatchTarget(HostName.getLocalhost(),
- size,
- containerClusterSize,
- nodesByHost,
- groups);
+ size,
+ containerClusterSize,
+ nodesByHost,
+ groups);
}
/* Testing only */
@@ -217,7 +217,10 @@ public class SearchCluster implements NodeManager<Node> {
setInRotationOnlyIf(hasWorkingNodes());
}
else if (usesLocalCorpusIn(node)) { // follow the status of this node
- setInRotationOnlyIf(nodeIsWorking);
+ // Do not take this out of rotation if we're a combined cluster of size 1,
+ // as that can't be helpful, and leads to a deadlock where this node is never taken back in servic e
+ if (nodeIsWorking || size() > 1)
+ setInRotationOnlyIf(nodeIsWorking);
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/query/Select.java b/container-search/src/main/java/com/yahoo/search/query/Select.java
index cb662dcd671..65ffd29efe0 100644
--- a/container-search/src/main/java/com/yahoo/search/query/Select.java
+++ b/container-search/src/main/java/com/yahoo/search/query/Select.java
@@ -57,12 +57,13 @@ public class Select implements Cloneable {
}
public Select(String where, String grouping, Query query) {
- this(where, grouping, query, Collections.emptyList());
+ this(where, grouping, null, query, Collections.emptyList());
}
- private Select(String where, String grouping, Query query, List<GroupingRequest> groupingRequests) {
+ private Select(String where, String grouping, String groupingExpressionString, Query query, List<GroupingRequest> groupingRequests) {
this.where = Objects.requireNonNull(where, "A Select must have a where string (possibly the empty string)");
this.grouping = Objects.requireNonNull(grouping, "A Select must have a select string (possibly the empty string)");
+ this.groupingExpressionString = groupingExpressionString;
this.parent = Objects.requireNonNull(query, "A Select must have a parent query");
this.groupingRequests = deepCopy(groupingRequests, this);
}
@@ -136,11 +137,11 @@ public class Select implements Cloneable {
@Override
public Object clone() {
- return new Select(where, grouping, parent, groupingRequests);
+ return new Select(where, grouping, groupingExpressionString, parent, groupingRequests);
}
public Select cloneFor(Query parent) {
- return new Select(where, grouping, parent, groupingRequests);
+ return new Select(where, grouping, groupingExpressionString, parent, groupingRequests);
}
}
diff --git a/container-search/src/test/java/com/yahoo/search/dispatch/searchcluster/SearchClusterTest.java b/container-search/src/test/java/com/yahoo/search/dispatch/searchcluster/SearchClusterTest.java
index cf90a1c6d81..ad281aeda7d 100644
--- a/container-search/src/test/java/com/yahoo/search/dispatch/searchcluster/SearchClusterTest.java
+++ b/container-search/src/test/java/com/yahoo/search/dispatch/searchcluster/SearchClusterTest.java
@@ -191,7 +191,7 @@ public class SearchClusterTest {
}
@Test
- public void requireThatVipStatusIsDefaultDownWithOnlySingleLocalDispatch() {
+ public void requireThatVipStatusStaysUpWithLocalDispatchAndClusterSize1() {
try (State test = new State("cluster.1", 1, HostName.getLocalhost())) {
assertTrue(test.searchCluster.localCorpusDispatchTarget().isPresent());
@@ -200,6 +200,20 @@ public class SearchClusterTest {
assertTrue(test.vipStatus.isInRotation());
test.numDocsPerNode.get(0).set(-1);
test.waitOneFullPingRound();
+ assertTrue(test.vipStatus.isInRotation());
+ }
+ }
+
+ @Test
+ public void requireThatVipStatusIsDefaultDownWithLocalDispatchAndClusterSize2() {
+ try (State test = new State("cluster.1", 1, HostName.getLocalhost(), "otherhost")) {
+ assertTrue(test.searchCluster.localCorpusDispatchTarget().isPresent());
+
+ assertFalse(test.vipStatus.isInRotation());
+ test.waitOneFullPingRound();
+ assertTrue(test.vipStatus.isInRotation());
+ test.numDocsPerNode.get(0).set(-1);
+ test.waitOneFullPingRound();
assertFalse(test.vipStatus.isInRotation());
}
}
diff --git a/container-search/src/test/java/com/yahoo/select/SelectTestCase.java b/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
index 7b1b4fe6362..1715ed38964 100644
--- a/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
+++ b/container-search/src/test/java/com/yahoo/select/SelectTestCase.java
@@ -724,6 +724,7 @@ public class SelectTestCase {
assertEquals("all(group(time.dayofmonth(a)) each(output(count())))", query.getSelect().getGrouping().get(0).toString());
Query clone = query.clone();
+ assertEquals(clone.getSelect().getGroupingExpressionString(), query.getSelect().getGroupingExpressionString());
assertNotSame(query.getSelect(), clone.getSelect());
assertNotSame(query.getSelect().getGrouping(), clone.getSelect().getGrouping());
assertNotSame(query.getSelect().getGrouping().get(0), clone.getSelect().getGrouping().get(0));
@@ -732,8 +733,15 @@ public class SelectTestCase {
assertEquals(query.getSelect().getGroupingString(), clone.getSelect().getGroupingString());
assertEquals(query.getSelect().getGrouping().get(0).toString(), clone.getSelect().getGrouping().get(0).toString());
assertEquals(query.getSelect().getGrouping().get(1).toString(), clone.getSelect().getGrouping().get(1).toString());
+ }
+ @Test
+ public void testCloneWithGroupingExpressionString() {
+ Query query = new Query();
+ query.getSelect().setGroupingExpressionString("all(group(foo) each(output(count())))");
+ Query clone = query.clone();
+ assertEquals(clone.getSelect().getGroupingExpressionString(), query.getSelect().getGroupingExpressionString());
}
//------------------------------------------------------------------- Assert methods
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java
index 4974192e213..3ac24bac7ca 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/TenantId.java
@@ -10,10 +10,6 @@ public class TenantId extends NonDefaultIdentifier {
super(id);
}
- public boolean isUser() {
- return id().startsWith("by-");
- }
-
@Override
public void validate() {
super.validate();
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java
index d2effc76827..f1a8e57ab03 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/identifiers/UserId.java
@@ -10,8 +10,4 @@ public class UserId extends NonDefaultIdentifier {
super(id);
}
- public TenantId toTenantId() {
- return new TenantId("by-" + id().replace('_', '-'));
- }
-
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
index bdd2fb247fa..5c11dfc2a55 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java
@@ -58,7 +58,8 @@ enum PathGroup {
"/application/v4/tenant/{tenant}/application/",
"/application/v4/tenant/{tenant}/cost",
"/application/v4/tenant/{tenant}/cost/{date}",
- "/routing/v1/status/tenant/{tenant}/{*}"),
+ "/routing/v1/status/tenant/{tenant}/{*}",
+ "/billing/v1/tenant/{tenant}/{*}"),
tenantKeys(Matcher.tenant,
PathPrefix.api,
@@ -201,7 +202,11 @@ enum PathGroup {
/** Paths used for "dry-running" system-wide feature flags. */
- systemFlagsDryrun(PathPrefix.none, "/system-flags/v1/dryrun");
+ systemFlagsDryrun(PathPrefix.none, "/system-flags/v1/dryrun"),
+
+ /** Paths used for receiving payment callbacks */
+ paymentProcessor(PathPrefix.none, "/payment/notification");
+
final List<String> pathSpecs;
final PathPrefix prefix;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
index 55512b38f95..cfe8d247e54 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java
@@ -137,7 +137,12 @@ enum Policy {
/** Access to /system-flags/v1/dryrun. */
systemFlagsDryrun(Privilege.grant(Action.update)
.on(PathGroup.systemFlagsDryrun)
- .in(SystemName.all()));
+ .in(SystemName.all())),
+
+ /** Access to /payment/notification */
+ paymentProcessor(Privilege.grant(Action.create)
+ .on(PathGroup.paymentProcessor)
+ .in(SystemName.PublicCd));
private final Set<Privilege> privileges;
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
index 532088e94aa..d3c5e412215 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Role.java
@@ -73,6 +73,9 @@ public abstract class Role {
/** Returns the role for system flag dryrun */
public static UnboundRole systemFlagsDryrunner() { return new UnboundRole(RoleDefinition.systemFlagsDryrunner); }
+ /** Returns the role of the payment processor */
+ public static UnboundRole paymentProcessor() { return new UnboundRole(RoleDefinition.paymentProcessor); }
+
/** Returns the role definition of this bound role. */
public RoleDefinition definition() { return roleDefinition; }
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
index c4ce70a8f1e..c05936ee593 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/RoleDefinition.java
@@ -76,7 +76,9 @@ public enum RoleDefinition {
systemFlagsDeployer(Policy.systemFlagsDeploy, Policy.systemFlagsDryrun),
- systemFlagsDryrunner(Policy.systemFlagsDryrun);
+ systemFlagsDryrunner(Policy.systemFlagsDryrun),
+
+ paymentProcessor(Policy.paymentProcessor);
private final Set<RoleDefinition> parents;
private final Set<Policy> policies;
diff --git a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
index 8e278240a02..fdba1ab2680 100644
--- a/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
+++ b/controller-api/src/test/java/com/yahoo/vespa/hosted/controller/api/identifiers/IdentifierTest.java
@@ -107,11 +107,6 @@ public class IdentifierTest {
}
@Test
- public void user_tenant_id_does_not_contain_underscore() {
- assertEquals("by-under-score-user", new UserId("under_score_user").toTenantId().id());
- }
-
- @Test
public void dns_names_has_no_underscore() {
assertEquals("a-b-c", new ApplicationId("a_b_c").toDns());
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
index 2b4b251f536..c3a9a8484c9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/athenz/impl/AthenzFacade.java
@@ -243,6 +243,10 @@ public class AthenzFacade implements AccessControl {
return hasAccess(dryRun ? "dryrun" : "deploy", new AthenzResourceName(service.getDomain(), "system-flags").toResourceNameString(), identity);
}
+ public boolean hasPaymentCallbackAccess(AthenzIdentity identity) {
+ return hasAccess("callback", new AthenzResourceName(service.getDomain().getName(), "payment-notification-resource").toResourceNameString(), identity);
+ }
+
/**
* Used when creating tenancies. As there are no tenancy policies at this point,
* we cannot use {@link #hasTenantAdminAccess(AthenzIdentity, AthenzDomain)}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
index 596e19bfe65..71be06deec5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/InternalStepRunner.java
@@ -941,8 +941,8 @@ public class InternalStepRunner implements StepRunner {
Duration endpoint() { return Duration.ofMinutes(15); }
Duration endpointCertificate() { return Duration.ofMinutes(15); }
Duration tester() { return Duration.ofMinutes(30); }
- Duration nodesDown() { return Duration.ofMinutes(system.isCd() ? 30 : 60); }
- Duration noNodesDown() { return Duration.ofMinutes(system.isCd() ? 30 : 120); }
+ Duration nodesDown() { return Duration.ofMinutes(system.isCd() ? 20 : 60); }
+ Duration noNodesDown() { return Duration.ofMinutes(system.isCd() ? 20 : 120); }
Duration testerCertificate() { return Duration.ofMinutes(300); }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
index e3cebfad31c..921bf045873 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/NodeWithServices.java
@@ -53,7 +53,8 @@ public class NodeWithServices {
}
public boolean needsPlatformUpgrade() {
- return node.wantedVersion().isAfter(node.currentVersion());
+ return node.wantedVersion().isAfter(node.currentVersion())
+ || ! node.wantedDockerImage().equals(node.currentDockerImage());
}
public boolean needsReboot() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index be3f4e50dc7..d7ad96ec5e7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
@@ -29,7 +29,6 @@ import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
-import com.yahoo.vespa.athenz.api.AthenzPrincipal;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -106,7 +105,6 @@ import java.time.Duration;
import java.time.Instant;
import java.time.YearMonth;
import java.time.format.DateTimeParseException;
-import java.util.ArrayList;
import java.util.Arrays;
import java.util.Base64;
import java.util.Comparator;
@@ -205,7 +203,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse handleGET(Path path, HttpRequest request) {
if (path.matches("/application/v4/")) return root(request);
- if (path.matches("/application/v4/user")) return authenticatedUser(request);
if (path.matches("/application/v4/tenant")) return tenants(request);
if (path.matches("/application/v4/tenant/{tenant}")) return tenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/cost")) return tenantCost(path.get("tenant"), request);
@@ -248,7 +245,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
}
private HttpResponse handlePUT(Path path, HttpRequest request) {
- if (path.matches("/application/v4/user")) return new EmptyResponse();
if (path.matches("/application/v4/tenant/{tenant}")) return updateTenant(path.get("tenant"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}/global-rotation/override")) return setGlobalRotationOverride(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), false, request);
@@ -325,24 +321,7 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
private HttpResponse root(HttpRequest request) {
return recurseOverTenants(request)
? recursiveRoot(request)
- : new ResourceResponse(request, "user", "tenant");
- }
-
- // TODO jonmv: Move to Athenz API.
- private HttpResponse authenticatedUser(HttpRequest request) {
- Principal user = requireUserPrincipal(request);
-
- String userName = user instanceof AthenzPrincipal ? ((AthenzPrincipal) user).getIdentity().getName() : user.getName();
- List<Tenant> tenants = controller.tenants().asList(new Credentials(user));
-
- Slime slime = new Slime();
- Cursor response = slime.setObject();
- response.setString("user", userName);
- Cursor tenantsArray = response.setArray("tenants");
- for (Tenant tenant : tenants)
- tenantInTenantsListToSlime(tenant, request.getUri(), tenantsArray.addObject());
- response.setBool("tenantExists", true);
- return new SlimeJsonResponse(slime);
+ : new ResourceResponse(request, "tenant");
}
private HttpResponse tenants(HttpRequest request) {
@@ -1062,12 +1041,8 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// Add zone endpoints
var endpointArray = response.setArray("endpoints");
- var serviceUrls = new ArrayList<URI>();
for (var endpoint : controller.routing().endpointsOf(deploymentId)) {
toSlime(endpoint, endpoint.name(), endpointArray.addObject());
- if (endpoint.routingMethod() == RoutingMethod.shared) {
- serviceUrls.add(endpoint.url());
- }
}
// Add global endpoints
var globalEndpoints = controller.routing().endpointsOf(application, deploymentId.applicationId().instance())
@@ -1077,9 +1052,6 @@ public class ApplicationApiHandler extends LoggingRequestHandler {
// TODO(mpolden): Pass cluster name. Cluster that a global endpoint points to is not available at this level.
toSlime(endpoint, "", endpointArray.addObject());
}
- // TODO(mpolden): Remove this once all clients stop reading it
- Cursor serviceUrlArray = response.setArray("serviceUrls");
- serviceUrls.forEach(url -> serviceUrlArray.addString(url.toString()));
response.setString("nodes", withPath("/zone/v2/" + deploymentId.zoneId().environment() + "/" + deploymentId.zoneId().region() + "/nodes/v2/node/?&recursive=true&application=" + deploymentId.applicationId().tenant() + "." + deploymentId.applicationId().application() + "." + deploymentId.applicationId().instance(), request.getUri()).toString());
response.setString("yamasUrl", monitoringSystemUri(deploymentId).toString());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
index afe8d156d00..4d3cfacab14 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilter.java
@@ -107,7 +107,8 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
if (identity.getDomain().equals(SCREWDRIVER_DOMAIN) && application.isPresent() && tenant.isPresent())
futures.add(executor.submit(() -> {
- if (hasDeployerAccess(identity, ((AthenzTenant) tenant.get()).domain(), application.get()))
+ if ( tenant.get().type() == Tenant.Type.athenz
+ && hasDeployerAccess(identity, ((AthenzTenant) tenant.get()).domain(), application.get()))
roleMemberships.add(Role.buildService(tenant.get().name(), application.get()));
}));
@@ -116,6 +117,11 @@ public class AthenzRoleFilter extends JsonSecurityRequestFilterBase {
roleMemberships.add(Role.systemFlagsDeployer());
}));
+ futures.add(executor.submit(() -> {
+ if (athenz.hasPaymentCallbackAccess(identity))
+ roleMemberships.add(Role.paymentProcessor());
+ }));
+
// Run last request in handler thread to avoid creating extra thread.
if (athenz.hasSystemFlagsAccess(identity, /*dryrun*/true))
roleMemberships.add(Role.systemFlagsDryrunner());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
index 497a0ddf5a0..351f530f623 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java
@@ -788,6 +788,7 @@ public class ControllerTest {
@Test
public void testDeployWithRoutingGeneratorEndpoints() {
+ ((InMemoryFlagSource) tester.controller().flagSource()).withBooleanFlag(Flags.DISABLE_ROUTING_GENERATOR.id(), false);
var context = tester.newDeploymentContext();
var applicationPackage = new ApplicationPackageBuilder()
.upgradePolicy("default")
@@ -804,6 +805,7 @@ public class ControllerTest {
List.of(new RoutingEndpoint("http://legacy-endpoint", "hostname",
false, "upstreamName")));
}
+
// Defer load balancer provisioning in all environments so that routing controller uses routing generator
context.deferLoadBalancerProvisioningIn(zones.stream().map(ZoneId::environment).collect(Collectors.toSet()))
.submit(applicationPackage)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
index 204e0a1c1b8..9e9d27fd744 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/ConfigServerMock.java
@@ -6,6 +6,7 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeResources;
@@ -72,6 +73,7 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
private final Map<DeploymentId, ServiceConvergence> serviceStatus = new HashMap<>();
private final Set<ApplicationId> disallowConvergenceCheckApplications = new HashSet<>();
private final Version initialVersion = new Version(6, 1, 0);
+ private final DockerImage initialDockerImage = DockerImage.fromString("dockerImage:6.1.0");
private final Set<DeploymentId> suspendedApplications = new HashSet<>();
private final Map<ZoneId, Set<LoadBalancer>> loadBalancers = new HashMap<>();
private final Set<Environment> deferLoadBalancerProvisioning = new HashSet<>();
@@ -105,6 +107,8 @@ public class ConfigServerMock extends AbstractComponent implements ConfigServer
.parentHostname(parent.hostname())
.currentVersion(initialVersion)
.wantedVersion(initialVersion)
+ .currentDockerImage(initialDockerImage)
+ .wantedDockerImage(initialDockerImage)
.currentOsVersion(Version.emptyVersion)
.wantedOsVersion(Version.emptyVersion)
.resources(new NodeResources(2, 8, 50, 1, slow, remote))
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
index 632b8499e11..4aab21a44fe 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java
@@ -208,7 +208,10 @@ public class NodeRepositoryMock implements NodeRepository {
public void doUpgrade(DeploymentId deployment, Optional<HostName> hostName, Version version) {
modifyNodes(deployment, hostName, node -> {
assert node.wantedVersion().equals(version);
- return new Node.Builder(node).currentVersion(version).build();
+ return new Node.Builder(node)
+ .currentVersion(version)
+ .currentDockerImage(node.wantedDockerImage())
+ .build();
});
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index fd0981e8427..2752ba64b61 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
@@ -176,24 +176,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
.oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT)
.data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}"),
new File("tenant-without-applications.json"));
- // GET the authenticated user (with associated tenants)
- tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID),
- new File("user.json"));
- // TODO jonmv: Remove when dashboard is gone.
- // PUT a user tenant — does nothing
- tester.assertResponse(request("/application/v4/user", PUT).userIdentity(USER_ID),
- "");
-
- // GET the authenticated user which now exists (with associated tenants)
- tester.assertResponse(request("/application/v4/user", GET).userIdentity(USER_ID),
- new File("user.json"));
-
- // DELETE the user — it doesn't exist, so access control fails
- tester.assertResponse(request("/application/v4/tenant/by-myuser", DELETE).userIdentity(USER_ID),
- "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}", 403);
- // GET all tenants
- tester.assertResponse(request("/application/v4/tenant/", GET).userIdentity(USER_ID),
- new File("tenant-list.json"));
// GET list of months for a tenant
tester.assertResponse(request("/application/v4/tenant/tenant1/cost", GET).userIdentity(USER_ID).oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
@@ -783,11 +765,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
.userIdentity(USER_ID),
"{\"message\":\"Aborting run 2 of staging-test for tenant1.application1.instance1\"}");
- // GET user lists only tenants for the authenticated user
- tester.assertResponse(request("/application/v4/user", GET)
- .userIdentity(new UserId("other_user")),
- "{\"user\":\"other_user\",\"tenants\":[],\"tenantExists\":true}");
-
// OPTIONS return 200 OK
tester.assertResponse(request("/application/v4/", Request.Method.OPTIONS)
.userIdentity(USER_ID),
@@ -1108,14 +1085,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"error-code\":\"BAD_REQUEST\",\"message\":\"New tenant or application names must start with a letter, may contain no more than 20 characters, and may only contain lowercase letters, digits or dashes, but no double-dashes.\"}",
400);
- // POST (add) an Athenz tenant with by- prefix
- tester.assertResponse(request("/application/v4/tenant/by-tenant2", POST)
- .userIdentity(USER_ID)
- .data("{\"athensDomain\":\"domain1\", \"property\":\"property1\"}")
- .oktaAccessToken(OKTA_AT).oktaIdentityToken(OKTA_IT),
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Athenz tenant name cannot have prefix 'by-'\"}",
- 400);
-
// POST (add) an Athenz tenant with a reserved name
tester.assertResponse(request("/application/v4/tenant/hosted-vespa", POST)
.userIdentity(USER_ID)
@@ -1395,25 +1364,12 @@ public class ApplicationApiTest extends ControllerContainerTest {
createAthenzDomainWithAdmin(ATHENZ_TENANT_DOMAIN, tenantAdmin);
allowLaunchOfService(new com.yahoo.vespa.athenz.api.AthenzService(ATHENZ_TENANT_DOMAIN, "service"));
- // Create tenant
- // PUT (create) the authenticated user
- tester.assertResponse(request("/application/v4/user?user=new_user&domain=by", PUT)
- .userIdentity(userId), // Normalized to by-new-user by API
- "");
-
ApplicationPackage applicationPackage = new ApplicationPackageBuilder()
.athenzIdentity(com.yahoo.config.provision.AthenzDomain.from("domain1"), com.yahoo.config.provision.AthenzService.from("service"))
.build();
- // POST (deploy) an application to a dev zone fails because user tenant is used — these do not exist.
- MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
- tester.assertResponse(request("/application/v4/tenant/by-new-user/application/application1/environment/dev/region/us-west-1/instance/default", POST)
- .data(entity)
- .userIdentity(userId),
- "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}",
- 403);
-
createTenantAndApplication();
+ MultiPartStreamer entity = createApplicationDeployData(applicationPackage, true);
// POST (deploy) an application to dev through a deployment job, with user instance and a proper tenant
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST)
.data(entity)
@@ -1426,13 +1382,6 @@ public class ApplicationApiTest extends ControllerContainerTest {
.domains.get(ATHENZ_TENANT_DOMAIN)
.admin(HostedAthenzIdentities.from(userId));
- // POST (deploy) an application to a dev zone fails because user tenant is used — these do not exist.
- tester.assertResponse(request("/application/v4/tenant/by-new-user/application/application1/environment/dev/region/us-west-1/instance/default", POST)
- .data(entity)
- .userIdentity(userId),
- "{\n \"code\" : 403,\n \"message\" : \"Access denied\"\n}",
- 403);
-
// POST (deploy) an application to dev through a deployment job
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/new-user/deploy/dev-us-east-1", POST)
.data(entity)
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
index cd47859c7cc..63e6e4b3937 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-with-routing-policy.json
@@ -20,9 +20,6 @@
"routingMethod": "shared"
}
],
- "serviceUrls": [
- "https://instance1--application1--tenant1.us-west-1.vespa.oath.cloud:4443/"
- ],
"nodes": "http://localhost:8080/zone/v2/prod/us-west-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
"yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-west-1&application=tenant1.application1.instance1",
"version": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
index 726df575028..928525a20d1 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment.json
@@ -20,9 +20,6 @@
"routingMethod": "shared"
}
],
- "serviceUrls": [
- "https://instance1--application1--tenant1.us-central-1.vespa.oath.cloud:4443/"
- ],
"nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
"yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1.instance1",
"version": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
index 7c231beb5ed..83fa1983957 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/dev-us-east-1.json
@@ -13,9 +13,6 @@
"routingMethod": "shared"
}
],
- "serviceUrls": [
- "https://instance1--application1--tenant1.us-east-1.dev.vespa.oath.cloud:4443/"
- ],
"nodes": "http://localhost:8080/zone/v2/dev/us-east-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
"yamasUrl": "http://monitoring-system.test/?environment=dev&region=us-east-1&application=tenant1.application1.instance1",
"version": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
index 41f3908f12f..4ffe809297d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/prod-us-central-1.json
@@ -23,9 +23,6 @@
"routingMethod": "shared"
}
],
- "serviceUrls": [
- "https://instance1--application1--tenant1.us-central-1.vespa.oath.cloud:4443/"
- ],
"nodes": "http://localhost:8080/zone/v2/prod/us-central-1/nodes/v2/node/%3F&recursive=true&application=tenant1.application1.instance1",
"yamasUrl": "http://monitoring-system.test/?environment=prod&region=us-central-1&application=tenant1.application1.instance1",
"version": "(ignore)",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/root.json
index 986245decca..d63a7ba7d56 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/root.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/root.json
@@ -1,9 +1,6 @@
{
"resources":[
{
- "url":"http://localhost:8080/application/v4/user/"
- },
- {
"url":"http://localhost:8080/application/v4/tenant/"
}
]
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user-which-exists.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user-which-exists.json
deleted file mode 100644
index f2703677738..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user-which-exists.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "user": "myuser",
- "tenants": @include(tenant-list-with-user.json),
- "tenantExists": true
-} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json
deleted file mode 100644
index 9902267dbb5..00000000000
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/user.json
+++ /dev/null
@@ -1,5 +0,0 @@
-{
- "user": "myuser",
- "tenants": @include(tenant-list.json),
- "tenantExists": true
-} \ No newline at end of file
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
index c49f7a90194..5e50e80b7a7 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/filter/AthenzRoleFilterTest.java
@@ -70,13 +70,13 @@ public class AthenzRoleFilterTest {
public void testTranslations() throws Exception {
// Hosted operators are always members of the hostedOperator role.
- assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.hostedSupporter()),
+ assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()),
filter.roles(HOSTED_OPERATOR, NO_CONTEXT_PATH));
- assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.hostedSupporter()),
+ assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()),
filter.roles(HOSTED_OPERATOR, TENANT_CONTEXT_PATH));
- assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.hostedSupporter()),
+ assertEquals(Set.of(Role.hostedOperator(), Role.systemFlagsDeployer(), Role.systemFlagsDryrunner(), Role.paymentProcessor(), Role.hostedSupporter()),
filter.roles(HOSTED_OPERATOR, APPLICATION_CONTEXT_PATH));
// Tenant admins are members of the athenzTenantAdmin role within their tenant subtree.
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
index 6db5bc9f523..51466e5b1e2 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/user/UserApiTest.java
@@ -71,11 +71,6 @@ public class UserApiTest extends ControllerContainerCloudTest {
.data("{\"token\":\"hello\"}"),
new File("tenant-without-applications.json"));
- // PUT a tenant is ignored.
- tester.assertResponse(request("/application/v4/user/", PUT)
- .roles(operator),
- "", 200);
-
// GET at user/v1 root fails as no access control is defined there.
tester.assertResponse(request("/user/v1/"),
accessDenied, 403);
diff --git a/dist/vespa.spec b/dist/vespa.spec
index 4281ce243fa..c89cd42df0f 100644
--- a/dist/vespa.spec
+++ b/dist/vespa.spec
@@ -8,6 +8,7 @@
%define _vespa_deps_prefix /opt/vespa-deps
%define _vespa_user vespa
%define _vespa_group vespa
+%undefine _vespa_user_uid
%define _create_vespa_group 1
%define _create_vespa_user 1
%define _create_vespa_service 1
@@ -103,7 +104,7 @@ BuildRequires: gtest-devel
BuildRequires: gmock-devel
%endif
%endif
-BuildRequires: xxhash-devel >= 0.6.5
+BuildRequires: xxhash-devel >= 0.7.3
BuildRequires: openblas-devel
BuildRequires: lz4-devel
BuildRequires: libzstd-devel
@@ -143,7 +144,7 @@ Requires: perl-URI
Requires: valgrind
Requires: Judy
Requires: xxhash
-Requires: xxhash-libs >= 0.6.5
+Requires: xxhash-libs >= 0.7.3
%if 0%{?el8}
Requires: openblas
%else
@@ -207,17 +208,107 @@ Requires: llvm-libs >= 10.0.0
%define _extra_link_directory %{_vespa_deps_prefix}/lib64
%define _extra_include_directory %{_vespa_deps_prefix}/include;/usr/include/openblas
%endif
-Requires: java-11-openjdk
-Requires(pre): shadow-utils
+Requires: %{name}-base = %{version}-%{release}
+Requires: %{name}-base-libs = %{version}-%{release}
+Requires: %{name}-clients = %{version}-%{release}
+Requires: %{name}-config-model-fat = %{version}-%{release}
+Requires: %{name}-jars = %{version}-%{release}
+Requires: %{name}-malloc = %{version}-%{release}
+Requires: %{name}-tools = %{version}-%{release}
# Ugly workaround because vespamalloc/src/vespamalloc/malloc/mmap.cpp uses the private
# _dl_sym function.
-Provides: libc.so.6(GLIBC_PRIVATE)(64bit)
+%global __requires_exclude ^libc\\.so\\.6\\(GLIBC_PRIVATE\\)\\(64bit\\)$
+
%description
Vespa - The open big data serving engine
+%package base
+
+Summary: Vespa - The open big data serving engine - base
+
+Requires: java-11-openjdk
+Requires: perl
+Requires: perl-Getopt-Long
+Requires(pre): shadow-utils
+
+%description base
+
+Vespa - The open big data serving engine - base
+
+%package base-libs
+
+Summary: Vespa - The open big data serving engine - base C++ libs
+
+Requires: xxhash-libs >= 0.7.3
+Requires: lz4
+Requires: libzstd
+%if 0%{?el7}
+Requires: vespa-openssl >= 1.1.1c-1
+%else
+Requires: openssl-libs
+%endif
+
+%description base-libs
+
+Vespa - The open big data serving engine - base C++ libs
+
+%package clients
+
+Summary: Vespa - The open big data serving engine - clients
+
+%description clients
+
+Vespa - The open big data serving engine - clients
+
+%package config-model-fat
+
+Summary: Vespa - The open big data serving engine - config models
+
+%description config-model-fat
+
+Vespa - The open big data serving engine - config models
+
+%package node-admin
+
+Summary: Vespa - The open big data serving engine - node-admin
+
+Requires: %{name}-base = %{version}-%{release}
+Requires: %{name}-jars = %{version}-%{release}
+
+%description node-admin
+
+Vespa - The open big data serving engine - node-admin
+
+%package jars
+
+Summary: Vespa - The open big data serving engine - shared java jar files
+
+%description jars
+
+Vespa - The open big data serving engine - shared java jar files
+
+%package malloc
+
+Summary: Vespa - The open big data serving engine - malloc library
+
+%description malloc
+
+Vespa - The open big data serving engine - malloc library
+
+%package tools
+
+Summary: Vespa - The open big data serving engine - tools
+
+Requires: %{name}-base = %{version}-%{release}
+Requires: %{name}-base-libs = %{version}-%{release}
+
+%description tools
+
+Vespa - The open big data serving engine - tools
+
%prep
%if 0%{?installdir:1}
%setup -c -D -T
@@ -245,7 +336,7 @@ cmake3 -DCMAKE_INSTALL_PREFIX=%{_prefix} \
-DCMAKE_PREFIX_PATH=%{_vespa_deps_prefix} \
-DEXTRA_LINK_DIRECTORY="%{_extra_link_directory}" \
-DEXTRA_INCLUDE_DIRECTORY="%{_extra_include_directory}" \
- -DCMAKE_INSTALL_RPATH="%{_prefix}/lib64%{?_extra_link_directory:;%{_extra_link_directory}};/usr/lib/jvm/jre-11-openjdk/lib" \
+ -DCMAKE_INSTALL_RPATH="%{_prefix}/lib64%{?_extra_link_directory:;%{_extra_link_directory}}" \
%{?_vespa_llvm_version:-DVESPA_LLVM_VERSION="%{_vespa_llvm_version}"} \
-DVESPA_USER=%{_vespa_user} \
-DVESPA_UNPRIVILEGED=no \
@@ -269,16 +360,18 @@ cp %{buildroot}/%{_prefix}/etc/systemd/system/vespa.service %{buildroot}/usr/lib
cp %{buildroot}/%{_prefix}/etc/systemd/system/vespa-configserver.service %{buildroot}/usr/lib/systemd/system
%endif
+ln -s /usr/lib/jvm/jre-11-openjdk %{buildroot}/%{_prefix}/jdk
+
%clean
rm -rf $RPM_BUILD_ROOT
-%pre
+%pre base
%if %{_create_vespa_group}
getent group %{_vespa_group} >/dev/null || groupadd -r %{_vespa_group}
%endif
%if %{_create_vespa_user}
getent passwd %{_vespa_user} >/dev/null || \
- useradd -r -g %{_vespa_group} --home-dir %{_prefix} -s /sbin/nologin \
+ useradd -r %{?_vespa_user_uid:-u %{_vespa_user_uid}} -g %{_vespa_group} --home-dir %{_prefix} -s /sbin/nologin \
-c "Create owner of all Vespa data files" %{_vespa_user}
%endif
echo "pathmunge %{_prefix}/bin" > /etc/profile.d/vespa.sh
@@ -297,11 +390,13 @@ exit 0
%systemd_preun vespa-configserver.service
%endif
-%postun
%if %{_create_vespa_service}
+%postun
%systemd_postun_with_restart vespa.service
%systemd_postun_with_restart vespa-configserver.service
%endif
+
+%postun base
if [ $1 -eq 0 ]; then # this is an uninstallation
rm -f /etc/profile.d/vespa.sh
%if %{_create_vespa_user}
@@ -311,6 +406,19 @@ if [ $1 -eq 0 ]; then # this is an uninstallation
! getent group %{_vespa_group} >/dev/null || groupdel %{_vespa_group}
%endif
fi
+# Keep modifications to conf/vespa/default-env.txt across
+# package uninstall + install.
+if test -f %{_prefix}/conf/vespa/default-env.txt.rpmsave
+then
+ if test -f %{_prefix}/conf/vespa/default-env.txt
+ then
+ # Temporarily remove default-env.txt.rpmsave when
+ # default-env.txt exists
+ rm -f %{_prefix}/conf/vespa/default-env.txt.rpmsave
+ else
+ mv %{_prefix}/conf/vespa/default-env.txt.rpmsave %{_prefix}/conf/vespa/default-env.txt
+ fi
+fi
%files
%if %{_defattr_is_vespa_vespa}
@@ -319,20 +427,73 @@ fi
%doc
%dir %{_prefix}
%{_prefix}/bin
+%exclude %{_prefix}/bin/vespa-destination
+%exclude %{_prefix}/bin/vespa-document-statistics
+%exclude %{_prefix}/bin/vespa-fbench
+%exclude %{_prefix}/bin/vespa-feeder
+%exclude %{_prefix}/bin/vespa-get
+%exclude %{_prefix}/bin/vespa-logfmt
+%exclude %{_prefix}/bin/vespa-query-profile-dump-tool
+%exclude %{_prefix}/bin/vespa-stat
+%exclude %{_prefix}/bin/vespa-security-env
+%exclude %{_prefix}/bin/vespa-summary-benchmark
+%exclude %{_prefix}/bin/vespa-visit
+%exclude %{_prefix}/bin/vespa-visit-target
%dir %{_prefix}/conf
%{_prefix}/conf/configserver
%{_prefix}/conf/configserver-app
+%exclude %{_prefix}/conf/configserver-app/components/config-model-fat.jar
+%exclude %{_prefix}/conf/configserver-app/config-models.xml
%dir %{_prefix}/conf/logd
-%{_prefix}/conf/node-admin-app
%dir %{_prefix}/conf/vespa
%dir %attr(-,%{_vespa_user},-) %{_prefix}/conf/zookeeper
%dir %{_prefix}/etc
%{_prefix}/etc/systemd
%{_prefix}/etc/vespa
+%exclude %{_prefix}/etc/vespamalloc.conf
%{_prefix}/include
-%{_prefix}/lib
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/application-model-jar-with-dependencies.jar
+%{_prefix}/lib/jars/application-preprocessor-jar-with-dependencies.jar
+%{_prefix}/lib/jars/athenz-identity-provider-service-jar-with-dependencies.jar
+%{_prefix}/lib/jars/clustercontroller-apps-jar-with-dependencies.jar
+%{_prefix}/lib/jars/clustercontroller-apputil-jar-with-dependencies.jar
+%{_prefix}/lib/jars/clustercontroller-core-jar-with-dependencies.jar
+%{_prefix}/lib/jars/clustercontroller-utils-jar-with-dependencies.jar
+%{_prefix}/lib/jars/config-models
+%{_prefix}/lib/jars/config-proxy-jar-with-dependencies.jar
+%{_prefix}/lib/jars/configserver-flags-jar-with-dependencies.jar
+%{_prefix}/lib/jars/configserver-jar-with-dependencies.jar
+%{_prefix}/lib/jars/document.jar
+%{_prefix}/lib/jars/filedistribution-jar-with-dependencies.jar
+%{_prefix}/lib/jars/jdisc_jetty.jar
+%{_prefix}/lib/jars/logserver-jar-with-dependencies.jar
+%{_prefix}/lib/jars/metrics-proxy-jar-with-dependencies.jar
+%{_prefix}/lib/jars/node-repository-jar-with-dependencies.jar
+%{_prefix}/lib/jars/orchestrator-jar-with-dependencies.jar
+%{_prefix}/lib/jars/predicate-search-jar-with-dependencies.jar
+%{_prefix}/lib/jars/searchlib.jar
+%{_prefix}/lib/jars/searchlib-jar-with-dependencies.jar
+%{_prefix}/lib/jars/service-monitor-jar-with-dependencies.jar
+%{_prefix}/lib/jars/vespa_feed_perf-jar-with-dependencies.jar
+%{_prefix}/lib/jars/vespa-testrunner-components.jar
+%{_prefix}/lib/jars/vespa-testrunner-components-jar-with-dependencies.jar
+%{_prefix}/lib/jars/zookeeper-command-line-client-jar-with-dependencies.jar
+%{_prefix}/lib/perl5
%{_prefix}/lib64
+%exclude %{_prefix}/lib64/libfastos.so
+%exclude %{_prefix}/lib64/libfnet.so
+%exclude %{_prefix}/lib64/libstaging_vespalib.so
+%exclude %{_prefix}/lib64/libvespadefaults.so
+%exclude %{_prefix}/lib64/libvespalib.so
+%exclude %{_prefix}/lib64/libvespalog.so
+%exclude %{_prefix}/lib64/vespa
%{_prefix}/libexec
+%exclude %{_prefix}/libexec/vespa/common-env.sh
+%exclude %{_prefix}/libexec/vespa/node-admin.sh
+%exclude %{_prefix}/libexec/vespa/standalone-container.sh
+%exclude %{_prefix}/libexec/vespa/vespa-curl-wrapper
%dir %attr(1777,-,-) %{_prefix}/logs
%dir %attr(1777,%{_vespa_user},-) %{_prefix}/logs/vespa
%dir %attr(-,%{_vespa_user},-) %{_prefix}/logs/vespa/configserver
@@ -349,11 +510,157 @@ fi
%dir %attr(-,%{_vespa_user},-) %{_prefix}/var/db/vespa/logcontrol
%dir %attr(-,%{_vespa_user},-) %{_prefix}/var/zookeeper
%config(noreplace) %{_prefix}/conf/logd/logd.cfg
-%config(noreplace) %{_prefix}/conf/vespa/default-env.txt
-%config(noreplace) %{_prefix}/etc/vespamalloc.conf
%if %{_create_vespa_service}
%attr(644,root,root) /usr/lib/systemd/system/vespa.service
%attr(644,root,root) /usr/lib/systemd/system/vespa-configserver.service
%endif
+%files base
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/bin
+%{_prefix}/bin/vespa-logfmt
+%{_prefix}/bin/vespa-security-env
+%dir %{_prefix}/conf
+%dir %{_prefix}/conf/vespa
+%config(noreplace) %{_prefix}/conf/vespa/default-env.txt
+%{_prefix}/jdk
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/security-tools-jar-with-dependencies.jar
+%dir %{_prefix}/libexec
+%dir %{_prefix}/libexec/vespa
+%{_prefix}/libexec/vespa/common-env.sh
+%{_prefix}/libexec/vespa/vespa-curl-wrapper
+
+%files base-libs
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/lib64
+%{_prefix}/lib64/libfastos.so
+%{_prefix}/lib64/libfnet.so
+%{_prefix}/lib64/libstaging_vespalib.so
+%{_prefix}/lib64/libvespadefaults.so
+%{_prefix}/lib64/libvespalib.so
+%{_prefix}/lib64/libvespalog.so
+
+%files clients
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/vespa-http-client-jar-with-dependencies.jar
+
+%files config-model-fat
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/conf
+%dir %{_prefix}/conf/configserver-app
+%dir %{_prefix}/conf/configserver-app/components
+%{_prefix}/conf/configserver-app/components/config-model-fat.jar
+%{_prefix}/conf/configserver-app/config-models.xml
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/config-model-fat.jar
+
+%files node-admin
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/conf
+%{_prefix}/conf/node-admin-app
+%dir %{_prefix}/libexec
+%dir %{_prefix}/libexec/vespa
+%{_prefix}/libexec/vespa/node-admin.sh
+
+%files jars
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/aopalliance-repackaged-*.jar
+%{_prefix}/lib/jars/bcpkix-jdk15on-*.jar
+%{_prefix}/lib/jars/bcprov-jdk15on-*.jar
+%{_prefix}/lib/jars/component-jar-with-dependencies.jar
+%{_prefix}/lib/jars/config-bundle-jar-with-dependencies.jar
+%{_prefix}/lib/jars/configdefinitions-jar-with-dependencies.jar
+%{_prefix}/lib/jars/config-model-api-jar-with-dependencies.jar
+%{_prefix}/lib/jars/config-model-jar-with-dependencies.jar
+%{_prefix}/lib/jars/config-provisioning-jar-with-dependencies.jar
+%{_prefix}/lib/jars/container-disc-jar-with-dependencies.jar
+%{_prefix}/lib/jars/container-jersey2-jar-with-dependencies.jar
+%{_prefix}/lib/jars/container-search-and-docproc-jar-with-dependencies.jar
+%{_prefix}/lib/jars/container-search-gui-jar-with-dependencies.jar
+%{_prefix}/lib/jars/defaults-jar-with-dependencies.jar
+%{_prefix}/lib/jars/docprocs-jar-with-dependencies.jar
+%{_prefix}/lib/jars/flags-jar-with-dependencies.jar
+%{_prefix}/lib/jars/hk2-*.jar
+%{_prefix}/lib/jars/jackson-*.jar
+%{_prefix}/lib/jars/javassist-*.jar
+%{_prefix}/lib/jars/javax.*.jar
+%{_prefix}/lib/jars/jdisc_core-jar-with-dependencies.jar
+%{_prefix}/lib/jars/jdisc_http_service-jar-with-dependencies.jar
+%{_prefix}/lib/jars/jdisc-security-filters-jar-with-dependencies.jar
+%{_prefix}/lib/jars/jersey-*.jar
+%{_prefix}/lib/jars/jetty-*.jar
+%{_prefix}/lib/jars/mimepull-*.jar
+%{_prefix}/lib/jars/model-evaluation-jar-with-dependencies.jar
+%{_prefix}/lib/jars/model-integration-jar-with-dependencies.jar
+%{_prefix}/lib/jars/osgi-resource-locator-*.jar
+%{_prefix}/lib/jars/security-utils-jar-with-dependencies.jar
+%{_prefix}/lib/jars/simplemetrics-jar-with-dependencies.jar
+%{_prefix}/lib/jars/standalone-container-jar-with-dependencies.jar
+%{_prefix}/lib/jars/validation-api-*.jar
+%{_prefix}/lib/jars/vespa-athenz-jar-with-dependencies.jar
+%{_prefix}/lib/jars/vespaclient-container-plugin-jar-with-dependencies.jar
+%{_prefix}/lib/jars/vespajlib.jar
+%{_prefix}/lib/jars/zkfacade-jar-with-dependencies.jar
+%{_prefix}/lib/jars/zookeeper-server-*-jar-with-dependencies.jar
+%{_prefix}/lib/jars/zookeeper-server-common-jar-with-dependencies.jar
+%{_prefix}/lib/jars/zookeeper-server-jar-with-dependencies.jar
+%dir %{_prefix}/libexec
+%dir %{_prefix}/libexec/vespa
+%{_prefix}/libexec/vespa/standalone-container.sh
+
+%files malloc
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/etc
+%config(noreplace) %{_prefix}/etc/vespamalloc.conf
+%dir %{_prefix}/lib64
+%{_prefix}/lib64/vespa
+
+%files tools
+%if %{_defattr_is_vespa_vespa}
+%defattr(-,%{_vespa_user},%{_vespa_group},-)
+%endif
+%dir %{_prefix}
+%dir %{_prefix}/bin
+%{_prefix}/bin/vespa-destination
+%{_prefix}/bin/vespa-document-statistics
+%{_prefix}/bin/vespa-fbench
+%{_prefix}/bin/vespa-feeder
+%{_prefix}/bin/vespa-get
+%{_prefix}/bin/vespa-query-profile-dump-tool
+%{_prefix}/bin/vespa-stat
+%{_prefix}/bin/vespa-summary-benchmark
+%{_prefix}/bin/vespa-visit
+%{_prefix}/bin/vespa-visit-target
+%dir %{_prefix}/lib
+%dir %{_prefix}/lib/jars
+%{_prefix}/lib/jars/vespaclient-java-jar-with-dependencies.jar
+
%changelog
diff --git a/document/src/tests/documentselectparsertest.cpp b/document/src/tests/documentselectparsertest.cpp
index 110153954af..9ac402f56ef 100644
--- a/document/src/tests/documentselectparsertest.cpp
+++ b/document/src/tests/documentselectparsertest.cpp
@@ -18,6 +18,7 @@
#include <vespa/document/select/compare.h>
#include <vespa/document/select/operator.h>
#include <vespa/document/select/parse_utils.h>
+#include <vespa/document/select/parser_limits.h>
#include <vespa/vespalib/util/exceptions.h>
#include <limits>
#include <gtest/gtest.h>
@@ -33,6 +34,8 @@ protected:
std::vector<Document::SP > _doc;
std::vector<DocumentUpdate::SP > _update;
+ ~DocumentSelectParserTest();
+
Document::SP createDoc(
const std::string& doctype, const std::string& id, uint32_t hint,
double hfloat, const std::string& hstr, const std::string& cstr,
@@ -64,6 +67,7 @@ protected:
void testDocumentUpdates4();
};
+DocumentSelectParserTest::~DocumentSelectParserTest() = default;
namespace {
std::shared_ptr<const DocumentTypeRepo> _repo;
@@ -1247,17 +1251,17 @@ TEST_F(DocumentSelectParserTest, testThatSimpleFieldValuesHaveCorrectFieldName)
TEST_F(DocumentSelectParserTest, testThatComplexFieldValuesHaveCorrectFieldNames)
{
- EXPECT_EQ(
- vespalib::string("headerval"),
- parseFieldValue("testdoctype1.headerval{test}")->getRealFieldName());
+ EXPECT_EQ(vespalib::string("headerval"),
+ parseFieldValue("testdoctype1.headerval{test}")->getRealFieldName());
- EXPECT_EQ(
- vespalib::string("headerval"),
- parseFieldValue("testdoctype1.headerval[42]")->getRealFieldName());
+ EXPECT_EQ(vespalib::string("headerval"),
+ parseFieldValue("testdoctype1.headerval[42]")->getRealFieldName());
- EXPECT_EQ(
- vespalib::string("headerval"),
- parseFieldValue("testdoctype1.headerval.meow.meow{test}")->getRealFieldName());
+ EXPECT_EQ(vespalib::string("headerval"),
+ parseFieldValue("testdoctype1.headerval.meow.meow{test}")->getRealFieldName());
+
+ EXPECT_EQ(vespalib::string("headerval"),
+ parseFieldValue("testdoctype1.headerval .meow.meow{test}")->getRealFieldName());
}
namespace {
@@ -1603,4 +1607,64 @@ TEST_F(DocumentSelectParserTest, redundant_glob_wildcards_are_collapsed_into_min
EXPECT_EQ(GlobOperator::convertToRegex("*?*?*?*"), "..*..*."); // Don't try this at home, kids!
}
+TEST_F(DocumentSelectParserTest, recursion_depth_is_bounded_for_field_exprs) {
+ createDocs();
+ std::string expr = "testdoctype1";
+ for (size_t i = 0; i < 50000; ++i) {
+ expr += ".foo";
+ }
+ expr += ".hash() != 0";
+ verifyFailedParse(expr, "ParsingFailedException: expression is too deeply nested (max 1024 levels)");
+}
+
+TEST_F(DocumentSelectParserTest, recursion_depth_is_bounded_for_arithmetic_exprs) {
+ createDocs();
+ std::string expr = "1";
+ for (size_t i = 0; i < 50000; ++i) {
+ expr += "+1";
+ }
+ expr += " != 0";
+ verifyFailedParse(expr, "ParsingFailedException: expression is too deeply nested (max 1024 levels)");
+}
+
+TEST_F(DocumentSelectParserTest, recursion_depth_is_bounded_for_binary_logical_exprs) {
+ createDocs();
+ // Also throw in some comparisons to ensure they carry over the max depth.
+ std::string expr = "1 == 2";
+ std::string cmp_subexpr = "3 != 4";
+ for (size_t i = 0; i < 10000; ++i) {
+ expr += (i % 2 == 0 ? " and " : " or ") + cmp_subexpr;
+ }
+ verifyFailedParse(expr, "ParsingFailedException: expression is too deeply nested (max 1024 levels)");
+}
+
+TEST_F(DocumentSelectParserTest, recursion_depth_is_bounded_for_unary_logical_exprs) {
+ createDocs();
+ std::string expr;
+ for (size_t i = 0; i < 10000; ++i) {
+ expr += "not ";
+ }
+ expr += "true";
+ verifyFailedParse(expr, "ParsingFailedException: expression is too deeply nested (max 1024 levels)");
+}
+
+TEST_F(DocumentSelectParserTest, selection_has_upper_limit_on_input_size) {
+ createDocs();
+ std::string expr = ("testdoctype1.a_biii"
+ + std::string(select::ParserLimits::MaxSelectionByteSize, 'i')
+ + "iiig_identifier");
+ verifyFailedParse(expr, "ParsingFailedException: expression is too large to be "
+ "parsed (max 1048576 bytes, got 1048610)");
+}
+
+TEST_F(DocumentSelectParserTest, lexing_does_not_have_superlinear_time_complexity) {
+ createDocs();
+ std::string expr = ("testdoctype1.hstringval == 'a_biii"
+ + std::string(select::ParserLimits::MaxSelectionByteSize - 100, 'i')
+ + "iiig string'");
+ // If the lexer is not compiled with the appropriate options, this will take a long time.
+ // A really, really long time.
+ PARSE(expr, *_doc[0], False);
+}
+
} // document
diff --git a/document/src/vespa/document/select/CMakeLists.txt b/document/src/vespa/document/select/CMakeLists.txt
index 81e5d86675c..f210e8abdd7 100644
--- a/document/src/vespa/document/select/CMakeLists.txt
+++ b/document/src/vespa/document/select/CMakeLists.txt
@@ -36,6 +36,7 @@ vespa_add_library(document_select OBJECT
parser.cpp
parse_utils.cpp
parsing_failed_exception.cpp
+ parser_limits.cpp
${BISON_DocSelParser_OUTPUTS}
${FLEX_DocSelLexer_OUTPUTS}
AFTER
diff --git a/document/src/vespa/document/select/branch.cpp b/document/src/vespa/document/select/branch.cpp
index b3d5f97ccab..9104e2c5544 100644
--- a/document/src/vespa/document/select/branch.cpp
+++ b/document/src/vespa/document/select/branch.cpp
@@ -8,7 +8,7 @@
namespace document::select {
And::And(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name)
- : Branch(name ? name : "and"),
+ : Branch(name ? name : "and", std::max(left->max_depth(), right->max_depth()) + 1),
_left(std::move(left)),
_right(std::move(right))
{
@@ -54,7 +54,7 @@ And::trace(const Context& context, std::ostream& out) const
}
Or::Or(std::unique_ptr<Node> left, std::unique_ptr<Node> right, const char* name)
- : Branch(name ? name : "or"),
+ : Branch(name ? name : "or", std::max(left->max_depth(), right->max_depth()) + 1),
_left(std::move(left)),
_right(std::move(right))
{
@@ -100,7 +100,7 @@ Or::trace(const Context& context, std::ostream& out) const
}
Not::Not(std::unique_ptr<Node> child, const char* name)
- : Branch(name ? name : "not"),
+ : Branch(name ? name : "not", child->max_depth() + 1),
_child(std::move(child))
{
assert(_child.get());
diff --git a/document/src/vespa/document/select/branch.h b/document/src/vespa/document/select/branch.h
index 8637b41de89..77ed74030b5 100644
--- a/document/src/vespa/document/select/branch.h
+++ b/document/src/vespa/document/select/branch.h
@@ -19,7 +19,8 @@ namespace document::select {
class Branch : public Node
{
public:
- Branch(vespalib::stringref name) : Node(name) {}
+ explicit Branch(vespalib::stringref name) : Node(name) {}
+ Branch(vespalib::stringref name, uint32_t max_depth) : Node(name, max_depth) {}
bool isLeafNode() const override { return false; }
};
@@ -30,7 +31,7 @@ class And : public Branch
std::unique_ptr<Node> _right;
public:
And(std::unique_ptr<Node> left, std::unique_ptr<Node> right,
- const char* name = 0);
+ const char* name = nullptr);
ResultList contains(const Context& context) const override {
return (_left->contains(context) && _right->contains(context));
@@ -53,7 +54,7 @@ class Or : public Branch
std::unique_ptr<Node> _right;
public:
Or(std::unique_ptr<Node> left, std::unique_ptr<Node> right,
- const char* name = 0);
+ const char* name = nullptr);
ResultList contains(const Context& context) const override {
return (_left->contains(context) || _right->contains(context));
@@ -74,7 +75,7 @@ class Not : public Branch
{
std::unique_ptr<Node> _child;
public:
- Not(std::unique_ptr<Node> child, const char* name = 0);
+ Not(std::unique_ptr<Node> child, const char* name = nullptr);
ResultList contains(const Context& context) const override { return !_child->contains(context); }
ResultList trace(const Context&, std::ostream& trace) const override;
diff --git a/document/src/vespa/document/select/compare.cpp b/document/src/vespa/document/select/compare.cpp
index 7db40929a64..caef1bdd250 100644
--- a/document/src/vespa/document/select/compare.cpp
+++ b/document/src/vespa/document/select/compare.cpp
@@ -15,7 +15,7 @@ Compare::Compare(std::unique_ptr<ValueNode> left,
const Operator& op,
std::unique_ptr<ValueNode> right,
const BucketIdFactory& bucketIdFactory)
- : Node("Compare"),
+ : Node("Compare", std::max(left->max_depth(), right->max_depth()) + 1),
_left(std::move(left)),
_right(std::move(right)),
_operator(op),
diff --git a/document/src/vespa/document/select/grammar/lexer.ll b/document/src/vespa/document/select/grammar/lexer.ll
index bd011c8ebf6..1222aac02a2 100644
--- a/document/src/vespa/document/select/grammar/lexer.ll
+++ b/document/src/vespa/document/select/grammar/lexer.ll
@@ -7,6 +7,13 @@
%option noyywrap nounput
%option yyclass="document::select::DocSelScanner"
+ /* Flex lexer must be compiled with batch mode (as opposed to interactive mode)
+ * or parsing of large tokens appears to trigger superlinear time complexity.
+ * Also use full, non-compressed lookup tables for maximum performance.
+ */
+%option batch
+%option full
+
/* Used to track source locations, see https://github.com/bingmann/flex-bison-cpp-example/blob/master/src/scanner.ll */
%{
#define YY_USER_ACTION yyloc->columns(yyleng);
diff --git a/document/src/vespa/document/select/node.h b/document/src/vespa/document/select/node.h
index 48a64ae63f5..9a3b687d81c 100644
--- a/document/src/vespa/document/select/node.h
+++ b/document/src/vespa/document/select/node.h
@@ -12,6 +12,7 @@
#include "resultlist.h"
#include "context.h"
+#include "parser_limits.h"
namespace document::select {
@@ -21,19 +22,33 @@ class Node : public Printable
{
protected:
vespalib::string _name;
+ uint32_t _max_depth;
bool _parentheses; // Set to true if parentheses was used around this part
// Set such that we can recreate original query in print.
public:
typedef std::unique_ptr<Node> UP;
typedef std::shared_ptr<Node> SP;
- Node(vespalib::stringref name) : _name(name), _parentheses(false) {}
- ~Node() override {}
+ Node(vespalib::stringref name, uint32_t max_depth)
+ : _name(name), _max_depth(max_depth), _parentheses(false)
+ {
+ throw_parse_error_if_max_depth_exceeded();
+ }
- void setParentheses() { _parentheses = true; }
+ explicit Node(vespalib::stringref name)
+ : _name(name), _max_depth(1), _parentheses(false)
+ {}
+ ~Node() override = default;
- void clearParentheses() { _parentheses = false; }
+ // Depth is explicitly tracked to limit recursion to a sane maximum when building and
+ // processing ASTs, as the Bison framework does not have anything useful for us there.
+ // The AST is built from the leaves up towards the root, so we can cheaply track depth
+ // of subtrees in O(1) time per node by computing a node's own depth based on immediate
+ // children at node construction time.
+ [[nodiscard]] uint32_t max_depth() const noexcept { return _max_depth; }
+ void setParentheses() { _parentheses = true; }
+ void clearParentheses() { _parentheses = false; }
bool hadParentheses() const { return _parentheses; }
virtual ResultList contains(const Context&) const = 0;
@@ -43,6 +58,12 @@ public:
virtual Node::UP clone() const = 0;
protected:
+ void throw_parse_error_if_max_depth_exceeded() const {
+ if (_max_depth > ParserLimits::MaxRecursionDepth) {
+ throw_max_depth_exceeded_exception();
+ }
+ }
+
Node::UP wrapParens(Node* node) const {
Node::UP ret(node);
if (_parentheses) {
diff --git a/document/src/vespa/document/select/parser.cpp b/document/src/vespa/document/select/parser.cpp
index 9f015409011..fadb46e5aa3 100644
--- a/document/src/vespa/document/select/parser.cpp
+++ b/document/src/vespa/document/select/parser.cpp
@@ -1,5 +1,6 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "parser.h"
+#include "parser_limits.h"
#include "scanner.h"
#include <vespa/document/base/exceptions.h>
#include <vespa/document/util/stringutil.h>
@@ -8,7 +9,20 @@
namespace document::select {
+namespace {
+
+void verify_expression_not_too_large(const std::string& expr) {
+ if (expr.size() > ParserLimits::MaxSelectionByteSize) {
+ throw ParsingFailedException(vespalib::make_string(
+ "expression is too large to be parsed (max %zu bytes, got %zu)",
+ ParserLimits::MaxSelectionByteSize, expr.size()));
+ }
+}
+
+}
+
std::unique_ptr<Node> Parser::parse(const std::string& str) const {
+ verify_expression_not_too_large(str);
try {
std::istringstream ss(str);
DocSelScanner scanner(&ss);
diff --git a/document/src/vespa/document/select/parser_limits.cpp b/document/src/vespa/document/select/parser_limits.cpp
new file mode 100644
index 00000000000..13e494b376f
--- /dev/null
+++ b/document/src/vespa/document/select/parser_limits.cpp
@@ -0,0 +1,13 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include "parser_limits.h"
+#include "parsing_failed_exception.h"
+#include <vespa/vespalib/util/stringfmt.h>
+
+namespace document::select {
+
+void throw_max_depth_exceeded_exception() {
+ throw ParsingFailedException(vespalib::make_string(
+ "expression is too deeply nested (max %u levels)", ParserLimits::MaxRecursionDepth));
+}
+
+}
diff --git a/document/src/vespa/document/select/parser_limits.h b/document/src/vespa/document/select/parser_limits.h
new file mode 100644
index 00000000000..24c0a165611
--- /dev/null
+++ b/document/src/vespa/document/select/parser_limits.h
@@ -0,0 +1,19 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#pragma once
+
+#include <cstdint>
+#include <cstddef>
+
+namespace document::select {
+
+// Any resource constraints set for parsing document selection expressions
+struct ParserLimits {
+ // Max depth allowed for nodes in the AST tree.
+ constexpr static uint32_t MaxRecursionDepth = 1024;
+ // Max size of entire input document selection string, in bytes.
+ constexpr static size_t MaxSelectionByteSize = 1024*1024;
+};
+
+void __attribute__((noinline)) throw_max_depth_exceeded_exception();
+
+}
diff --git a/document/src/vespa/document/select/valuenode.h b/document/src/vespa/document/select/valuenode.h
index 04ed8178b40..8dd535a736a 100644
--- a/document/src/vespa/document/select/valuenode.h
+++ b/document/src/vespa/document/select/valuenode.h
@@ -5,12 +5,13 @@
*
* @brief Node representing a value in the tree
*
- * @author H�kon Humberset
+ * @author HÃ¥kon Humberset
*/
#pragma once
#include "value.h"
+#include "parser_limits.h"
namespace document::select {
@@ -22,8 +23,19 @@ class ValueNode : public Printable
public:
using UP = std::unique_ptr<ValueNode>;
- ValueNode() : _parentheses(false) {}
- virtual ~ValueNode() {}
+ explicit ValueNode(uint32_t max_depth)
+ : _max_depth(max_depth), _parentheses(false)
+ {
+ throw_parse_error_if_max_depth_exceeded();
+ }
+ ValueNode() : _max_depth(1), _parentheses(false) {}
+ ~ValueNode() override = default;
+
+ // See comments for same function in node.h for a description on how and why
+ // we track this. Since Node and ValueNode live in completely separate type
+ // hierarchies, this particular bit of code duplication is unfortunate but
+ // incurs the least cognitive overhead.
+ [[nodiscard]] uint32_t max_depth() const noexcept { return _max_depth; }
void setParentheses() { _parentheses = true; }
void clearParentheses() { _parentheses = false; }
@@ -34,9 +46,17 @@ public:
virtual ValueNode::UP clone() const = 0;
virtual std::unique_ptr<Value> traceValue(const Context &context, std::ostream &out) const;
private:
+ uint32_t _max_depth;
bool _parentheses; // Set to true if parentheses was used around this part
// Set such that we can recreate original query in print.
+
protected:
+ void throw_parse_error_if_max_depth_exceeded() const {
+ if (_max_depth > ParserLimits::MaxRecursionDepth) {
+ throw_max_depth_exceeded_exception();
+ }
+ }
+
ValueNode::UP wrapParens(ValueNode* node) const {
ValueNode::UP ret(node);
if (_parentheses) {
diff --git a/document/src/vespa/document/select/valuenodes.cpp b/document/src/vespa/document/select/valuenodes.cpp
index 95cf2f4e7e5..026623cf83c 100644
--- a/document/src/vespa/document/select/valuenodes.cpp
+++ b/document/src/vespa/document/select/valuenodes.cpp
@@ -21,10 +21,6 @@ LOG_SETUP(".document.select.valuenode");
namespace document::select {
namespace {
- static const std::regex FIELD_NAME_REGEX("^([_A-Za-z][_A-Za-z0-9]*).*");
-}
-
-namespace {
bool documentTypeEqualsName(const DocumentType& type, vespalib::stringref name)
{
if (type.getName() == name) return true;
@@ -40,7 +36,7 @@ namespace {
InvalidValueNode::InvalidValueNode(vespalib::stringref name)
: _name(name)
-{ }
+{}
void
@@ -194,15 +190,33 @@ FieldValueNode::FieldValueNode(const vespalib::string& doctype,
FieldValueNode::~FieldValueNode() = default;
-vespalib::string
-FieldValueNode::extractFieldName(const std::string & fieldExpression) {
- std::smatch match;
+namespace {
- if (std::regex_match(fieldExpression, match, FIELD_NAME_REGEX) && match[1].matched) {
- return vespalib::string(match[1].first, match[1].second);
+size_t first_ident_length_or_npos(const vespalib::string& expr) {
+ for (size_t i = 0; i < expr.size(); ++i) {
+ switch (expr[i]) {
+ case '.':
+ case '{':
+ case '[':
+ case ' ':
+ case '\n':
+ case '\t':
+ return i;
+ default:
+ continue;
+ }
}
+ return vespalib::string::npos;
+}
- throw ParsingFailedException("Fatal: could not extract field name from field expression '" + fieldExpression + "'");
+}
+
+// TODO remove this pile of fun in favor of actually parsed AST nodes...!
+vespalib::string
+FieldValueNode::extractFieldName(const vespalib::string & fieldExpression) {
+ // When we get here the actual contents of the field expression shall already
+ // have been structurally and syntactically verified by the parser.
+ return fieldExpression.substr(0, first_ident_length_or_npos(fieldExpression));
}
namespace {
@@ -844,7 +858,8 @@ FunctionValueNode::print(std::ostream& out, bool verbose,
ArithmeticValueNode::ArithmeticValueNode(
std::unique_ptr<ValueNode> left, vespalib::stringref op,
std::unique_ptr<ValueNode> right)
- : _operator(),
+ : ValueNode(std::max(left->max_depth(), right->max_depth()) + 1),
+ _operator(),
_left(std::move(left)),
_right(std::move(right))
{
diff --git a/document/src/vespa/document/select/valuenodes.h b/document/src/vespa/document/select/valuenodes.h
index 8009542c364..a7d5fa15f37 100644
--- a/document/src/vespa/document/select/valuenodes.h
+++ b/document/src/vespa/document/select/valuenodes.h
@@ -160,7 +160,7 @@ public:
FieldValueNode & operator = (const FieldValueNode &) = delete;
FieldValueNode(FieldValueNode &&) = default;
FieldValueNode & operator = (FieldValueNode &&) = default;
- ~FieldValueNode();
+ ~FieldValueNode() override;
const vespalib::string& getDocType() const { return _doctype; }
const vespalib::string& getRealFieldName() const { return _fieldName; }
@@ -175,7 +175,7 @@ public:
return wrapParens(new FieldValueNode(_doctype, _fieldExpression));
}
- static vespalib::string extractFieldName(const std::string & fieldExpression);
+ static vespalib::string extractFieldName(const vespalib::string & fieldExpression);
private:
@@ -192,13 +192,15 @@ class FieldExprNode final : public ValueNode {
public:
explicit FieldExprNode(const vespalib::string& doctype) : _left_expr(), _right_expr(doctype) {}
FieldExprNode(std::unique_ptr<FieldExprNode> left_expr, vespalib::stringref right_expr)
- : _left_expr(std::move(left_expr)), _right_expr(right_expr)
+ : ValueNode(left_expr->max_depth() + 1),
+ _left_expr(std::move(left_expr)),
+ _right_expr(right_expr)
{}
FieldExprNode(const FieldExprNode &) = delete;
FieldExprNode & operator = (const FieldExprNode &) = delete;
FieldExprNode(FieldExprNode &&) = default;
FieldExprNode & operator = (FieldExprNode &&) = default;
- ~FieldExprNode();
+ ~FieldExprNode() override;
std::unique_ptr<FieldValueNode> convert_to_field_value() const;
std::unique_ptr<FunctionValueNode> convert_to_function_call() const;
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-CreateVisitorMessage.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-CreateVisitorMessage.dat
index a7bb5b0e896..27e64170701 100644
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-CreateVisitorMessage.dat
+++ b/documentapi/test/crosslanguagefiles/6.221-cpp-CreateVisitorMessage.dat
Binary files differ
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
index 66b86866c3e..8a0212211e1 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceDownloader.java
@@ -37,7 +37,8 @@ public class FileReferenceDownloader {
private final static Duration rpcTimeout = Duration.ofSeconds(10);
private final ExecutorService downloadExecutor =
- Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors(), new DaemonThreadFactory("filereference downloader"));
+ Executors.newFixedThreadPool(Math.max(8, Runtime.getRuntime().availableProcessors()),
+ new DaemonThreadFactory("filereference downloader"));
private final ConnectionPool connectionPool;
private final Map<FileReference, FileReferenceDownload> downloads = new LinkedHashMap<>();
private final Map<FileReference, Double> downloadStatus = new HashMap<>(); // between 0 and 1
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
index c3d9d74ed68..862d83461fd 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -67,6 +67,13 @@ public class Flags {
"Takes effect on next host admin tick.",
HOSTNAME);
+ public static final UnboundBooleanFlag USE_NEW_VESPA_RPMS = defineFeatureFlag(
+ "use-new-vespa-rpms", false,
+ "Whether to use the new vespa-rpms YUM repo when upgrading/downgrading. The vespa-version " +
+ "when fetching the flag value is the wanted version of the host.",
+ "Takes effect when upgrading or downgrading host admin to a different version.",
+ HOSTNAME, NODE_TYPE, VESPA_VERSION);
+
public static final UnboundListFlag<String> DISABLED_HOST_ADMIN_TASKS = defineListFlag(
"disabled-host-admin-tasks", List.of(), String.class,
"List of host-admin task names (as they appear in the log, e.g. root>main>UpgradeTask) that should be skipped",
@@ -103,7 +110,7 @@ public class Flags {
APPLICATION_ID);
public static final UnboundStringFlag TLS_INSECURE_MIXED_MODE = defineStringFlag(
- "tls-insecure-mixed-mode", "tls_client_mixed_server",
+ "tls-insecure-mixed-mode", "tls_client_tls_server",
"TLS insecure mixed mode. Allowed values: ['plaintext_client_mixed_server', 'tls_client_mixed_server', 'tls_client_tls_server']",
"Takes effect on restart of Docker container",
NODE_TYPE, APPLICATION_ID, HOSTNAME);
@@ -193,12 +200,6 @@ public class Flags {
"Takes effect on next node agent tick (but does not clear existing failure reports)",
HOSTNAME);
- public static final UnboundBooleanFlag GENERATE_L4_ROUTING_CONFIG = defineFeatureFlag(
- "generate-l4-routing-config", false,
- "Whether routing nodes should generate L4 routing config",
- "Takes effect immediately",
- ZONE_ID, HOSTNAME);
-
public static final UnboundBooleanFlag USE_REFRESHED_ENDPOINT_CERTIFICATE = defineFeatureFlag(
"use-refreshed-endpoint-certificate", false,
"Whether an application should start using a newer certificate/key pair if available",
@@ -251,17 +252,22 @@ public class Flags {
APPLICATION_ID);
public static final UnboundBooleanFlag DISABLE_ROUTING_GENERATOR = defineFeatureFlag(
- "disable-routing-generator", false,
+ "disable-routing-generator", true,
"Whether the controller should stop asking the routing layer for endpoints",
"Takes effect immediately",
APPLICATION_ID);
public static final UnboundBooleanFlag DEDICATED_NODES_WHEN_UNSPECIFIED = defineFeatureFlag(
- "dedicated-nodes-when-unspecified", false,
+ "dedicated-nodes-when-unspecified", true,
"Whether config-server should allocate dedicated container nodes when <nodes/> is not specified in services.xml",
"Takes effect on redeploy",
APPLICATION_ID);
+ public static final UnboundBooleanFlag NGINX_UPSTREAM_PROXY_PROTOCOL = defineFeatureFlag(
+ "nginx-upstream-proxy-protocol", false,
+ "Whether the nginx should enable proxy-protocol for all upstreams",
+ "Takes effect immediately");
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, String description,
String modificationEffect, FetchVector.Dimension... dimensions) {
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
index afaf94b26b6..db2f0a3a197 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalCondition.java
@@ -5,7 +5,6 @@ import com.yahoo.component.Version;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.json.wire.WireCondition;
-import java.util.List;
import java.util.function.Predicate;
/**
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java
index c5ad195e0d2..72bc5627112 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/json/RelationalPredicate.java
@@ -23,7 +23,7 @@ public class RelationalPredicate {
for (var operator : operatorsByDecendingLength) {
if (predicateString.startsWith(operator.toText())) {
- String suffix = predicateString.substring(operator.toText().length());
+ String suffix = predicateString.substring(operator.toText().length()).trim();
return new RelationalPredicate(predicateString, operator, suffix);
}
}
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
index 84d18350993..ee30f6fd471 100644
--- a/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/ControllerHttpClient.java
@@ -434,15 +434,16 @@ public abstract class ControllerHttpClient {
private static DeploymentLog.Status valueOf(String status) {
switch (status) {
- case "running": return DeploymentLog.Status.running;
- case "aborted": return DeploymentLog.Status.aborted;
- case "error": return DeploymentLog.Status.error;
- case "testFailure": return DeploymentLog.Status.testFailure;
- case "outOfCapacity": return DeploymentLog.Status.outOfCapacity;
- case "installationFailed": return DeploymentLog.Status.installationFailed;
- case "deploymentFailed": return DeploymentLog.Status.deploymentFailed;
- case "success": return DeploymentLog.Status.success;
- default: throw new IllegalArgumentException("Unexpected status '" + status + "'");
+ case "running": return DeploymentLog.Status.running;
+ case "aborted": return DeploymentLog.Status.aborted;
+ case "error": return DeploymentLog.Status.error;
+ case "testFailure": return DeploymentLog.Status.testFailure;
+ case "outOfCapacity": return DeploymentLog.Status.outOfCapacity;
+ case "installationFailed": return DeploymentLog.Status.installationFailed;
+ case "deploymentFailed": return DeploymentLog.Status.deploymentFailed;
+ case "endpointCertificateTimeout": return DeploymentLog.Status.endpointCertificateTimeout;
+ case "success": return DeploymentLog.Status.success;
+ default: throw new IllegalArgumentException("Unexpected status '" + status + "'");
}
}
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/DeploymentLog.java b/hosted-api/src/main/java/ai/vespa/hosted/api/DeploymentLog.java
index 9eae9a33cff..90e973da49d 100644
--- a/hosted-api/src/main/java/ai/vespa/hosted/api/DeploymentLog.java
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/DeploymentLog.java
@@ -112,6 +112,7 @@ public class DeploymentLog {
outOfCapacity,
installationFailed,
deploymentFailed,
+ endpointCertificateTimeout,
success;
}
diff --git a/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java b/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
index fd640bcc235..22c32cfa9ec 100644
--- a/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
+++ b/hosted-api/src/main/java/ai/vespa/hosted/api/Properties.java
@@ -52,7 +52,7 @@ public class Properties {
return getNonBlankProperty("apiCertificateFile").map(Paths::get);
}
- /** Returns the actual private key as a string */
+ /** Returns the actual private key as a string. */
public static Optional<String> apiKey() {
return getNonBlankProperty("apiKey");
}
diff --git a/jdisc_http_service/abi-spec.json b/jdisc_http_service/abi-spec.json
index bb6285ab94f..c5a0a676a70 100644
--- a/jdisc_http_service/abi-spec.json
+++ b/jdisc_http_service/abi-spec.json
@@ -42,6 +42,9 @@
"public com.yahoo.jdisc.http.ConnectorConfig$Builder tlsClientAuthEnforcer(com.yahoo.jdisc.http.ConnectorConfig$TlsClientAuthEnforcer$Builder)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder healthCheckProxy(com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy$Builder)",
"public com.yahoo.jdisc.http.ConnectorConfig$Builder proxyProtocol(com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol$Builder)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Builder secureRedirect(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Builder maxRequestsPerConnection(int)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$Builder maxConnectionLife(double)",
"public final boolean dispatchGetConfig(com.yahoo.config.ConfigInstance$Producer)",
"public final java.lang.String getDefMd5()",
"public final java.lang.String getDefName()",
@@ -53,7 +56,8 @@
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder ssl",
"public com.yahoo.jdisc.http.ConnectorConfig$TlsClientAuthEnforcer$Builder tlsClientAuthEnforcer",
"public com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy$Builder healthCheckProxy",
- "public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol$Builder proxyProtocol"
+ "public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol$Builder proxyProtocol",
+ "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder secureRedirect"
]
},
"com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy$Builder": {
@@ -133,6 +137,37 @@
],
"fields": []
},
+ "com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder": {
+ "superClass": "java.lang.Object",
+ "interfaces": [
+ "com.yahoo.config.ConfigBuilder"
+ ],
+ "attributes": [
+ "public"
+ ],
+ "methods": [
+ "public void <init>()",
+ "public void <init>(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder enabled(boolean)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder port(int)",
+ "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect build()"
+ ],
+ "fields": []
+ },
+ "com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect": {
+ "superClass": "com.yahoo.config.InnerNode",
+ "interfaces": [],
+ "attributes": [
+ "public",
+ "final"
+ ],
+ "methods": [
+ "public void <init>(com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect$Builder)",
+ "public boolean enabled()",
+ "public int port()"
+ ],
+ "fields": []
+ },
"com.yahoo.jdisc.http.ConnectorConfig$Ssl$Builder": {
"superClass": "java.lang.Object",
"interfaces": [
@@ -323,7 +358,10 @@
"public com.yahoo.jdisc.http.ConnectorConfig$Ssl ssl()",
"public com.yahoo.jdisc.http.ConnectorConfig$TlsClientAuthEnforcer tlsClientAuthEnforcer()",
"public com.yahoo.jdisc.http.ConnectorConfig$HealthCheckProxy healthCheckProxy()",
- "public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol proxyProtocol()"
+ "public com.yahoo.jdisc.http.ConnectorConfig$ProxyProtocol proxyProtocol()",
+ "public com.yahoo.jdisc.http.ConnectorConfig$SecureRedirect secureRedirect()",
+ "public int maxRequestsPerConnection()",
+ "public double maxConnectionLife()"
],
"fields": [
"public static final java.lang.String CONFIG_DEF_MD5",
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
index 71dcb7d0682..b9d686c1d6b 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/HttpRequestDispatch.java
@@ -10,6 +10,7 @@ import com.yahoo.jdisc.handler.BindingNotFoundException;
import com.yahoo.jdisc.handler.ContentChannel;
import com.yahoo.jdisc.handler.OverloadException;
import com.yahoo.jdisc.handler.RequestHandler;
+import com.yahoo.jdisc.http.ConnectorConfig;
import com.yahoo.jdisc.http.HttpHeaders;
import com.yahoo.jdisc.http.HttpRequest;
import org.eclipse.jetty.io.EofException;
@@ -22,6 +23,7 @@ import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
+import java.time.Instant;
import java.util.Arrays;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
@@ -34,6 +36,7 @@ import java.util.logging.Logger;
import static com.yahoo.jdisc.http.HttpHeaders.Values.APPLICATION_X_WWW_FORM_URLENCODED;
import static com.yahoo.jdisc.http.core.HttpServletRequestUtils.getConnection;
import static com.yahoo.jdisc.http.server.jetty.Exceptions.throwUnchecked;
+import static com.yahoo.jdisc.http.server.jetty.JDiscHttpServlet.getConnector;
/**
* @author Simon Thoresen Hult
@@ -64,14 +67,13 @@ class HttpRequestDispatch {
this.jettyRequest = (Request) servletRequest;
this.metricReporter = new MetricReporter(jDiscContext.metric, metricContext, jettyRequest.getTimeStamp());
- honourMaxKeepAliveRequests();
this.servletResponseController = new ServletResponseController(
servletRequest,
servletResponse,
jDiscContext.janitor,
metricReporter,
jDiscContext.developerMode());
-
+ markConnectionAsNonPersistentIfThresholdReached(servletRequest);
this.async = servletRequest.startAsync();
async.setTimeout(0);
metricReporter.uriLength(jettyRequest.getOriginalURI().length());
@@ -102,15 +104,6 @@ class HttpRequestDispatch {
}
}
- private void honourMaxKeepAliveRequests() {
- if (jDiscContext.serverConfig.maxKeepAliveRequests() > 0) {
- HttpConnection connection = getConnection(jettyRequest);
- if (connection.getMessagesIn() >= jDiscContext.serverConfig.maxKeepAliveRequests()) {
- connection.getGenerator().setPersistent(false);
- }
- }
- }
-
private BiConsumer<Void, Throwable> completeRequestCallback;
{
AtomicBoolean completeRequestCalled = new AtomicBoolean(false);
@@ -151,6 +144,25 @@ class HttpRequestDispatch {
};
}
+ private static void markConnectionAsNonPersistentIfThresholdReached(HttpServletRequest request) {
+ ConnectorConfig connectorConfig = getConnector(request).connectorConfig();
+ int maxRequestsPerConnection = connectorConfig.maxRequestsPerConnection();
+ if (maxRequestsPerConnection > 0) {
+ HttpConnection connection = getConnection(request);
+ if (connection.getMessagesIn() >= maxRequestsPerConnection) {
+ connection.getGenerator().setPersistent(false);
+ }
+ }
+ double maxConnectionLifeInSeconds = connectorConfig.maxConnectionLife();
+ if (maxConnectionLifeInSeconds > 0) {
+ HttpConnection connection = getConnection(request);
+ Instant expireAt = Instant.ofEpochMilli((long)(connection.getCreatedTimeStamp() + maxConnectionLifeInSeconds * 1000));
+ if (Instant.now().isAfter(expireAt)) {
+ connection.getGenerator().setPersistent(false);
+ }
+ }
+ }
+
@SafeVarargs
@SuppressWarnings("varargs")
private static boolean isErrorOfType(Throwable throwable, Class<? extends Throwable>... handledTypes) {
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
index cf66af31a79..5cbe7320f0e 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JDiscHttpServlet.java
@@ -100,6 +100,8 @@ class JDiscHttpServlet extends HttpServlet {
}
}
+
+
static JDiscServerConnector getConnector(HttpServletRequest request) {
return (JDiscServerConnector)getConnection(request).getConnector();
}
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
index 71284e09669..c5f42ff9cc5 100644
--- a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/JettyHttpServer.java
@@ -34,9 +34,6 @@ import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.JavaUtilLog;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
-import org.osgi.framework.BundleContext;
-import org.osgi.framework.InvalidSyntaxException;
-import org.osgi.framework.ServiceReference;
import javax.management.remote.JMXServiceURL;
import javax.servlet.DispatcherType;
@@ -44,10 +41,8 @@ import java.io.IOException;
import java.lang.management.ManagementFactory;
import java.net.BindException;
import java.net.MalformedURLException;
-import java.nio.channels.ServerSocketChannel;
import java.util.ArrayList;
import java.util.Arrays;
-import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.List;
@@ -246,10 +241,13 @@ public class JettyHttpServer extends AbstractServerProvider {
servletContextHandler.addServlet(jdiscServlet, "/*");
+ List<ConnectorConfig> connectorConfigs = connectors.stream().map(JDiscServerConnector::connectorConfig).collect(toList());
+ var secureRedirectHandler = new SecuredRedirectHandler(connectorConfigs);
+ secureRedirectHandler.setHandler(servletContextHandler);
+
var proxyHandler = new HealthCheckProxyHandler(connectors);
- proxyHandler.setHandler(servletContextHandler);
+ proxyHandler.setHandler(secureRedirectHandler);
- List<ConnectorConfig> connectorConfigs = connectors.stream().map(JDiscServerConnector::connectorConfig).collect(toList());
var authEnforcer = new TlsClientAuthenticationEnforcer(connectorConfigs);
authEnforcer.setHandler(proxyHandler);
@@ -282,25 +280,6 @@ public class JettyHttpServer extends AbstractServerProvider {
return ports.stream().map(Object::toString).collect(Collectors.joining(":"));
}
- private ServerSocketChannel getChannelFromServiceLayer(int listenPort, BundleContext bundleContext) {
- log.log(Level.FINE, "Retrieving channel for port " + listenPort + " from " + bundleContext.getClass().getName());
- Collection<ServiceReference<ServerSocketChannel>> refs;
- final String filter = "(port=" + listenPort + ")";
- try {
- refs = bundleContext.getServiceReferences(ServerSocketChannel.class, filter);
- } catch (InvalidSyntaxException e) {
- throw new IllegalStateException("OSGi framework rejected filter " + filter, e);
- }
- if (refs.isEmpty()) {
- return null;
- }
- if (refs.size() != 1) {
- throw new IllegalStateException("Got more than one service reference for " + ServerSocketChannel.class + " port " + listenPort + ".");
- }
- ServiceReference<ServerSocketChannel> ref = refs.iterator().next();
- return bundleContext.getService(ref);
- }
-
private static ExecutorService newJanitor(ThreadFactory factory) {
int threadPoolSize = Runtime.getRuntime().availableProcessors();
log.info("Creating janitor executor with " + threadPoolSize + " threads");
diff --git a/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java
new file mode 100644
index 00000000000..32c0628186a
--- /dev/null
+++ b/jdisc_http_service/src/main/java/com/yahoo/jdisc/http/server/jetty/SecuredRedirectHandler.java
@@ -0,0 +1,52 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.jdisc.http.server.jetty;
+
+import com.yahoo.jdisc.http.ConnectorConfig;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.HandlerWrapper;
+import org.eclipse.jetty.util.URIUtil;
+
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A secure redirect handler inspired by {@link org.eclipse.jetty.server.handler.SecuredRedirectHandler}.
+ *
+ * @author bjorncs
+ */
+class SecuredRedirectHandler extends HandlerWrapper {
+
+ private final Map<Integer, Integer> redirectMap;
+
+ SecuredRedirectHandler(List<ConnectorConfig> connectorConfigs) {
+ this.redirectMap = createRedirectMap(connectorConfigs);
+ }
+
+ @Override
+ public void handle(String target, Request request, HttpServletRequest servletRequest, HttpServletResponse servletResponse) throws IOException, ServletException {
+ int localPort = servletRequest.getLocalPort();
+ if (!redirectMap.containsKey(localPort)) {
+ _handler.handle(target, request, servletRequest, servletResponse);
+ return;
+ }
+ servletResponse.setContentLength(0);
+ servletResponse.sendRedirect(
+ URIUtil.newURI("https", request.getServerName(), redirectMap.get(localPort), request.getRequestURI(), request.getQueryString()));
+ request.setHandled(true);
+ }
+
+ private static Map<Integer, Integer> createRedirectMap(List<ConnectorConfig> connectorConfigs) {
+ var redirectMap = new HashMap<Integer, Integer>();
+ for (ConnectorConfig connectorConfig : connectorConfigs) {
+ if (connectorConfig.secureRedirect().enabled()) {
+ redirectMap.put(connectorConfig.listenPort(), connectorConfig.secureRedirect().port());
+ }
+ }
+ return redirectMap;
+ }
+}
diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
index 8027525521c..fa7ed6657d9 100644
--- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
+++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.connector.def
@@ -106,3 +106,15 @@ proxyProtocol.enabled bool default=false
# Allow https in parallel with proxy protocol
proxyProtocol.mixedMode bool default=false
+
+# Redirect all requests to https port
+secureRedirect.enabled bool default=false
+
+# Target port for redirect
+secureRedirect.port int default=443
+
+# Maximum number of request per connection before server marks connections as non-persistent. Set to '0' to disable.
+maxRequestsPerConnection int default=0
+
+# Maximum number of seconds a connection can live before it's marked as non-persistent. Set to '0' to disable.
+maxConnectionLife double default=0.0
diff --git a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.server.def b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.server.def
index 0836a080e1f..33f82963243 100644
--- a/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.server.def
+++ b/jdisc_http_service/src/main/resources/configdefinitions/jdisc.http.server.def
@@ -7,13 +7,16 @@ developerMode bool default=false
# The gzip compression level to use, if compression is enabled in a request.
responseCompressionLevel int default=6
-# Whether to enable HTTP keep-alive for requests that support this.
+# DEPRECATED - Ignored, no longer in use.
httpKeepAliveEnabled bool default=true
+# TODO Vespa 8 Remove httpKeepAliveEnabled
# Maximum number of request per http connection before server will hangup.
# Naming taken from apache http server.
# 0 means never hangup.
+# DEPRECATED - Ignored, no longer in use. Use similar parameter in connector config instead.
maxKeepAliveRequests int default=0
+# TODO Vespa 8 Remove maxKeepAliveRequests
# Whether the request body of POSTed forms should be removed (form parameters are available as request parameters).
removeRawPostBodyForWwwUrlEncodedPost bool default=false
diff --git a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
index 6ace9699b42..f2f3fb0ef11 100644
--- a/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
+++ b/jdisc_http_service/src/test/java/com/yahoo/jdisc/http/server/jetty/HttpServerTest.java
@@ -484,8 +484,8 @@ public class HttpServerTest {
public void requireThatConnectionIsClosedAfterXRequests() throws Exception {
final int MAX_KEEPALIVE_REQUESTS = 100;
final TestDriver driver = TestDrivers.newConfiguredInstance(new EchoRequestHandler(),
- new ServerConfig.Builder().maxKeepAliveRequests(MAX_KEEPALIVE_REQUESTS),
- new ConnectorConfig.Builder());
+ new ServerConfig.Builder(),
+ new ConnectorConfig.Builder().maxRequestsPerConnection(MAX_KEEPALIVE_REQUESTS));
for (int i = 0; i < MAX_KEEPALIVE_REQUESTS - 1; i++) {
driver.client().get("/status.html")
.expectStatusCode(is(OK))
diff --git a/jrt/src/com/yahoo/jrt/Acceptor.java b/jrt/src/com/yahoo/jrt/Acceptor.java
index 9e9dafcbcb5..aed22ac090c 100644
--- a/jrt/src/com/yahoo/jrt/Acceptor.java
+++ b/jrt/src/com/yahoo/jrt/Acceptor.java
@@ -101,7 +101,7 @@ public class Acceptor {
while (serverChannel.isOpen()) {
try {
TransportThread tt = parent.selectThread();
- tt.addConnection(new Connection(tt, owner, serverChannel.accept()));
+ tt.addConnection(new Connection(tt, owner, serverChannel.accept(), parent.getTcpNoDelay()));
tt.sync();
} catch (ClosedChannelException ignore) {
} catch (Exception e) {
diff --git a/jrt/src/com/yahoo/jrt/Connection.java b/jrt/src/com/yahoo/jrt/Connection.java
index c9c6d78ffba..5a4478cf91e 100644
--- a/jrt/src/com/yahoo/jrt/Connection.java
+++ b/jrt/src/com/yahoo/jrt/Connection.java
@@ -36,6 +36,7 @@ class Connection extends Target {
private final Buffer output = new Buffer(WRITE_SIZE * 2);
private int maxInputSize = 64*1024;
private int maxOutputSize = 64*1024;
+ private final boolean tcpNoDelay;
private final Map<Integer, ReplyHandler> replyMap = new HashMap<>();
private final Map<TargetWatcher, TargetWatcher> watchers = new IdentityHashMap<>();
private int activeReqs = 0;
@@ -89,21 +90,23 @@ class Connection extends Target {
}
public Connection(TransportThread parent, Supervisor owner,
- SocketChannel channel) {
+ SocketChannel channel, boolean tcpNoDelay) {
this.parent = parent;
this.owner = owner;
this.socket = parent.transport().createServerCryptoSocket(channel);
this.spec = null;
+ this.tcpNoDelay = tcpNoDelay;
server = true;
owner.sessionInit(this);
}
- public Connection(TransportThread parent, Supervisor owner, Spec spec, Object context) {
+ public Connection(TransportThread parent, Supervisor owner, Spec spec, Object context, boolean tcpNoDelay) {
super(context);
this.parent = parent;
this.owner = owner;
this.spec = spec;
+ this.tcpNoDelay = tcpNoDelay;
server = false;
owner.sessionInit(this);
}
@@ -184,7 +187,7 @@ class Connection extends Target {
}
try {
socket.channel().configureBlocking(false);
- socket.channel().socket().setTcpNoDelay(true);
+ socket.channel().socket().setTcpNoDelay(tcpNoDelay);
selectionKey = socket.channel().register(selector,
SelectionKey.OP_READ | SelectionKey.OP_WRITE,
this);
diff --git a/jrt/src/com/yahoo/jrt/Transport.java b/jrt/src/com/yahoo/jrt/Transport.java
index 8abd3942a39..02a6e3e05f7 100644
--- a/jrt/src/com/yahoo/jrt/Transport.java
+++ b/jrt/src/com/yahoo/jrt/Transport.java
@@ -25,6 +25,7 @@ public class Transport {
private final Connector connector;
private final Worker worker;
private final AtomicInteger runCnt;
+ private final boolean tcpNoDelay;
private final TransportMetrics metrics = TransportMetrics.getInstance();
private final ArrayList<TransportThread> threads = new ArrayList<>();
@@ -40,11 +41,10 @@ public class Transport {
* @param cryptoEngine crypto engine to use
* @param numThreads number of {@link TransportThread}s.
**/
- public Transport(FatalErrorHandler fatalHandler, CryptoEngine cryptoEngine, int numThreads) {
- synchronized (this) {
- this.fatalHandler = fatalHandler; // NB: this must be set first
- }
+ public Transport(FatalErrorHandler fatalHandler, CryptoEngine cryptoEngine, int numThreads, boolean tcpNoDelay) {
+ this.fatalHandler = fatalHandler; // NB: this must be set first
this.cryptoEngine = cryptoEngine;
+ this.tcpNoDelay = tcpNoDelay;
connector = new Connector();
worker = new Worker(this);
runCnt = new AtomicInteger(numThreads);
@@ -52,10 +52,10 @@ public class Transport {
threads.add(new TransportThread(this));
}
}
- public Transport(CryptoEngine cryptoEngine, int numThreads) { this(null, cryptoEngine, numThreads); }
- public Transport(FatalErrorHandler fatalHandler, int numThreads) { this(fatalHandler, CryptoEngine.createDefault(), numThreads); }
- public Transport(int numThreads) { this(null, CryptoEngine.createDefault(), numThreads); }
- public Transport() { this(null, CryptoEngine.createDefault(), 1); }
+ public Transport(CryptoEngine cryptoEngine, int numThreads) { this(null, cryptoEngine, numThreads, true); }
+ public Transport(int numThreads) { this(null, CryptoEngine.createDefault(), numThreads, true); }
+ public Transport(int numThreads, boolean tcpNoDelay) { this(null, CryptoEngine.createDefault(), numThreads, tcpNoDelay); }
+ public Transport() { this(null, CryptoEngine.createDefault(), 1, true); }
/**
* Select a random transport thread
@@ -66,6 +66,8 @@ public class Transport {
return threads.get(rnd.nextInt(threads.size()));
}
+ boolean getTcpNoDelay() { return tcpNoDelay; }
+
/**
* Use the underlying CryptoEngine to create a CryptoSocket for
* the client side of a connection.
@@ -130,7 +132,7 @@ public class Transport {
* @param context application context for the new connection
*/
Connection connect(Supervisor owner, Spec spec, Object context) {
- Connection conn = new Connection(selectThread(), owner, spec, context);
+ Connection conn = new Connection(selectThread(), owner, spec, context, getTcpNoDelay());
connector.connectLater(conn);
return conn;
}
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java
index 0fd52e9bdbc..554977d7eb1 100644
--- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetwork.java
@@ -67,10 +67,15 @@ public class RPCNetwork implements Network, MethodHandler {
new ThreadPoolExecutor(getNumThreads(), getNumThreads(), 0L, TimeUnit.SECONDS,
new SynchronousQueue<>(false),
ThreadFactoryFactory.getDaemonThreadFactory("mbus.net"), new ThreadPoolExecutor.CallerRunsPolicy());
+
private static int getNumThreads() {
return Math.max(2, Runtime.getRuntime().availableProcessors()/2);
}
+ private static boolean shouldEnableTcpNodelay(RPCNetworkParams.Optimization optimization) {
+ return optimization == RPCNetworkParams.Optimization.LATENCY;
+ }
+
/**
* Create an RPCNetwork. The servicePrefix is combined with session names to create service names. If the service
* prefix is 'a/b' and the session name is 'c', the resulting service name that identifies the session on the
@@ -82,7 +87,7 @@ public class RPCNetwork implements Network, MethodHandler {
public RPCNetwork(RPCNetworkParams params, SlobrokConfigSubscriber slobrokConfig) {
this.slobroksConfig = slobrokConfig;
identity = params.getIdentity();
- orb = new Supervisor(new Transport(2));
+ orb = new Supervisor(new Transport(params.getNumNetworkThreads(), shouldEnableTcpNodelay(params.getOptimization())));
orb.setMaxInputBufferSize(params.getMaxInputBufferSize());
orb.setMaxOutputBufferSize(params.getMaxOutputBufferSize());
targetPool = new RPCTargetPool(params.getConnectionExpireSecs(), params.getNumTargetsPerSpec());
diff --git a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java
index d6d7603f54a..e77cddd8b06 100755
--- a/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java
+++ b/messagebus/src/main/java/com/yahoo/messagebus/network/rpc/RPCNetworkParams.java
@@ -20,6 +20,9 @@ public class RPCNetworkParams {
private int maxOutputBufferSize = 256 * 1024;
private double connectionExpireSecs = 30;
private int numTargetsPerSpec = 1;
+ private int numNetworkThreads = 2;
+ public enum Optimization {LATENCY, THROUGHPUT}
+ Optimization optimization = Optimization.LATENCY;
/**
* Constructs a new instance of this class with reasonable default values.
@@ -42,6 +45,8 @@ public class RPCNetworkParams {
maxInputBufferSize = params.maxInputBufferSize;
maxOutputBufferSize = params.maxOutputBufferSize;
numTargetsPerSpec = params.numTargetsPerSpec;
+ numNetworkThreads = params.numNetworkThreads;
+ optimization = params.optimization;
}
/**
@@ -152,6 +157,22 @@ public class RPCNetworkParams {
return numTargetsPerSpec;
}
+ public RPCNetworkParams setNumNetworkThreads(int numNetworkThreads) {
+ this.numNetworkThreads = numNetworkThreads;
+ return this;
+ }
+ int getNumNetworkThreads() {
+ return numNetworkThreads;
+ }
+
+ public RPCNetworkParams setOptimization(Optimization optimization) {
+ this.optimization = optimization;
+ return this;
+ }
+ Optimization getOptimization() {
+ return optimization;
+ }
+
/**
* Returns the maximum input buffer size allowed for the underlying FNET connection.
*
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp b/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp
index c6f61b383bc..4b498c4c014 100644
--- a/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpcnetwork.cpp
@@ -130,6 +130,7 @@ RPCNetwork::RPCNetwork(const RPCNetworkParams &params) :
{
_transport->SetMaxInputBufferSize(params.getMaxInputBufferSize());
_transport->SetMaxOutputBufferSize(params.getMaxOutputBufferSize());
+ _transport->SetTCPNoDelay(params.getTcpNoDelay());
}
RPCNetwork::~RPCNetwork()
@@ -306,8 +307,8 @@ RPCNetwork::resolveServiceAddress(RoutingNode &recipient, const string &serviceN
make_string("Failed to connect to service '%s' from host '%s'.",
serviceName.c_str(), getIdentity().getHostname().c_str()));
}
- ret->setTarget(target); // free by freeServiceAddress()
- recipient.setServiceAddress(IServiceAddress::UP(ret.release()));
+ ret->setTarget(std::move(target)); // free by freeServiceAddress()
+ recipient.setServiceAddress(std::move(ret));
return Error();
}
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp
index bd87e4dbbe2..5bf277a8ee6 100644
--- a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.cpp
@@ -15,6 +15,7 @@ RPCNetworkParams::RPCNetworkParams(config::ConfigUri configUri) :
_maxInputBufferSize(256*1024),
_maxOutputBufferSize(256*1024),
_numThreads(4),
+ _tcpNoDelay(true),
_dispatchOnEncode(true),
_dispatchOnDecode(false),
_connectionExpireSecs(600),
diff --git a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h
index ba530257030..140f81c611c 100644
--- a/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h
+++ b/messagebus/src/vespa/messagebus/network/rpcnetworkparams.h
@@ -20,6 +20,7 @@ private:
uint32_t _maxInputBufferSize;
uint32_t _maxOutputBufferSize;
uint32_t _numThreads;
+ bool _tcpNoDelay;
bool _dispatchOnEncode;
bool _dispatchOnDecode;
double _connectionExpireSecs;
@@ -106,6 +107,13 @@ public:
uint32_t getNumThreads() const { return _numThreads; }
+ RPCNetworkParams &setTcpNoDelay(bool tcpNoDelay) {
+ _tcpNoDelay = tcpNoDelay;
+ return *this;
+ }
+
+ bool getTcpNoDelay() const { return _tcpNoDelay; }
+
/**
* Returns the number of seconds before an idle network connection expires.
*
diff --git a/messagebus/src/vespa/messagebus/network/rpcservice.cpp b/messagebus/src/vespa/messagebus/network/rpcservice.cpp
index 6e7c73b38ee..fd1b84f545f 100644
--- a/messagebus/src/vespa/messagebus/network/rpcservice.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpcservice.cpp
@@ -5,8 +5,7 @@
namespace mbus {
-RPCService::RPCService(const Mirror &mirror,
- const string &pattern) :
+RPCService::RPCService(const Mirror &mirror, const string &pattern) :
_mirror(mirror),
_pattern(pattern),
_addressIdx(random()),
@@ -14,7 +13,7 @@ RPCService::RPCService(const Mirror &mirror,
_addressList()
{ }
-RPCService::~RPCService() {}
+RPCService::~RPCService() = default;
RPCServiceAddress::UP
RPCService::resolve()
@@ -22,9 +21,7 @@ RPCService::resolve()
if (_pattern.find("tcp/") == 0) {
size_t pos = _pattern.find_last_of('/');
if (pos != string::npos && pos < _pattern.size() - 1) {
- RPCServiceAddress::UP ret(new RPCServiceAddress(
- _pattern,
- _pattern.substr(0, pos)));
+ auto ret = std::make_unique<RPCServiceAddress>(_pattern, _pattern.substr(0, pos));
if (!ret->isMalformed()) {
return ret;
}
@@ -37,9 +34,7 @@ RPCService::resolve()
if (!_addressList.empty()) {
_addressIdx = (_addressIdx + 1) % _addressList.size();
const AddressList::value_type &entry = _addressList[_addressIdx];
- return RPCServiceAddress::UP(new RPCServiceAddress(
- entry.first,
- entry.second));
+ return std::make_unique<RPCServiceAddress>(entry.first, entry.second);
}
}
return RPCServiceAddress::UP();
diff --git a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp
index eac33195caa..e76832f0620 100644
--- a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.cpp
@@ -16,7 +16,7 @@ RPCServiceAddress::RPCServiceAddress(const string &serviceName,
}
}
-RPCServiceAddress::~RPCServiceAddress() {}
+RPCServiceAddress::~RPCServiceAddress() = default;
bool
RPCServiceAddress::isMalformed()
diff --git a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h
index 36dde19bd18..99a9f383e75 100644
--- a/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h
+++ b/messagebus/src/vespa/messagebus/network/rpcserviceaddress.h
@@ -32,8 +32,7 @@ public:
* @param serviceName The full service name of the address.
* @param connectionSpec The connection specification.
*/
- RPCServiceAddress(const string &serviceName,
- const string &connectionSpec);
+ RPCServiceAddress(const string &serviceName, const string &connectionSpec);
~RPCServiceAddress();
/**
@@ -69,7 +68,7 @@ public:
*
* @param target The target to set.
*/
- void setTarget(RPCTarget::SP target) { _target = target; }
+ void setTarget(RPCTarget::SP target) { _target = std::move(target); }
/**
* Returns the RPC target to be used when communicating with the remove service. Make sure that {@link
@@ -84,7 +83,7 @@ public:
*
* @return True if target is set.
*/
- bool hasTarget() const { return _target.get() != nullptr; }
+ bool hasTarget() const { return bool(_target); }
};
} // namespace mbus
diff --git a/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp b/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp
index b306cf29cf9..fb40ccff62b 100644
--- a/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpcservicepool.cpp
@@ -14,17 +14,16 @@ RPCServicePool::RPCServicePool(RPCNetwork &net, uint32_t maxSize) :
assert(maxSize > 0);
}
-RPCServicePool::~RPCServicePool()
-{
-}
+RPCServicePool::~RPCServicePool() = default;
RPCServiceAddress::UP
RPCServicePool::resolve(const string &pattern)
{
- if (_lru.hasKey(pattern)) {
- return _lru[pattern]->resolve();
+ std::unique_ptr<RPCService> * found = _lru.findAndRef(pattern);
+ if (found) {
+ return (*found)->resolve();
} else {
- RPCService::UP service(new RPCService(_net.getMirror(), pattern));
+ auto service = std::make_unique<RPCService>(_net.getMirror(), pattern);
auto result = service->resolve();
_lru[pattern] = std::move(service);
return result;
diff --git a/messagebus/src/vespa/messagebus/network/rpctarget.cpp b/messagebus/src/vespa/messagebus/network/rpctarget.cpp
index 63470b6b707..ea21010e21c 100644
--- a/messagebus/src/vespa/messagebus/network/rpctarget.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpctarget.cpp
@@ -25,12 +25,14 @@ RPCTarget::~RPCTarget()
void
RPCTarget::resolveVersion(duration timeout, RPCTarget::IVersionHandler &handler)
{
- bool hasVersion = false;
bool shouldInvoke = false;
- {
+ ResolveState state = _state.load(std::memory_order_acquire);
+ bool hasVersion = (state == VERSION_RESOLVED);
+ if ( ! hasVersion ) {
vespalib::MonitorGuard guard(_lock);
- if (_state == VERSION_RESOLVED || _state == PROCESSING_HANDLERS) {
- while (_state == PROCESSING_HANDLERS) {
+ state = _state.load(std::memory_order_relaxed);
+ if (state == VERSION_RESOLVED || state == PROCESSING_HANDLERS) {
+ while (_state.load(std::memory_order::memory_order_relaxed) == PROCESSING_HANDLERS) {
guard.wait();
}
hasVersion = true;
@@ -54,11 +56,11 @@ RPCTarget::resolveVersion(duration timeout, RPCTarget::IVersionHandler &handler)
bool
RPCTarget::isValid() const
{
- vespalib::MonitorGuard guard(_lock);
if (_target.IsValid()) {
return true;
}
- if (_state == TARGET_INVOKED || _state == PROCESSING_HANDLERS) {
+ ResolveState state = _state.load(std::memory_order_relaxed);
+ if (state == TARGET_INVOKED || state == PROCESSING_HANDLERS) {
return true; // keep alive until RequestDone() is called
}
return false;
diff --git a/messagebus/src/vespa/messagebus/network/rpctarget.h b/messagebus/src/vespa/messagebus/network/rpctarget.h
index b6488f25cb7..d927292f26d 100644
--- a/messagebus/src/vespa/messagebus/network/rpctarget.h
+++ b/messagebus/src/vespa/messagebus/network/rpctarget.h
@@ -50,13 +50,13 @@ private:
};
typedef std::unique_ptr<vespalib::Version> Version_UP;
- vespalib::Monitor _lock;
- FRT_Supervisor &_orb;
- string _name;
- FRT_Target &_target;
- ResolveState _state;
- Version_UP _version;
- HandlerList _versionHandlers;
+ vespalib::Monitor _lock;
+ FRT_Supervisor &_orb;
+ string _name;
+ FRT_Target &_target;
+ std::atomic<ResolveState> _state;
+ Version_UP _version;
+ HandlerList _versionHandlers;
public:
/**
diff --git a/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp b/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp
index 7fcc214faa7..cc09e44c460 100644
--- a/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp
+++ b/messagebus/src/vespa/messagebus/network/rpctargetpool.cpp
@@ -63,19 +63,20 @@ RPCTargetPool::size()
RPCTarget::SP
RPCTargetPool::getTarget(FRT_Supervisor &orb, const RPCServiceAddress &address)
{
+ const string & spec = address.getConnectionSpec();
+ uint64_t currentTime = _timer->getMilliTime();
vespalib::LockGuard guard(_lock);
- string spec = address.getConnectionSpec();
- TargetMap::iterator it = _targets.find(spec);
+ auto it = _targets.find(spec);
if (it != _targets.end()) {
Entry &entry = it->second;
if (entry._target->isValid()) {
- entry._lastUse = _timer->getMilliTime();
+ entry._lastUse = currentTime;
return entry._target;
}
_targets.erase(it);
}
- RPCTarget::SP ret(new RPCTarget(spec, orb));
- _targets.insert(TargetMap::value_type(spec, Entry(ret, _timer->getMilliTime())));
+ auto ret = std::make_shared<RPCTarget>(spec, orb);
+ _targets.insert(TargetMap::value_type(spec, Entry(ret, currentTime)));
return ret;
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
index 01f602d1a57..9c8b0ec2b86 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java
@@ -52,7 +52,8 @@ public class NodeAgentImpl implements NodeAgent {
private static final long BYTES_IN_GB = 1_000_000_000L;
// Container is started with uncapped CPU and is kept that way until the first successful health check + this duration
- private static final Duration DEFAULT_WARM_UP_DURATION = Duration.ofMinutes(1);
+ // Subtract 1 second to avoid warmup coming in lockstep with tick time and always end up using an extra tick when there are just a few ms left
+ private static final Duration DEFAULT_WARM_UP_DURATION = Duration.ofSeconds(90).minus(Duration.ofSeconds(1));
private static final Logger logger = Logger.getLogger(NodeAgentImpl.class.getName());
@@ -103,7 +104,7 @@ public class NodeAgentImpl implements NodeAgent {
FlagSource flagSource, Optional<CredentialsMaintainer> credentialsMaintainer,
Optional<AclMaintainer> aclMaintainer, Optional<HealthChecker> healthChecker, Clock clock) {
this(contextSupplier, nodeRepository, orchestrator, dockerOperations, storageMaintainer, flagSource, credentialsMaintainer,
- aclMaintainer, healthChecker, clock, DEFAULT_WARM_UP_DURATION);
+ aclMaintainer, healthChecker, clock, DEFAULT_WARM_UP_DURATION);
}
public NodeAgentImpl(NodeAgentContextSupplier contextSupplier, NodeRepository nodeRepository,
@@ -211,7 +212,7 @@ public class NodeAgentImpl implements NodeAgent {
private Container startContainer(NodeAgentContext context) {
ContainerData containerData = createContainerData(context);
- ContainerResources wantedResources = context.nodeType() != NodeType.tenant || warmUpDuration.isNegative() ?
+ ContainerResources wantedResources = context.nodeType() != NodeType.tenant || warmUpDuration(context.zone()).isNegative() ?
getContainerResources(context) : getContainerResources(context).withUnlimitedCpus();
dockerOperations.createContainer(context, containerData, wantedResources);
dockerOperations.startContainer(context);
@@ -357,7 +358,7 @@ public class NodeAgentImpl implements NodeAgent {
ContainerResources wantedContainerResources = getContainerResources(context);
if (healthChecker.isPresent() && firstSuccessfulHealthCheckInstant
- .map(clock.instant().minus(warmUpDuration)::isBefore)
+ .map(clock.instant().minus(warmUpDuration(context.zone()))::isBefore)
.orElse(true))
return existingContainer;
@@ -473,7 +474,7 @@ public class NodeAgentImpl implements NodeAgent {
if (firstSuccessfulHealthCheckInstant.isEmpty())
firstSuccessfulHealthCheckInstant = Optional.of(clock.instant());
- Duration timeLeft = Duration.between(clock.instant(), firstSuccessfulHealthCheckInstant.get().plus(warmUpDuration));
+ Duration timeLeft = Duration.between(clock.instant(), firstSuccessfulHealthCheckInstant.get().plus(warmUpDuration(context.zone())));
if (!container.get().resources.equalsCpu(getContainerResources(context)))
throw new ConvergenceException("Refusing to resume until warm up period ends (" +
(timeLeft.isNegative() ? "next tick" : "in " + timeLeft) + ")");
@@ -604,4 +605,10 @@ public class NodeAgentImpl implements NodeAgent {
protected Optional<CredentialsMaintainer> credentialsMaintainer() {
return credentialsMaintainer;
}
+
+ private Duration warmUpDuration(ZoneApi zone) {
+ return zone.getSystemName().isCd() || zone.getEnvironment().isTest()
+ ? Duration.ofSeconds(-1)
+ : warmUpDuration;
+ }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriter.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriter.java
index 83ac3eeeaf4..22cabdc8ed9 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriter.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriter.java
@@ -1,6 +1,8 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util;
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
+
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
@@ -10,6 +12,7 @@ import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
+import java.util.logging.Logger;
import static com.yahoo.vespa.hosted.node.admin.task.util.file.IOExceptionUtil.ifExists;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -23,6 +26,8 @@ import static java.util.stream.Collectors.joining;
*/
public class DefaultEnvWriter {
+ private static final Logger logger = Logger.getLogger(DefaultEnvWriter.class.getName());
+
private final Map<String, Operation> operations = new LinkedHashMap<>();
public DefaultEnvWriter addOverride(String name, String value) {
@@ -50,12 +55,13 @@ public class DefaultEnvWriter {
*
* @return true if the file was modified
*/
- public boolean updateFile(Path defaultEnvFile) {
+ public boolean updateFile(TaskContext context, Path defaultEnvFile) {
List<String> currentDefaultEnvLines = ifExists(() -> Files.readAllLines(defaultEnvFile)).orElse(List.of());
List<String> newDefaultEnvLines = generateContent(currentDefaultEnvLines);
if (currentDefaultEnvLines.equals(newDefaultEnvLines)) {
return false;
} else {
+ context.log(logger, "Updating " + defaultEnvFile.toString());
Path tempFile = Paths.get(defaultEnvFile.toString() + ".tmp");
uncheck(() -> Files.write(tempFile, newDefaultEnvLines));
uncheck(() -> Files.move(tempFile, defaultEnvFile, ATOMIC_MOVE));
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
index 268e0a5ccfd..07e73eb0ee7 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPath.java
@@ -11,6 +11,7 @@ import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.FileAttribute;
+import java.nio.file.attribute.FileTime;
import java.nio.file.attribute.GroupPrincipal;
import java.nio.file.attribute.PosixFileAttributeView;
import java.nio.file.attribute.PosixFileAttributes;
@@ -37,22 +38,33 @@ import static com.yahoo.yolean.Exceptions.uncheck;
public class UnixPath {
private final Path path;
- public UnixPath(Path path) {
- this.path = path;
- }
+ public UnixPath(Path path) { this.path = path; }
+ public UnixPath(String path) { this(Paths.get(path)); }
- public UnixPath(String path) {
- this(Paths.get(path));
- }
+ public Path toPath() { return path; }
+ public UnixPath resolve(String relativeOrAbsolutePath) { return new UnixPath(path.resolve(relativeOrAbsolutePath)); }
+
+ public UnixPath getParent() {
+ Path parentPath = path.getParent();
+ if (parentPath == null) {
+ throw new IllegalStateException("Path has no parent directory: '" + path + "'");
+ }
- public Path toPath() {
- return path;
+ return new UnixPath(parentPath);
}
- public boolean exists() {
- return Files.exists(path);
+ public String getFilename() {
+ Path filename = path.getFileName();
+ if (filename == null) {
+ // E.g. "/".
+ throw new IllegalStateException("Path has no filename: '" + path.toString() + "'");
+ }
+
+ return filename.toString();
}
+ public boolean exists() { return Files.exists(path); }
+
public String readUtf8File() {
return new String(readBytes(), StandardCharsets.UTF_8);
}
@@ -91,6 +103,18 @@ public class UnixPath {
return this;
}
+ public UnixPath atomicWriteUt8(String content) {
+ return atomicWriteBytes(content.getBytes(StandardCharsets.UTF_8));
+ }
+
+ /** Write a file to the same dir as this, and then atomically move it to this' path. */
+ public UnixPath atomicWriteBytes(byte[] content) {
+ UnixPath temporaryPath = getParent().resolve(getFilename() + ".10Ia2f4N5");
+ temporaryPath.writeBytes(content);
+ temporaryPath.atomicMove(path);
+ return this;
+ }
+
public String getPermissions() {
return getAttributes().permissions();
}
@@ -135,6 +159,11 @@ public class UnixPath {
return getAttributes().lastModifiedTime();
}
+ public UnixPath updateLastModifiedTime() {
+ uncheck(() -> Files.setLastModifiedTime(path, FileTime.from(Instant.now())));
+ return this;
+ }
+
public FileAttributes getAttributes() {
PosixFileAttributes attributes = uncheck(() ->
Files.getFileAttributeView(path, PosixFileAttributeView.class).readAttributes());
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
index a4138b215c4..5f7fbdd0d69 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/Yum.java
@@ -69,7 +69,7 @@ public class Yum {
*
* @return false only if the package was already locked and installed at the given version (no-op)
*/
- public boolean installFixedVersion(TaskContext context, YumPackageName yumPackage) {
+ public boolean installFixedVersion(TaskContext context, YumPackageName yumPackage, String... repos) {
String targetVersionLockName = yumPackage.toVersionLockName();
boolean alreadyLocked = terminal
@@ -118,25 +118,25 @@ public class Yum {
// - "Nothing to do"
// And in case we need to downgrade and return true from converge()
- CommandLine commandLine = terminal
- .newCommandLine(context)
- .add("yum", "install", "--assumeyes", yumPackage.toName());
+ var installCommand = terminal.newCommandLine(context).add("yum", "install");
+ for (String repo : repos) installCommand.add("--enablerepo=" + repo);
+ installCommand.add("--assumeyes", yumPackage.toName());
- String output = commandLine.executeSilently().getUntrimmedOutput();
+ String output = installCommand.executeSilently().getUntrimmedOutput();
if (NOTHING_TO_DO_PATTERN.matcher(output).find()) {
if (CHECKING_FOR_UPDATE_PATTERN.matcher(output).find()) {
// case 3.
- terminal.newCommandLine(context)
- .add("yum", "downgrade", "--assumeyes", yumPackage.toName())
- .execute();
+ var upgradeCommand = terminal.newCommandLine(context).add("yum", "downgrade", "--assumeyes");
+ for (String repo : repos) upgradeCommand.add("--enablerepo=" + repo);
+ upgradeCommand.add(yumPackage.toName()).execute();
modified = true;
} else {
// case 2.
}
} else {
// case 1.
- commandLine.recordSilentExecutionAsSystemModification();
+ installCommand.recordSilentExecutionAsSystemModification();
modified = true;
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
index 54c8719bceb..3f5c3025850 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageName.java
@@ -72,6 +72,15 @@ public class YumPackageName {
architecture = packageName.architecture;
}
+ /**
+ * Set the epoch of the YUM package.
+ *
+ * <p>WARNING: Should only be invoked if the YUM package actually has an epoch. Typically
+ * YUM packages doesn't have one explicitly set, and in case "0" will be used with
+ * {@link #toVersionLockName()} (otherwise it fails), but it will be absent from an
+ * install with {@link #toName()} (otherwise it fails). This typically means that
+ * you should set this only if the epoch is != "0".</p>
+ */
public Builder setEpoch(String epoch) { this.epoch = Optional.of(epoch); return this; }
public Builder setName(String name) { this.name = name; return this; }
public Builder setVersion(String version) { this.version = Optional.of(version); return this; }
@@ -235,7 +244,7 @@ public class YumPackageName {
*/
public String toVersionLockName() {
return String.format("%s:%s-%s-%s.%s",
- epoch.orElseThrow(() -> new IllegalStateException("Epoch is missing for YUM package " + name)),
+ epoch.orElse("0"),
name,
version.orElseThrow(() -> new IllegalStateException("Version is missing for YUM package " + name)),
release.orElseThrow(() -> new IllegalStateException("Release is missing for YUM package " + name)),
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriterTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriterTest.java
index a81ad8ff2eb..a2457266560 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriterTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/DefaultEnvWriterTest.java
@@ -1,6 +1,7 @@
// Copyright 2019 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.task.util;
+import com.yahoo.vespa.hosted.node.admin.component.TaskContext;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
@@ -9,11 +10,16 @@ import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.util.logging.Logger;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
/**
* @author bjorncs
@@ -26,6 +32,8 @@ public class DefaultEnvWriterTest {
private static final Path EXAMPLE_FILE = Paths.get("src/test/resources/default-env-example.txt");
private static final Path EXPECTED_RESULT_FILE = Paths.get("src/test/resources/default-env-rewritten.txt");
+ private final TaskContext context = mock(TaskContext.class);
+
@Test
public void default_env_is_correctly_rewritten() throws IOException {
Path tempFile = temporaryFolder.newFile().toPath();
@@ -36,14 +44,16 @@ public class DefaultEnvWriterTest {
writer.addFallback("VESPA_CONFIGSERVER", "new-fallback-configserver");
writer.addOverride("VESPA_TLS_CONFIG_FILE", "/override/path/to/config.file");
- boolean modified = writer.updateFile(tempFile);
+ boolean modified = writer.updateFile(context, tempFile);
assertTrue(modified);
assertEquals(Files.readString(EXPECTED_RESULT_FILE), Files.readString(tempFile));
+ verify(context, times(1)).log(any(Logger.class), any(String.class));
- modified = writer.updateFile(tempFile);
+ modified = writer.updateFile(context, tempFile);
assertFalse(modified);
assertEquals(Files.readString(EXPECTED_RESULT_FILE), Files.readString(tempFile));
+ verify(context, times(1)).log(any(Logger.class), any(String.class));
}
@Test
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java
index 3b839f7f446..3159689c22e 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/file/UnixPathTest.java
@@ -3,6 +3,7 @@
package com.yahoo.vespa.hosted.node.admin.task.util.file;
import com.yahoo.vespa.test.file.TestFileSystem;
+import org.junit.ComparisonFailure;
import org.junit.Test;
import java.nio.file.FileSystem;
@@ -13,6 +14,7 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
/**
* @author hakonhall
@@ -124,4 +126,44 @@ public class UnixPathTest {
assertFalse(dir1 + " deleted recursively", Files.exists(dir1));
}
+ @Test
+ public void atomicWrite() {
+ var path = new UnixPath(fs.getPath("/dir/foo"));
+ path.createParents();
+ path.writeUtf8File("bar");
+ path.atomicWriteUt8("bar v2");
+ assertEquals("bar v2", path.readUtf8File());
+ }
+
+ @Test
+ public void testParentAndFilename() {
+ var absolutePath = new UnixPath("/foo/bar");
+ assertEquals("/foo", absolutePath.getParent().toString());
+ assertEquals("bar", absolutePath.getFilename());
+
+ var pathWithoutSlash = new UnixPath("foo");
+ assertRuntimeException(IllegalStateException.class, "Path has no parent directory: 'foo'", () -> pathWithoutSlash.getParent());
+ assertEquals("foo", pathWithoutSlash.getFilename());
+
+ var pathWithSlash = new UnixPath("/foo");
+ assertEquals("/", pathWithSlash.getParent().toString());
+ assertEquals("foo", pathWithSlash.getFilename());
+
+ assertRuntimeException(IllegalStateException.class, "Path has no parent directory: '/'", () -> new UnixPath("/").getParent());
+ assertRuntimeException(IllegalStateException.class, "Path has no filename: '/'", () -> new UnixPath("/").getFilename());
+ }
+
+ private <T extends RuntimeException> void assertRuntimeException(Class<T> baseClass, String message, Runnable runnable) {
+ try {
+ runnable.run();
+ fail("No exception was thrown");
+ } catch (RuntimeException e) {
+ if (!baseClass.isInstance(e)) {
+ throw new ComparisonFailure("Exception class mismatch", baseClass.getName(), e.getClass().getName());
+ }
+
+ assertEquals(message, e.getMessage());
+ }
+ }
+
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java
index 01664f5c22b..64e2997d486 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/task/util/yum/YumPackageNameTest.java
@@ -56,7 +56,7 @@ public class YumPackageNameTest {
"1.el7",
null,
"docker-engine-selinux-1.12.6-1.el7",
- null);
+ "0:docker-engine-selinux-1.12.6-1.el7.*");
// name-ver-rel.arch
verifyPackageName("docker-engine-selinux-1.12.6-1.el7.x86_64",
@@ -66,7 +66,7 @@ public class YumPackageNameTest {
"1.el7",
"x86_64",
"docker-engine-selinux-1.12.6-1.el7.x86_64",
- null);
+ "0:docker-engine-selinux-1.12.6-1.el7.*");
// name-epoch:ver-rel.arch
verifyPackageName(
@@ -112,7 +112,7 @@ public class YumPackageNameTest {
yumPackageName.toVersionLockName();
fail();
} catch (IllegalStateException e) {
- assertThat(e.getMessage(), containsStringIgnoringCase("epoch is missing"));
+ assertThat(e.getMessage(), containsStringIgnoringCase("Version is missing "));
}
} else {
assertEquals(toVersionName, yumPackageName.toVersionLockName());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
index 3cf2442f6f7..77fa7dfc7e4 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/NodeRepository.java
@@ -19,6 +19,7 @@ import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.flags.Flags;
+import com.yahoo.vespa.hosted.provision.applications.Applications;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerId;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance;
@@ -98,6 +99,7 @@ public class NodeRepository extends AbstractComponent {
private final FirmwareChecks firmwareChecks;
private final DockerImages dockerImages;
private final JobControl jobControl;
+ private final Applications applications;
/**
* Creates a node repository from a zookeeper provider.
@@ -124,6 +126,7 @@ public class NodeRepository extends AbstractComponent {
this.firmwareChecks = new FirmwareChecks(db, clock);
this.dockerImages = new DockerImages(db, dockerImage);
this.jobControl = new JobControl(db);
+ this.applications = new Applications();
// read and write all nodes to make sure they are stored in the latest version of the serialized format
for (State state : State.values())
@@ -154,6 +157,9 @@ public class NodeRepository extends AbstractComponent {
/** Returns the status of maintenance jobs managed by this. */
public JobControl jobControl() { return jobControl; }
+ /** Returns this node repo's view of the applications deployed to it */
+ public Applications applications() { return applications; }
+
// ---------------- Query API ----------------------------------------------------------------
/**
@@ -192,6 +198,11 @@ public class NodeRepository extends AbstractComponent {
return NodeList.copyOf(getNodes());
}
+ /** Returns a filterable list of all nodes of an application */
+ public NodeList list(ApplicationId application) {
+ return NodeList.copyOf(getNodes(application));
+ }
+
/** Returns a locked list of all nodes in this repository */
public LockedNodeList list(Mutex lock) {
return new LockedNodeList(getNodes(), lock);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java
new file mode 100644
index 00000000000..e56e426b499
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Application.java
@@ -0,0 +1,59 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.applications;
+
+import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.ClusterSpec;
+import com.yahoo.transaction.Mutex;
+
+import java.util.Map;
+import java.util.HashMap;
+import java.util.Optional;
+
+/**
+ * The node repository's view of an application deployment.
+ *
+ * This is immutable, and must be locked with the application lock on read-modify-write.
+ *
+ * @author bratseth
+ */
+public class Application {
+
+ private final Map<ClusterSpec.Id, Cluster> clusters;
+
+ public Application() {
+ this(Map.of());
+ }
+
+ private Application(Map<ClusterSpec.Id, Cluster> clusters) {
+ this.clusters = Map.copyOf(clusters);
+ }
+
+ /** Returns the cluster with the given id or null if none */
+ public Cluster cluster(ClusterSpec.Id id) { return clusters.get(id); }
+
+ public Application with(ClusterSpec.Id id, Cluster cluster) {
+ Map<ClusterSpec.Id, Cluster> clusters = new HashMap<>(this.clusters);
+ clusters.put(id, cluster);
+ return new Application(clusters);
+ }
+
+ /**
+ * Returns an application with the given cluster having the min and max resource limits of the given cluster.
+ * If the cluster has a target which is not inside the new limits, the target is removed.
+ */
+ public Application withClusterLimits(ClusterSpec.Id id, ClusterResources min, ClusterResources max) {
+ Cluster cluster = clusters.get(id);
+ return with(id, new Cluster(min, max, cluster == null ? Optional.empty() : cluster.targetResources()));
+ }
+
+ /**
+ * Returns an application with the given target for the given cluster,
+ * if it exists and the target is within the bounds
+ */
+ public Application withClusterTarget(ClusterSpec.Id id, ClusterResources target) {
+ Cluster cluster = clusters.get(id);
+ if (cluster == null) return this;
+ return with(id, cluster.withTarget(target));
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Applications.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Applications.java
new file mode 100644
index 00000000000..879fcc5f6cb
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Applications.java
@@ -0,0 +1,29 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.applications;
+
+import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.transaction.Mutex;
+
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * An (in-memory, for now) repository of the node repo's view of applications.
+ *
+ * This is multithread safe.
+ *
+ * @author bratseth
+ */
+public class Applications {
+
+ private final ConcurrentHashMap<ApplicationId, Application> applications = new ConcurrentHashMap<>();
+
+ /** Returns the application with the given id, or null if it does not exist and should not be created */
+ public Application get(ApplicationId applicationId, boolean create) {
+ return applications.computeIfAbsent(applicationId, id -> create ? new Application() : null);
+ }
+
+ public void set(ApplicationId id, Application application, Mutex applicationLock) {
+ applications.put(id, application);
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java
new file mode 100644
index 00000000000..6ff7f41be8f
--- /dev/null
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/applications/Cluster.java
@@ -0,0 +1,66 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.applications;
+
+import com.yahoo.config.provision.ClusterResources;
+import com.yahoo.config.provision.NodeResources;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * The node repo's view of a cluster in an application deployment.
+ *
+ * This is immutable, and must be locked with the application lock on read-modify-write.
+ *
+ * @author bratseth
+ */
+public class Cluster {
+
+ private final ClusterResources min, max;
+ private final Optional<ClusterResources> target;
+
+ Cluster(ClusterResources minResources, ClusterResources maxResources, Optional<ClusterResources> targetResources) {
+ this.min = Objects.requireNonNull(minResources);
+ this.max = Objects.requireNonNull(maxResources);
+ Objects.requireNonNull(targetResources);
+
+ if (targetResources.isPresent() && ! targetResources.get().isWithin(minResources, maxResources))
+ this.target = Optional.empty();
+ else
+ this.target = targetResources;
+ }
+
+ /** Returns the configured minimal resources in this cluster */
+ public ClusterResources minResources() { return min; }
+
+ /** Returns the configured maximal resources in this cluster */
+ public ClusterResources maxResources() { return max; }
+
+ /**
+ * Returns the computed resources (between min and max, inclusive) this cluster should
+ * have allocated at the moment, or empty if the system currently have no opinion on this.
+ */
+ public Optional<ClusterResources> targetResources() { return target; }
+
+ public Cluster withTarget(ClusterResources target) {
+ return new Cluster(min, max, Optional.of(target));
+ }
+
+ public Cluster withoutTarget() {
+ return new Cluster(min, max, Optional.empty());
+ }
+
+ public NodeResources capAtLimits(NodeResources resources) {
+ resources = resources.withVcpu(between(min.nodeResources().vcpu(), max.nodeResources().vcpu(), resources.vcpu()));
+ resources = resources.withMemoryGb(between(min.nodeResources().memoryGb(), max.nodeResources().memoryGb(), resources.memoryGb()));
+ resources = resources.withDiskGb(between(min.nodeResources().diskGb(), max.nodeResources().diskGb(), resources.diskGb()));
+ return resources;
+ }
+
+ private double between(double min, double max, double value) {
+ value = Math.max(min, value);
+ value = Math.min(max, value);
+ return value;
+ }
+
+}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
index 40af5f43312..5ca09ddf51c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/AllocatableClusterResources.java
@@ -1,6 +1,7 @@
// Copyright Verizon Media. 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.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
@@ -14,6 +15,11 @@ import java.util.List;
*/
public class AllocatableClusterResources {
+ // We only depend on the ratios between these values
+ private static final double cpuUnitCost = 12.0;
+ private static final double memoryUnitCost = 1.2;
+ private static final double diskUnitCost = 0.045;
+
/** The node count in the cluster */
private final int nodes;
@@ -25,28 +31,40 @@ public class AllocatableClusterResources {
private final ClusterSpec.Type clusterType;
+ private final double fulfilment;
+
public AllocatableClusterResources(List<Node> nodes, HostResourcesCalculator calculator) {
this.advertisedResources = nodes.get(0).flavor().resources();
this.realResources = calculator.realResourcesOf(nodes.get(0));
this.nodes = nodes.size();
this.groups = (int)nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
this.clusterType = nodes.get(0).allocation().get().membership().cluster().type();
+ this.fulfilment = 1;
}
- public AllocatableClusterResources(ClusterResources realResources, NodeResources advertisedResources) {
+ public AllocatableClusterResources(ClusterResources realResources,
+ NodeResources advertisedResources,
+ NodeResources idealResources,
+ ClusterSpec.Type clusterType) {
this.realResources = realResources.nodeResources();
this.advertisedResources = advertisedResources;
this.nodes = realResources.nodes();
this.groups = realResources.groups();
- this.clusterType = realResources.clusterType();
+ this.clusterType = clusterType;
+ this.fulfilment = fulfilment(realResources.nodeResources(), idealResources);
}
- public AllocatableClusterResources(ClusterResources realResources, Flavor flavor, HostResourcesCalculator calculator) {
+ public AllocatableClusterResources(ClusterResources realResources,
+ Flavor flavor,
+ NodeResources idealResources,
+ ClusterSpec.Type clusterType,
+ HostResourcesCalculator calculator) {
this.realResources = realResources.nodeResources();
this.advertisedResources = calculator.advertisedResourcesOf(flavor);
this.nodes = realResources.nodes();
this.groups = realResources.groups();
- this.clusterType = realResources.clusterType();
+ this.clusterType = clusterType;
+ this.fulfilment = fulfilment(realResources.nodeResources(), idealResources);
}
/**
@@ -61,15 +79,46 @@ public class AllocatableClusterResources {
*/
public NodeResources advertisedResources() { return advertisedResources; }
- public double cost() { return nodes * Autoscaler.costOf(advertisedResources); }
+ public ClusterResources toAdvertisedClusterResources() {
+ return new ClusterResources(nodes, groups, advertisedResources);
+ }
public int nodes() { return nodes; }
public int groups() { return groups; }
public ClusterSpec.Type clusterType() { return clusterType; }
+ public double cost() { return nodes * costOf(advertisedResources); }
+
+ /**
+ * Returns the fraction measuring how well the real resources fulfils the ideal: 1 means completely fulfiled,
+ * 0 means we have zero real resources.
+ * The real may be short of the ideal due to resource limits imposed by the system or application.
+ */
+ public double fulfilment() { return fulfilment; }
+
+ private static double costOf(NodeResources resources) {
+ return resources.vcpu() * cpuUnitCost +
+ resources.memoryGb() * memoryUnitCost +
+ resources.diskGb() * diskUnitCost;
+ }
+
+ private static double fulfilment(NodeResources realResources, NodeResources idealResources) {
+ double vcpuFulfilment = Math.min(1, realResources.vcpu() / idealResources.vcpu());
+ double memoryGbFulfilment = Math.min(1, realResources.memoryGb() / idealResources.memoryGb());
+ double diskGbFulfilment = Math.min(1, realResources.diskGb() / idealResources.diskGb());
+ return (vcpuFulfilment + memoryGbFulfilment + diskGbFulfilment) / 3;
+ }
+
+ public boolean preferableTo(AllocatableClusterResources other) {
+ if (this.fulfilment > other.fulfilment) return true; // we always want to fulfil as much as possible
+ return this.cost() < other.cost(); // otherwise, prefer lower cost
+ }
+
@Override
public String toString() {
- return "$" + cost() + ": " + realResources();
+ return nodes + " nodes with " + realResources() +
+ " at cost $" + cost() +
+ (fulfilment < 1.0 ? " (fulfilment " + fulfilment + ")" : "");
}
}
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 0e70178f71e..447d3494fbc 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
@@ -2,12 +2,14 @@
package com.yahoo.vespa.hosted.provision.autoscale;
import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.host.FlavorOverrides;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
import com.yahoo.vespa.hosted.provision.provisioning.NodeResourceLimits;
@@ -35,15 +37,10 @@ public class Autoscaler {
private static final int minimumMeasurements = 500; // TODO: Per node instead? Also say something about interval?
- /** What cost difference factor warrants reallocation? */
- private static final double costDifferenceRatioWorthReallocation = 0.1;
- /** What difference factor from ideal (for any resource) warrants a change? */
- private static final double idealDivergenceWorthReallocation = 0.1;
-
- // We only depend on the ratios between these values
- private static final double cpuUnitCost = 12.0;
- private static final double memoryUnitCost = 1.2;
- private static final double diskUnitCost = 0.045;
+ /** What cost difference factor is worth a reallocation? */
+ private static final double costDifferenceWorthReallocation = 0.1;
+ /** What difference factor for a resource is worth a reallocation? */
+ private static final double resourceDifferenceWorthReallocation = 0.1;
private final HostResourcesCalculator resourcesCalculator;
private final NodeMetricsDb metricsDb;
@@ -65,12 +62,8 @@ public class Autoscaler {
* @param clusterNodes the list of all the active nodes in a cluster
* @return a new suggested allocation for this cluster, or empty if it should not be rescaled at this time
*/
- public Optional<AllocatableClusterResources> autoscale(List<Node> clusterNodes) {
- if (clusterNodes.stream().anyMatch(node -> node.status().wantToRetire() ||
- node.allocation().get().membership().retired() ||
- node.allocation().get().isRemovable())) {
- return Optional.empty(); // Don't autoscale clusters that are in flux
- }
+ public Optional<AllocatableClusterResources> autoscale(Cluster cluster, List<Node> clusterNodes) {
+ if (unstable(clusterNodes)) return Optional.empty();
ClusterSpec.Type clusterType = clusterNodes.get(0).allocation().get().membership().cluster().type();
AllocatableClusterResources currentAllocation = new AllocatableClusterResources(clusterNodes, resourcesCalculator);
@@ -82,37 +75,38 @@ public class Autoscaler {
Optional<AllocatableClusterResources> bestAllocation = findBestAllocation(cpuLoad.get(),
memoryLoad.get(),
diskLoad.get(),
- currentAllocation);
+ currentAllocation,
+ cluster);
if (bestAllocation.isEmpty()) return Optional.empty();
-
- if (closeToIdeal(Resource.cpu, cpuLoad.get()) &&
- closeToIdeal(Resource.memory, memoryLoad.get()) &&
- closeToIdeal(Resource.disk, diskLoad.get()) &&
- similarCost(bestAllocation.get().cost(), currentAllocation.cost())) {
- return Optional.empty(); // Avoid small, unnecessary changes
- }
+ if (similar(bestAllocation.get(), currentAllocation)) return Optional.empty();
return bestAllocation;
}
private Optional<AllocatableClusterResources> findBestAllocation(double cpuLoad, double memoryLoad, double diskLoad,
- AllocatableClusterResources currentAllocation) {
+ AllocatableClusterResources currentAllocation,
+ Cluster cluster) {
Optional<AllocatableClusterResources> bestAllocation = Optional.empty();
- for (ResourceIterator i = new ResourceIterator(cpuLoad, memoryLoad, diskLoad, currentAllocation); i.hasNext(); ) {
- ClusterResources allocation = i.next();
- Optional<AllocatableClusterResources> allocatableResources = toAllocatableResources(allocation);
+ for (ResourceIterator i = new ResourceIterator(cpuLoad, memoryLoad, diskLoad, currentAllocation, cluster); i.hasNext(); ) {
+ Optional<AllocatableClusterResources> allocatableResources = toAllocatableResources(i.next(),
+ currentAllocation.clusterType(),
+ cluster);
if (allocatableResources.isEmpty()) continue;
- if (bestAllocation.isEmpty() || allocatableResources.get().cost() < bestAllocation.get().cost())
+ if (bestAllocation.isEmpty() || allocatableResources.get().preferableTo(bestAllocation.get()))
bestAllocation = allocatableResources;
}
return bestAllocation;
}
- private boolean similarCost(double cost1, double cost2) {
- return similar(cost1, cost2, costDifferenceRatioWorthReallocation);
- }
-
- private boolean closeToIdeal(Resource resource, double value) {
- return similar(resource.idealAverageLoad(), value, idealDivergenceWorthReallocation);
+ /** Returns true if both total real resources and total cost are similar */
+ private boolean similar(AllocatableClusterResources a, AllocatableClusterResources b) {
+ return similar(a.cost(), b.cost(), costDifferenceWorthReallocation) &&
+ similar(a.realResources().vcpu() * a.nodes(),
+ b.realResources().vcpu() * b.nodes(), resourceDifferenceWorthReallocation) &&
+ similar(a.realResources().memoryGb() * a.nodes(),
+ b.realResources().memoryGb() * b.nodes(), resourceDifferenceWorthReallocation) &&
+ similar(a.realResources().diskGb() * a.nodes(),
+ b.realResources().diskGb() * b.nodes(),
+ resourceDifferenceWorthReallocation);
}
private boolean similar(double r1, double r2, double threshold) {
@@ -123,15 +117,22 @@ public class Autoscaler {
* Returns the smallest allocatable node resources larger than the given node resources,
* or empty if none available.
*/
- private Optional<AllocatableClusterResources> toAllocatableResources(ClusterResources resources) {
- NodeResources nodeResources = nodeResourceLimits.enlargeToLegal(resources.nodeResources(),
- resources.clusterType());
+ private Optional<AllocatableClusterResources> toAllocatableResources(ClusterResources resources,
+ ClusterSpec.Type clusterType,
+ Cluster cluster) {
+ NodeResources nodeResources = resources.nodeResources();
+ if ( ! cluster.minResources().equals(cluster.maxResources())) // enforce application limits unless suggest mode
+ nodeResources = cluster.capAtLimits(nodeResources);
+ nodeResources = nodeResourceLimits.enlargeToLegal(nodeResources, clusterType); // enforce system limits
+
if (allowsHostSharing(nodeRepository.zone().cloud())) {
// return the requested resources, or empty if they cannot fit on existing hosts
for (Flavor flavor : nodeRepository.getAvailableFlavors().getFlavors()) {
if (flavor.resources().satisfies(nodeResources))
return Optional.of(new AllocatableClusterResources(resources.with(nodeResources),
- nodeResources));
+ nodeResources,
+ resources.nodeResources(),
+ clusterType));
}
return Optional.empty();
}
@@ -145,6 +146,8 @@ public class Autoscaler {
flavor = flavor.with(FlavorOverrides.ofDisk(nodeResources.diskGb()));
var candidate = new AllocatableClusterResources(resources.with(flavor.resources()),
flavor,
+ resources.nodeResources(),
+ clusterType,
resourcesCalculator);
if (best.isEmpty() || candidate.cost() <= best.get().cost())
@@ -181,10 +184,10 @@ public class Autoscaler {
return true;
}
- static double costOf(NodeResources resources) {
- return resources.vcpu() * cpuUnitCost +
- resources.memoryGb() * memoryUnitCost +
- resources.diskGb() * diskUnitCost;
+ public static boolean unstable(List<Node> nodes) {
+ return nodes.stream().anyMatch(node -> node.status().wantToRetire() ||
+ node.allocation().get().membership().retired() ||
+ node.allocation().get().isRemovable());
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterResources.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterResources.java
deleted file mode 100644
index ebceba8c97f..00000000000
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ClusterResources.java
+++ /dev/null
@@ -1,66 +0,0 @@
-// Copyright Verizon Media. 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.ClusterSpec;
-import com.yahoo.config.provision.NodeResources;
-import com.yahoo.vespa.hosted.provision.Node;
-
-import java.util.List;
-import java.util.Objects;
-
-/** A description of the resources of a cluster */
-public class ClusterResources {
-
- /** The node count in the cluster */
- private final int nodes;
-
- /** The number of node groups in the cluster */
- private final int groups;
-
- /** The resources of each node in the cluster */
- private final NodeResources nodeResources;
-
- /** The kind of cluster these resources are for */
- private final ClusterSpec.Type clusterType;
-
- public ClusterResources(int nodes, int groups, NodeResources nodeResources, ClusterSpec.Type clusterType) {
- this.nodes = nodes;
- this.groups = groups;
- this.nodeResources = nodeResources;
- this.clusterType = clusterType;
- }
-
- /** Returns the total number of allocated nodes (over all groups) */
- public int nodes() { return nodes; }
- public int groups() { return groups; }
- public NodeResources nodeResources() { return nodeResources; }
- public ClusterSpec.Type clusterType() { return clusterType; }
-
- public ClusterResources with(NodeResources resources) {
- return new ClusterResources(nodes, groups, resources, clusterType);
- }
-
- @Override
- public boolean equals(Object o) {
- if (o == this) return true;
- if ( ! (o instanceof ClusterResources)) return false;
-
- ClusterResources other = (ClusterResources)o;
- if (other.nodes != this.nodes) return false;
- if (other.groups != this.groups) return false;
- if (other.nodeResources != this.nodeResources) return false;
- if (other.clusterType != this.clusterType) return false;
- return true;
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(nodes, groups, nodeResources, clusterType);
- }
-
- @Override
- public String toString() {
- return clusterType + " cluster resources: " + nodes + " * " + nodeResources + (groups > 1 ? " in " + groups + " groups" : "");
- }
-
-}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcher.java
index 7137b69b9ac..232fee1df6a 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcher.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcher.java
@@ -7,6 +7,7 @@ import com.yahoo.component.AbstractComponent;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.applicationmodel.HostName;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.orchestrator.HostNameNotFoundException;
import com.yahoo.vespa.orchestrator.Orchestrator;
@@ -51,13 +52,15 @@ public class NodeMetricsFetcher extends AbstractComponent implements NodeMetrics
@Override
public Collection<MetricValue> fetchMetrics(ApplicationId application) {
- Optional<Node> metricsV2Container = nodeRepository.list()
- .owner(application)
- .state(Node.State.active)
- .container()
- .filter(node -> expectedUp(node))
- .stream()
- .findFirst();
+ NodeList applicationNodes = nodeRepository.list(application).state(Node.State.active);
+
+ // Do not try to draw conclusions from utilization while unstable
+ if (Autoscaler.unstable(applicationNodes.asList())) return Collections.emptyList();
+
+ Optional<Node> metricsV2Container = applicationNodes.container()
+ .filter(node -> expectedUp(node))
+ .stream()
+ .findFirst();
if (metricsV2Container.isEmpty()) return Collections.emptyList();
String url = "http://" + metricsV2Container.get().hostname() + ":" + 4080 + apiPath + "?consumer=default";
String response = httpClient.get(url);
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java
index e84544e7e7b..3d5ce8881e0 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/Resource.java
@@ -12,7 +12,7 @@ public enum Resource {
/** Cpu utilization ratio */
cpu {
- String metricName() { return "cpu.util"; }
+ public String metricName() { return "cpu.util"; }
double idealAverageLoad() { return 0.2; }
double valueFrom(NodeResources resources) { return resources.vcpu(); }
double valueFromMetric(double metricValue) { return metricValue / 100; } // % to ratio
@@ -20,7 +20,7 @@ public enum Resource {
/** Memory utilization ratio */
memory {
- String metricName() { return "mem_total.util"; }
+ public String metricName() { return "mem_total.util"; }
double idealAverageLoad() { return 0.7; }
double valueFrom(NodeResources resources) { return resources.memoryGb(); }
double valueFromMetric(double metricValue) { return metricValue / 100; } // % to ratio
@@ -28,13 +28,13 @@ public enum Resource {
/** Disk utilization ratio */
disk {
- String metricName() { return "disk.util"; }
+ public String metricName() { return "disk.util"; }
double idealAverageLoad() { return 0.6; }
double valueFrom(NodeResources resources) { return resources.diskGb(); }
double valueFromMetric(double metricValue) { return metricValue / 100; } // % to ratio
};
- abstract String metricName();
+ public abstract String metricName();
/** The load we should have of this resource on average, when one node in the cluster is down */
abstract double idealAverageLoad();
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java
index ee1af65753a..bc14ca1779c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/autoscale/ResourceIterator.java
@@ -1,7 +1,9 @@
// Copyright Verizon Media. 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.ClusterResources;
import com.yahoo.config.provision.NodeResources;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
/**
* Provides iteration over possible cluster resource allocations given a target total load
@@ -9,16 +11,19 @@ import com.yahoo.config.provision.NodeResources;
*/
public class ResourceIterator {
- // Configured min and max nodes TODO: These should come from the application package
- private static final int minimumNodesPerCluster = 3; // Since this is with redundancy it cannot be lower than 2
- private static final int maximumNodesPerCluster = 150;
+ // Configured min and max nodes for suggestions for apps which have not activated autoscaling
+ private static final int minimumNodes = 3; // Since this is with redundancy it cannot be lower than 2
+ private static final int maximumNodes = 150;
// When a query is issued on a node the cost is the sum of a fixed cost component and a cost component
// proportional to document count. We must account for this when comparing configurations with more or fewer nodes.
// TODO: Measure this, and only take it into account with queries
private static final double fixedCpuCostFraction = 0.1;
- // Describes the observed state
+ // Prescribed state
+ private final Cluster cluster;
+
+ // Observed state
private final AllocatableClusterResources allocation;
private final double cpuLoad;
private final double memoryLoad;
@@ -32,7 +37,9 @@ public class ResourceIterator {
// Iterator state
private int currentNodes;
- public ResourceIterator(double cpuLoad, double memoryLoad, double diskLoad, AllocatableClusterResources currentAllocation) {
+ public ResourceIterator(double cpuLoad, double memoryLoad, double diskLoad,
+ AllocatableClusterResources currentAllocation,
+ Cluster cluster) {
this.cpuLoad = cpuLoad;
this.memoryLoad = memoryLoad;
this.diskLoad = diskLoad;
@@ -41,6 +48,8 @@ public class ResourceIterator {
groupSize = (int)Math.ceil((double)currentAllocation.nodes() / currentAllocation.groups());
allocation = currentAllocation;
+ this.cluster = cluster;
+
// What number of nodes is it effective to add or remove at the time from this cluster?
// This is the group size, since we (for now) assume the group size is decided by someone wiser than us
// and we decide the number of groups.
@@ -48,31 +57,52 @@ public class ResourceIterator {
singleGroupMode = currentAllocation.groups() == 1;
nodeIncrement = singleGroupMode ? 1 : groupSize;
+ // Step down to the right starting point
currentNodes = currentAllocation.nodes();
- while (currentNodes - nodeIncrement >= minimumNodesPerCluster
- && (singleGroupMode || currentNodes - nodeIncrement > groupSize)) // group level redundancy
+ while (currentNodes - nodeIncrement >= minNodes()
+ && ( singleGroupMode || currentNodes - nodeIncrement > groupSize)) // group level redundancy
currentNodes -= nodeIncrement;
}
+ /** If autoscaling is not enabled (meaning max and min resources are the same), we want to suggest */
+ private boolean suggestMode() {
+ return cluster.minResources().equals(cluster.maxResources());
+ }
+
public ClusterResources next() {
- int nodesWithRedundancy = currentNodes - (singleGroupMode ? 1 : groupSize);
- ClusterResources next = new ClusterResources(currentNodes,
- singleGroupMode ? 1 : currentNodes / groupSize,
- resourcesFor(nodesWithRedundancy),
- allocation.clusterType());
+ ClusterResources next = resourcesWith(currentNodes);
currentNodes += nodeIncrement;
return next;
}
public boolean hasNext() {
- return currentNodes <= maximumNodesPerCluster;
+ return currentNodes <= maxNodes();
+ }
+
+ private int minNodes() {
+ if (suggestMode()) return minimumNodes;
+ if (singleGroupMode) return cluster.minResources().nodes();
+ return Math.max(cluster.minResources().nodes(), cluster.minResources().groups() * groupSize );
+ }
+
+ private int maxNodes() {
+ if (suggestMode()) return maximumNodes;
+ if (singleGroupMode) return cluster.maxResources().nodes();
+ return Math.min(cluster.maxResources().nodes(), cluster.maxResources().groups() * groupSize );
+ }
+
+ private ClusterResources resourcesWith(int nodes) {
+ int nodesWithRedundancy = nodes - (singleGroupMode ? 1 : groupSize);
+ return new ClusterResources(nodes,
+ singleGroupMode ? 1 : nodes / groupSize,
+ nodeResourcesWith(nodesWithRedundancy));
}
/**
* For the observed load this instance is initialized with, returns the resources needed per node to be at
* ideal load given a target node count
*/
- private NodeResources resourcesFor(int nodeCount) {
+ private NodeResources nodeResourcesWith(int nodeCount) {
// Cpu: Scales with cluster size (TODO: Only reads, writes scales with group size)
// Memory and disk: Scales with group size
@@ -103,6 +133,7 @@ public class ResourceIterator {
disk = nodeUsage(Resource.disk, diskLoad) / Resource.disk.idealAverageLoad();
}
}
+
return allocation.realResources().withVcpu(cpu).withMemoryGb(memory).withDiskGb(disk);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
index d4a237f8e23..abfe65408b6 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java
@@ -8,9 +8,10 @@ import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.applications.Application;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.autoscale.AllocatableClusterResources;
import com.yahoo.vespa.hosted.provision.autoscale.Autoscaler;
-import com.yahoo.vespa.hosted.provision.autoscale.ClusterResources;
import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb;
import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
@@ -53,29 +54,60 @@ public class AutoscalingMaintainer extends Maintainer {
private void autoscale(ApplicationId application, List<Node> applicationNodes) {
try (MaintenanceDeployment deployment = new MaintenanceDeployment(application, deployer, nodeRepository())) {
if ( ! deployment.isValid()) return; // Another config server will consider this application
- nodesByCluster(applicationNodes).forEach((clusterId, clusterNodes) -> autoscale(application, clusterId, clusterNodes));
+ nodesByCluster(applicationNodes).forEach((clusterId, clusterNodes) -> autoscale(application, clusterId, clusterNodes, deployment));
}
}
- private void autoscale(ApplicationId application, ClusterSpec.Id clusterId, List<Node> clusterNodes) {
- Optional<AllocatableClusterResources> target = autoscaler.autoscale(clusterNodes);
- if (target.isEmpty()) return;
+ private void autoscale(ApplicationId applicationId,
+ ClusterSpec.Id clusterId,
+ List<Node> clusterNodes,
+ MaintenanceDeployment deployment) {
+ Application application = nodeRepository().applications().get(applicationId, true);
+ Cluster cluster = application.cluster(clusterId);
+ if (cluster == null) return; // no information on limits for this cluster
+ Optional<AllocatableClusterResources> target = autoscaler.autoscale(cluster, clusterNodes);
+ if (target.isEmpty()) return; // current resources are fine
+ if (cluster.minResources().equals(cluster.maxResources())) { // autoscaling is deactivated
+ logAutoscaling("Scaling suggestion for ", target.get(), applicationId, clusterId, clusterNodes);
+ }
+ else {
+ logAutoscaling("Autoscaling ", target.get(), applicationId, clusterId, clusterNodes);
+ autoscaleTo(target.get(), applicationId, clusterId, application, deployment);
+ }
+ }
+
+ private void autoscaleTo(AllocatableClusterResources target,
+ ApplicationId applicationId,
+ ClusterSpec.Id clusterId,
+ Application application,
+ MaintenanceDeployment deployment) {
+ nodeRepository().applications().set(applicationId,
+ application.withClusterTarget(clusterId, target.toAdvertisedClusterResources()),
+ deployment.applicationLock().get());
+ deployment.activate();
+ }
+
+ private void logAutoscaling(String prefix,
+ AllocatableClusterResources target,
+ ApplicationId application,
+ ClusterSpec.Id clusterId,
+ List<Node> clusterNodes) {
Instant lastLogTime = lastLogged.get(new Pair<>(application, clusterId));
if (lastLogTime != null && lastLogTime.isAfter(nodeRepository().clock().instant().minus(Duration.ofHours(1)))) return;
- int currentGroups = (int) clusterNodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
+ int currentGroups = (int)clusterNodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
ClusterSpec.Type clusterType = clusterNodes.get(0).allocation().get().membership().cluster().type();
- log.info("Autoscale: " + application + " " + clusterType + " " + clusterId +
+ log.info(prefix + application + " " + clusterType + " " + clusterId + ":" +
"\nfrom " + toString(clusterNodes.size(), currentGroups, clusterNodes.get(0).flavor().resources()) +
- "\nto " + toString(target.get().nodes(), target.get().groups(), target.get().advertisedResources()));
+ "\nto " + toString(target.nodes(), target.groups(), target.advertisedResources()));
lastLogged.put(new Pair<>(application, clusterId), nodeRepository().clock().instant());
}
private String toString(int nodes, int groups, NodeResources resources) {
return String.format(nodes + (groups > 1 ? " (in " + groups + " groups)" : "") +
- " * [vcpu: %1$.1f, memory: %2$.1f Gb, disk %3$.1f Gb]" +
- " (total: [vcpu: %4$.1f, memory: %5$.1f Gb, disk: %6$.1f Gb])," +
+ " * [vcpu: %0$.1f, memory: %1$.1f Gb, disk %2$.1f Gb]" +
+ " (total: [vcpu: %3$.1f, memory: %4$.1f Gb, disk: %5$.1f Gb])",
resources.vcpu(), resources.memoryGb(), resources.diskGb(),
nodes * resources.vcpu(), nodes * resources.memoryGb(), nodes * resources.diskGb());
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
index 856de2609be..d9e06f87db7 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceDeployment.java
@@ -36,10 +36,8 @@ class MaintenanceDeployment implements Closeable {
public MaintenanceDeployment(ApplicationId application, Deployer deployer, NodeRepository nodeRepository) {
this.application = application;
Optional<Mutex> lock = tryLock(application, nodeRepository);
-
try {
deployment = tryDeployment(lock, application, deployer, nodeRepository);
-
this.lock = lock;
lock = Optional.empty();
} finally {
@@ -52,6 +50,16 @@ class MaintenanceDeployment implements Closeable {
return deployment.isPresent();
}
+ /**
+ * Returns the application lock held by this, or empty if it is not held.
+ *
+ * @throws IllegalStateException id this is called when closed
+ */
+ public Optional<Mutex> applicationLock() {
+ if (closed) throw new IllegalStateException(this + " is closed");
+ return lock;
+ }
+
public boolean prepare() {
return doStep(() -> deployment.get().prepare());
}
@@ -61,7 +69,7 @@ class MaintenanceDeployment implements Closeable {
}
private boolean doStep(Runnable action) {
- if (closed) throw new IllegalStateException("Deployment of '" + application + "' is closed");
+ if (closed) throw new IllegalStateException(this + "' is closed");
if ( ! isValid()) return false;
try {
action.run();
@@ -101,4 +109,9 @@ class MaintenanceDeployment implements Closeable {
closed = true;
}
+ @Override
+ public String toString() {
+ return "deployment of " + application;
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
index e107abf8fbb..098d706bf05 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java
@@ -29,6 +29,7 @@ import java.util.function.Supplier;
import java.util.stream.Collectors;
import static com.yahoo.config.provision.NodeResources.DiskSpeed.any;
+import static com.yahoo.vespa.hosted.provision.Node.State.active;
/**
* @author oyving
@@ -249,16 +250,16 @@ public class MetricsReporter extends Maintainer {
}
private static NodeResources getCapacityTotal(NodeList nodes) {
- return nodes.nodeType(NodeType.host).asList().stream()
+ return nodes.nodeType(NodeType.host).state(active).asList().stream()
.map(host -> host.flavor().resources())
- .map(resources -> resources.justNumbers())
+ .map(NodeResources::justNumbers)
.reduce(new NodeResources(0, 0, 0, 0, any), NodeResources::add);
}
private static NodeResources getFreeCapacityTotal(NodeList nodes) {
- return nodes.nodeType(NodeType.host).asList().stream()
+ return nodes.nodeType(NodeType.host).state(active).asList().stream()
.map(n -> freeCapacityOf(nodes, n))
- .map(resources -> resources.justNumbers())
+ .map(NodeResources::justNumbers)
.reduce(new NodeResources(0, 0, 0, 0, any), NodeResources::add);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
index 94f8fb29b9a..648bf52f455 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/CapacityPolicies.java
@@ -31,46 +31,42 @@ public class CapacityPolicies {
this.isUsingAdvertisedResources = zone.cloud().value().equals("aws");
}
- public int decideSize(Capacity capacity, ClusterSpec cluster, ApplicationId application) {
- int requestedNodes = capacity.nodeCount();
-
+ public int decideSize(int requested, Capacity capacity, ClusterSpec cluster, ApplicationId application) {
if (application.instance().isTester()) return 1;
- ensureRedundancy(requestedNodes, cluster, capacity.canFail());
-
- if (capacity.isRequired()) return requestedNodes;
-
+ ensureRedundancy(requested, cluster, capacity.canFail());
+ if (capacity.isRequired()) return requested;
switch(zone.environment()) {
case dev : case test : return 1;
- case perf : return Math.min(capacity.nodeCount(), 3);
- case staging: return requestedNodes <= 1 ? requestedNodes : Math.max(2, requestedNodes / 10);
- case prod : return requestedNodes;
+ case perf : return Math.min(requested, 3);
+ case staging: return requested <= 1 ? requested : Math.max(2, requested / 10);
+ case prod : return requested;
default : throw new IllegalArgumentException("Unsupported environment " + zone.environment());
}
}
- public NodeResources decideNodeResources(Capacity capacity, ClusterSpec cluster) {
- NodeResources resources = capacity.nodeResources().orElse(defaultNodeResources(cluster.type()));
- ensureSufficientResources(resources, cluster);
+ public NodeResources decideNodeResources(NodeResources requested, Capacity capacity, ClusterSpec cluster) {
+ if (requested == NodeResources.unspecified)
+ requested = defaultNodeResources(cluster.type());
+ ensureSufficientResources(requested, cluster);
- if (capacity.isRequired()) return resources;
+ if (capacity.isRequired()) return requested;
// Allow slow storage in zones which are not performance sensitive
if (zone.system().isCd() || zone.environment() == Environment.dev || zone.environment() == Environment.test)
- resources = resources.with(NodeResources.DiskSpeed.any).with(NodeResources.StorageType.any);
+ requested = requested.with(NodeResources.DiskSpeed.any).with(NodeResources.StorageType.any);
// Dev does not cap the cpu of containers since usage is spotty: Allocate just a small amount exclusively
// Do not cap in AWS as hosts are allocated on demand and 1-to-1, so the node can use the entire host
if (zone.environment() == Environment.dev && !zone.region().value().contains("aws-"))
- resources = resources.withVcpu(0.1);
+ requested = requested.withVcpu(0.1);
- return resources;
+ return requested;
}
private void ensureSufficientResources(NodeResources resources, ClusterSpec cluster) {
double minMemoryGb = nodeResourceLimits.minMemoryGb(cluster.type());
if (resources.memoryGb() >= minMemoryGb) return;
-
throw new IllegalArgumentException(String.format(Locale.ENGLISH,
"Must specify at least %.2f Gb of memory for %s cluster '%s', was: %.2f Gb",
minMemoryGb, cluster.type().name(), cluster.id().value(), resources.memoryGb()));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImages.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImages.java
index 4beab8a3e80..9edcfd6c697 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImages.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImages.java
@@ -16,7 +16,7 @@ import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
/**
- * Multithread safe class to get and set docker images for given host types.
+ * Multithread safe class to get and set docker images for given node types.
*
* @author freva
*/
@@ -57,9 +57,11 @@ public class DockerImages {
return dockerImages.get();
}
- /** Returns the current docker image for given node type, or default */
+ /** Returns the current docker image for given node type, or the type for corresponding child nodes
+ * if it is a Docker host, or default */
public DockerImage dockerImageFor(NodeType type) {
- return getDockerImages().getOrDefault(type, defaultImage);
+ NodeType typeToUseForLookup = type.isDockerHost() ? type.childNodeType() : type;
+ return getDockerImages().getOrDefault(typeToUseForLookup, defaultImage);
}
/** Set the docker image for nodes of given type */
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImpl.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImpl.java
index 1086a3a7cd9..fb83385920c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImpl.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/InfraDeployerImpl.java
@@ -86,12 +86,10 @@ public class InfraDeployerImpl implements InfraDeployer {
try (Mutex lock = nodeRepository.lock(application.getApplicationId())) {
NodeType nodeType = application.getCapacity().type();
Version targetVersion = infrastructureVersions.getTargetVersionFor(nodeType);
- hostSpecs = provisioner.prepare(
- application.getApplicationId(),
- application.getClusterSpecWithVersion(targetVersion),
- application.getCapacity(),
- 1, // groups
- logger::log);
+ hostSpecs = provisioner.prepare(application.getApplicationId(),
+ application.getClusterSpecWithVersion(targetVersion),
+ application.getCapacity(),
+ logger::log);
prepared = true;
}
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 73061acd9c1..d03aa0cac91 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.google.inject.Inject;
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.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -15,10 +16,14 @@ import com.yahoo.config.provision.ProvisionLogger;
import com.yahoo.config.provision.Provisioner;
import com.yahoo.config.provision.Zone;
import com.yahoo.log.LogLevel;
+import com.yahoo.transaction.Mutex;
import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.flags.FlagSource;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeList;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.applications.Application;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.node.Allocation;
import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter;
import com.yahoo.vespa.hosted.provision.node.filter.NodeHostFilter;
@@ -70,46 +75,53 @@ public class NodeRepositoryProvisioner implements Provisioner {
this.activator = new Activator(nodeRepository, loadBalancerProvisioner);
}
+
/**
* Returns a list of nodes in the prepared or active state, matching the given constraints.
* The nodes are ordered by increasing index number.
*/
+ @Deprecated // TODO: Remove after April 2020
@Override
- public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity requestedCapacity,
+ public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity requestedCapacity,
int wantedGroups, ProvisionLogger logger) {
- if (cluster.group().isPresent()) throw new IllegalArgumentException("Node requests cannot specify a group");
- if (requestedCapacity.nodeCount() > 0 && requestedCapacity.nodeCount() % wantedGroups != 0)
- throw new IllegalArgumentException("Requested " + requestedCapacity.nodeCount() + " nodes in " + wantedGroups + " groups, " +
- "which doesn't allow the nodes to be divided evenly into groups");
+ return prepare(application, cluster, requestedCapacity.withGroups(wantedGroups), logger);
+ }
+ /**
+ * Returns a list of nodes in the prepared or active state, matching the given constraints.
+ * The nodes are ordered by increasing index number.
+ */
+ @Override
+ public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity requested,
+ ProvisionLogger logger) {
log.log(zone.system().isCd() ? Level.INFO : LogLevel.DEBUG,
- () -> "Received deploy prepare request for " + requestedCapacity + " in " +
- wantedGroups + " groups for application " + application + ", cluster " + cluster);
-
- int effectiveGroups;
- NodeSpec requestedNodes;
- Optional<NodeResources> resources = requestedCapacity.nodeResources();
- if ( requestedCapacity.type() == NodeType.tenant) {
- int nodeCount = capacityPolicies.decideSize(requestedCapacity, cluster, application);
- if (zone.environment().isManuallyDeployed() && nodeCount < requestedCapacity.nodeCount())
- logger.log(Level.INFO, "Requested " + requestedCapacity.nodeCount() + " nodes for " + cluster +
- ", downscaling to " + nodeCount + " nodes in " + zone.environment());
- resources = Optional.of(capacityPolicies.decideNodeResources(requestedCapacity, cluster));
- boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive());
- effectiveGroups = Math.min(wantedGroups, nodeCount); // cannot have more groups than nodes
- requestedNodes = NodeSpec.from(nodeCount, resources.get(), exclusive, requestedCapacity.canFail());
+ () -> "Received deploy prepare request for " + requested +
+ " for application " + application + ", cluster " + cluster);
+
+ if (cluster.group().isPresent()) throw new IllegalArgumentException("Node requests cannot specify a group");
- if ( ! hasQuota(application, nodeCount))
- throw new IllegalArgumentException(requestedCapacity + " requested for " + cluster +
- (requestedCapacity.nodeCount() != nodeCount ? " resolved to " + nodeCount + " nodes" : "") +
- " exceeds your quota. Resolve this at https://cloud.vespa.ai/quota");
+ if ( ! hasQuota(application, requested.maxResources().nodes()))
+ throw new IllegalArgumentException(requested + " requested for " + cluster +
+ ". Max value exceeds your quota. Resolve this at https://cloud.vespa.ai/quota");
+
+ int groups;
+ NodeResources resources;
+ NodeSpec nodeSpec;
+ if ( requested.type() == NodeType.tenant) {
+ ClusterResources target = decideTargetResources(application, cluster.id(), requested);
+ int nodeCount = capacityPolicies.decideSize(target.nodes(), requested, cluster, application);
+ resources = capacityPolicies.decideNodeResources(target.nodeResources(), requested, cluster);
+ boolean exclusive = capacityPolicies.decideExclusivity(cluster.isExclusive());
+ groups = Math.min(target.groups(), nodeCount); // cannot have more groups than nodes
+ nodeSpec = NodeSpec.from(nodeCount, resources, exclusive, requested.canFail());
+ logIfDownscaled(target.nodes(), nodeCount, cluster, logger);
}
else {
- requestedNodes = NodeSpec.from(requestedCapacity.type());
- effectiveGroups = 1; // type request with multiple groups is not supported
+ groups = 1; // type request with multiple groups is not supported
+ resources = requested.minResources().nodeResources();
+ nodeSpec = NodeSpec.from(requested.type());
}
-
- return asSortedHosts(preparer.prepare(application, cluster, requestedNodes, effectiveGroups), resources);
+ return asSortedHosts(preparer.prepare(application, cluster, nodeSpec, groups), resources);
}
@Override
@@ -129,6 +141,40 @@ public class NodeRepositoryProvisioner implements Provisioner {
loadBalancerProvisioner.ifPresent(lbProvisioner -> lbProvisioner.deactivate(application, transaction));
}
+ /**
+ * Returns the target cluster resources, a value between the min and max in the requested capacity,
+ * and updates the application store with the received min and max,
+ */
+ private ClusterResources decideTargetResources(ApplicationId applicationId, ClusterSpec.Id clusterId, Capacity requested) {
+ try (Mutex lock = nodeRepository.lock(applicationId)) {
+ Application application = nodeRepository.applications().get(applicationId, true);
+ application = application.withClusterLimits(clusterId, requested.minResources(), requested.maxResources());
+ nodeRepository.applications().set(applicationId, application, lock);
+ return application.cluster(clusterId).targetResources()
+ .orElseGet(() -> currentResources(applicationId, clusterId, requested)
+ .orElse(requested.minResources()));
+ }
+ }
+
+ /** Returns the current resources of this cluster, if it's already deployed and inside the requested limits */
+ private Optional<ClusterResources> currentResources(ApplicationId applicationId,
+ ClusterSpec.Id clusterId,
+ Capacity requested) {
+ List<Node> nodes = NodeList.copyOf(nodeRepository.getNodes(applicationId, Node.State.active))
+ .cluster(clusterId).not().retired().asList();
+ if (nodes.size() < 1) return Optional.empty();
+ long groups = nodes.stream().map(node -> node.allocation().get().membership().cluster().group()).distinct().count();
+ var resources = new ClusterResources(nodes.size(), (int)groups, nodes.get(0).flavor().resources());
+ if ( ! resources.isWithin(requested.minResources(), requested.maxResources())) return Optional.empty();
+ return Optional.of(resources);
+ }
+
+ private void logIfDownscaled(int targetNodes, int actualNodes, ClusterSpec cluster, ProvisionLogger logger) {
+ if (zone.environment().isManuallyDeployed() && actualNodes < targetNodes)
+ logger.log(Level.INFO, "Requested " + targetNodes + " nodes for " + cluster +
+ ", downscaling to " + actualNodes + " nodes in " + zone.environment());
+ }
+
private boolean hasQuota(ApplicationId application, int requestedNodes) {
if ( ! this.zone.system().isPublic()) return true; // no quota management
@@ -137,7 +183,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
return requestedNodes <= 5;
}
- private List<HostSpec> asSortedHosts(List<Node> nodes, Optional<NodeResources> requestedResources) {
+ private List<HostSpec> asSortedHosts(List<Node> nodes, NodeResources requestedResources) {
nodes.sort(Comparator.comparingInt(node -> node.allocation().get().membership().index()));
List<HostSpec> hosts = new ArrayList<>(nodes.size());
for (Node node : nodes) {
@@ -149,7 +195,7 @@ public class NodeRepositoryProvisioner implements Provisioner {
Optional.of(nodeAllocation.membership()),
node.status().vespaVersion(),
nodeAllocation.networkPorts(),
- requestedResources,
+ requestedResources == NodeResources.unspecified ? Optional.empty() : Optional.of(requestedResources),
node.status().dockerImage().map(DockerImage::repository)));
if (nodeAllocation.networkPorts().isPresent()) {
log.log(LogLevel.DEBUG, () -> "Prepared node " + node.hostname() + " has port allocations");
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
index 248cfbec662..a4412a502aa 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/v2/NodesResponse.java
@@ -219,7 +219,7 @@ class NodesResponse extends HttpResponse {
object.setString("storageType", serializer.toString(resources.storageType()));
}
- // Hack: For non-docker noder, return current docker image as default prefix + current Vespa version
+ // Hack: For non-docker nodes, return current docker image as default prefix + current Vespa version
// TODO: Remove current + wanted docker image from response for non-docker types
private Optional<DockerImage> currentDockerImage(Node node) {
return node.status().dockerImage()
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
index 7ab42093bab..5ec5c2c08e8 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockDeployer.java
@@ -146,8 +146,8 @@ public class MockDeployer implements Deployer {
this.clusterContexts = clusterContexts;
}
- public ApplicationContext(ApplicationId id, ClusterSpec cluster, Capacity capacity, int groups) {
- this(id, List.of(new ClusterContext(id, cluster, capacity, groups)));
+ public ApplicationContext(ApplicationId id, ClusterSpec cluster, Capacity capacity) {
+ this(id, List.of(new ClusterContext(id, cluster, capacity)));
}
public ApplicationId id() { return id; }
@@ -169,13 +169,11 @@ public class MockDeployer implements Deployer {
private final ApplicationId id;
private final ClusterSpec cluster;
private final Capacity capacity;
- private final int groups;
- public ClusterContext(ApplicationId id, ClusterSpec cluster, Capacity capacity, int groups) {
+ public ClusterContext(ApplicationId id, ClusterSpec cluster, Capacity capacity) {
this.id = id;
this.cluster = cluster;
this.capacity = capacity;
- this.groups = groups;
}
public ApplicationId id() { return id; }
@@ -183,7 +181,7 @@ public class MockDeployer implements Deployer {
public ClusterSpec cluster() { return cluster; }
private List<HostSpec> prepare(NodeRepositoryProvisioner provisioner) {
- return provisioner.prepare(id, cluster, capacity, groups, null);
+ return provisioner.prepare(id, cluster, capacity, null);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
index 4f72864b087..8ff17a8e5a3 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockNodeRepository.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Flavor;
@@ -139,19 +140,19 @@ public class MockNodeRepository extends NodeRepository {
ApplicationId zoneApp = ApplicationId.from(TenantName.from("zoneapp"), ApplicationName.from("zoneapp"), InstanceName.from("zoneapp"));
ClusterSpec zoneCluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build();
- activate(provisioner.prepare(zoneApp, zoneCluster, Capacity.fromRequiredNodeType(NodeType.host), 1, null), zoneApp, provisioner);
+ activate(provisioner.prepare(zoneApp, zoneCluster, Capacity.fromRequiredNodeType(NodeType.host), null), zoneApp, provisioner);
ApplicationId app1 = ApplicationId.from(TenantName.from("tenant1"), ApplicationName.from("application1"), InstanceName.from("instance1"));
ClusterSpec cluster1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id1")).vespaVersion("6.42").build();
- provisioner.prepare(app1, cluster1, Capacity.fromCount(2, new NodeResources(2, 8, 50, 1)), 1, null);
+ provisioner.prepare(app1, cluster1, Capacity.from(new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1))), null);
ApplicationId app2 = ApplicationId.from(TenantName.from("tenant2"), ApplicationName.from("application2"), InstanceName.from("instance2"));
ClusterSpec cluster2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id2")).vespaVersion("6.42").build();
- activate(provisioner.prepare(app2, cluster2, Capacity.fromCount(2, new NodeResources(2, 8, 50, 1)), 1, null), app2, provisioner);
+ activate(provisioner.prepare(app2, cluster2, Capacity.from(new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1))), null), app2, provisioner);
ApplicationId app3 = ApplicationId.from(TenantName.from("tenant3"), ApplicationName.from("application3"), InstanceName.from("instance3"));
ClusterSpec cluster3 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("id3")).vespaVersion("6.42").build();
- activate(provisioner.prepare(app3, cluster3, Capacity.fromCount(2, new NodeResources(1, 4, 100, 1), false, true), 1, null), app3, provisioner);
+ activate(provisioner.prepare(app3, cluster3, Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 100, 1)), false, true), null), app3, provisioner);
List<Node> largeNodes = new ArrayList<>();
largeNodes.add(createNode("node13", "host13.yahoo.com", ipConfig(13), Optional.empty(),
@@ -162,7 +163,7 @@ public class MockNodeRepository extends NodeRepository {
setReady(largeNodes, Agent.system, getClass().getSimpleName());
ApplicationId app4 = ApplicationId.from(TenantName.from("tenant4"), ApplicationName.from("application4"), InstanceName.from("instance4"));
ClusterSpec cluster4 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("id4")).vespaVersion("6.42").build();
- activate(provisioner.prepare(app4, cluster4, Capacity.fromCount(2, new NodeResources(10, 48, 500, 1), false, true), 1, null), app4, provisioner);
+ activate(provisioner.prepare(app4, cluster4, Capacity.from(new ClusterResources(2, 1, new NodeResources(10, 48, 500, 1)), false, true), null), app4, provisioner);
}
private void activate(List<HostSpec> hosts, ApplicationId application, NodeRepositoryProvisioner provisioner) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java
index 3915ae41d6e..045d0cad1ad 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/testutils/MockProvisioner.java
@@ -20,11 +20,17 @@ import java.util.List;
public class MockProvisioner implements Provisioner {
@Override
+ @Deprecated // TODO: Remove after April 2020
public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, int groups, ProvisionLogger logger) {
return Collections.emptyList();
}
@Override
+ public List<HostSpec> prepare(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity, ProvisionLogger logger) {
+ return Collections.emptyList();
+ }
+
+ @Override
public void activate(NestedTransaction transaction, ApplicationId application, Collection<HostSpec> hosts) {
}
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 d154af4f025..f02acdc1fca 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
@@ -2,12 +2,15 @@
package com.yahoo.vespa.hosted.provision.autoscale;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.test.ManualClock;
import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.applications.Application;
+import com.yahoo.vespa.hosted.provision.applications.Cluster;
import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
import com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock;
import org.junit.Test;
@@ -47,7 +50,13 @@ public class AutoscalingIntegrationTest {
tester.nodeMetricsDb().gc(tester.clock());
}
- var scaledResources = autoscaler.autoscale(tester.nodeRepository().getNodes(application1));
+ ClusterResources min = new ClusterResources(2, 1, nodes);
+ ClusterResources max = new ClusterResources(2, 1, nodes);
+
+ Application application = tester.nodeRepository().applications().get(application1, true).withClusterLimits(cluster1.id(), min, max);
+ tester.nodeRepository().applications().set(application1, application, tester.nodeRepository().lock(application1));
+ var scaledResources = autoscaler.autoscale(application.cluster(cluster1.id()),
+ tester.nodeRepository().getNodes(application1));
assertTrue(scaledResources.isPresent());
}
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 39259bf44f8..8bfb17c0bd4 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
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.autoscale;
import com.google.common.collect.Sets;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.CloudName;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
@@ -31,6 +32,8 @@ public class AutoscalingTest {
@Test
public void testAutoscalingSingleContentGroup() {
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));
AutoscalingTester tester = new AutoscalingTester(resources);
ApplicationId application1 = tester.applicationId("application1");
@@ -39,37 +42,39 @@ public class AutoscalingTest {
// deploy
tester.deploy(application1, cluster1, 5, 1, resources);
- assertTrue("No measurements -> No change", tester.autoscale(application1).isEmpty());
+ assertTrue("No measurements -> No change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty());
tester.addMeasurements(Resource.cpu, 0.25f, 1f, 60, application1);
- assertTrue("Too few measurements -> No change", tester.autoscale(application1).isEmpty());
+ assertTrue("Too few measurements -> No change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty());
tester.addMeasurements(Resource.cpu, 0.25f, 1f, 60, application1);
AllocatableClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high",
15, 1, 1.3, 28.6, 28.6,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
tester.deploy(application1, cluster1, scaledResources);
- assertTrue("Cluster in flux -> No further change", tester.autoscale(application1).isEmpty());
+ assertTrue("Cluster in flux -> No further change", tester.autoscale(application1, cluster1.id(), min, max).isEmpty());
tester.deactivateRetired(application1, cluster1, scaledResources);
tester.addMeasurements(Resource.cpu, 0.8f, 1f, 3, application1);
assertTrue("Load change is large, but insufficient measurements for new config -> No change",
- tester.autoscale(application1).isEmpty());
+ tester.autoscale(application1, cluster1.id(), min, max).isEmpty());
tester.addMeasurements(Resource.cpu, 0.19f, 1f, 100, application1);
- assertEquals("Load change is small -> No change", Optional.empty(), tester.autoscale(application1));
+ assertEquals("Load change is small -> No change", Optional.empty(), tester.autoscale(application1, cluster1.id(), min, max));
tester.addMeasurements(Resource.cpu, 0.1f, 1f, 120, application1);
- tester.assertResources("Scaling down since resource usage has gone down significantly",
- 26, 1, 0.6, 16.0, 16.0,
- tester.autoscale(application1));
+ tester.assertResources("Scaling down to minimum since usage has gone down significantly",
+ 14, 1, 1.0, 30.8, 30.8,
+ tester.autoscale(application1, cluster1.id(), min, max));
}
/** We prefer fewer nodes for container clusters as (we assume) they all use the same disk and memory */
@Test
public void testAutoscalingSingleContainerGroup() {
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));
AutoscalingTester tester = new AutoscalingTester(resources);
ApplicationId application1 = tester.applicationId("application1");
@@ -81,7 +86,7 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.cpu, 0.25f, 1f, 120, application1);
AllocatableClusterResources scaledResources = tester.assertResources("Scaling up since cpu usage is too high",
7, 1, 2.6, 80.0, 80.0,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
tester.deploy(application1, cluster1, scaledResources);
tester.deactivateRetired(application1, cluster1, scaledResources);
@@ -89,12 +94,92 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.cpu, 0.1f, 1f, 120, application1);
tester.assertResources("Scaling down since cpu usage has gone down",
4, 1, 2.4, 68.6, 68.6,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
+ }
+
+ @Test
+ public void testAutoscalingRespectsUpperLimit() {
+ NodeResources hostResources = new NodeResources(3, 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));
+ 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(Resource.cpu, 0.25f, 120, application1);
+ tester.addMeasurements(Resource.memory, 0.95f, 120, application1);
+ tester.addMeasurements(Resource.disk, 0.95f, 120, application1);
+ tester.assertResources("Scaling up to limit since resource usage is too high",
+ 6, 1, 2.4, 78.0, 79.0,
+ tester.autoscale(application1, cluster1.id(), min, max));
+ }
+
+ @Test
+ public void testAutoscalingRespectsLowerLimit() {
+ 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));
+ AutoscalingTester tester = new AutoscalingTester(resources);
+
+ ApplicationId application1 = tester.applicationId("application1");
+ ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1");
+
+ // deploy
+ tester.deploy(application1, cluster1, 5, 1, resources);
+ tester.addMeasurements(Resource.cpu, 0.05f, 120, application1);
+ tester.addMeasurements(Resource.memory, 0.05f, 120, application1);
+ tester.addMeasurements(Resource.disk, 0.05f, 120, application1);
+ tester.assertResources("Scaling down to limit since resource usage is low",
+ 4, 1, 1.8, 7.4, 8.5,
+ tester.autoscale(application1, cluster1.id(), min, max));
+ }
+
+ @Test
+ public void testAutoscalingRespectsGroupLimit() {
+ 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));
+ 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.addMeasurements(Resource.cpu, 0.3f, 1f, 240, application1);
+ tester.assertResources("Scaling up since resource usage is too high",
+ 6, 6, 3.6, 8.0, 8.0,
+ tester.autoscale(application1, cluster1.id(), min, max));
+ }
+
+ /** This condition ensures we get recommendation suggestions when deactivated */
+ @Test
+ public void testAutoscalingLimitsAreIgnoredIfMinEqualsMax() {
+ 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);
+
+ ApplicationId application1 = tester.applicationId("application1");
+ ClusterSpec cluster1 = tester.clusterSpec(ClusterSpec.Type.container, "cluster1");
+
+ // deploy
+ tester.deploy(application1, cluster1, 5, 1, resources);
+ tester.addMeasurements(Resource.cpu, 0.25f, 1f, 120, application1);
+ tester.assertResources("Scaling up since resource usage is too high",
+ 7, 1, 2.6, 80.0, 80.0,
+ tester.autoscale(application1, cluster1.id(), min, max));
}
@Test
public void testAutoscalingGroupSize1() {
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));
AutoscalingTester tester = new AutoscalingTester(resources);
ApplicationId application1 = tester.applicationId("application1");
@@ -105,12 +190,14 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.cpu, 0.25f, 1f, 120, application1);
tester.assertResources("Scaling up since resource usage is too high",
7, 7, 2.5, 80.0, 80.0,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
}
@Test
public void testAutoscalingGroupSize3() {
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));
AutoscalingTester tester = new AutoscalingTester(resources);
ApplicationId application1 = tester.applicationId("application1");
@@ -121,12 +208,14 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.cpu, 0.22f, 1f, 120, application1);
tester.assertResources("Scaling up since resource usage is too high",
9, 3, 2.7, 83.3, 83.3,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
}
@Test
public void testAutoscalingAvoidsIllegalConfigurations() {
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));
AutoscalingTester tester = new AutoscalingTester(resources);
ApplicationId application1 = tester.applicationId("application1");
@@ -137,11 +226,13 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.memory, 0.02f, 1f, 120, application1);
tester.assertResources("Scaling down",
6, 1, 3.0, 4.0, 100.0,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
}
@Test
public void testAutoscalingAws() {
+ ClusterResources min = new ClusterResources( 2, 1, new NodeResources(1, 1, 1, 1));
+ ClusterResources max = new ClusterResources(20, 1, new NodeResources(100, 1000, 1000, 1));
List<Flavor> flavors = new ArrayList<>();
flavors.add(new Flavor("aws-xlarge", new NodeResources(3, 200, 100, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote)));
flavors.add(new Flavor("aws-large", new NodeResources(3, 150, 100, 1, NodeResources.DiskSpeed.fast, NodeResources.StorageType.remote)));
@@ -160,7 +251,7 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.memory, 0.9f, 0.6f, 120, application1);
AllocatableClusterResources scaledResources = tester.assertResources("Scaling up since resource usage is too high.",
8, 1, 3, 83, 34.3,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
tester.deploy(application1, cluster1, scaledResources);
tester.deactivateRetired(application1, cluster1, scaledResources);
@@ -168,7 +259,7 @@ public class AutoscalingTest {
tester.addMeasurements(Resource.memory, 0.3f, 0.6f, 1000, application1);
tester.assertResources("Scaling down since resource usage has gone down",
5, 1, 3, 83, 36,
- tester.autoscale(application1));
+ tester.autoscale(application1, cluster1.id(), min, max));
}
}
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 3ba230f9ccd..ebc4d158ded 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
@@ -3,6 +3,7 @@ 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.Flavor;
@@ -19,6 +20,7 @@ import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.applications.Application;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.node.IP;
import com.yahoo.vespa.hosted.provision.provisioning.FatalProvisioningException;
@@ -82,7 +84,7 @@ class AutoscalingTester {
}
public List<HostSpec> deploy(ApplicationId application, ClusterSpec cluster, int nodes, int groups, NodeResources resources) {
- List<HostSpec> hosts = provisioningTester.prepare(application, cluster, Capacity.fromCount(nodes, resources), groups);
+ List<HostSpec> hosts = provisioningTester.prepare(application, cluster, Capacity.from(new ClusterResources(nodes, groups, resources)));
for (HostSpec host : hosts)
makeReady(host.hostname());
provisioningTester.deployZoneApp();
@@ -139,8 +141,25 @@ class AutoscalingTester {
}
}
- public Optional<AllocatableClusterResources> autoscale(ApplicationId application) {
- return autoscaler.autoscale(nodeRepository().getNodes(application, Node.State.active));
+ public void addMeasurements(Resource resource, float value, int count, ApplicationId applicationId) {
+ List<Node> nodes = nodeRepository().getNodes(applicationId, Node.State.active);
+ for (int i = 0; i < count; i++) {
+ clock().advance(Duration.ofMinutes(1));
+ for (Node node : nodes) {
+ db.add(List.of(new NodeMetrics.MetricValue(node.hostname(),
+ resource.metricName(),
+ clock().instant().toEpochMilli(),
+ value * 100))); // the metrics are in %
+ }
+ }
+ }
+
+ public Optional<AllocatableClusterResources> autoscale(ApplicationId applicationId, ClusterSpec.Id clusterId,
+ ClusterResources min, ClusterResources max) {
+ Application application = nodeRepository().applications().get(applicationId, true).withClusterLimits(clusterId, min, max);
+ nodeRepository().applications().set(applicationId, application, nodeRepository().lock(applicationId));
+ return autoscaler.autoscale(application.cluster(clusterId),
+ nodeRepository().getNodes(applicationId, Node.State.active));
}
public AllocatableClusterResources assertResources(String message,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java
index 8c1a27f2fd1..6bf52218302 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/autoscale/NodeMetricsFetcherTest.java
@@ -3,7 +3,10 @@ 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.NodeResources;
+import com.yahoo.vdslib.state.NodeState;
+import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
import com.yahoo.vespa.hosted.provision.testutils.OrchestratorMock;
import com.yahoo.vespa.applicationmodel.HostName;
@@ -13,6 +16,7 @@ import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
public class NodeMetricsFetcherTest {
@@ -29,8 +33,8 @@ public class NodeMetricsFetcherTest {
ApplicationId application1 = tester.makeApplicationId();
ApplicationId application2 = tester.makeApplicationId();
- tester.deploy(application1, Capacity.fromCount(2, resources)); // host-1.yahoo.com, host-2.yahoo.com
- tester.deploy(application2, Capacity.fromCount(2, resources)); // host-4.yahoo.com, host-3.yahoo.com
+ tester.deploy(application1, Capacity.from(new ClusterResources(2, 1, resources))); // host-1.yahoo.com, host-2.yahoo.com
+ tester.deploy(application2, Capacity.from(new ClusterResources(2, 1, resources))); // host-4.yahoo.com, host-3.yahoo.com
orchestrator.suspend(new HostName("host-4.yahoo.com"));
@@ -57,6 +61,12 @@ public class NodeMetricsFetcherTest {
assertEquals("metric value mem_total.util: 15.0 at 1970-01-01T00:21:40Z for host-3.yahoo.com", values.get(1).toString());
assertEquals("metric value disk.util: 20.0 at 1970-01-01T00:21:40Z for host-3.yahoo.com", values.get(2).toString());
}
+
+ {
+ tester.nodeRepository().write(tester.nodeRepository().getNodes(application1, Node.State.active).get(0).retire(tester.clock().instant()),
+ tester.nodeRepository().lock(application1));
+ assertTrue("No metrics fetching while unstable", fetcher.fetchMetrics(application1).isEmpty());
+ }
}
private static class MockHttpClient implements NodeMetricsFetcher.HttpClient {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java
new file mode 100644
index 00000000000..da169cba08f
--- /dev/null
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainerTest.java
@@ -0,0 +1,113 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.provision.maintenance;
+
+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.Flavor;
+import com.yahoo.config.provision.NodeResources;
+import com.yahoo.config.provision.NodeType;
+import com.yahoo.config.provision.RegionName;
+import com.yahoo.config.provision.Zone;
+import com.yahoo.config.provisioning.FlavorsConfig;
+import com.yahoo.vespa.hosted.provision.Node;
+import com.yahoo.vespa.hosted.provision.NodeRepository;
+import com.yahoo.vespa.hosted.provision.autoscale.NodeMetrics;
+import com.yahoo.vespa.hosted.provision.autoscale.NodeMetricsDb;
+import com.yahoo.vespa.hosted.provision.autoscale.Resource;
+import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
+import com.yahoo.vespa.hosted.provision.provisioning.ProvisioningTester;
+import com.yahoo.vespa.hosted.provision.testutils.MockDeployer;
+import org.junit.Test;
+
+import java.time.Duration;
+import java.util.List;
+import java.util.Map;
+
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Tests the autoscaling maintainer integration.
+ * The specific recommendations of the autoscaler are not tested here.
+ *
+ * @author bratseth
+ */
+public class AutoscalingMaintainerTest {
+
+ @Test
+ public void testAutoscalingMaintainer() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east3"))).flavorsConfig(flavorsConfig()).build();
+
+ ApplicationId app1 = tester.makeApplicationId("app1");
+ ClusterSpec cluster1 = tester.clusterSpec();
+
+ ApplicationId app2 = tester.makeApplicationId("app2");
+ ClusterSpec cluster2 = tester.clusterSpec();
+
+ NodeResources lowResources = new NodeResources(4, 4, 10, 0.1);
+ NodeResources highResources = new NodeResources(6.5, 9, 20, 0.1);
+
+ Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
+ app1, new MockDeployer.ApplicationContext(app1, cluster1, Capacity.from(new ClusterResources(2, 1, lowResources))),
+ app2, new MockDeployer.ApplicationContext(app2, cluster2, Capacity.from(new ClusterResources(2, 1, highResources))));
+ MockDeployer deployer = new MockDeployer(tester.provisioner(), tester.clock(), apps);
+
+ NodeMetricsDb nodeMetricsDb = new NodeMetricsDb();
+ AutoscalingMaintainer maintainer = new AutoscalingMaintainer(tester.nodeRepository(),
+ tester.identityHostResourcesCalculator(),
+ nodeMetricsDb,
+ deployer,
+ Duration.ofMinutes(1));
+ maintainer.maintain(); // noop
+ assertTrue(deployer.lastDeployTime(app1).isEmpty());
+ assertTrue(deployer.lastDeployTime(app2).isEmpty());
+
+ tester.makeReadyNodes(20, "flt", NodeType.host, 8);
+ tester.deployZoneApp();
+
+ tester.deploy(app1, cluster1, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
+ new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
+ false, true));
+ tester.deploy(app2, cluster2, Capacity.from(new ClusterResources(5, 1, new NodeResources(4, 4, 10, 0.1)),
+ new ClusterResources(10, 1, new NodeResources(6.5, 9, 20, 0.1)),
+ false, true));
+
+ maintainer.maintain(); // noop
+ assertTrue(deployer.lastDeployTime(app1).isEmpty());
+ assertTrue(deployer.lastDeployTime(app2).isEmpty());
+
+ addMeasurements(Resource.cpu, 0.9f, 500, app1, tester.nodeRepository(), nodeMetricsDb);
+ addMeasurements(Resource.memory, 0.9f, 500, app1, tester.nodeRepository(), nodeMetricsDb);
+ addMeasurements(Resource.disk, 0.9f, 500, app1, tester.nodeRepository(), nodeMetricsDb);
+ addMeasurements(Resource.cpu, 0.9f, 500, app2, tester.nodeRepository(), nodeMetricsDb);
+ addMeasurements(Resource.memory, 0.9f, 500, app2, tester.nodeRepository(), nodeMetricsDb);
+ addMeasurements(Resource.disk, 0.9f, 500, app2, tester.nodeRepository(), nodeMetricsDb);
+
+ maintainer.maintain();
+ assertTrue(deployer.lastDeployTime(app1).isEmpty()); // since autoscaling is off
+ assertTrue(deployer.lastDeployTime(app2).isPresent());
+ }
+
+ public void addMeasurements(Resource resource, float value, int count, ApplicationId applicationId,
+ NodeRepository nodeRepository, NodeMetricsDb db) {
+ List<Node> nodes = nodeRepository.getNodes(applicationId, Node.State.active);
+ for (int i = 0; i < count; i++) {
+ for (Node node : nodes)
+ db.add(List.of(new NodeMetrics.MetricValue(node.hostname(),
+ resource.metricName(),
+ nodeRepository.clock().instant().toEpochMilli(),
+ value * 100))); // the metrics are in %
+ }
+ }
+
+ private FlavorsConfig flavorsConfig() {
+ FlavorConfigBuilder b = new FlavorConfigBuilder();
+ b.addFlavor("flt", 30, 30, 40, 3, Flavor.Type.BARE_METAL);
+ b.addFlavor("cpu", 40, 20, 40, 3, Flavor.Type.BARE_METAL);
+ b.addFlavor("mem", 20, 40, 40, 3, Flavor.Type.BARE_METAL);
+ return b.build();
+ }
+
+}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
index e8b9813a8a2..17521261e1b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/FailedExpirerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -322,12 +323,12 @@ public class FailedExpirerTest {
public FailureScenario allocate(ClusterSpec.Type clusterType, NodeResources flavor, String... hostname) {
ClusterSpec clusterSpec = ClusterSpec.request(clusterType, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- Capacity capacity = Capacity.fromCount(hostname.length, Optional.of(flavor), false, true);
+ Capacity capacity = Capacity.from(new ClusterResources(hostname.length, 1, flavor), false, true);
return allocate(applicationId, clusterSpec, capacity);
}
public FailureScenario allocate(ApplicationId applicationId, ClusterSpec clusterSpec, Capacity capacity) {
- List<HostSpec> preparedNodes = provisioner.prepare(applicationId, clusterSpec, capacity, 1, null);
+ List<HostSpec> preparedNodes = provisioner.prepare(applicationId, clusterSpec, capacity, null);
NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator));
provisioner.activate(transaction, applicationId, Set.copyOf(preparedNodes));
transaction.commit();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
index 17d8b702269..9bb3a55abfd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/InactiveAndFailedExpirerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
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.HostSpec;
@@ -54,7 +55,7 @@ public class InactiveAndFailedExpirerTest {
// Allocate then deallocate 2 nodes
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.fromCount(2, nodeResources), 1);
+ List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources)));
tester.activate(applicationId, new HashSet<>(preparedNodes));
assertEquals(2, tester.getNodes(applicationId, Node.State.active).size());
tester.deactivate(applicationId);
@@ -92,7 +93,7 @@ public class InactiveAndFailedExpirerTest {
// Allocate and deallocate a single node
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.fromCount(2, nodeResources), 1);
+ List<HostSpec> preparedNodes = tester.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources)));
tester.activate(applicationId, new HashSet<>(preparedNodes));
assertEquals(2, tester.getNodes(applicationId, Node.State.active).size());
tester.deactivate(applicationId);
@@ -124,8 +125,7 @@ public class InactiveAndFailedExpirerTest {
{
List<HostSpec> hostSpecs = tester.prepare(applicationId,
cluster,
- Capacity.fromCount(2, nodeResources),
- 1);
+ Capacity.from(new ClusterResources(2, 1, nodeResources)));
tester.activate(applicationId, new HashSet<>(hostSpecs));
assertEquals(2, tester.getNodes(applicationId, Node.State.active).size());
}
@@ -134,7 +134,7 @@ public class InactiveAndFailedExpirerTest {
{
Node toRetire = tester.getNodes(applicationId, Node.State.active).asList().get(0);
tester.patchNode(toRetire.withWantToRetire(true, Agent.operator, tester.clock().instant()));
- List<HostSpec> hostSpecs = tester.prepare(applicationId, cluster, Capacity.fromCount(2, nodeResources), 1);
+ List<HostSpec> hostSpecs = tester.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources)));
tester.activate(applicationId, new HashSet<>(hostSpecs));
}
@@ -146,10 +146,8 @@ public class InactiveAndFailedExpirerTest {
Collections.singletonMap(
applicationId,
new MockDeployer.ApplicationContext(applicationId, cluster,
- Capacity.fromCount(2,
- nodeResources,
- false, true),
- 1)
+ Capacity.from(new ClusterResources(2, 1, nodeResources),
+ false, true))
)
);
Orchestrator orchestrator = mock(Orchestrator.class);
@@ -174,7 +172,7 @@ public class InactiveAndFailedExpirerTest {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
tester.makeReadyNodes(1, nodeResources);
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- List<HostSpec> preparedNodes = tester.prepare(testerId, cluster, Capacity.fromCount(2, nodeResources), 1);
+ List<HostSpec> preparedNodes = tester.prepare(testerId, cluster, Capacity.from(new ClusterResources(2, 1, nodeResources)));
tester.activate(testerId, new HashSet<>(preparedNodes));
assertEquals(1, tester.getNodes(testerId, Node.State.active).size());
tester.deactivate(testerId);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceTester.java
index 4344016c6fe..664809dc3ab 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MaintenanceTester.java
@@ -3,7 +3,9 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
+import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeFlavors;
+import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
@@ -14,6 +16,7 @@ import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.node.Agent;
import com.yahoo.vespa.hosted.provision.provisioning.FlavorConfigBuilder;
+import com.yahoo.vespa.hosted.provision.provisioning.HostResourcesCalculator;
import com.yahoo.vespa.hosted.provision.testutils.MockNameResolver;
import java.time.Instant;
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
index aa262bdf751..ca7c33f96bd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporterTest.java
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
import com.yahoo.jdisc.Metric;
import com.yahoo.test.ManualClock;
+import com.yahoo.transaction.NestedTransaction;
import com.yahoo.vespa.applicationmodel.ApplicationInstance;
import com.yahoo.vespa.applicationmodel.ApplicationInstanceReference;
import com.yahoo.vespa.curator.Curator;
@@ -163,6 +164,10 @@ public class MetricsReporterTest {
container2 = container2.with(allocation(Optional.of("app2"), container2).get());
nodeRepository.addDockerNodes(new LockedNodeList(List.of(container2), nodeRepository.lockUnallocated()));
+ NestedTransaction transaction = new NestedTransaction();
+ nodeRepository.activate(nodeRepository.getNodes(NodeType.host), transaction);
+ transaction.commit();
+
Orchestrator orchestrator = mock(Orchestrator.class);
when(orchestrator.getHostInfo(eq(reference), any())).thenReturn(HostInfo.createNoRemarks());
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
index 0efca6ef826..585d57aae4e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailTester.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
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.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -89,17 +90,17 @@ public class NodeFailTester {
// Create applications
ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- Capacity capacity1 = Capacity.fromCount(5, nodeResources, false, true);
- Capacity capacity2 = Capacity.fromCount(7, nodeResources, false, true);
+ Capacity capacity1 = Capacity.from(new ClusterResources(5, 1, nodeResources), false, true);
+ Capacity capacity2 = Capacity.from(new ClusterResources(7, 1, nodeResources), false, true);
tester.activate(app1, clusterApp1, capacity1);
tester.activate(app2, clusterApp2, capacity2);
- assertEquals(capacity1.nodeCount(), tester.nodeRepository.getNodes(app1, Node.State.active).size());
- assertEquals(capacity2.nodeCount(), tester.nodeRepository.getNodes(app2, Node.State.active).size());
+ assertEquals(capacity1.minResources().nodes(), tester.nodeRepository.getNodes(app1, Node.State.active).size());
+ assertEquals(capacity2.minResources().nodes(), tester.nodeRepository.getNodes(app2, Node.State.active).size());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- app1, new MockDeployer.ApplicationContext(app1, clusterApp1, capacity1, 1),
- app2, new MockDeployer.ApplicationContext(app2, clusterApp2, capacity2, 1));
+ app1, new MockDeployer.ApplicationContext(app1, clusterApp1, capacity1),
+ app2, new MockDeployer.ApplicationContext(app2, clusterApp2, capacity2));
tester.deployer = new MockDeployer(tester.provisioner, tester.clock(), apps);
tester.serviceMonitor = new ServiceMonitorStub(apps, tester.nodeRepository);
tester.metric = new MetricsReporterTest.TestMetric();
@@ -122,20 +123,20 @@ public class NodeFailTester {
ClusterSpec clusterApp1 = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("6.75.0").build();
ClusterSpec clusterApp2 = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.75.0").build();
Capacity allHosts = Capacity.fromRequiredNodeType(NodeType.host);
- Capacity capacity1 = Capacity.fromCount(3, new NodeResources(1, 4, 10, 0.3), false, true);
- Capacity capacity2 = Capacity.fromCount(5, new NodeResources(1, 4, 10, 0.3), false, true);
+ Capacity capacity1 = Capacity.from(new ClusterResources(3, 1, new NodeResources(1, 4, 10, 0.3)), false, true);
+ Capacity capacity2 = Capacity.from(new ClusterResources(5, 1, new NodeResources(1, 4, 10, 0.3)), false, true);
tester.activate(tenantHostApp, clusterNodeAdminApp, allHosts);
tester.activate(app1, clusterApp1, capacity1);
tester.activate(app2, clusterApp2, capacity2);
assertEquals(Set.of(tester.nodeRepository.getNodes(NodeType.host)),
Set.of(tester.nodeRepository.getNodes(tenantHostApp, Node.State.active)));
- assertEquals(capacity1.nodeCount(), tester.nodeRepository.getNodes(app1, Node.State.active).size());
- assertEquals(capacity2.nodeCount(), tester.nodeRepository.getNodes(app2, Node.State.active).size());
+ assertEquals(capacity1.minResources().nodes(), tester.nodeRepository.getNodes(app1, Node.State.active).size());
+ assertEquals(capacity2.minResources().nodes(), tester.nodeRepository.getNodes(app2, Node.State.active).size());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- tenantHostApp, new MockDeployer.ApplicationContext(tenantHostApp, clusterNodeAdminApp, allHosts, 1),
- app1, new MockDeployer.ApplicationContext(app1, clusterApp1, capacity1, 1),
- app2, new MockDeployer.ApplicationContext(app2, clusterApp2, capacity2, 1));
+ tenantHostApp, new MockDeployer.ApplicationContext(tenantHostApp, clusterNodeAdminApp, allHosts),
+ app1, new MockDeployer.ApplicationContext(app1, clusterApp1, capacity1),
+ app2, new MockDeployer.ApplicationContext(app2, clusterApp2, capacity2));
tester.deployer = new MockDeployer(tester.provisioner, tester.clock(), apps);
tester.serviceMonitor = new ServiceMonitorStub(apps, tester.nodeRepository);
tester.metric = new MetricsReporterTest.TestMetric();
@@ -154,7 +155,7 @@ public class NodeFailTester {
assertEquals(count, tester.nodeRepository.getNodes(nodeType, Node.State.active).size());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- app1, new MockDeployer.ApplicationContext(app1, clusterApp1, allNodes, 1));
+ app1, new MockDeployer.ApplicationContext(app1, clusterApp1, allNodes));
tester.deployer = new MockDeployer(tester.provisioner, tester.clock(), apps);
tester.serviceMonitor = new ServiceMonitorStub(apps, tester.nodeRepository);
tester.metric = new MetricsReporterTest.TestMetric();
@@ -252,7 +253,7 @@ public class NodeFailTester {
}
private void activate(ApplicationId applicationId, ClusterSpec cluster, Capacity capacity) {
- List<HostSpec> hosts = provisioner.prepare(applicationId, cluster, capacity, 1, null);
+ List<HostSpec> hosts = provisioner.prepare(applicationId, cluster, capacity, null);
NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator));
provisioner.activate(transaction, applicationId, hosts);
transaction.commit();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
index cf93e8ecf6e..2dda6c714a7 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/OperatorChangeApplicationMaintainerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -148,9 +149,9 @@ public class OperatorChangeApplicationMaintainerTest {
new InMemoryFlagSource());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromCount(wantedNodesApp1, nodeResources), 1),
- app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.fromCount(wantedNodesApp2, nodeResources), 1),
- app3, new MockDeployer.ApplicationContext(app3, clusterApp3, Capacity.fromRequiredNodeType(NodeType.proxy), 0)) ;
+ app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.from(new ClusterResources(wantedNodesApp1, 1, nodeResources))),
+ app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.from(new ClusterResources(wantedNodesApp2, 1, nodeResources))),
+ app3, new MockDeployer.ApplicationContext(app3, clusterApp3, Capacity.fromRequiredNodeType(NodeType.proxy))) ;
this.deployer = new MockDeployer(provisioner, nodeRepository.clock(), apps);
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
index 4ca88c10bfe..3037d5972e5 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/PeriodicApplicationMaintainerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.DockerImage;
@@ -258,8 +259,8 @@ public class PeriodicApplicationMaintainerTest {
new InMemoryFlagSource());
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.fromCount(wantedNodesApp1, nodeResources), 1),
- app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.fromCount(wantedNodesApp2, nodeResources), 1));
+ app1, new MockDeployer.ApplicationContext(app1, clusterApp1, Capacity.from(new ClusterResources(wantedNodesApp1, 1, nodeResources))),
+ app2, new MockDeployer.ApplicationContext(app2, clusterApp2, Capacity.from(new ClusterResources(wantedNodesApp2, 1, nodeResources))));
this.deployer = new MockDeployer(provisioner, nodeRepository.clock(), apps);
this.maintainer = new TestablePeriodicApplicationMaintainer(deployer, nodeRepository, Duration.ofDays(1), // Long duration to prevent scheduled runs during test
Duration.ofMinutes(30));
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java
index b4e713ae492..387f614c5eb 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RebalancerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
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.Flavor;
@@ -48,13 +49,13 @@ public class RebalancerTest {
MetricsReporterTest.TestMetric metric = new MetricsReporterTest.TestMetric();
Map<ApplicationId, MockDeployer.ApplicationContext> apps = Map.of(
- cpuApp, new MockDeployer.ApplicationContext(cpuApp, clusterSpec("c"), Capacity.fromCount(1, cpuResources), 1),
- memApp, new MockDeployer.ApplicationContext(memApp, clusterSpec("c"), Capacity.fromCount(1, memResources), 1));
+ cpuApp, new MockDeployer.ApplicationContext(cpuApp, clusterSpec("c"), Capacity.from(new ClusterResources(1, 1, cpuResources))),
+ memApp, new MockDeployer.ApplicationContext(memApp, clusterSpec("c"), Capacity.from(new ClusterResources(1, 1, memResources))));
MockDeployer deployer = new MockDeployer(tester.provisioner(), tester.clock(), apps);
Rebalancer rebalancer = new Rebalancer(deployer,
tester.nodeRepository(),
- new IdentityHostResourcesCalculator(),
+ tester.identityHostResourcesCalculator(),
Optional.empty(),
metric,
tester.clock(),
@@ -148,18 +149,4 @@ public class RebalancerTest {
return b.build();
}
- private static class IdentityHostResourcesCalculator implements HostResourcesCalculator {
-
- @Override
- public NodeResources realResourcesOf(Node node) {
- return node.flavor().resources();
- }
-
- @Override
- public NodeResources advertisedResourcesOf(Flavor flavor) {
- return flavor.resources();
- }
-
- }
-
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
index b4f55c437cd..0fd967cad1b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/ReservationExpirerTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
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.DockerImage;
import com.yahoo.config.provision.Flavor;
@@ -61,7 +62,7 @@ public class ReservationExpirerTest {
nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
ApplicationId applicationId = new ApplicationId.Builder().tenant("foo").applicationName("bar").instanceName("fuz").build();
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build();
- provisioner.prepare(applicationId, cluster, Capacity.fromCount(2, new NodeResources(2, 8, 50, 1)), 1, null);
+ provisioner.prepare(applicationId, cluster, Capacity.from(new ClusterResources(2, 1, new NodeResources(2, 8, 50, 1))), null);
assertEquals(2, nodeRepository.getNodes(NodeType.tenant, Node.State.reserved).size());
// Reservation times out
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
index 78600d360c9..276b9484ad4 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/RetiredExpirerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.maintenance;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Deployer;
import com.yahoo.config.provision.DockerImage;
@@ -98,7 +99,9 @@ public class RetiredExpirerTest {
MockDeployer deployer =
new MockDeployer(provisioner,
clock,
- Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromCount(wantedNodes, nodeResources), 1)));
+ Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId,
+ cluster,
+ Capacity.from(new ClusterResources(wantedNodes, 1, nodeResources)))));
createRetiredExpirer(deployer).run();
assertEquals(3, nodeRepository.getNodes(applicationId, Node.State.active).size());
assertEquals(4, nodeRepository.getNodes(applicationId, Node.State.inactive).size());
@@ -127,7 +130,9 @@ public class RetiredExpirerTest {
MockDeployer deployer =
new MockDeployer(provisioner,
clock,
- Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromCount(2, nodeResources), 1)));
+ Collections.singletonMap(applicationId, new MockDeployer.ApplicationContext(applicationId,
+ cluster,
+ Capacity.from(new ClusterResources(2, 1, nodeResources)))));
createRetiredExpirer(deployer).run();
assertEquals(2, nodeRepository.getNodes(applicationId, Node.State.active).size());
assertEquals(6, nodeRepository.getNodes(applicationId, Node.State.inactive).size());
@@ -161,7 +166,9 @@ public class RetiredExpirerTest {
clock,
Collections.singletonMap(
applicationId,
- new MockDeployer.ApplicationContext(applicationId, cluster, Capacity.fromCount(wantedNodes, nodeResources), 1)));
+ new MockDeployer.ApplicationContext(applicationId,
+ cluster,
+ Capacity.from(new ClusterResources(wantedNodes, 1, nodeResources)))));
// Allow the 1st and 3rd retired nodes permission to inactivate
doNothing()
@@ -197,7 +204,7 @@ public class RetiredExpirerTest {
}
private void activate(ApplicationId applicationId, ClusterSpec cluster, int nodes, int groups, NodeRepositoryProvisioner provisioner) {
- List<HostSpec> hosts = provisioner.prepare(applicationId, cluster, Capacity.fromCount(nodes, nodeResources), groups, null);
+ List<HostSpec> hosts = provisioner.prepare(applicationId, cluster, Capacity.from(new ClusterResources(nodes, groups, nodeResources)), null);
NestedTransaction transaction = new NestedTransaction().add(new CuratorTransaction(curator));
provisioner.activate(transaction, applicationId, hosts);
transaction.commit();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
index 1d028f13340..bbba87f443e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/AclProvisioningTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.hosted.provision.Node;
@@ -45,7 +46,7 @@ public class AclProvisioningTest {
// Allocate 2 nodes
ApplicationId application = tester.makeApplicationId();
- List<Node> activeNodes = tester.deploy(application, Capacity.fromCount(2, new NodeResources(1, 4, 10, 1), false, true));
+ List<Node> activeNodes = tester.deploy(application, Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 10, 1)), false, true));
assertEquals(2, activeNodes.size());
// Get trusted nodes for the first active node
@@ -210,7 +211,7 @@ public class AclProvisioningTest {
}
private List<Node> deploy(ApplicationId application, int nodeCount) {
- return tester.deploy(application, Capacity.fromCount(nodeCount, nodeResources));
+ return tester.deploy(application, Capacity.from(new ClusterResources(nodeCount, 1, nodeResources)));
}
private static void assertAcls(List<List<Node>> expected, NodeAcl actual) {
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java
index 2eca8998931..cd6ae587b04 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerImagesTest.java
@@ -7,6 +7,8 @@ import com.yahoo.config.provision.NodeType;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import org.junit.Test;
+import java.util.Optional;
+
import static org.junit.Assert.assertEquals;
/**
@@ -19,6 +21,9 @@ public class DockerImagesTest {
var flagSource = new InMemoryFlagSource();
var tester = new ProvisioningTester.Builder().flagSource(flagSource).build();
+ var proxyImage = DockerImage.fromString("docker-registry.domain.tld:8080/dist/proxy");
+ tester.nodeRepository().dockerImages().setDockerImage(NodeType.proxy, Optional.of(proxyImage));
+
// Host uses tenant default image (for preload purposes)
var defaultImage = DockerImage.fromString("docker-registry.domain.tld:8080/dist/vespa");
var hosts = tester.makeReadyNodes(2, "default", NodeType.host);
@@ -35,6 +40,12 @@ public class DockerImagesTest {
assertEquals(defaultImage, tester.nodeRepository().dockerImages().dockerImageFor(node.type()));
}
}
+
+ // Proxy host uses image used by child nodes (proxy nodes), which is overridden in this case (for preload purposes)
+ var proxyHosts = tester.makeReadyNodes(2, "default", NodeType.proxyhost);
+ for (var host : proxyHosts) {
+ assertEquals(proxyImage, tester.nodeRepository().dockerImages().dockerImageFor(host.type()));
+ }
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
index 7c315a83ddc..5079fce4418 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DockerProvisioningTest.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
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.HostSpec;
@@ -95,7 +96,7 @@ public class DockerProvisioningTest {
// Activate the zone-app, thereby allocating the parents
List<HostSpec> hosts = tester.prepare(zoneApplication,
ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("zone-app")).vespaVersion(wantedVespaVersion).build(),
- Capacity.fromRequiredNodeType(NodeType.host), 1);
+ Capacity.fromRequiredNodeType(NodeType.host));
tester.activate(zoneApplication, hosts);
// Try allocating tenants again
@@ -298,8 +299,7 @@ public class DockerProvisioningTest {
private void prepareAndActivate(ApplicationId application, int nodeCount, boolean exclusive, ProvisioningTester tester) {
Set<HostSpec> hosts = new HashSet<>(tester.prepare(application,
ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("myContainer")).vespaVersion("6.39").exclusive(exclusive).build(),
- Capacity.fromCount(nodeCount, Optional.of(dockerFlavor), false, true),
- 1));
+ Capacity.from(new ClusterResources(nodeCount, 1, dockerFlavor), false, true)));
tester.activate(application, hosts);
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
index 661f379f271..4eca0542992 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/DynamicDockerAllocationTest.java
@@ -5,6 +5,7 @@ import com.google.common.collect.ImmutableSet;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.Flavor;
@@ -401,11 +402,11 @@ public class DynamicDockerAllocationTest {
ApplicationId application = tester.makeApplicationId();
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("test")).vespaVersion("1").build();
- List<HostSpec> hosts1 = tester.prepare(application, cluster, Capacity.fromCount(2, Optional.of(NodeResources.fromLegacyName("d-2-8-50")), false, true), 1);
+ List<HostSpec> hosts1 = tester.prepare(application, cluster, Capacity.from(new ClusterResources(2, 1, NodeResources.fromLegacyName("d-2-8-50")), false, true));
tester.activate(application, hosts1);
NodeResources resources = new NodeResources(1.5, 8, 50, 0.3);
- List<HostSpec> hosts2 = tester.prepare(application, cluster, Capacity.fromCount(2, resources), 1);
+ List<HostSpec> hosts2 = tester.prepare(application, cluster, Capacity.from(new ClusterResources(2, 1, resources)));
tester.activate(application, hosts2);
assertEquals(hosts1, hosts2);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
index 273fc6bbb2d..ad9d13355dc 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisionerTest.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.google.common.collect.Iterators;
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.HostName;
import com.yahoo.config.provision.HostSpec;
@@ -154,7 +155,7 @@ public class LoadBalancerProvisionerTest {
@Test
public void provision_load_balancers_with_dynamic_node_provisioning() {
- var nodes = prepare(app1, Capacity.fromCount(2, new NodeResources(1, 4, 10, 0.3), false, true),
+ var nodes = prepare(app1, Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 10, 0.3)), false, true),
true,
clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")));
Supplier<LoadBalancer> lb = () -> tester.nodeRepository().loadBalancers(app1).asList().get(0);
@@ -172,7 +173,7 @@ public class LoadBalancerProvisionerTest {
assertSame("Load balancer is deactivated", LoadBalancer.State.inactive, lb.get().state());
// Application is redeployed
- nodes = prepare(app1, Capacity.fromCount(2, new NodeResources(1, 4, 10, 0.3), false, true),
+ nodes = prepare(app1, Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 10, 0.3)), false, true),
true,
clusterRequest(ClusterSpec.Type.container, ClusterSpec.Id.from("qrs")));
assertTrue("Load balancer is reconfigured with empty reals", tester.loadBalancerService().instances().get(lb.get().id()).reals().isEmpty());
@@ -229,7 +230,7 @@ public class LoadBalancerProvisionerTest {
}
private Set<HostSpec> prepare(ApplicationId application, ClusterSpec... specs) {
- return prepare(application, Capacity.fromCount(2, new NodeResources(1, 4, 10, 0.3), false, true), false, specs);
+ return prepare(application, Capacity.from(new ClusterResources(2, 1, new NodeResources(1, 4, 10, 0.3)), false, true), false, specs);
}
private Set<HostSpec> prepare(ApplicationId application, Capacity capacity, boolean dynamicDockerNodes, ClusterSpec... specs) {
@@ -240,7 +241,7 @@ public class LoadBalancerProvisionerTest {
}
Set<HostSpec> allNodes = new LinkedHashSet<>();
for (ClusterSpec spec : specs) {
- allNodes.addAll(tester.prepare(application, spec, capacity, 1, false));
+ allNodes.addAll(tester.prepare(application, spec, capacity, false));
}
return allNodes;
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
index cb5116b541e..4a75d86f530 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/MultigroupProvisioningTest.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.provision.provisioning;
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.HostSpec;
@@ -20,7 +21,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
-import java.util.Optional;
import java.util.Set;
import static org.junit.Assert.assertEquals;
@@ -103,8 +103,8 @@ public class MultigroupProvisioningTest {
tester.makeReadyNodes(10, small);
- deploy(application1, Capacity.fromCount(1, Optional.of(small), true, true), 1, tester);
- deploy(application1, Capacity.fromCount(2, Optional.of(small), true, true), 2, tester);
+ deploy(application1, Capacity.from(new ClusterResources(1, 1, small), true, true), tester);
+ deploy(application1, Capacity.from(new ClusterResources(2, 2, small), true, true), tester);
}
@Test
@@ -116,8 +116,8 @@ public class MultigroupProvisioningTest {
tester.makeReadyNodes(10, small);
tester.makeReadyNodes(10, large);
- deploy(application1, Capacity.fromCount(1, Optional.of(small), true, true), 1, tester);
- deploy(application1, Capacity.fromCount(2, Optional.of(large), true, true), 2, tester);
+ deploy(application1, Capacity.from(new ClusterResources(1, 1, small), true, true), tester);
+ deploy(application1, Capacity.from(new ClusterResources(2, 2, large), true, true), tester);
}
@Test
@@ -139,7 +139,7 @@ public class MultigroupProvisioningTest {
tester.clock(),
Collections.singletonMap(application1,
new MockDeployer.ApplicationContext(application1, cluster(),
- Capacity.fromCount(8, Optional.of(large), false, true), 1)));
+ Capacity.from(new ClusterResources(8, 1, large), false, true))));
new RetiredExpirer(tester.nodeRepository(), tester.orchestrator(), deployer, tester.clock(), Duration.ofDays(30),
Duration.ofHours(12)).run();
@@ -148,21 +148,21 @@ public class MultigroupProvisioningTest {
}
private void deploy(ApplicationId application, int nodeCount, int groupCount, NodeResources resources, ProvisioningTester tester) {
- deploy(application, Capacity.fromCount(nodeCount, Optional.of(resources), false, true), groupCount, tester);
+ deploy(application, Capacity.from(new ClusterResources(nodeCount, groupCount, resources), false, true), tester);
}
private void deploy(ApplicationId application, int nodeCount, int groupCount, ProvisioningTester tester) {
- deploy(application, Capacity.fromCount(nodeCount, Optional.of(large), false, true), groupCount, tester);
+ deploy(application, Capacity.from(new ClusterResources(nodeCount, groupCount, large), false, true), tester);
}
- private void deploy(ApplicationId application, Capacity capacity, int wantedGroups, ProvisioningTester tester) {
- int nodeCount = capacity.nodeCount();
- NodeResources nodeResources = capacity.nodeResources().get();
+ private void deploy(ApplicationId application, Capacity capacity, ProvisioningTester tester) {
+ int nodeCount = capacity.minResources().nodes();
+ NodeResources nodeResources = capacity.minResources().nodeResources();
int previousActiveNodeCount = tester.getNodes(application, Node.State.active).resources(nodeResources).size();
- tester.activate(application, prepare(application, capacity, wantedGroups, tester));
+ tester.activate(application, prepare(application, capacity, tester));
assertEquals("Superfluous nodes are retired, but no others - went from " + previousActiveNodeCount + " to " + nodeCount + " nodes",
- Math.max(0, previousActiveNodeCount - capacity.nodeCount()),
+ Math.max(0, previousActiveNodeCount - capacity.minResources().nodes()),
tester.getNodes(application, Node.State.active).retired().resources(nodeResources).size());
assertEquals("Other flavors are retired",
0, tester.getNodes(application, Node.State.active).not().retired().not().resources(nodeResources).size());
@@ -187,26 +187,27 @@ public class MultigroupProvisioningTest {
ClusterSpec.Group group = node.allocation().get().membership().cluster().group().get();
nonretiredGroups.put(group, nonretiredGroups.getOrDefault(group, 0) + 1);
- if (wantedGroups > 1)
- assertTrue("Group indexes are always in [0, wantedGroups>", group.index() < wantedGroups);
+ if (capacity.minResources().groups() > 1)
+ assertTrue("Group indexes are always in [0, wantedGroups>",
+ group.index() < capacity.minResources().groups());
}
assertEquals("Total nonretired nodes", nodeCount, indexes.size());
- assertEquals("Total nonretired groups", wantedGroups, nonretiredGroups.size());
+ assertEquals("Total nonretired groups", capacity.minResources().groups(), nonretiredGroups.size());
for (Integer groupSize : nonretiredGroups.values())
- assertEquals("Group size", (long)nodeCount / wantedGroups, (long)groupSize);
+ assertEquals("Group size", (long)nodeCount / capacity.minResources().groups(), (long)groupSize);
Map<ClusterSpec.Group, Integer> allGroups = new HashMap<>();
for (Node node : tester.getNodes(application, Node.State.active).resources(nodeResources)) {
ClusterSpec.Group group = node.allocation().get().membership().cluster().group().get();
allGroups.put(group, nonretiredGroups.getOrDefault(group, 0) + 1);
}
- assertEquals("No additional groups are retained containing retired nodes", wantedGroups, allGroups.size());
+ assertEquals("No additional groups are retained containing retired nodes", capacity.minResources().groups(), allGroups.size());
}
private ClusterSpec cluster() { return ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("test")).vespaVersion("6.42").build(); }
- private Set<HostSpec> prepare(ApplicationId application, Capacity capacity, int groupCount, ProvisioningTester tester) {
- return new HashSet<>(tester.prepare(application, cluster(), capacity, groupCount));
+ private Set<HostSpec> prepare(ApplicationId application, Capacity capacity, ProvisioningTester tester) {
+ return new HashSet<>(tester.prepare(application, cluster(), capacity));
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
index f4a65244e6f..8420bdeacfe 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/NodeTypeProvisioningTest.java
@@ -93,7 +93,7 @@ public class NodeTypeProvisioningTest {
tester.provisioner(),
tester.clock(),
Collections.singletonMap(
- application, new MockDeployer.ApplicationContext(application, clusterSpec, capacity, 1)));
+ application, new MockDeployer.ApplicationContext(application, clusterSpec, capacity)));
RetiredExpirer retiredExpirer = new RetiredExpirer(tester.nodeRepository(), tester.orchestrator(), deployer,
tester.clock(), Duration.ofDays(30), Duration.ofMinutes(10));
@@ -157,7 +157,7 @@ public class NodeTypeProvisioningTest {
MockDeployer deployer = new MockDeployer(tester.provisioner(),
tester.clock(),
Collections.singletonMap(application,
- new MockDeployer.ApplicationContext(application, clusterSpec, capacity, 1)));
+ new MockDeployer.ApplicationContext(application, clusterSpec, capacity)));
RetiredExpirer retiredExpirer = new RetiredExpirer(tester.nodeRepository(),
tester.orchestrator(),
deployer,
@@ -263,7 +263,7 @@ public class NodeTypeProvisioningTest {
}
private List<HostSpec> deployProxies(ApplicationId application, ProvisioningTester tester) {
- return tester.prepare(application, clusterSpec, capacity, 1);
+ return tester.prepare(application, clusterSpec, capacity);
}
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
index 931d87a3265..bd8be5063fd 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.Capacity;
import com.yahoo.config.provision.ClusterMembership;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Environment;
@@ -402,6 +403,21 @@ public class ProvisioningTest {
prepare(application, 1, 2, 3, 3, defaultResources, tester);
}
+ @Test
+ public void below_resource_limit() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+
+ ApplicationId application = tester.makeApplicationId();
+ tester.makeReadyNodes(10, defaultResources);
+ try {
+ prepare(application, 2, 2, 3, 3,
+ new NodeResources(2, 2, 10, 2), tester);
+ }
+ catch (IllegalArgumentException e) {
+ assertEquals("Must specify at least 4.00 Gb of memory for container cluster 'container0', was: 2.00 Gb", e.getMessage());
+ }
+ }
+
/** Dev always uses the zone default flavor */
@Test
public void dev_deployment_flavor() {
@@ -492,7 +508,7 @@ public class ProvisioningTest {
fail("Expected exception");
}
catch (IllegalArgumentException e) {
- assertEquals("6 nodes [vcpu: 1.0, memory: 4.0 Gb, disk 10.0 Gb, bandwidth: 4.0 Gbps] requested for content cluster 'content0' 6.42 exceeds your quota. Resolve this at https://cloud.vespa.ai/quota",
+ assertEquals("6 nodes with [vcpu: 1.0, memory: 4.0 Gb, disk 10.0 Gb, bandwidth: 4.0 Gbps] requested for content cluster 'content0' 6.42. Max value exceeds your quota. Resolve this at https://cloud.vespa.ai/quota",
e.getMessage());
}
}
@@ -514,7 +530,7 @@ public class ProvisioningTest {
tester.makeReadyNodes(4, defaultResources);
ApplicationId application = tester.makeApplicationId();
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("4.5.6").build();
- tester.prepare(application, cluster, Capacity.fromCount(5, Optional.empty(), false, false), 1);
+ tester.prepare(application, cluster, Capacity.from(new ClusterResources(5, 1, NodeResources.unspecified), false, false));
// No exception; Success
}
@@ -537,8 +553,8 @@ public class ProvisioningTest {
@Test
public void want_to_retire_but_cannot_fail() {
- Capacity capacity = Capacity.fromCount(5, Optional.of(defaultResources), false, true);
- Capacity capacityFORCED = Capacity.fromCount(5, Optional.of(defaultResources), false, false);
+ Capacity capacity = Capacity.from(new ClusterResources(5, 1, defaultResources), false, true);
+ Capacity capacityFORCED = Capacity.from(new ClusterResources(5, 1, defaultResources), false, false);
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
@@ -548,14 +564,14 @@ public class ProvisioningTest {
tester.makeReadyNodes(10, defaultResources);
// Allocate 5 nodes
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("4.5.6").build();
- tester.activate(application, tester.prepare(application, cluster, capacity, 1));
+ tester.activate(application, tester.prepare(application, cluster, capacity));
assertEquals(5, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).not().retired().size());
assertEquals(0, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).retired().size());
// Mark the nodes as want to retire
tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> tester.patchNode(node.with(node.status().withWantToRetire(true))));
// redeploy without allow failing
- tester.activate(application, tester.prepare(application, cluster, capacityFORCED, 1));
+ tester.activate(application, tester.prepare(application, cluster, capacityFORCED));
// Nodes are not retired since that is unsafe when we cannot fail
assertEquals(5, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).not().retired().size());
@@ -564,7 +580,7 @@ public class ProvisioningTest {
tester.nodeRepository().getNodes(application, Node.State.active).forEach(node -> assertTrue(node.status().wantToRetire()));
// redeploy with allowing failing
- tester.activate(application, tester.prepare(application, cluster, capacity, 1));
+ tester.activate(application, tester.prepare(application, cluster, capacity));
// ... old nodes are now retired
assertEquals(5, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).not().retired().size());
assertEquals(5, NodeList.copyOf(tester.nodeRepository().getNodes(application, Node.State.active)).retired().size());
@@ -718,14 +734,12 @@ public class ProvisioningTest {
// Application allocates two content nodes initially, with cluster type content
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("1.2.3").build();
var initialNodes = tester.activate(application, tester.prepare(application, cluster,
- Capacity.fromCount(2, defaultResources, false, false),
- 1));
+ Capacity.from(new ClusterResources(2, 1, defaultResources), false, false)));
// Application is redeployed with cluster type combined
cluster = ClusterSpec.request(ClusterSpec.Type.combined, ClusterSpec.Id.from("music")).vespaVersion("1.2.3").build();
var newNodes = tester.activate(application, tester.prepare(application, cluster,
- Capacity.fromCount(2, defaultResources, false, false),
- 1));
+ Capacity.from(new ClusterResources(2, 1, defaultResources), false, false)));
assertEquals("Node allocation remains the same", initialNodes, newNodes);
assertEquals("Cluster type is updated",
@@ -735,8 +749,7 @@ public class ProvisioningTest {
// Application is redeployed with cluster type content again
cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("1.2.3").build();
newNodes = tester.activate(application, tester.prepare(application, cluster,
- Capacity.fromCount(2, defaultResources, false, false),
- 1));
+ Capacity.from(new ClusterResources(2, 1, defaultResources), false, false)));
assertEquals("Node allocation remains the same", initialNodes, newNodes);
assertEquals("Cluster type is updated",
Set.of(ClusterSpec.Type.content),
@@ -773,11 +786,11 @@ public class ProvisioningTest {
allHosts.addAll(content0);
allHosts.addAll(content1);
- Function<Integer, Capacity> capacity = count -> Capacity.fromCount(count, Optional.empty(), required, true);
- int expectedContainer0Size = tester.capacityPolicies().decideSize(capacity.apply(container0Size), containerCluster0, application);
- int expectedContainer1Size = tester.capacityPolicies().decideSize(capacity.apply(container1Size), containerCluster1, application);
- int expectedContent0Size = tester.capacityPolicies().decideSize(capacity.apply(content0Size), contentCluster0, application);
- int expectedContent1Size = tester.capacityPolicies().decideSize(capacity.apply(content1Size), contentCluster1, application);
+ Function<Integer, Capacity> capacity = count -> Capacity.from(new ClusterResources(count, 1, NodeResources.unspecified), required, true);
+ int expectedContainer0Size = tester.capacityPolicies().decideSize(container0Size, capacity.apply(container0Size), containerCluster0, application);
+ int expectedContainer1Size = tester.capacityPolicies().decideSize(container1Size, capacity.apply(container1Size), containerCluster1, application);
+ int expectedContent0Size = tester.capacityPolicies().decideSize(content0Size, capacity.apply(content0Size), contentCluster0, application);
+ int expectedContent1Size = tester.capacityPolicies().decideSize(content1Size, capacity.apply(content1Size), contentCluster1, application);
assertEquals("Hosts in each group cluster is disjunct and the total number of unretired nodes is correct",
expectedContainer0Size + expectedContainer1Size + expectedContent0Size + expectedContent1Size,
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
index c0ab137066c..a8df47aab1a 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTester.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.Capacity;
+import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
import com.yahoo.config.provision.Flavor;
@@ -127,19 +128,19 @@ public class ProvisioningTester {
}
public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, int nodeCount, int groups, boolean required, NodeResources resources) {
- return prepare(application, cluster, Capacity.fromCount(nodeCount, Optional.ofNullable(resources), required, true), groups);
+ return prepare(application, cluster, Capacity.from(new ClusterResources(nodeCount, groups, resources), required, true));
}
- public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity capacity, int groups) {
- return prepare(application, cluster, capacity, groups, true);
+ public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity capacity) {
+ return prepare(application, cluster, capacity, true);
}
- public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity capacity, int groups, boolean idempotentPrepare) {
+ public List<HostSpec> prepare(ApplicationId application, ClusterSpec cluster, Capacity capacity, boolean idempotentPrepare) {
Set<String> reservedBefore = toHostNames(nodeRepository.getNodes(application, Node.State.reserved));
Set<String> inactiveBefore = toHostNames(nodeRepository.getNodes(application, Node.State.inactive));
- List<HostSpec> hosts1 = provisioner.prepare(application, cluster, capacity, groups, provisionLogger);
+ List<HostSpec> hosts1 = provisioner.prepare(application, cluster, capacity, provisionLogger);
if (idempotentPrepare) { // prepare twice to ensure idempotence
- List<HostSpec> hosts2 = provisioner.prepare(application, cluster, capacity, groups, provisionLogger);
+ List<HostSpec> hosts2 = provisioner.prepare(application, cluster, capacity, provisionLogger);
assertEquals(hosts1, hosts2);
}
Set<String> newlyActivated = toHostNames(nodeRepository.getNodes(application, Node.State.reserved));
@@ -160,7 +161,7 @@ public class ProvisioningTester {
public void prepareAndActivateInfraApplication(ApplicationId application, NodeType nodeType, Version version) {
ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from(nodeType.toString())).vespaVersion(version).build();
Capacity capacity = Capacity.fromRequiredNodeType(nodeType);
- List<HostSpec> hostSpecs = prepare(application, cluster, capacity, 1, true);
+ List<HostSpec> hostSpecs = prepare(application, cluster, capacity, true);
activate(application, hostSpecs);
}
@@ -232,6 +233,10 @@ public class ProvisioningTester {
InstanceName.from(UUID.randomUUID().toString()));
}
+ public ApplicationId makeApplicationId(String applicationName) {
+ return ApplicationId.from("tenant", applicationName, "default");
+ }
+
public List<Node> makeReadyNodes(int n, String flavor) {
return makeReadyNodes(n, flavor, NodeType.tenant);
}
@@ -333,11 +338,9 @@ public class ProvisioningTester {
nodeRepository.setReady(nodes, Agent.system, getClass().getSimpleName());
ConfigServerApplication application = new ConfigServerApplication();
- List<HostSpec> hosts = prepare(
- application.getApplicationId(),
- application.getClusterSpecWithVersion(configServersVersion),
- application.getCapacity(),
- 1);
+ List<HostSpec> hosts = prepare(application.getApplicationId(),
+ application.getClusterSpecWithVersion(configServersVersion),
+ application.getCapacity());
activate(application.getApplicationId(), new HashSet<>(hosts));
return nodeRepository.getNodes(application.getApplicationId(), Node.State.active);
}
@@ -409,9 +412,8 @@ public class ProvisioningTester {
public void deployZoneApp() {
ApplicationId applicationId = makeApplicationId();
List<HostSpec> list = prepare(applicationId,
- ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build(),
- Capacity.fromRequiredNodeType(NodeType.host),
- 1);
+ ClusterSpec.request(ClusterSpec.Type.container, ClusterSpec.Id.from("node-admin")).vespaVersion("6.42").build(),
+ Capacity.fromRequiredNodeType(NodeType.host));
activate(applicationId, Set.copyOf(list));
}
@@ -420,12 +422,15 @@ public class ProvisioningTester {
}
public List<Node> deploy(ApplicationId application, Capacity capacity) {
- List<HostSpec> prepared = prepare(application, clusterSpec(), capacity, 1);
+ return deploy(application, clusterSpec(), capacity);
+ }
+
+ public List<Node> deploy(ApplicationId application, ClusterSpec cluster, Capacity capacity) {
+ List<HostSpec> prepared = prepare(application, cluster, capacity);
activate(application, Set.copyOf(prepared));
return getNodes(application, Node.State.active).asList();
}
-
/** Returns the hosts from the input list which are not retired */
public List<HostSpec> nonRetired(Collection<HostSpec> hosts) {
return hosts.stream().filter(host -> ! host.membership().get().retired()).collect(Collectors.toList());
@@ -524,4 +529,22 @@ public class ProvisioningTester {
@Override public void log(Level level, String message) { }
}
+ public IdentityHostResourcesCalculator identityHostResourcesCalculator() {
+ return new IdentityHostResourcesCalculator();
+ }
+
+ private static class IdentityHostResourcesCalculator implements HostResourcesCalculator {
+
+ @Override
+ public NodeResources realResourcesOf(Node node) {
+ return node.flavor().resources();
+ }
+
+ @Override
+ public NodeResources advertisedResourcesOf(Flavor flavor) {
+ return flavor.resources();
+ }
+
+ }
+
}
diff --git a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
index 11054566985..898f014cea3 100644
--- a/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/document_subdbs/document_subdbs_test.cpp
@@ -447,8 +447,8 @@ void
assertAttributes2(const AttributeGuardList &attributes)
{
EXPECT_EQUAL(2u, attributes.size());
- EXPECT_EQUAL("attr2", attributes[0]->getName());
- EXPECT_EQUAL("attr1", attributes[1]->getName());
+ EXPECT_EQUAL("attr1", attributes[0]->getName());
+ EXPECT_EQUAL("attr2", attributes[1]->getName());
}
void
@@ -833,8 +833,8 @@ requireThatAttributesArePopulatedDuringReprocessing(FixtureType &f)
std::vector<AttributeGuard> attrs;
f.getAttributeManager()->getAttributeList(attrs);
EXPECT_EQUAL(2u, attrs.size());
- TEST_DO(assertAttribute1(attrs[1], CFG_SERIAL, 40));
- TEST_DO(assertAttribute2(attrs[0], 40, 40));
+ TEST_DO(assertAttribute1(attrs[0], CFG_SERIAL, 40));
+ TEST_DO(assertAttribute2(attrs[1], 40, 40));
}
}
diff --git a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
index 30fed6fa49e..6c9ffc210a1 100644
--- a/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
+++ b/searchcore/src/tests/proton/documentdb/feedhandler/feedhandler_test.cpp
@@ -55,7 +55,6 @@ using storage::spi::RemoveResult;
using storage::spi::Result;
using storage::spi::Timestamp;
using storage::spi::UpdateResult;
-using vespalib::BlockingThreadStackExecutor;
using vespalib::ThreadStackExecutor;
using vespalib::ThreadStackExecutorBase;
using vespalib::makeClosure;
diff --git a/searchcore/src/tests/proton/index/indexmanager_test.cpp b/searchcore/src/tests/proton/index/indexmanager_test.cpp
index 264cf6d8cfa..8cc075773f7 100644
--- a/searchcore/src/tests/proton/index/indexmanager_test.cpp
+++ b/searchcore/src/tests/proton/index/indexmanager_test.cpp
@@ -48,7 +48,6 @@ using search::memoryindex::FieldIndexCollection;
using search::queryeval::Source;
using std::set;
using std::string;
-using vespalib::BlockingThreadStackExecutor;
using vespalib::ThreadStackExecutor;
using vespalib::makeLambdaTask;
using std::chrono::duration_cast;
diff --git a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
index dfb1268aaa6..c26b008f769 100644
--- a/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
+++ b/searchcore/src/tests/proton/proton_configurer/proton_configurer_test.cpp
@@ -243,7 +243,6 @@ struct MyLog
struct MyProtonConfigurerOwner : public IProtonConfigurerOwner,
public MyLog
{
- using InitializeThreads = std::shared_ptr<vespalib::ThreadStackExecutorBase>;
vespalib::ThreadStackExecutor _executor;
std::map<DocTypeName, std::shared_ptr<MyDocumentDBConfigOwner>> _dbs;
@@ -254,9 +253,9 @@ struct MyProtonConfigurerOwner : public IProtonConfigurerOwner,
_dbs()
{
}
- virtual ~MyProtonConfigurerOwner() { }
+ ~MyProtonConfigurerOwner() { }
- virtual std::shared_ptr<DocumentDBConfigOwner> addDocumentDB(const DocTypeName &docTypeName,
+ std::shared_ptr<DocumentDBConfigOwner> addDocumentDB(const DocTypeName &docTypeName,
document::BucketSpace bucketSpace,
const vespalib::string &configId,
const std::shared_ptr<BootstrapConfig> &bootstrapConfig,
@@ -275,14 +274,14 @@ struct MyProtonConfigurerOwner : public IProtonConfigurerOwner,
_log.push_back(os.str());
return db;
}
- virtual void removeDocumentDB(const DocTypeName &docTypeName) override {
+ void removeDocumentDB(const DocTypeName &docTypeName) override {
ASSERT_FALSE(_dbs.find(docTypeName) == _dbs.end());
_dbs.erase(docTypeName);
std::ostringstream os;
os << "remove db " << docTypeName.getName();
_log.push_back(os.str());
}
- virtual void applyConfig(const std::shared_ptr<BootstrapConfig> &bootstrapConfig) override {
+ void applyConfig(const std::shared_ptr<BootstrapConfig> &bootstrapConfig) override {
std::ostringstream os;
os << "apply config " << bootstrapConfig->getGeneration();
_log.push_back(os.str());
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
index 93d8ef3fff4..3e9ef787f74 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/documentmetastore.cpp
@@ -1,24 +1,25 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "documentmetastore.h"
-#include "search_context.h"
#include "documentmetastoresaver.h"
+#include "search_context.h"
+#include <vespa/fastos/file.h>
+#include <vespa/searchcore/proton/bucketdb/bucketsessionbase.h>
+#include <vespa/searchcore/proton/bucketdb/joinbucketssession.h>
+#include <vespa/searchcore/proton/bucketdb/splitbucketsession.h>
#include <vespa/searchlib/attribute/attributevector.hpp>
+#include <vespa/searchlib/attribute/load_utils.h>
#include <vespa/searchlib/attribute/readerbase.h>
+#include <vespa/searchlib/common/i_gid_to_lid_mapper.h>
+#include <vespa/searchlib/query/query_term_simple.h>
#include <vespa/vespalib/btree/btree.hpp>
-#include <vespa/vespalib/btree/btreenodestore.hpp>
+#include <vespa/vespalib/btree/btreebuilder.hpp>
#include <vespa/vespalib/btree/btreenodeallocator.hpp>
+#include <vespa/vespalib/btree/btreenodestore.hpp>
#include <vespa/vespalib/btree/btreeroot.hpp>
-#include <vespa/vespalib/btree/btreebuilder.hpp>
-#include <vespa/searchlib/common/i_gid_to_lid_mapper.h>
-#include <vespa/searchcore/proton/bucketdb/bucketsessionbase.h>
-#include <vespa/searchcore/proton/bucketdb/joinbucketssession.h>
-#include <vespa/searchcore/proton/bucketdb/splitbucketsession.h>
-#include <vespa/searchlib/query/query_term_simple.h>
#include <vespa/vespalib/util/bufferwriter.h>
#include <vespa/vespalib/util/exceptions.h>
#include <vespa/vespalib/util/rcuvector.hpp>
-#include <vespa/fastos/file.h>
#include "document_meta_store_versions.h"
#include <vespa/log/log.h>
@@ -32,7 +33,7 @@ using search::FileReader;
using search::GrowStrategy;
using search::IAttributeSaveTarget;
using search::LidUsageStats;
-using vespalib::MemoryUsage;
+using search::attribute::LoadUtils;
using search::attribute::SearchContextParams;
using search::btree::BTreeNoLeafData;
using search::fef::TermFieldMatchData;
@@ -42,6 +43,7 @@ using storage::spi::Timestamp;
using vespalib::GenerationHandler;
using vespalib::GenerationHeldBase;
using vespalib::IllegalStateException;
+using vespalib::MemoryUsage;
using vespalib::make_string;
namespace proton {
@@ -260,7 +262,7 @@ DocumentMetaStore::readNextDoc(documentmetastore::Reader & reader, TreeType::Bui
bool
DocumentMetaStore::onLoad()
{
- documentmetastore::Reader reader(openDAT());
+ documentmetastore::Reader reader(LoadUtils::openDAT(*this));
unload();
size_t numElems = reader.getNumElems();
size_t docIdLimit = reader.getDocIdLimit();
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.cpp b/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.cpp
index 710c072aa53..d4204473578 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.cpp
+++ b/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.cpp
@@ -7,16 +7,19 @@ namespace proton {
void
ExecutorMetrics::update(const vespalib::ThreadStackExecutorBase::Stats &stats)
{
- maxPending.set(stats.maxPendingTasks);
+ maxPending.set(stats.queueSize.max());
accepted.inc(stats.acceptedTasks);
rejected.inc(stats.rejectedTasks);
+ const auto & qSize = stats.queueSize;
+ queueSize.addValueBatch(qSize.average(), qSize.count(), qSize.min(), qSize.max());
}
ExecutorMetrics::ExecutorMetrics(const std::string &name, metrics::MetricSet *parent)
: metrics::MetricSet(name, {}, "Instance specific thread executor metrics", parent),
maxPending("maxpending", {}, "Maximum number of pending (active + queued) tasks", this),
accepted("accepted", {}, "Number of accepted tasks", this),
- rejected("rejected", {}, "Number of rejected tasks", this)
+ rejected("rejected", {}, "Number of rejected tasks", this),
+ queueSize("queuesize", {}, "Size of task queue", this)
{
}
diff --git a/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.h b/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.h
index a347edffd4b..6b638391d1e 100644
--- a/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.h
+++ b/searchcore/src/vespa/searchcore/proton/metrics/executor_metrics.h
@@ -11,9 +11,10 @@ namespace proton {
struct ExecutorMetrics : metrics::MetricSet
{
- metrics::LongValueMetric maxPending;
+ metrics::LongValueMetric maxPending; // TODO Remove on Vespa 8 or sooner if possible.
metrics::LongCountMetric accepted;
metrics::LongCountMetric rejected;
+ metrics::LongAverageMetric queueSize;
void update(const vespalib::ThreadStackExecutorBase::Stats &stats);
ExecutorMetrics(const std::string &name, metrics::MetricSet *parent);
diff --git a/searchcore/src/vespa/searchcore/proton/server/documentdb.h b/searchcore/src/vespa/searchcore/proton/server/documentdb.h
index f296e264903..1a1d97a657b 100644
--- a/searchcore/src/vespa/searchcore/proton/server/documentdb.h
+++ b/searchcore/src/vespa/searchcore/proton/server/documentdb.h
@@ -85,7 +85,7 @@ private:
DocumentStoreCacheStats() : total(), readySubDb(), notReadySubDb(), removedSubDb() {}
};
- using InitializeThreads = std::shared_ptr<vespalib::ThreadStackExecutorBase>;
+ using InitializeThreads = std::shared_ptr<vespalib::SyncableThreadExecutor>;
using IFlushTargetList = std::vector<std::shared_ptr<searchcorespi::IFlushTarget>>;
using StatusReportUP = std::unique_ptr<StatusReport>;
using ProtonConfig = const vespa::config::search::core::internal::InternalProtonType;
diff --git a/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.cpp b/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.cpp
index 6ca385711b0..b13fa2baed3 100644
--- a/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.cpp
@@ -2,13 +2,14 @@
#include "executor_thread_service.h"
#include <vespa/vespalib/util/lambdatask.h>
+#include <vespa/vespalib/util/gate.h>
#include <vespa/fastos/thread.h>
using vespalib::makeLambdaTask;
using vespalib::Executor;
using vespalib::Gate;
using vespalib::Runnable;
-using vespalib::ThreadStackExecutorBase;
+using vespalib::SyncableThreadExecutor;
namespace proton {
@@ -28,7 +29,7 @@ sampleThreadId(FastOS_ThreadId *threadId)
}
std::unique_ptr<internal::ThreadId>
-getThreadId(ThreadStackExecutorBase &executor)
+getThreadId(SyncableThreadExecutor &executor)
{
std::unique_ptr<internal::ThreadId> id = std::make_unique<internal::ThreadId>();
executor.execute(makeLambdaTask([threadId=&id->_id] { sampleThreadId(threadId);}));
@@ -45,7 +46,7 @@ runRunnable(Runnable *runnable, Gate *gate)
} // namespace
-ExecutorThreadService::ExecutorThreadService(ThreadStackExecutorBase &executor)
+ExecutorThreadService::ExecutorThreadService(SyncableThreadExecutor &executor)
: _executor(executor),
_threadId(getThreadId(executor))
{
diff --git a/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.h b/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.h
index ccdfb6b72cd..26069b4b8dd 100644
--- a/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.h
+++ b/searchcore/src/vespa/searchcore/proton/server/executor_thread_service.h
@@ -2,7 +2,7 @@
#pragma once
#include <vespa/searchcorespi/index/i_thread_service.h>
-#include <vespa/vespalib/util/threadstackexecutorbase.h>
+#include <vespa/vespalib/util/threadexecutor.h>
namespace proton {
@@ -14,11 +14,11 @@ namespace internal { struct ThreadId; }
class ExecutorThreadService : public searchcorespi::index::IThreadService
{
private:
- vespalib::ThreadStackExecutorBase &_executor;
+ vespalib::SyncableThreadExecutor &_executor;
std::unique_ptr<internal::ThreadId> _threadId;
public:
- ExecutorThreadService(vespalib::ThreadStackExecutorBase &executor);
+ ExecutorThreadService(vespalib::SyncableThreadExecutor &executor);
~ExecutorThreadService();
Stats getStats() override;
@@ -31,6 +31,10 @@ public:
_executor.sync();
return *this;
}
+ ExecutorThreadService & shutdown() override {
+ _executor.shutdown();
+ return *this;
+ }
bool isCurrentThread() const override;
size_t getNumThreads() const override { return _executor.getNumThreads(); }
diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
index 6e7b4967f6d..a725b00d485 100644
--- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.cpp
@@ -3,23 +3,42 @@
#include "executorthreadingservice.h"
#include <vespa/searchcore/proton/metrics/executor_threading_service_stats.h>
#include <vespa/searchlib/common/sequencedtaskexecutor.h>
+#include <vespa/vespalib/util/singleexecutor.h>
+#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
-using vespalib::ThreadStackExecutorBase;
+
+using vespalib::SyncableThreadExecutor;
+using vespalib::BlockingThreadStackExecutor;
+using vespalib::SingleExecutor;
using search::SequencedTaskExecutor;
+using OptimizeFor = vespalib::Executor::OptimizeFor;
namespace proton {
-ExecutorThreadingService::ExecutorThreadingService(vespalib::ThreadStackExecutorBase & sharedExecutor,
+namespace {
+
+std::unique_ptr<SyncableThreadExecutor>
+createExecutorWithOneThread(uint32_t stackSize, uint32_t taskLimit, OptimizeFor optimize) {
+ if (optimize == OptimizeFor::THROUGHPUT) {
+ return std::make_unique<SingleExecutor>(taskLimit);
+ } else {
+ return std::make_unique<BlockingThreadStackExecutor>(1, stackSize, taskLimit);
+ }
+}
+
+}
+
+ExecutorThreadingService::ExecutorThreadingService(vespalib::SyncableThreadExecutor & sharedExecutor,
uint32_t threads, uint32_t stackSize, uint32_t taskLimit,
OptimizeFor optimize)
: _sharedExecutor(sharedExecutor),
_masterExecutor(1, stackSize),
- _indexExecutor(1, stackSize, taskLimit),
- _summaryExecutor(1, stackSize, taskLimit),
+ _indexExecutor(createExecutorWithOneThread(stackSize, taskLimit, optimize)),
+ _summaryExecutor(createExecutorWithOneThread(stackSize, taskLimit, optimize)),
_masterService(_masterExecutor),
- _indexService(_indexExecutor),
- _summaryService(_summaryExecutor),
+ _indexService(*_indexExecutor),
+ _summaryService(*_summaryExecutor),
_indexFieldInverter(SequencedTaskExecutor::create(threads, taskLimit)),
_indexFieldWriter(SequencedTaskExecutor::create(threads, taskLimit)),
_attributeFieldWriter(SequencedTaskExecutor::create(threads, taskLimit, optimize))
@@ -36,8 +55,8 @@ ExecutorThreadingService::sync()
_masterExecutor.sync();
}
_attributeFieldWriter->sync();
- _indexExecutor.sync();
- _summaryExecutor.sync();
+ _indexExecutor->sync();
+ _summaryExecutor->sync();
_indexFieldInverter->sync();
_indexFieldWriter->sync();
if (!isMasterThread) {
@@ -52,10 +71,10 @@ ExecutorThreadingService::shutdown()
_masterExecutor.shutdown();
_masterExecutor.sync();
_attributeFieldWriter->sync();
- _summaryExecutor.shutdown();
- _summaryExecutor.sync();
- _indexExecutor.shutdown();
- _indexExecutor.sync();
+ _summaryExecutor->shutdown();
+ _summaryExecutor->sync();
+ _indexExecutor->shutdown();
+ _indexExecutor->sync();
_indexFieldInverter->sync();
_indexFieldWriter->sync();
}
@@ -63,8 +82,8 @@ ExecutorThreadingService::shutdown()
void
ExecutorThreadingService::setTaskLimit(uint32_t taskLimit, uint32_t summaryTaskLimit)
{
- _indexExecutor.setTaskLimit(taskLimit);
- _summaryExecutor.setTaskLimit(summaryTaskLimit);
+ _indexExecutor->setTaskLimit(taskLimit);
+ _summaryExecutor->setTaskLimit(summaryTaskLimit);
_indexFieldInverter->setTaskLimit(taskLimit);
_indexFieldWriter->setTaskLimit(taskLimit);
_attributeFieldWriter->setTaskLimit(taskLimit);
@@ -74,8 +93,8 @@ ExecutorThreadingServiceStats
ExecutorThreadingService::getStats()
{
return ExecutorThreadingServiceStats(_masterExecutor.getStats(),
- _indexExecutor.getStats(),
- _summaryExecutor.getStats(),
+ _indexExecutor->getStats(),
+ _summaryExecutor->getStats(),
_sharedExecutor.getStats(),
_indexFieldInverter->getStats(),
_indexFieldWriter->getStats(),
diff --git a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h
index 2e4dd2035f3..4d018e2b6f3 100644
--- a/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h
+++ b/searchcore/src/vespa/searchcore/proton/server/executorthreadingservice.h
@@ -3,7 +3,6 @@
#include "executor_thread_service.h"
#include <vespa/searchcorespi/index/ithreadingservice.h>
-#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
namespace proton {
@@ -17,16 +16,16 @@ class ExecutorThreadingServiceStats;
class ExecutorThreadingService : public searchcorespi::index::IThreadingService
{
private:
- vespalib::ThreadStackExecutorBase & _sharedExecutor;
- vespalib::ThreadStackExecutor _masterExecutor;
- vespalib::BlockingThreadStackExecutor _indexExecutor;
- vespalib::BlockingThreadStackExecutor _summaryExecutor;
- ExecutorThreadService _masterService;
- ExecutorThreadService _indexService;
- ExecutorThreadService _summaryService;
- std::unique_ptr<search::ISequencedTaskExecutor> _indexFieldInverter;
- std::unique_ptr<search::ISequencedTaskExecutor> _indexFieldWriter;
- std::unique_ptr<search::ISequencedTaskExecutor> _attributeFieldWriter;
+ vespalib::SyncableThreadExecutor & _sharedExecutor;
+ vespalib::ThreadStackExecutor _masterExecutor;
+ std::unique_ptr<vespalib::SyncableThreadExecutor> _indexExecutor;
+ std::unique_ptr<vespalib::SyncableThreadExecutor> _summaryExecutor;
+ ExecutorThreadService _masterService;
+ ExecutorThreadService _indexService;
+ ExecutorThreadService _summaryService;
+ std::unique_ptr<search::ISequencedTaskExecutor> _indexFieldInverter;
+ std::unique_ptr<search::ISequencedTaskExecutor> _indexFieldWriter;
+ std::unique_ptr<search::ISequencedTaskExecutor> _attributeFieldWriter;
public:
using OptimizeFor = vespalib::Executor::OptimizeFor;
@@ -36,7 +35,7 @@ public:
* @stackSize The size of the stack of the underlying executors.
* @taskLimit The task limit for the index executor.
*/
- ExecutorThreadingService(vespalib::ThreadStackExecutorBase &sharedExecutor,
+ ExecutorThreadingService(vespalib::SyncableThreadExecutor &sharedExecutor,
uint32_t threads = 1,
uint32_t stackSize = 128 * 1024,
uint32_t taskLimit = 1000,
@@ -56,11 +55,11 @@ public:
vespalib::ThreadStackExecutorBase &getMasterExecutor() {
return _masterExecutor;
}
- vespalib::ThreadStackExecutorBase &getIndexExecutor() {
- return _indexExecutor;
+ vespalib::SyncableThreadExecutor &getIndexExecutor() {
+ return *_indexExecutor;
}
- vespalib::ThreadStackExecutorBase &getSummaryExecutor() {
- return _summaryExecutor;
+ vespalib::SyncableThreadExecutor &getSummaryExecutor() {
+ return *_summaryExecutor;
}
/**
@@ -76,7 +75,7 @@ public:
searchcorespi::index::IThreadService &summary() override {
return _summaryService;
}
- vespalib::ThreadExecutor &shared() override {
+ vespalib::SyncableThreadExecutor &shared() override {
return _sharedExecutor;
}
diff --git a/searchcore/src/vespa/searchcore/proton/server/i_proton_configurer_owner.h b/searchcore/src/vespa/searchcore/proton/server/i_proton_configurer_owner.h
index fec8430e41d..5a457b168ec 100644
--- a/searchcore/src/vespa/searchcore/proton/server/i_proton_configurer_owner.h
+++ b/searchcore/src/vespa/searchcore/proton/server/i_proton_configurer_owner.h
@@ -18,8 +18,8 @@ class DocumentDBConfigOwner;
*/
class IProtonConfigurerOwner
{
- using InitializeThreads = std::shared_ptr<vespalib::ThreadStackExecutorBase>;
public:
+ using InitializeThreads = std::shared_ptr<vespalib::SyncableThreadExecutor>;
virtual ~IProtonConfigurerOwner() { }
virtual std::shared_ptr<DocumentDBConfigOwner> addDocumentDB(const DocTypeName &docTypeName,
document::BucketSpace bucketSpace,
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.cpp b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
index 20de5bb07c1..3f8db3f2ff9 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.cpp
@@ -32,6 +32,7 @@
#include <vespa/vespalib/util/host_name.h>
#include <vespa/vespalib/util/random.h>
#include <vespa/vespalib/net/state_server.h>
+#include <vespa/vespalib/util/blockingthreadstackexecutor.h>
#include <vespa/searchlib/aggregation/forcelink.hpp>
#include <vespa/searchlib/expression/forcelink.hpp>
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton.h b/searchcore/src/vespa/searchcore/proton/server/proton.h
index 410f45162e4..4c9d4c77cc4 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton.h
+++ b/searchcore/src/vespa/searchcore/proton/server/proton.h
@@ -57,7 +57,7 @@ private:
typedef search::engine::MonitorClient MonitorClient;
typedef std::map<DocTypeName, DocumentDB::SP> DocumentDBMap;
typedef BootstrapConfig::ProtonConfigSP ProtonConfigSP;
- using InitializeThreads = std::shared_ptr<vespalib::ThreadStackExecutorBase>;
+ using InitializeThreads = std::shared_ptr<vespalib::SyncableThreadExecutor>;
using BucketSpace = document::BucketSpace;
struct MetricsUpdateHook : metrics::UpdateHook
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
index 0b9293a4aab..45e3c978dd9 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.cpp
@@ -39,7 +39,7 @@ getBucketSpace(const BootstrapConfig &bootstrapConfig, const DocTypeName &name)
}
-ProtonConfigurer::ProtonConfigurer(vespalib::ThreadStackExecutorBase &executor,
+ProtonConfigurer::ProtonConfigurer(vespalib::SyncableThreadExecutor &executor,
IProtonConfigurerOwner &owner,
const std::unique_ptr<IProtonDiskLayout> &diskLayout)
: IProtonConfigurer(),
diff --git a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.h b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.h
index c896f12bd4f..54399a26365 100644
--- a/searchcore/src/vespa/searchcore/proton/server/proton_configurer.h
+++ b/searchcore/src/vespa/searchcore/proton/server/proton_configurer.h
@@ -25,7 +25,7 @@ class IProtonDiskLayout;
class ProtonConfigurer : public IProtonConfigurer
{
using DocumentDBs = std::map<DocTypeName, std::pair<std::weak_ptr<IDocumentDBConfigOwner>, std::weak_ptr<DocumentDBDirectoryHolder>>>;
- using InitializeThreads = std::shared_ptr<vespalib::ThreadStackExecutorBase>;
+ using InitializeThreads = std::shared_ptr<vespalib::SyncableThreadExecutor>;
ExecutorThreadService _executor;
IProtonConfigurerOwner &_owner;
@@ -48,11 +48,11 @@ class ProtonConfigurer : public IProtonConfigurer
void pruneInitialDocumentDBDirs(const ProtonConfigSnapshot &configSnapshot);
public:
- ProtonConfigurer(vespalib::ThreadStackExecutorBase &executor,
+ ProtonConfigurer(vespalib::SyncableThreadExecutor &executor,
IProtonConfigurerOwner &owner,
const std::unique_ptr<IProtonDiskLayout> &diskLayout);
- ~ProtonConfigurer();
+ ~ProtonConfigurer() override;
void setAllowReconfig(bool allowReconfig);
diff --git a/searchcore/src/vespa/searchcore/proton/test/thread_service_observer.h b/searchcore/src/vespa/searchcore/proton/test/thread_service_observer.h
index 766bdeeefb0..127b696c4ab 100644
--- a/searchcore/src/vespa/searchcore/proton/test/thread_service_observer.h
+++ b/searchcore/src/vespa/searchcore/proton/test/thread_service_observer.h
@@ -31,6 +31,10 @@ public:
_service.sync();
return *this;
}
+ ThreadServiceObserver &shutdown() override {
+ _service.shutdown();
+ return *this;
+ }
bool isCurrentThread() const override {
return _service.isCurrentThread();
}
diff --git a/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h b/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h
index 7ac9c0c68f2..23c62d179b1 100644
--- a/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h
+++ b/searchcore/src/vespa/searchcore/proton/test/threading_service_observer.h
@@ -69,6 +69,7 @@ public:
search::ISequencedTaskExecutor &attributeFieldWriter() override {
return _attributeFieldWriter;
}
+
};
}
diff --git a/searchlib/CMakeLists.txt b/searchlib/CMakeLists.txt
index aaf8f91387e..a76baeced04 100644
--- a/searchlib/CMakeLists.txt
+++ b/searchlib/CMakeLists.txt
@@ -90,6 +90,7 @@ vespa_define_module(
src/tests/attribute/postinglist
src/tests/attribute/postinglistattribute
src/tests/attribute/reference_attribute
+ src/tests/attribute/save_target
src/tests/attribute/searchable
src/tests/attribute/searchcontext
src/tests/attribute/sourceselector
@@ -214,6 +215,7 @@ vespa_define_module(
src/tests/tensor/dense_tensor_store
src/tests/tensor/distance_functions
src/tests/tensor/hnsw_index
+ src/tests/tensor/hnsw_saver
src/tests/transactionlog
src/tests/transactionlogstress
src/tests/true
diff --git a/searchlib/src/tests/attribute/enum_attribute_compaction/enum_attribute_compaction_test.cpp b/searchlib/src/tests/attribute/enum_attribute_compaction/enum_attribute_compaction_test.cpp
index 31af5945337..45d432c29be 100644
--- a/searchlib/src/tests/attribute/enum_attribute_compaction/enum_attribute_compaction_test.cpp
+++ b/searchlib/src/tests/attribute/enum_attribute_compaction/enum_attribute_compaction_test.cpp
@@ -221,7 +221,7 @@ TEST_P(IntegerCompactionTest, compact)
test_enum_store_compaction();
}
-INSTANTIATE_TEST_CASE_P(IntegerCompactionTestSet, IntegerCompactionTest, ::testing::Values(CollectionType::SINGLE, CollectionType::ARRAY, CollectionType::WSET));
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(IntegerCompactionTestSet, IntegerCompactionTest, ::testing::Values(CollectionType::SINGLE, CollectionType::ARRAY, CollectionType::WSET));
using StringCompactionTest = CompactionTest<StringAttribute>;
@@ -230,6 +230,6 @@ TEST_P(StringCompactionTest, compact)
test_enum_store_compaction();
}
-INSTANTIATE_TEST_CASE_P(StringCompactionTestSet, StringCompactionTest, ::testing::Values(CollectionType::SINGLE, CollectionType::ARRAY, CollectionType::WSET));
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(StringCompactionTestSet, StringCompactionTest, ::testing::Values(CollectionType::SINGLE, CollectionType::ARRAY, CollectionType::WSET));
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp b/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
index bf829f6607a..41313fc7c53 100644
--- a/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
+++ b/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
@@ -108,6 +108,17 @@ public:
}
IAttributeFileWriter &udatWriter() override { return _udatWriter; }
+ bool setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc) override {
+ (void) file_suffix;
+ (void) desc;
+ abort();
+ }
+ IAttributeFileWriter& get_writer(const vespalib::string& file_suffix) override {
+ (void) file_suffix;
+ abort();
+ }
+
bool bufEqual(const Buffer &lhs, const Buffer &rhs) const;
bool operator==(const MemAttr &rhs) const;
diff --git a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
index 3a885dda233..43e694f0bcd 100644
--- a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
+++ b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
@@ -72,7 +72,7 @@ public:
#endif
using FloatEnumStoreTestTypes = ::testing::Types<FloatEnumStore, DoubleEnumStore>;
-TYPED_TEST_CASE(FloatEnumStoreTest, FloatEnumStoreTestTypes);
+VESPA_GTEST_TYPED_TEST_SUITE(FloatEnumStoreTest, FloatEnumStoreTestTypes);
TYPED_TEST(FloatEnumStoreTest, numbers_can_be_inserted_and_retrieved)
{
@@ -452,7 +452,7 @@ LoaderTest<StringEnumStore>::load_values(enumstore::EnumeratedLoaderBase& loader
#endif
using LoaderTestTypes = ::testing::Types<NumericEnumStore, FloatEnumStore, StringEnumStore>;
-TYPED_TEST_CASE(LoaderTest, LoaderTestTypes);
+VESPA_GTEST_TYPED_TEST_SUITE(LoaderTest, LoaderTestTypes);
TYPED_TEST(LoaderTest, store_is_instantiated_with_enumerated_loader)
{
diff --git a/searchlib/src/tests/attribute/save_target/CMakeLists.txt b/searchlib/src/tests/attribute/save_target/CMakeLists.txt
new file mode 100644
index 00000000000..e127f66579e
--- /dev/null
+++ b/searchlib/src/tests/attribute/save_target/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_attribute_save_target_test_app TEST
+ SOURCES
+ attribute_save_target_test.cpp
+ DEPENDS
+ searchlib
+ gtest
+)
+vespa_add_test(NAME searchlib_attribute_save_target_test_app COMMAND searchlib_attribute_save_target_test_app)
diff --git a/searchlib/src/tests/attribute/save_target/attribute_save_target_test.cpp b/searchlib/src/tests/attribute/save_target/attribute_save_target_test.cpp
new file mode 100644
index 00000000000..c746a0aa120
--- /dev/null
+++ b/searchlib/src/tests/attribute/save_target/attribute_save_target_test.cpp
@@ -0,0 +1,148 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchlib/attribute/attributefilesavetarget.h>
+#include <vespa/searchlib/attribute/attributememorysavetarget.h>
+#include <vespa/searchlib/common/tunefileinfo.h>
+#include <vespa/searchlib/index/dummyfileheadercontext.h>
+#include <vespa/searchlib/test/directory_handler.h>
+#include <vespa/searchlib/util/fileutil.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vespalib/util/bufferwriter.h>
+#include <vespa/vespalib/util/exceptions.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("attribute_save_target_test");
+
+using namespace search;
+using namespace search::attribute;
+
+using search::index::DummyFileHeaderContext;
+using search::test::DirectoryHandler;
+
+const vespalib::string test_dir = "test_data/";
+
+class SaveTargetTest : public ::testing::Test {
+public:
+ DirectoryHandler dir_handler;
+ TuneFileAttributes tune_file;
+ DummyFileHeaderContext file_header_ctx;
+ IAttributeSaveTarget& target;
+ vespalib::string base_file_name;
+
+ SaveTargetTest(IAttributeSaveTarget& target_in)
+ : dir_handler(test_dir),
+ tune_file(),
+ file_header_ctx(),
+ target(target_in),
+ base_file_name(test_dir + "test_file")
+ {
+ }
+ ~SaveTargetTest() {}
+ void set_header(const vespalib::string& file_name) {
+ target.setHeader(AttributeHeader(file_name));
+ }
+ IAttributeFileWriter& setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc) {
+ bool res = target.setup_writer(file_suffix, desc);
+ assert(res);
+ return target.get_writer(file_suffix);
+ }
+ void setup_writer_and_fill(const vespalib::string& file_suffix,
+ const vespalib::string& desc,
+ int value) {
+ auto& writer = setup_writer(file_suffix, desc);
+ auto buf = writer.allocBufferWriter();
+ buf->write(&value, sizeof(int));
+ buf->flush();
+ }
+ void validate_loaded_file(const vespalib::string& file_suffix,
+ const vespalib::string& exp_desc,
+ int exp_value)
+ {
+ vespalib::string file_name = base_file_name + "." + file_suffix;
+ EXPECT_TRUE(vespalib::fileExists(file_name));
+ auto loaded = FileUtil::loadFile(file_name);
+ EXPECT_FALSE(loaded->empty());
+
+ const auto& header = loaded->getHeader();
+ EXPECT_EQ(file_name, header.getTag("fileName").asString());
+ EXPECT_EQ(exp_desc, header.getTag("desc").asString());
+
+ EXPECT_EQ(sizeof(int), loaded->size());
+ int act_value = (reinterpret_cast<const int*>(loaded->buffer()))[0];
+ EXPECT_EQ(exp_value, act_value);
+ }
+};
+
+class FileSaveTargetTest : public SaveTargetTest {
+public:
+ AttributeFileSaveTarget file_target;
+
+ FileSaveTargetTest()
+ : SaveTargetTest(file_target),
+ file_target(tune_file, file_header_ctx)
+ {
+ set_header(base_file_name);
+ }
+};
+
+TEST_F(FileSaveTargetTest, can_setup_and_return_writers)
+{
+ setup_writer_and_fill("my1", "desc 1", 123);
+ setup_writer_and_fill("my2", "desc 2", 456);
+ target.close();
+
+ validate_loaded_file("my1", "desc 1", 123);
+ validate_loaded_file("my2", "desc 2", 456);
+}
+
+TEST_F(FileSaveTargetTest, setup_fails_if_writer_already_exists)
+{
+ setup_writer("my", "my desc");
+ EXPECT_FALSE(target.setup_writer("my", "my desc"));
+}
+
+TEST_F(FileSaveTargetTest, get_throws_if_writer_does_not_exists)
+{
+ EXPECT_THROW(target.get_writer("na"), vespalib::IllegalArgumentException);
+}
+
+class MemorySaveTargetTest : public SaveTargetTest {
+public:
+ AttributeMemorySaveTarget memory_target;
+
+ MemorySaveTargetTest()
+ : SaveTargetTest(memory_target),
+ memory_target()
+ {
+ set_header(base_file_name);
+ }
+ void write_to_file() {
+ bool res = memory_target.writeToFile(tune_file, file_header_ctx);
+ ASSERT_TRUE(res);
+ }
+};
+
+TEST_F(MemorySaveTargetTest, can_setup_and_return_writers)
+{
+ setup_writer_and_fill("my1", "desc 1", 123);
+ setup_writer_and_fill("my2", "desc 2", 456);
+ write_to_file();
+
+ validate_loaded_file("my1", "desc 1", 123);
+ validate_loaded_file("my2", "desc 2", 456);
+}
+
+TEST_F(MemorySaveTargetTest, setup_fails_if_writer_already_exists)
+{
+ setup_writer("my", "my desc");
+ EXPECT_FALSE(target.setup_writer("my", "my desc"));
+}
+
+TEST_F(MemorySaveTargetTest, get_throws_if_writer_does_not_exists)
+{
+ EXPECT_THROW(target.get_writer("na"), vespalib::IllegalArgumentException);
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
+
diff --git a/searchlib/src/tests/attribute/tensorattribute/CMakeLists.txt b/searchlib/src/tests/attribute/tensorattribute/CMakeLists.txt
index 3794fd88fc3..44ff45d02d3 100644
--- a/searchlib/src/tests/attribute/tensorattribute/CMakeLists.txt
+++ b/searchlib/src/tests/attribute/tensorattribute/CMakeLists.txt
@@ -5,5 +5,4 @@ vespa_add_executable(searchlib_tensorattribute_test_app TEST
DEPENDS
searchlib
)
-vespa_add_test(NAME searchlib_tensorattribute_test_app COMMAND ${CMAKE_CURRENT_SOURCE_DIR}/tensorattribute_test.sh
- DEPENDS searchlib_tensorattribute_test_app)
+vespa_add_test(NAME searchlib_tensorattribute_test_app COMMAND searchlib_tensorattribute_test_app)
diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
index d9a4431f89b..39a7e53ca8c 100644
--- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
+++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
@@ -5,8 +5,8 @@
#include <vespa/eval/tensor/dense/dense_tensor.h>
#include <vespa/eval/tensor/tensor.h>
#include <vespa/fastos/file.h>
-#include <vespa/searchlib/attribute/attributeguard.h>
#include <vespa/searchlib/attribute/attribute_read_guard.h>
+#include <vespa/searchlib/attribute/attributeguard.h>
#include <vespa/searchlib/tensor/default_nearest_neighbor_index_factory.h>
#include <vespa/searchlib/tensor/dense_tensor_attribute.h>
#include <vespa/searchlib/tensor/doc_vector_access.h>
@@ -14,11 +14,15 @@
#include <vespa/searchlib/tensor/hnsw_index.h>
#include <vespa/searchlib/tensor/nearest_neighbor_index.h>
#include <vespa/searchlib/tensor/nearest_neighbor_index_factory.h>
+#include <vespa/searchlib/tensor/nearest_neighbor_index_saver.h>
#include <vespa/searchlib/tensor/tensor_attribute.h>
+#include <vespa/searchlib/test/directory_handler.h>
+#include <vespa/searchlib/util/fileutil.h>
#include <vespa/vespalib/data/fileheader.h>
#include <vespa/vespalib/io/fileutil.h>
#include <vespa/vespalib/test/insertion_operators.h>
#include <vespa/vespalib/testkit/test_kit.h>
+#include <vespa/vespalib/util/bufferwriter.h>
#include <vespa/log/log.h>
LOG_SETUP("tensorattribute_test");
@@ -33,8 +37,10 @@ using search::tensor::DenseTensorAttribute;
using search::tensor::DocVectorAccess;
using search::tensor::GenericTensorAttribute;
using search::tensor::HnswIndex;
+using search::tensor::HnswNode;
using search::tensor::NearestNeighborIndex;
using search::tensor::NearestNeighborIndexFactory;
+using search::tensor::NearestNeighborIndexSaver;
using search::tensor::TensorAttribute;
using vespalib::eval::TensorSpec;
using vespalib::eval::ValueType;
@@ -75,6 +81,18 @@ vec_2d(double x0, double x1)
return TensorSpec(vec_2d_spec).add({{"x", 0}}, x0).add({{"x", 1}}, x1);
}
+class MockIndexSaver : public NearestNeighborIndexSaver {
+private:
+ int _index_value;
+
+public:
+ MockIndexSaver(int index_value) : _index_value(index_value) {}
+ void save(search::BufferWriter& writer) const override {
+ writer.write(&_index_value, sizeof(int));
+ writer.flush();
+ }
+};
+
class MockNearestNeighborIndex : public NearestNeighborIndex {
private:
using Entry = std::pair<uint32_t, DoubleVector>;
@@ -86,6 +104,7 @@ private:
generation_t _transfer_gen;
generation_t _trim_gen;
mutable size_t _memory_usage_cnt;
+ int _index_value;
public:
MockNearestNeighborIndex(const DocVectorAccess& vectors)
@@ -94,13 +113,20 @@ public:
_removes(),
_transfer_gen(std::numeric_limits<generation_t>::max()),
_trim_gen(std::numeric_limits<generation_t>::max()),
- _memory_usage_cnt(0)
+ _memory_usage_cnt(0),
+ _index_value(0)
{
}
void clear() {
_adds.clear();
_removes.clear();
}
+ int get_index_value() const {
+ return _index_value;
+ }
+ void save_index_with_value(int value) {
+ _index_value = value;
+ }
void expect_empty_add() const {
EXPECT_TRUE(_adds.empty());
}
@@ -143,6 +169,17 @@ public:
return vespalib::MemoryUsage();
}
void get_state(const vespalib::slime::Inserter&) const override {}
+ std::unique_ptr<NearestNeighborIndexSaver> make_saver() const override {
+ if (_index_value != 0) {
+ return std::make_unique<MockIndexSaver>(_index_value);
+ }
+ return std::unique_ptr<NearestNeighborIndexSaver>();
+ }
+ bool load(const search::fileutil::LoadedBuffer& buf) override {
+ ASSERT_EQUAL(sizeof(int), buf.size());
+ _index_value = (reinterpret_cast<const int*>(buf.buffer()))[0];
+ return true;
+ }
std::vector<Neighbor> find_top_k(uint32_t k, vespalib::tensor::TypedCells vector, uint32_t explore_k) const override {
(void) k;
(void) vector;
@@ -166,12 +203,15 @@ class MockNearestNeighborIndexFactory : public NearestNeighborIndexFactory {
}
};
-struct Fixture
-{
+const vespalib::string test_dir = "test_data/";
+const vespalib::string attr_name = test_dir + "my_attr";
+
+struct Fixture {
using BasicType = search::attribute::BasicType;
using CollectionType = search::attribute::CollectionType;
using Config = search::attribute::Config;
+ search::test::DirectoryHandler _dir_handler;
Config _cfg;
vespalib::string _name;
vespalib::string _typeSpec;
@@ -185,8 +225,9 @@ struct Fixture
bool useDenseTensorAttribute = false,
bool enable_hnsw_index = false,
bool use_mock_index = false)
- : _cfg(BasicType::TENSOR, CollectionType::SINGLE),
- _name("test"),
+ : _dir_handler(test_dir),
+ _cfg(BasicType::TENSOR, CollectionType::SINGLE),
+ _name(attr_name),
_typeSpec(typeSpec),
_index_factory(std::make_unique<DefaultNearestNeighborIndexFactory>()),
_tensorAttr(),
@@ -225,11 +266,20 @@ struct Fixture
return *result;
}
- MockNearestNeighborIndex& mock_index() {
+ template <typename IndexType>
+ IndexType& get_nearest_neighbor_index() {
assert(as_dense_tensor().nearest_neighbor_index() != nullptr);
- auto mock_index = dynamic_cast<const MockNearestNeighborIndex*>(as_dense_tensor().nearest_neighbor_index());
- assert(mock_index != nullptr);
- return *const_cast<MockNearestNeighborIndex*>(mock_index);
+ auto index = dynamic_cast<const IndexType*>(as_dense_tensor().nearest_neighbor_index());
+ assert(index != nullptr);
+ return *const_cast<IndexType*>(index);
+ }
+
+ HnswIndex& hnsw_index() {
+ return get_nearest_neighbor_index<HnswIndex>();
+ }
+
+ MockNearestNeighborIndex& mock_index() {
+ return get_nearest_neighbor_index<MockNearestNeighborIndex>();
}
void ensureSpace(uint32_t docId) {
@@ -322,7 +372,6 @@ struct Fixture
void testEmptyTensor();
};
-
void
Fixture::testEmptyAttribute()
{
@@ -383,7 +432,6 @@ Fixture::testSaveLoad()
TEST_DO(assertGetNoTensor(2));
}
-
void
Fixture::testCompaction()
{
@@ -438,7 +486,8 @@ Fixture::testTensorTypeFileHeaderTag()
vespalib::FileHeader header;
FastOS_File file;
- EXPECT_TRUE(file.OpenReadOnly("test.dat"));
+ vespalib::string file_name = attr_name + ".dat";
+ EXPECT_TRUE(file.OpenReadOnly(file_name.c_str()));
(void) header.readFile(file);
file.Close();
EXPECT_TRUE(header.hasTag("tensortype"));
@@ -450,7 +499,6 @@ Fixture::testTensorTypeFileHeaderTag()
}
}
-
void
Fixture::testEmptyTensor()
{
@@ -465,7 +513,6 @@ Fixture::testEmptyTensor()
}
}
-
template <class MakeFixture>
void testAll(MakeFixture &&f)
{
@@ -499,21 +546,49 @@ TEST_F("Hnsw index is NOT instantiated in dense tensor attribute by default",
EXPECT_TRUE(tensor.nearest_neighbor_index() == nullptr);
}
-TEST_F("Hnsw index is instantiated in dense tensor attribute when specified in config",
- Fixture(vec_2d_spec, true, true))
+class DenseTensorAttributeHnswIndex : public Fixture {
+public:
+ DenseTensorAttributeHnswIndex() : Fixture(vec_2d_spec, true, true, false) {}
+};
+
+TEST_F("Hnsw index is instantiated in dense tensor attribute when specified in config", DenseTensorAttributeHnswIndex)
{
- const auto& tensor = f.as_dense_tensor();
- ASSERT_TRUE(tensor.nearest_neighbor_index() != nullptr);
- auto hnsw_index = dynamic_cast<const HnswIndex*>(tensor.nearest_neighbor_index());
- ASSERT_TRUE(hnsw_index != nullptr);
+ auto& index = f.hnsw_index();
- const auto& cfg = hnsw_index->config();
+ const auto& cfg = index.config();
EXPECT_EQUAL(8u, cfg.max_links_at_level_0());
EXPECT_EQUAL(4u, cfg.max_links_on_inserts());
EXPECT_EQUAL(20u, cfg.neighbors_to_explore_at_construction());
EXPECT_TRUE(cfg.heuristic_select_neighbors());
}
+void
+expect_level_0(uint32_t exp_docid, const HnswNode& node)
+{
+ ASSERT_GREATER_EQUAL(node.size(), 1u);
+ ASSERT_EQUAL(1u, node.level(0).size());
+ EXPECT_EQUAL(exp_docid, node.level(0)[0]);
+}
+
+TEST_F("Hnsw index is integrated in dense tensor attribute and can be saved and loaded", DenseTensorAttributeHnswIndex)
+{
+ // Set two points that will be linked together in level 0 of the hnsw graph.
+ f.set_tensor(1, vec_2d(3, 5));
+ f.set_tensor(2, vec_2d(7, 9));
+
+ auto &index_a = f.hnsw_index();
+ expect_level_0(2, index_a.get_node(1));
+ expect_level_0(1, index_a.get_node(2));
+ f.save();
+ EXPECT_TRUE(vespalib::fileExists(attr_name + ".nnidx"));
+
+ f.load();
+ auto &index_b = f.hnsw_index();
+ EXPECT_NOT_EQUAL(&index_a, &index_b);
+ expect_level_0(2, index_b.get_node(1));
+ expect_level_0(1, index_b.get_node(2));
+}
+
class DenseTensorAttributeMockIndex : public Fixture {
public:
DenseTensorAttributeMockIndex() : Fixture(vec_2d_spec, true, true, true) {}
@@ -551,17 +626,6 @@ TEST_F("clearDoc() updates nearest neighbor index", DenseTensorAttributeMockInde
index.expect_empty_add();
}
-TEST_F("onLoad() updates nearest neighbor index", DenseTensorAttributeMockIndex)
-{
- f.set_tensor(1, vec_2d(3, 5));
- f.set_tensor(2, vec_2d(7, 9));
- f.save();
- f.load();
- auto& index = f.mock_index();
- index.expect_adds({{1, {3, 5}}, {2, {7, 9}}});
-}
-
-
TEST_F("commit() ensures transfer and trim hold lists on nearest neighbor index", DenseTensorAttributeMockIndex)
{
auto& index = f.mock_index();
@@ -598,4 +662,32 @@ TEST_F("Memory usage is extracted from index when updating stats on attribute",
EXPECT_EQUAL(before + 1, after);
}
-TEST_MAIN() { TEST_RUN_ALL(); vespalib::unlink("test.dat"); }
+TEST_F("Nearest neighbor index can be saved to disk and then loaded from file", DenseTensorAttributeMockIndex)
+{
+ f.set_tensor(1, vec_2d(3, 5));
+ f.set_tensor(2, vec_2d(7, 9));
+ f.mock_index().save_index_with_value(123);
+ f.save();
+ EXPECT_TRUE(vespalib::fileExists(attr_name + ".nnidx"));
+
+ f.load(); // index is loaded from saved file
+ auto& index = f.mock_index();
+ EXPECT_EQUAL(123, index.get_index_value());
+ index.expect_adds({});
+}
+
+TEST_F("onLoad() reconstructs nearest neighbor index if save file does not exists", DenseTensorAttributeMockIndex)
+{
+ f.set_tensor(1, vec_2d(3, 5));
+ f.set_tensor(2, vec_2d(7, 9));
+ f.save();
+ EXPECT_FALSE(vespalib::fileExists(attr_name + ".nnidx"));
+
+ f.load(); // index is reconstructed by adding all loaded tensors
+ auto& index = f.mock_index();
+ EXPECT_EQUAL(0, index.get_index_value());
+ index.expect_adds({{1, {3, 5}}, {2, {7, 9}}});
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
+
diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.sh b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.sh
deleted file mode 100755
index dd9399dea78..00000000000
--- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.sh
+++ /dev/null
@@ -1,5 +0,0 @@
-#!/bin/bash
-# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-set -e
-$VALGRIND ./searchlib_tensorattribute_test_app
-rm -rf *.dat
diff --git a/searchlib/src/tests/common/sequencedtaskexecutor/CMakeLists.txt b/searchlib/src/tests/common/sequencedtaskexecutor/CMakeLists.txt
index 6c593d20683..fd6cd9efc43 100644
--- a/searchlib/src/tests/common/sequencedtaskexecutor/CMakeLists.txt
+++ b/searchlib/src/tests/common/sequencedtaskexecutor/CMakeLists.txt
@@ -13,3 +13,11 @@ vespa_add_executable(searchlib_sequencedtaskexecutor_test_app TEST
searchlib
)
vespa_add_test(NAME searchlib_sequencedtaskexecutor_test_app COMMAND searchlib_sequencedtaskexecutor_test_app)
+
+vespa_add_executable(searchlib_adaptive_sequenced_executor_test_app TEST
+ SOURCES
+ adaptive_sequenced_executor_test.cpp
+ DEPENDS
+ searchlib
+)
+vespa_add_test(NAME searchlib_adaptive_sequenced_executor_test_app COMMAND searchlib_adaptive_sequenced_executor_test_app)
diff --git a/searchlib/src/tests/common/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp b/searchlib/src/tests/common/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp
new file mode 100644
index 00000000000..ba66b28108c
--- /dev/null
+++ b/searchlib/src/tests/common/sequencedtaskexecutor/adaptive_sequenced_executor_test.cpp
@@ -0,0 +1,251 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchlib/common/adaptive_sequenced_executor.h>
+#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/test/insertion_operators.h>
+
+#include <mutex>
+#include <condition_variable>
+#include <unistd.h>
+
+#include <vespa/log/log.h>
+LOG_SETUP("adaptive_sequenced_executor_test");
+
+namespace search::common {
+
+
+class Fixture
+{
+public:
+ AdaptiveSequencedExecutor _threads;
+
+ Fixture() : _threads(2, 2, 0, 1000) { }
+};
+
+
+class TestObj
+{
+public:
+ std::mutex _m;
+ std::condition_variable _cv;
+ int _done;
+ int _fail;
+ int _val;
+
+ TestObj()
+ : _m(),
+ _cv(),
+ _done(0),
+ _fail(0),
+ _val(0)
+ {
+ }
+
+ void
+ modify(int oldValue, int newValue)
+ {
+ {
+ std::lock_guard<std::mutex> guard(_m);
+ if (_val == oldValue) {
+ _val = newValue;
+ } else {
+ ++_fail;
+ }
+ ++_done;
+ }
+ _cv.notify_all();
+ }
+
+ void
+ wait(int wantDone)
+ {
+ std::unique_lock<std::mutex> guard(_m);
+ _cv.wait(guard, [&] { return this->_done >= wantDone; });
+ }
+};
+
+TEST_F("testExecute", Fixture) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(1, [&]() { tv->modify(0, 42); });
+ tv->wait(1);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+
+TEST_F("require that task with same component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(0, [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+TEST_F("require that task with different component ids are not serialized", Fixture)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < 100; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(0, [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(2, [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads.sync();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with same string component id are serialized", Fixture)
+{
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ auto test2 = [&]() { tv->modify(14, 42); };
+ f._threads.execute(f._threads.getExecutorId("0"), [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(f._threads.getExecutorId("0"), test2);
+ tv->wait(2);
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+ f._threads.sync();
+ EXPECT_EQUAL(0, tv->_fail);
+ EXPECT_EQUAL(42, tv->_val);
+}
+
+namespace {
+
+int detectSerializeFailure(Fixture &f, vespalib::stringref altComponentId, int tryLimit)
+{
+ int tryCnt = 0;
+ for (tryCnt = 0; tryCnt < tryLimit; ++tryCnt) {
+ std::shared_ptr<TestObj> tv(std::make_shared<TestObj>());
+ EXPECT_EQUAL(0, tv->_val);
+ f._threads.execute(f._threads.getExecutorId("0"), [&]() { usleep(2000); tv->modify(0, 14); });
+ f._threads.execute(f._threads.getExecutorId(altComponentId), [&]() { tv->modify(14, 42); });
+ tv->wait(2);
+ if (tv->_fail != 1) {
+ continue;
+ }
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ f._threads.sync();
+ EXPECT_EQUAL(1, tv->_fail);
+ EXPECT_EQUAL(14, tv->_val);
+ break;
+ }
+ return tryCnt;
+}
+
+vespalib::string makeAltComponentId(Fixture &f)
+{
+ int tryCnt = 0;
+ char altComponentId[20];
+ ISequencedTaskExecutor::ExecutorId executorId0 = f._threads.getExecutorId("0");
+ for (tryCnt = 1; tryCnt < 100; ++tryCnt) {
+ sprintf(altComponentId, "%d", tryCnt);
+ if (f._threads.getExecutorId(altComponentId) == executorId0) {
+ break;
+ }
+ }
+ EXPECT_TRUE(tryCnt < 100);
+ return altComponentId;
+}
+
+}
+
+TEST_F("require that task with different string component ids are not serialized", Fixture)
+{
+ int tryCnt = detectSerializeFailure(f, "2", 100);
+ EXPECT_TRUE(tryCnt < 100);
+}
+
+
+TEST_F("require that task with different string component ids mapping to the same executor id are serialized",
+ Fixture)
+{
+ vespalib::string altComponentId = makeAltComponentId(f);
+ LOG(info, "second string component id is \"%s\"", altComponentId.c_str());
+ int tryCnt = detectSerializeFailure(f, altComponentId, 100);
+ EXPECT_TRUE(tryCnt == 100);
+}
+
+
+TEST_F("require that execute works with const lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads.execute(0, lambda);
+ f._threads.execute(0, lambda);
+ f._threads.sync();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that execute works with reference to lambda", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ auto &lambdaref = lambda;
+ f._threads.execute(0, lambdaref);
+ f._threads.execute(0, lambdaref);
+ f._threads.sync();
+ std::vector<int> exp({5, 4, 5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST_F("require that executeLambda works", Fixture)
+{
+ int i = 5;
+ std::vector<int> res;
+ const auto lambda = [i, &res]() mutable
+ { res.push_back(i--); res.push_back(i--); };
+ f._threads.executeLambda(ISequencedTaskExecutor::ExecutorId(0), lambda);
+ f._threads.sync();
+ std::vector<int> exp({5, 4});
+ EXPECT_EQUAL(exp, res);
+ EXPECT_EQUAL(5, i);
+}
+
+TEST("require that you get correct number of executors") {
+ AdaptiveSequencedExecutor seven(7, 1, 0, 10);
+ EXPECT_EQUAL(7u, seven.getNumExecutors());
+}
+
+TEST("require that you distribute well") {
+ AdaptiveSequencedExecutor seven(7, 1, 0, 10);
+ EXPECT_EQUAL(7u, seven.getNumExecutors());
+ EXPECT_EQUAL(97u, seven.getComponentHashSize());
+ EXPECT_EQUAL(0u, seven.getComponentEffectiveHashSize());
+ for (uint32_t id=0; id < 1000; id++) {
+ EXPECT_EQUAL((id%97)%7, seven.getExecutorId(id).getId());
+ }
+ EXPECT_EQUAL(97u, seven.getComponentHashSize());
+ EXPECT_EQUAL(97u, seven.getComponentEffectiveHashSize());
+}
+
+}
+
+TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/searchlib/src/tests/common/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp b/searchlib/src/tests/common/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp
index 9491617c135..362dc28d36a 100644
--- a/searchlib/src/tests/common/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp
+++ b/searchlib/src/tests/common/sequencedtaskexecutor/sequencedtaskexecutor_benchmark.cpp
@@ -1,30 +1,70 @@
// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/searchlib/common/sequencedtaskexecutor.h>
+#include <vespa/searchlib/common/adaptive_sequenced_executor.h>
#include <vespa/vespalib/util/lambdatask.h>
+#include <vespa/vespalib/util/time.h>
#include <atomic>
+using search::ISequencedTaskExecutor;
using search::SequencedTaskExecutor;
+using search::AdaptiveSequencedExecutor;
using ExecutorId = search::ISequencedTaskExecutor::ExecutorId;
-int main(int argc, char *argv[]) {
- unsigned long numTasks = 1000000;
- unsigned numThreads = 4;
- unsigned taskLimit = 1000;
- vespalib::Executor::OptimizeFor optimize = vespalib::Executor::OptimizeFor::LATENCY;
- std::atomic<long> counter(0);
- if (argc > 1)
- numTasks = atol(argv[1]);
- if (argc > 2)
- numThreads = atoi(argv[2]);
- if (argc > 3)
- taskLimit = atoi(argv[3]);
- if (argc > 4)
- optimize = vespalib::Executor::OptimizeFor::THROUGHPUT;
+size_t do_work(size_t size) {
+ size_t ret = 0;
+ for (size_t i = 0; i < size; ++i) {
+ for (size_t j = 0; j < 128; ++j) {
+ ret = (ret + i) * j;
+ }
+ }
+ return ret;
+}
- auto executor = SequencedTaskExecutor::create(numThreads, taskLimit, optimize);
- for (unsigned long tid(0); tid < numTasks; tid++) {
- executor->executeTask(ExecutorId(tid%numThreads), vespalib::makeLambdaTask([&counter] { counter++; }));
+struct SimpleParams {
+ int argc;
+ char **argv;
+ int idx;
+ SimpleParams(int argc_in, char **argv_in) : argc(argc_in), argv(argv_in), idx(0) {}
+ int next(const char *name, int fallback) {
+ ++idx;
+ int value = 0;
+ if (argc > idx) {
+ value = atoi(argv[idx]);
+ } else {
+ value = fallback;
+ }
+ fprintf(stderr, "param %s: %d\n", name, value);
+ return value;
+ }
+};
+
+int main(int argc, char **argv) {
+ SimpleParams params(argc, argv);
+ bool use_adaptive_executor = params.next("use_adaptive_executor", 0);
+ bool optimize_for_throughput = params.next("optimize_for_throughput", 0);
+ size_t num_tasks = params.next("num_tasks", 1000000);
+ size_t num_strands = params.next("num_strands", 4);
+ size_t task_limit = params.next("task_limit", 1000);
+ size_t num_threads = params.next("num_threads", num_strands);
+ size_t max_waiting = params.next("max_waiting", optimize_for_throughput ? 32 : 0);
+ size_t work_size = params.next("work_size", 0);
+ std::atomic<long> counter(0);
+ std::unique_ptr<ISequencedTaskExecutor> executor;
+ if (use_adaptive_executor) {
+ executor = std::make_unique<AdaptiveSequencedExecutor>(num_strands, num_threads, max_waiting, task_limit);
+ } else {
+ auto optimize = optimize_for_throughput
+ ? vespalib::Executor::OptimizeFor::THROUGHPUT
+ : vespalib::Executor::OptimizeFor::LATENCY;
+ executor = SequencedTaskExecutor::create(num_strands, task_limit, optimize);
+ }
+ vespalib::Timer timer;
+ for (size_t task_id = 0; task_id < num_tasks; ++task_id) {
+ executor->executeTask(ExecutorId(task_id % num_strands),
+ vespalib::makeLambdaTask([&counter,work_size] { (void) do_work(work_size); counter++; }));
}
- return 0;
+ executor.reset();
+ fprintf(stderr, "\ntotal time: %zu ms\n", vespalib::count_ms(timer.elapsed()));
+ return (size_t(counter) == num_tasks) ? 0 : 1;
}
diff --git a/searchlib/src/tests/features/prod_features.cpp b/searchlib/src/tests/features/prod_features.cpp
index 64a999906d8..1c07c81bc2f 100644
--- a/searchlib/src/tests/features/prod_features.cpp
+++ b/searchlib/src/tests/features/prod_features.cpp
@@ -1238,10 +1238,11 @@ Test::testDotProduct()
assertDotProduct(0, "(f:5,g:5)", 1, "wsextstr");
assertDotProduct(550, "(a:1,b:2,c:3,d:4,e:5)", 1, "wsextstr");
}
- for (const char * name : {"wsbyte", "wsint"}) {
- assertDotProduct(0, "()", 1, name);
- assertDotProduct(0, "(6:5,7:5)", 1, name);
- assertDotProduct(55, "(1:1,2:2,3:3,4:4,5:5)", 1, name);
+ for (const char * name : {"wsbyte", "wsint", "wsint_fast"}) {
+ TEST_DO(assertDotProduct(0, "()", 1, name));
+ TEST_DO(assertDotProduct(0, "(6:5,7:5)", 1, name));
+ TEST_DO(assertDotProduct(18, "(4:4.5)", 1, name));
+ TEST_DO(assertDotProduct(57, "(1:1,2:2,3:3,4:4.5,5:5)", 1, name));
}
for (const char * name : {"arrbyte", "arrint", "arrfloat", "arrint_fast", "arrfloat_fast"}) {
assertDotProduct(0, "()", 1, name);
@@ -1300,6 +1301,7 @@ Test::setupForDotProductTest(FtFeatureTest & ft)
};
std::vector<Config> cfgList = { {"wsint", AVBT::INT32, AVCT::WSET, false},
{"wsbyte", AVBT::INT8, AVCT::WSET, false},
+ {"wsint_fast", AVBT::INT8, AVCT::WSET, true},
{"arrbyte", AVBT::INT8, AVCT::ARRAY, false},
{"arrint", AVBT::INT32, AVCT::ARRAY, false},
{"arrfloat", AVBT::FLOAT, AVCT::ARRAY, false},
diff --git a/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp b/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
index c562c0cf29c..7a0c240dea5 100644
--- a/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
+++ b/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
@@ -517,7 +517,7 @@ struct FieldIndexTest : public ::testing::Test {
};
using FieldIndexTestTypes = ::testing::Types<FieldIndex<false>, FieldIndex<true>>;
-TYPED_TEST_CASE(FieldIndexTest, FieldIndexTestTypes);
+VESPA_GTEST_TYPED_TEST_SUITE(FieldIndexTest, FieldIndexTestTypes);
// Disable warnings emitted by gtest generated files when using typed tests
#pragma GCC diagnostic push
diff --git a/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt b/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt
new file mode 100644
index 00000000000..90202e222a7
--- /dev/null
+++ b/searchlib/src/tests/tensor/hnsw_saver/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(searchlib_hnsw_save_load_test_app TEST
+ SOURCES
+ hnsw_save_load_test.cpp
+ DEPENDS
+ searchlib
+ gtest
+)
+vespa_add_test(NAME searchlib_hnsw_save_load_test_app COMMAND searchlib_hnsw_save_load_test_app)
diff --git a/searchlib/src/tests/tensor/hnsw_saver/hnsw_save_load_test.cpp b/searchlib/src/tests/tensor/hnsw_saver/hnsw_save_load_test.cpp
new file mode 100644
index 00000000000..b9e27d413f3
--- /dev/null
+++ b/searchlib/src/tests/tensor/hnsw_saver/hnsw_save_load_test.cpp
@@ -0,0 +1,150 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchlib/tensor/hnsw_graph.h>
+#include <vespa/searchlib/tensor/hnsw_index_saver.h>
+#include <vespa/searchlib/tensor/hnsw_index_loader.h>
+#include <vespa/vespalib/util/bufferwriter.h>
+#include <vespa/searchlib/util/fileutil.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vector>
+
+#include <vespa/log/log.h>
+LOG_SETUP("hnsw_save_load_test");
+
+using namespace search::tensor;
+using search::BufferWriter;
+using search::fileutil::LoadedBuffer;
+
+class VectorBufferWriter : public BufferWriter {
+private:
+ char tmp[1024];
+public:
+ std::vector<char> output;
+ VectorBufferWriter() {
+ setup(tmp, 1024);
+ }
+ ~VectorBufferWriter() {}
+ void flush() override {
+ for (size_t i = 0; i < usedLen(); ++i) {
+ output.push_back(tmp[i]);
+ }
+ rewind();
+ }
+};
+
+using V = std::vector<uint32_t>;
+
+void populate(HnswGraph &graph) {
+ // no 0
+ graph.make_node_for_document(1, 1);
+ graph.make_node_for_document(2, 2);
+ // no 3
+ graph.make_node_for_document(4, 2);
+ graph.make_node_for_document(5, 0);
+ graph.make_node_for_document(6, 1);
+
+ graph.set_link_array(1, 0, V{2, 4, 6});
+ graph.set_link_array(2, 0, V{1, 4, 6});
+ graph.set_link_array(4, 0, V{1, 2, 6});
+ graph.set_link_array(6, 0, V{1, 2, 4});
+ graph.set_link_array(2, 1, V{4});
+ graph.set_link_array(4, 1, V{2});
+ graph.set_entry_node(2, 1);
+}
+
+void modify(HnswGraph &graph) {
+ graph.remove_node_for_document(2);
+ graph.remove_node_for_document(6);
+ graph.make_node_for_document(7, 2);
+
+ graph.set_link_array(1, 0, V{7, 4});
+ graph.set_link_array(4, 0, V{7, 2});
+ graph.set_link_array(7, 0, V{4, 2});
+ graph.set_link_array(4, 1, V{7});
+ graph.set_link_array(7, 1, V{4});
+
+ graph.set_entry_node(4, 1);
+}
+
+
+class CopyGraphTest : public ::testing::Test {
+public:
+ HnswGraph original;
+ HnswGraph copy;
+
+ void expect_empty_d(uint32_t docid) const {
+ EXPECT_FALSE(copy.node_refs[docid].load_acquire().valid());
+ }
+
+ void expect_level_0(uint32_t docid, const V& exp_links) const {
+ auto levels = copy.get_level_array(docid);
+ EXPECT_GE(levels.size(), 1);
+ auto links = copy.get_link_array(docid, 0);
+ EXPECT_EQ(exp_links.size(), links.size());
+ for (size_t i = 0; i < exp_links.size() && i < links.size(); ++i) {
+ EXPECT_EQ(exp_links[i], links[i]);
+ }
+ }
+
+ void expect_level_1(uint32_t docid, const V& exp_links) const {
+ auto levels = copy.get_level_array(docid);
+ EXPECT_EQ(2, levels.size());
+ auto links = copy.get_link_array(docid, 1);
+ EXPECT_EQ(exp_links.size(), links.size());
+ for (size_t i = 0; i < exp_links.size() && i < links.size(); ++i) {
+ EXPECT_EQ(exp_links[i], links[i]);
+ }
+ }
+
+ std::vector<char> save_original() const {
+ HnswIndexSaver saver(original);
+ VectorBufferWriter vector_writer;
+ saver.save(vector_writer);
+ return vector_writer.output;
+ }
+ void load_copy(std::vector<char> data) {
+ HnswIndexLoader loader(copy);
+ LoadedBuffer buffer(&data[0], data.size());
+ loader.load(buffer);
+ }
+
+ void expect_copy_as_populated() const {
+ EXPECT_EQ(copy.size(), 7);
+ EXPECT_EQ(copy.entry_docid, 2);
+ EXPECT_EQ(copy.entry_level, 1);
+
+ expect_empty_d(0);
+ expect_empty_d(3);
+ expect_empty_d(5);
+
+ expect_level_0(1, {2, 4, 6});
+ expect_level_0(2, {1, 4, 6});
+ expect_level_0(4, {1, 2, 6});
+ expect_level_0(6, {1, 2, 4});
+
+ expect_level_1(2, {4});
+ expect_level_1(4, {2});
+ }
+};
+
+TEST_F(CopyGraphTest, reconstructs_graph)
+{
+ populate(original);
+ auto data = save_original();
+ load_copy(data);
+ expect_copy_as_populated();
+}
+
+TEST_F(CopyGraphTest, later_changes_ignored)
+{
+ populate(original);
+ HnswIndexSaver saver(original);
+ modify(original);
+ VectorBufferWriter vector_writer;
+ saver.save(vector_writer);
+ auto data = vector_writer.output;
+ load_copy(data);
+ expect_copy_as_populated();
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
index 224d5758028..3d7010ba6c3 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
@@ -22,7 +22,12 @@ const vespalib::string predicateUpperBoundTag = "predicate.upper_bound";
}
AttributeHeader::AttributeHeader()
- : _fileName(""),
+ : AttributeHeader("")
+{
+}
+
+AttributeHeader::AttributeHeader(const vespalib::string &fileName)
+ : _fileName(fileName),
_basicType(attribute::BasicType::Type::NONE),
_collectionType(attribute::CollectionType::Type::SINGLE),
_tensorType(vespalib::eval::ValueType::error_type()),
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.h b/searchlib/src/vespa/searchlib/attribute/attribute_header.h
index 303c469e755..24eac8336b4 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_header.h
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.h
@@ -35,6 +35,7 @@ private:
void internalExtractTags(const vespalib::GenericHeader &header);
public:
AttributeHeader();
+ AttributeHeader(const vespalib::string &fileName);
AttributeHeader(const vespalib::string &fileName,
BasicType basicType,
CollectionType collectionType,
diff --git a/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.cpp b/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.cpp
index f57094ae592..f284fecbf98 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.cpp
@@ -3,14 +3,16 @@
#include "attributefilesavetarget.h"
#include "attributevector.h"
#include <vespa/searchlib/common/fileheadercontext.h>
-#include <vespa/vespalib/data/fileheader.h>
#include <vespa/vespalib/data/databuffer.h>
+#include <vespa/vespalib/data/fileheader.h>
#include <vespa/vespalib/util/error.h>
+#include <vespa/vespalib/util/exceptions.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.attribute.attributefilesavetarget");
using vespalib::getLastErrorString;
+using vespalib::IllegalArgumentException;
namespace search {
@@ -18,13 +20,16 @@ using common::FileHeaderContext;
AttributeFileSaveTarget::
-AttributeFileSaveTarget(const TuneFileAttributes &tuneFileAttributes,
- const FileHeaderContext &fileHeaderContext)
+AttributeFileSaveTarget(const TuneFileAttributes& tune_file,
+ const FileHeaderContext& file_header_ctx)
: IAttributeSaveTarget(),
- _datWriter(tuneFileAttributes, fileHeaderContext, _header, "Attribute vector data file"),
- _idxWriter(tuneFileAttributes, fileHeaderContext, _header, "Attribute vector idx file"),
- _weightWriter(tuneFileAttributes, fileHeaderContext, _header, "Attribute vector weight file"),
- _udatWriter(tuneFileAttributes, fileHeaderContext, _header, "Attribute vector unique data file")
+ _tune_file(tune_file),
+ _file_header_ctx(file_header_ctx),
+ _datWriter(tune_file, file_header_ctx, _header, "Attribute vector data file"),
+ _idxWriter(tune_file, file_header_ctx, _header, "Attribute vector idx file"),
+ _weightWriter(tune_file, file_header_ctx, _header, "Attribute vector weight file"),
+ _udatWriter(tune_file, file_header_ctx, _header, "Attribute vector unique data file"),
+ _writers()
{
}
@@ -66,23 +71,23 @@ AttributeFileSaveTarget::close()
_udatWriter.close();
_idxWriter.close();
_weightWriter.close();
+ for (auto& writer : _writers) {
+ writer.second->close();
+ }
}
-
IAttributeFileWriter &
AttributeFileSaveTarget::datWriter()
{
return _datWriter;
}
-
IAttributeFileWriter &
AttributeFileSaveTarget::idxWriter()
{
return _idxWriter;
}
-
IAttributeFileWriter &
AttributeFileSaveTarget::weightWriter()
{
@@ -95,6 +100,33 @@ AttributeFileSaveTarget::udatWriter()
return _udatWriter;
}
+bool
+AttributeFileSaveTarget::setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc)
+{
+ vespalib::string file_name(_header.getFileName() + "." + file_suffix);
+ auto writer = std::make_unique<AttributeFileWriter>(_tune_file, _file_header_ctx,
+ _header, desc);
+ if (!writer->open(file_name)) {
+ return false;
+ }
+ auto itr = _writers.find(file_suffix);
+ if (itr != _writers.end()) {
+ return false;
+ }
+ _writers.insert(std::make_pair(file_suffix, std::move(writer)));
+ return true;
+}
+
+IAttributeFileWriter&
+AttributeFileSaveTarget::get_writer(const vespalib::string& file_suffix)
+{
+ auto itr = _writers.find(file_suffix);
+ if (itr == _writers.end()) {
+ throw IllegalArgumentException("File writer with suffix '" + file_suffix + "' does not exist");
+ }
+ return *itr->second;
+}
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.h b/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.h
index acb3daf82e0..9a9d38615ea 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributefilesavetarget.h
@@ -4,24 +4,30 @@
#include "iattributesavetarget.h"
#include "attributefilewriter.h"
+#include <vespa/vespalib/stllike/hash_fun.h>
+#include <unordered_map>
-namespace search
-{
+namespace search {
/**
* Class used to save an attribute vector to file(s).
**/
-class AttributeFileSaveTarget : public IAttributeSaveTarget
-{
+class AttributeFileSaveTarget : public IAttributeSaveTarget {
private:
+ using FileWriterUP = std::unique_ptr<AttributeFileWriter>;
+ using WriterMap = std::unordered_map<vespalib::string, FileWriterUP, vespalib::hash<vespalib::string>>;
+
+ const TuneFileAttributes& _tune_file;
+ const search::common::FileHeaderContext& _file_header_ctx;
AttributeFileWriter _datWriter;
AttributeFileWriter _idxWriter;
AttributeFileWriter _weightWriter;
AttributeFileWriter _udatWriter;
+ WriterMap _writers;
public:
- AttributeFileSaveTarget(const TuneFileAttributes &tuneFileAttributes,
- const search::common::FileHeaderContext &fileHeaderContext);
+ AttributeFileSaveTarget(const TuneFileAttributes& tune_file,
+ const search::common::FileHeaderContext& file_header_ctx);
~AttributeFileSaveTarget() override;
// Implements IAttributeSaveTarget
@@ -35,6 +41,11 @@ public:
IAttributeFileWriter &idxWriter() override;
IAttributeFileWriter &weightWriter() override;
IAttributeFileWriter &udatWriter() override;
+
+ bool setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc) override;
+ IAttributeFileWriter& get_writer(const vespalib::string& file_suffix) override;
+
};
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.cpp b/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.cpp
index 372168143ab..b28887691e5 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.cpp
@@ -1,24 +1,25 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "attributememorysavetarget.h"
#include "attributefilesavetarget.h"
+#include "attributememorysavetarget.h"
#include "attributevector.h"
+#include <vespa/vespalib/util/exceptions.h>
namespace search {
using search::common::FileHeaderContext;
+using vespalib::IllegalArgumentException;
AttributeMemorySaveTarget::AttributeMemorySaveTarget()
: _datWriter(),
_idxWriter(),
_weightWriter(),
- _udatWriter()
+ _udatWriter(),
+ _writers()
{
}
-AttributeMemorySaveTarget::~AttributeMemorySaveTarget() {
-}
-
+AttributeMemorySaveTarget::~AttributeMemorySaveTarget() = default;
IAttributeFileWriter &
AttributeMemorySaveTarget::datWriter()
@@ -26,28 +27,24 @@ AttributeMemorySaveTarget::datWriter()
return _datWriter;
}
-
IAttributeFileWriter &
AttributeMemorySaveTarget::idxWriter()
{
return _idxWriter;
}
-
IAttributeFileWriter &
AttributeMemorySaveTarget::weightWriter()
{
return _weightWriter;
}
-
IAttributeFileWriter &
AttributeMemorySaveTarget::udatWriter()
{
return _udatWriter;
}
-
bool
AttributeMemorySaveTarget::
writeToFile(const TuneFileAttributes &tuneFileAttributes,
@@ -68,9 +65,39 @@ writeToFile(const TuneFileAttributes &tuneFileAttributes,
_weightWriter.writeTo(saveTarget.weightWriter());
}
}
+ for (const auto& entry : _writers) {
+ if (!saveTarget.setup_writer(entry.first, entry.second.desc)) {
+ return false;
+ }
+ auto& file_writer = saveTarget.get_writer(entry.first);
+ entry.second.writer->writeTo(file_writer);
+ }
saveTarget.close();
return true;
}
+bool
+AttributeMemorySaveTarget::setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc)
+{
+ auto writer = std::make_unique<AttributeMemoryFileWriter>();
+ auto itr = _writers.find(file_suffix);
+ if (itr != _writers.end()) {
+ return false;
+ }
+ _writers.insert(std::make_pair(file_suffix, WriterEntry(std::move(writer), desc)));
+ return true;
+}
+
+IAttributeFileWriter&
+AttributeMemorySaveTarget::get_writer(const vespalib::string& file_suffix)
+{
+ auto itr = _writers.find(file_suffix);
+ if (itr == _writers.end()) {
+ throw IllegalArgumentException("File writer with suffix '" + file_suffix + "' does not exist");
+ }
+ return *itr->second.writer;
+}
+
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.h b/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.h
index f06764fa34b..9533b881099 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributememorysavetarget.h
@@ -2,11 +2,13 @@
#pragma once
-#include "iattributesavetarget.h"
#include "attributememoryfilewriter.h"
+#include "iattributesavetarget.h"
+#include <vespa/searchlib/common/tunefileinfo.h>
#include <vespa/searchlib/util/rawbuf.h>
+#include <vespa/vespalib/stllike/hash_fun.h>
#include <memory>
-#include <vespa/searchlib/common/tunefileinfo.h>
+#include <unordered_map>
namespace search::common { class FileHeaderContext; }
@@ -16,13 +18,22 @@ class AttributeVector;
/**
* Class used to save an attribute vector to memory buffer(s).
**/
-class AttributeMemorySaveTarget : public IAttributeSaveTarget
-{
+class AttributeMemorySaveTarget : public IAttributeSaveTarget {
private:
+ using FileWriterUP = std::unique_ptr<AttributeMemoryFileWriter>;
+ struct WriterEntry {
+ FileWriterUP writer;
+ vespalib::string desc;
+ WriterEntry(FileWriterUP writer_in, const vespalib::string& desc_in)
+ : writer(std::move(writer_in)), desc(desc_in) {}
+ };
+ using WriterMap = std::unordered_map<vespalib::string, WriterEntry, vespalib::hash<vespalib::string>>;
+
AttributeMemoryFileWriter _datWriter;
AttributeMemoryFileWriter _idxWriter;
AttributeMemoryFileWriter _weightWriter;
AttributeMemoryFileWriter _udatWriter;
+ WriterMap _writers;
public:
AttributeMemorySaveTarget();
@@ -40,6 +51,11 @@ public:
IAttributeFileWriter &idxWriter() override;
IAttributeFileWriter &weightWriter() override;
IAttributeFileWriter &udatWriter() override;
+
+ bool setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc) override;
+ IAttributeFileWriter& get_writer(const vespalib::string& file_suffix) override;
+
};
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
index b043bb4aaf8..ffc62d806e2 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
@@ -266,79 +266,6 @@ const IEnumStore* AttributeVector::getEnumStoreBase() const { return nullptr; }
IEnumStore* AttributeVector::getEnumStoreBase() { return nullptr; }
const attribute::MultiValueMappingBase * AttributeVector::getMultiValueBase() const { return nullptr; }
-std::unique_ptr<FastOS_FileInterface>
-AttributeVector::openFile(const char *suffix)
-{
- BaseName::string fileName(getBaseFileName());
- fileName += suffix;
- return FileUtil::openFile(fileName);
-}
-
-
-std::unique_ptr<FastOS_FileInterface>
-AttributeVector::openDAT()
-{
- return openFile(".dat");
-}
-
-
-std::unique_ptr<FastOS_FileInterface>
-AttributeVector::openIDX()
-{
- return openFile(".idx");
-}
-
-
-std::unique_ptr<FastOS_FileInterface>
-AttributeVector::openWeight()
-{
- return openFile(".weight");
-}
-
-
-std::unique_ptr<FastOS_FileInterface>
-AttributeVector::openUDAT()
-{
- return openFile(".dat");
-}
-
-fileutil::LoadedBuffer::UP
-AttributeVector::loadDAT()
-{
- return loadFile(".dat");
-}
-
-
-fileutil::LoadedBuffer::UP
-AttributeVector::loadIDX()
-{
- return loadFile(".idx");
-}
-
-
-fileutil::LoadedBuffer::UP
-AttributeVector::loadWeight()
-{
- return loadFile(".weight");
-}
-
-
-fileutil::LoadedBuffer::UP
-AttributeVector::loadUDAT()
-{
- return loadFile(".udat");
-}
-
-
-fileutil::LoadedBuffer::UP
-AttributeVector::loadFile(const char *suffix)
-{
- BaseName::string fileName(getBaseFileName());
- fileName += suffix;
- return FileUtil::loadFile(fileName);
-}
-
-
bool
AttributeVector::save(vespalib::stringref fileName)
{
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.h b/searchlib/src/vespa/searchlib/attribute/attributevector.h
index a396fb70b7c..4a53f2dd5a2 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.h
@@ -212,11 +212,6 @@ protected:
void setNumDocs(uint32_t n) { _status.setNumDocs(n); }
void incNumDocs() { _status.incNumDocs(); }
- LoadedBufferUP loadDAT();
- LoadedBufferUP loadIDX();
- LoadedBufferUP loadWeight();
- LoadedBufferUP loadUDAT();
-
class ValueModifier
{
public:
@@ -269,10 +264,6 @@ protected:
}
public:
- std::unique_ptr<FastOS_FileInterface> openDAT();
- std::unique_ptr<FastOS_FileInterface> openIDX();
- std::unique_ptr<FastOS_FileInterface> openWeight();
- std::unique_ptr<FastOS_FileInterface> openUDAT();
void incGeneration();
void removeAllOldGenerations();
@@ -572,8 +563,6 @@ private:
virtual bool applyWeight(DocId doc, const FieldValue &fv, const ArithmeticValueUpdate &wAdjust);
virtual void onSave(IAttributeSaveTarget & saveTarget);
virtual bool onLoad();
- std::unique_ptr<FastOS_FileInterface> openFile(const char *suffix);
- LoadedBufferUP loadFile(const char *suffix);
BaseName _baseFileName;
diff --git a/searchlib/src/vespa/searchlib/attribute/attrvector.cpp b/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
index 0304aa8f38e..59771d7ffae 100644
--- a/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attrvector.cpp
@@ -3,6 +3,7 @@
#include "attrvector.h"
#include "attrvector.hpp"
#include "iattributesavetarget.h"
+#include "load_utils.h"
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.attribute.attr_vector");
@@ -123,7 +124,7 @@ bool StringDirectAttribute::onLoad()
setCommittedDocIdLimit(0);
}
- fileutil::LoadedBuffer::UP tmpBuffer(loadDAT());
+ auto tmpBuffer = attribute::LoadUtils::loadDAT(*this);
bool rc(tmpBuffer.get());
if (rc) {
if ( ! tmpBuffer->empty()) {
@@ -158,7 +159,7 @@ bool StringDirectAttribute::onLoad()
}
if (hasMultiValue()) {
- fileutil::LoadedBuffer::UP tmpIdx(loadIDX());
+ auto tmpIdx = attribute::LoadUtils::loadIDX(*this);
size_t tmpIdxLen(tmpIdx->size(sizeof(uint32_t)));
_idx.clear();
_idx.reserve(tmpIdxLen);
diff --git a/searchlib/src/vespa/searchlib/attribute/attrvector.hpp b/searchlib/src/vespa/searchlib/attribute/attrvector.hpp
index cdd34725e69..4ce7575b28d 100644
--- a/searchlib/src/vespa/searchlib/attribute/attrvector.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/attrvector.hpp
@@ -2,6 +2,7 @@
#pragma once
#include "attrvector.h"
+#include "load_utils.h"
#include <vespa/vespalib/util/hdr_abort.h>
#include <vespa/fastlib/io/bufferedfile.h>
#include <vespa/searchlib/util/filekit.h>
@@ -23,7 +24,7 @@ NumericDirectAttribute<B>::~NumericDirectAttribute() = default;
template <typename B>
bool NumericDirectAttribute<B>::onLoad()
{
- fileutil::LoadedBuffer::UP dataBuffer(B::loadDAT());
+ auto dataBuffer = attribute::LoadUtils::loadDAT(*this);
bool rc(dataBuffer.get());
if (rc) {
const BaseType * tmpData(static_cast <const BaseType *>(dataBuffer->buffer()));
@@ -56,7 +57,7 @@ bool NumericDirectAttribute<B>::onLoad()
}
dataBuffer.reset();
if (this->hasMultiValue()) {
- fileutil::LoadedBuffer::UP idxBuffer(B::loadIDX());
+ auto idxBuffer = attribute::LoadUtils::loadIDX(*this);
rc = idxBuffer.get();
if (rc) {
const uint32_t * tmpIdx(static_cast<const uint32_t *>(idxBuffer->buffer()));
diff --git a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
index 1e4bba95b4b..895e6a6f4c0 100644
--- a/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/flagattribute.cpp
@@ -89,7 +89,7 @@ FlagAttributeT<B>::onLoadEnumerated(ReaderBase &attrReader)
if (numValues > 0)
_bitVectorSize = numDocs;
- fileutil::LoadedBuffer::UP udatBuffer(this->loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
assert((udatBuffer->size() % sizeof(TT)) == 0);
vespalib::ConstArrayRef<TT> map(reinterpret_cast<const TT *>(udatBuffer->buffer()),
udatBuffer->size() / sizeof(TT));
diff --git a/searchlib/src/vespa/searchlib/attribute/iattributesavetarget.h b/searchlib/src/vespa/searchlib/attribute/iattributesavetarget.h
index 9f90544bb83..8946fc2fcdb 100644
--- a/searchlib/src/vespa/searchlib/attribute/iattributesavetarget.h
+++ b/searchlib/src/vespa/searchlib/attribute/iattributesavetarget.h
@@ -37,6 +37,19 @@ public:
virtual IAttributeFileWriter &weightWriter() = 0;
virtual IAttributeFileWriter &udatWriter() = 0;
+ /**
+ * Setups a custom file writer with the given file suffix and description in the file header.
+ * Returns false if the file writer cannot be setup or if it already exists, true otherwise.
+ */
+ virtual bool setup_writer(const vespalib::string& file_suffix,
+ const vespalib::string& desc) = 0;
+
+ /**
+ * Returns the file writer with the given file suffix.
+ * Throws vespalib::IllegalArgumentException if the file writer does not exists.
+ */
+ virtual IAttributeFileWriter& get_writer(const vespalib::string& file_suffix) = 0;
+
virtual ~IAttributeSaveTarget();
};
diff --git a/searchlib/src/vespa/searchlib/attribute/load_utils.cpp b/searchlib/src/vespa/searchlib/attribute/load_utils.cpp
index 041daa08cd5..701c8eaf702 100644
--- a/searchlib/src/vespa/searchlib/attribute/load_utils.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/load_utils.cpp
@@ -5,13 +5,81 @@
#include "loadedenumvalue.h"
#include "multi_value_mapping.h"
#include "multivalue.h"
+#include <vespa/fastos/file.h>
+#include <vespa/searchlib/util/fileutil.h>
+#include <vespa/vespalib/io/fileutil.h>
#include <vespa/vespalib/util/array.hpp>
using search::multivalue::Value;
using search::multivalue::WeightedValue;
-namespace search {
-namespace attribute {
+namespace search::attribute {
+
+using FileInterfaceUP = LoadUtils::FileInterfaceUP;
+using LoadedBufferUP = LoadUtils::LoadedBufferUP;
+
+FileInterfaceUP
+LoadUtils::openFile(const AttributeVector& attr, const vespalib::string& suffix)
+{
+ return FileUtil::openFile(attr.getBaseFileName() + "." + suffix);
+}
+
+
+
+FileInterfaceUP
+LoadUtils::openDAT(const AttributeVector& attr)
+{
+ return openFile(attr, "dat");
+}
+
+FileInterfaceUP
+LoadUtils::openIDX(const AttributeVector& attr)
+{
+ return openFile(attr, "idx");
+}
+
+FileInterfaceUP
+LoadUtils::openWeight(const AttributeVector& attr)
+{
+ return openFile(attr, "weight");
+}
+
+bool
+LoadUtils::file_exists(const AttributeVector& attr, const vespalib::string& suffix)
+{
+ return vespalib::fileExists(attr.getBaseFileName() + "." + suffix);
+}
+
+LoadedBufferUP
+LoadUtils::loadFile(const AttributeVector& attr, const vespalib::string& suffix)
+{
+ return FileUtil::loadFile(attr.getBaseFileName() + "." + suffix);
+}
+
+LoadedBufferUP
+LoadUtils::loadDAT(const AttributeVector& attr)
+{
+ return loadFile(attr, "dat");
+}
+
+LoadedBufferUP
+LoadUtils::loadIDX(const AttributeVector& attr)
+{
+ return loadFile(attr, "idx");
+}
+
+LoadedBufferUP
+LoadUtils::loadWeight(const AttributeVector& attr)
+{
+ return loadFile(attr, "weight");
+}
+
+LoadedBufferUP
+LoadUtils::loadUDAT(const AttributeVector& attr)
+{
+ return loadFile(attr, "udat");
+}
+
#define INSTANTIATE_ARRAY(ValueType, Saver) \
template uint32_t loadFromEnumeratedMultiValue(MultiValueMapping<Value<ValueType>> &, ReaderBase &, vespalib::ConstArrayRef<ValueType>, Saver)
@@ -40,5 +108,4 @@ INSTANTIATE_VALUE(int64_t);
INSTANTIATE_VALUE(float);
INSTANTIATE_VALUE(double);
-} // namespace search::attribute
-} // namespace search
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/load_utils.h b/searchlib/src/vespa/searchlib/attribute/load_utils.h
index 050d7726ecd..cd9d98084d5 100644
--- a/searchlib/src/vespa/searchlib/attribute/load_utils.h
+++ b/searchlib/src/vespa/searchlib/attribute/load_utils.h
@@ -6,10 +6,34 @@
#include "readerbase.h"
#include <vespa/vespalib/util/arrayref.h>
-namespace search {
-namespace attribute {
+namespace search::attribute {
-/*
+/**
+ * Helper functions used to open / load attribute vector data files from disk.
+ */
+class LoadUtils {
+public:
+ using FileInterfaceUP = std::unique_ptr<FastOS_FileInterface>;
+ using LoadedBufferUP = std::unique_ptr<fileutil::LoadedBuffer>;
+
+private:
+ static FileInterfaceUP openFile(const AttributeVector& attr, const vespalib::string& suffix);
+
+public:
+ static FileInterfaceUP openDAT(const AttributeVector& attr);
+ static FileInterfaceUP openIDX(const AttributeVector& attr);
+ static FileInterfaceUP openWeight(const AttributeVector& attr);
+
+ static bool file_exists(const AttributeVector& attr, const vespalib::string& suffix);
+ static LoadedBufferUP loadFile(const AttributeVector& attr, const vespalib::string& suffix);
+
+ static LoadedBufferUP loadDAT(const AttributeVector& attr);
+ static LoadedBufferUP loadIDX(const AttributeVector& attr);
+ static LoadedBufferUP loadWeight(const AttributeVector& attr);
+ static LoadedBufferUP loadUDAT(const AttributeVector& attr);
+};
+
+/**
* Function for loading mapping from document id to array of enum indexes
* or values from enumerated attribute reader.
*/
@@ -20,7 +44,7 @@ loadFromEnumeratedMultiValue(MvMapping &mapping,
vespalib::ConstArrayRef<typename MvMapping::MultiValueType::ValueType> enumValueToValueMap,
Saver saver) __attribute((noinline));
-/*
+/**
* Function for loading mapping from document id to enum index or
* value from enumerated attribute reader.
*/
@@ -32,5 +56,4 @@ loadFromEnumeratedSingleValue(Vector &vector,
vespalib::ConstArrayRef<typename Vector::ValueType> enumValueToValueMap,
Saver saver) __attribute((noinline));
-} // namespace search::attribute
-} // namespace search
+}
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
index 1efa2789fcb..3ca7423c38c 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericattribute.hpp
@@ -117,7 +117,7 @@ MultiValueNumericAttribute<B, M>::onLoadEnumerated(ReaderBase & attrReader)
this->setCommittedDocIdLimit(numDocs);
this->_mvMapping.reserve(numDocs+1);
- LoadedBuffer::UP udatBuffer(this->loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
assert((udatBuffer->size() % sizeof(T)) == 0);
vespalib::ConstArrayRef<T> map(reinterpret_cast<const T *>(udatBuffer->buffer()), udatBuffer->size() / sizeof(T));
uint32_t maxvc = attribute::loadFromEnumeratedMultiValue(this->_mvMapping, attrReader, map, attribute::NoSaveLoadedEnum());
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
index 9ee365fc7cc..e17d41a5521 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
@@ -2,13 +2,14 @@
#pragma once
-#include "multinumericenumattribute.h"
-#include "loadednumericvalue.h"
#include "attributeiterators.hpp"
-#include <vespa/searchlib/util/fileutil.hpp>
+#include "load_utils.h"
+#include "loadednumericvalue.h"
+#include "multinumericenumattribute.h"
#include <vespa/fastlib/io/bufferedfile.h>
#include <vespa/searchlib/query/query_term_simple.h>
#include <vespa/searchlib/queryeval/emptysearch.h>
+#include <vespa/searchlib/util/fileutil.hpp>
namespace search {
@@ -52,7 +53,7 @@ template <typename B, typename M>
bool
MultiValueNumericEnumAttribute<B, M>::onLoadEnumerated(ReaderBase &attrReader)
{
- LoadedBuffer::UP udatBuffer(this->loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
uint32_t numDocs = attrReader.getNumIdx() - 1;
uint64_t numValues = attrReader.getNumValues();
diff --git a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
index b0e4df65c2b..72cc6e38fac 100644
--- a/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/predicate_attribute.cpp
@@ -1,12 +1,13 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "predicate_attribute.h"
-#include "iattributesavetarget.h"
#include "attribute_header.h"
-#include <vespa/searchlib/predicate/predicate_index.h>
-#include <vespa/searchlib/util/fileutil.h>
+#include "iattributesavetarget.h"
+#include "load_utils.h"
+#include "predicate_attribute.h"
#include <vespa/document/fieldvalue/predicatefieldvalue.h>
#include <vespa/document/predicate/predicate.h>
+#include <vespa/searchlib/predicate/predicate_index.h>
+#include <vespa/searchlib/util/fileutil.h>
#include <vespa/vespalib/data/slime/slime.h>
#include <vespa/log/log.h>
@@ -183,7 +184,7 @@ struct DummyObserver : SimpleIndexDeserializeObserver<> {
bool PredicateAttribute::onLoad()
{
- fileutil::LoadedBuffer::UP loaded_buffer = loadDAT();
+ auto loaded_buffer = attribute::LoadUtils::loadDAT(*this);
char *rawBuffer = const_cast<char *>(static_cast<const char *>(loaded_buffer->buffer()));
size_t size = loaded_buffer->size();
DataBuffer buffer(rawBuffer, size);
diff --git a/searchlib/src/vespa/searchlib/attribute/readerbase.cpp b/searchlib/src/vespa/searchlib/attribute/readerbase.cpp
index 62936ecaaf4..a396fe9efd8 100644
--- a/searchlib/src/vespa/searchlib/attribute/readerbase.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/readerbase.cpp
@@ -1,10 +1,11 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "readerbase.h"
#include "attributevector.h"
+#include "load_utils.h"
+#include "readerbase.h"
#include <vespa/fastlib/io/bufferedfile.h>
-#include <vespa/vespalib/util/exceptions.h>
#include <vespa/searchlib/util/filesizecalculator.h>
+#include <vespa/vespalib/util/exceptions.h>
#include <vespa/log/log.h>
LOG_SETUP(".search.attribute.readerbase");
@@ -12,28 +13,27 @@ LOG_SETUP(".search.attribute.readerbase");
namespace search {
namespace {
- const vespalib::string versionTag = "version";
- const vespalib::string docIdLimitTag = "docIdLimit";
- const vespalib::string createSerialNumTag = "createSerialNum";
- constexpr size_t DIRECTIO_ALIGNMENT(4096);
+const vespalib::string versionTag = "version";
+const vespalib::string docIdLimitTag = "docIdLimit";
+const vespalib::string createSerialNumTag = "createSerialNum";
- uint64_t
- extractCreateSerialNum(const vespalib::GenericHeader &header)
- {
- return (header.hasTag(createSerialNumTag)) ? header.getTag(createSerialNumTag).asInteger() : 0u;
- }
+constexpr size_t DIRECTIO_ALIGNMENT(4096);
+uint64_t
+extractCreateSerialNum(const vespalib::GenericHeader &header)
+{
+ return (header.hasTag(createSerialNumTag)) ? header.getTag(createSerialNumTag).asInteger() : 0u;
+}
}
ReaderBase::ReaderBase(AttributeVector &attr)
- : _datFile(attr.openDAT()),
+ : _datFile(attribute::LoadUtils::openDAT(attr)),
_weightFile(attr.hasWeightedSetType() ?
- attr.openWeight() : std::unique_ptr<Fast_BufferedFile>()),
+ attribute::LoadUtils::openWeight(attr) : std::unique_ptr<Fast_BufferedFile>()),
_idxFile(attr.hasMultiValue() ?
- attr.openIDX() : std::unique_ptr<Fast_BufferedFile>()),
- _udatFile(),
+ attribute::LoadUtils::openIDX(attr) : std::unique_ptr<Fast_BufferedFile>()),
_weightReader(*_weightFile),
_idxReader(*_idxFile),
_enumReader(*_datFile),
@@ -41,7 +41,6 @@ ReaderBase::ReaderBase(AttributeVector &attr)
_datHeaderLen(0u),
_idxHeaderLen(0u),
_weightHeaderLen(0u),
- _udatHeaderLen(0u),
_createSerialNum(0u),
_fixedWidth(attr.getFixedWidth()),
_enumerated(false),
@@ -83,20 +82,12 @@ ReaderBase::ReaderBase(AttributeVector &attr)
}
if (hasData() && AttributeVector::isEnumerated(_datHeader)) {
_enumerated = true;
- _udatFile = attr.openUDAT();
- vespalib::FileHeader udatHeader(DIRECTIO_ALIGNMENT);
- _udatHeaderLen = udatHeader.readFile(*_udatFile);
- _udatFile->SetPosition(_udatHeaderLen);
- if (!attr.headerTypeOK(udatHeader))
- _udatFile->Close();
}
_hasLoadData = hasData() &&
(!attr.hasMultiValue() || hasIdx()) &&
- (!attr.hasWeightedSetType() || hasWeight()) &&
- (!getEnumerated() || hasUData());
+ (!attr.hasWeightedSetType() || hasWeight());
}
-
ReaderBase::~ReaderBase() = default;
bool
@@ -115,11 +106,6 @@ ReaderBase::hasData() const {
}
bool
-ReaderBase::hasUData() const {
- return _udatFile.get() && _udatFile->IsOpened();
-}
-
-bool
ReaderBase::
extractFileSize(const vespalib::GenericHeader &header,
FastOS_FileInterface &file, uint64_t &fileSize)
@@ -129,7 +115,6 @@ extractFileSize(const vespalib::GenericHeader &header,
file.GetFileName(), fileSize);
}
-
void
ReaderBase::rewind()
{
@@ -142,12 +127,8 @@ ReaderBase::rewind()
if (hasWeight()) {
_weightFile->SetPosition(_weightHeaderLen);
}
- if (getEnumerated()) {
- _udatFile->SetPosition(_udatHeaderLen);
- }
}
-
size_t
ReaderBase::getNumValues()
{
@@ -169,7 +150,6 @@ ReaderBase::getNumValues()
}
}
-
uint32_t
ReaderBase::getNextValueCount()
{
diff --git a/searchlib/src/vespa/searchlib/attribute/readerbase.h b/searchlib/src/vespa/searchlib/attribute/readerbase.h
index 09db52f5e25..a7685e4532a 100644
--- a/searchlib/src/vespa/searchlib/attribute/readerbase.h
+++ b/searchlib/src/vespa/searchlib/attribute/readerbase.h
@@ -19,7 +19,6 @@ public:
bool hasWeight() const;
bool hasIdx() const;
bool hasData() const;
- bool hasUData() const;
uint32_t getNumIdx() const {
return (_idxFileSize - _idxHeaderLen) /sizeof(uint32_t);
@@ -51,7 +50,6 @@ protected:
private:
std::unique_ptr<FastOS_FileInterface> _weightFile;
std::unique_ptr<FastOS_FileInterface> _idxFile;
- std::unique_ptr<FastOS_FileInterface> _udatFile;
FileReader<int32_t> _weightReader;
FileReader<uint32_t> _idxReader;
FileReader<uint32_t> _enumReader;
@@ -59,7 +57,6 @@ private:
uint32_t _datHeaderLen;
uint32_t _idxHeaderLen;
uint32_t _weightHeaderLen;
- uint32_t _udatHeaderLen;
uint64_t _createSerialNum;
size_t _fixedWidth;
bool _enumerated;
diff --git a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
index b055af7c084..9421730f335 100644
--- a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
@@ -1,6 +1,7 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "attributesaver.h"
+#include "load_utils.h"
#include "readerbase.h"
#include "reference_attribute.h"
#include "reference_attribute_saver.h"
@@ -223,7 +224,7 @@ ReferenceAttribute::onLoad()
uint64_t numValues(0);
numValues = attrReader.getEnumCount();
numDocs = numValues;
- fileutil::LoadedBuffer::UP udatBuffer(loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
const GenericHeader &header = udatBuffer->getHeader();
uint32_t uniqueValueCount = extractUniqueValueCount(header);
assert(uniqueValueCount * sizeof(GlobalId) == udatBuffer->size());
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
index 69d4e6a5ee9..681c2af1f07 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
@@ -1,12 +1,12 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#pragma once
-#include "singlenumericattribute.h"
+#include "attributeiterators.hpp"
#include "attributevector.hpp"
-#include "singlenumericattributesaver.h"
#include "load_utils.h"
#include "primitivereader.h"
-#include "attributeiterators.hpp"
+#include "singlenumericattribute.h"
+#include "singlenumericattributesaver.h"
#include <vespa/searchlib/query/query_term_simple.h>
#include <vespa/searchlib/queryeval/emptysearch.h>
@@ -114,7 +114,7 @@ SingleValueNumericAttribute<B>::onLoadEnumerated(ReaderBase &attrReader)
this->setCommittedDocIdLimit(numDocs);
_data.unsafe_reserve(numDocs);
- fileutil::LoadedBuffer::UP udatBuffer(this->loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
assert((udatBuffer->size() % sizeof(T)) == 0);
vespalib::ConstArrayRef<T> map(reinterpret_cast<const T *>(udatBuffer->buffer()),
udatBuffer->size() / sizeof(T));
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
index 990388d2a12..5fb587c908e 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
@@ -2,14 +2,15 @@
#pragma once
-#include "singlenumericenumattribute.h"
-#include <vespa/searchlib/common/sort.h>
-#include "singleenumattribute.hpp"
+#include "attributeiterators.hpp"
+#include "load_utils.h"
#include "loadednumericvalue.h"
#include "primitivereader.h"
-#include "attributeiterators.hpp"
-#include <vespa/searchlib/queryeval/emptysearch.h>
+#include "singleenumattribute.hpp"
+#include "singlenumericenumattribute.h"
+#include <vespa/searchlib/common/sort.h>
#include <vespa/searchlib/query/query_term_simple.h>
+#include <vespa/searchlib/queryeval/emptysearch.h>
#include <vespa/searchlib/util/fileutil.hpp>
namespace search {
@@ -79,7 +80,7 @@ template <typename B>
bool
SingleValueNumericEnumAttribute<B>::onLoadEnumerated(ReaderBase &attrReader)
{
- fileutil::LoadedBuffer::UP udatBuffer(this->loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
uint64_t numValues = attrReader.getEnumCount();
uint32_t numDocs = numValues;
diff --git a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
index 32b5b3ca373..40e706e924d 100644
--- a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
@@ -1,11 +1,12 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "stringbase.h"
#include "attributevector.hpp"
+#include "load_utils.h"
#include "readerbase.h"
+#include "stringbase.h"
#include <vespa/document/fieldvalue/fieldvalue.h>
-#include <vespa/searchlib/util/fileutil.hpp>
#include <vespa/searchlib/query/query_term_ucs4.h>
+#include <vespa/searchlib/util/fileutil.hpp>
#include <vespa/vespalib/locale/c.h>
#include <vespa/vespalib/util/array.hpp>
@@ -316,7 +317,7 @@ bool StringAttribute::apply(DocId, const ArithmeticValueUpdate & )
bool
StringAttribute::onLoadEnumerated(ReaderBase &attrReader)
{
- fileutil::LoadedBuffer::UP udatBuffer(loadUDAT());
+ auto udatBuffer = attribute::LoadUtils::loadUDAT(*this);
bool hasIdx(attrReader.hasIdx());
size_t numDocs(0);
diff --git a/searchlib/src/vespa/searchlib/common/CMakeLists.txt b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
index 2ee722902c8..f4a9e27b79d 100644
--- a/searchlib/src/vespa/searchlib/common/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
@@ -1,6 +1,7 @@
# Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_library(searchlib_common OBJECT
SOURCES
+ adaptive_sequenced_executor.cpp
allocatedbitvector.cpp
bitvector.cpp
bitvectorcache.cpp
diff --git a/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.cpp b/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.cpp
new file mode 100644
index 00000000000..f31172b1eba
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.cpp
@@ -0,0 +1,324 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "adaptive_sequenced_executor.h"
+
+namespace search {
+
+//-----------------------------------------------------------------------------
+
+AdaptiveSequencedExecutor::Strand::Strand()
+ : state(State::IDLE),
+ queue()
+{
+}
+
+AdaptiveSequencedExecutor::Strand::~Strand()
+{
+ assert(queue.empty());
+}
+
+//-----------------------------------------------------------------------------
+
+AdaptiveSequencedExecutor::Worker::Worker()
+ : cond(),
+ state(State::RUNNING),
+ strand(nullptr)
+{
+}
+
+AdaptiveSequencedExecutor::Worker::~Worker()
+{
+ assert(state == State::DONE);
+ assert(strand == nullptr);
+}
+
+//-----------------------------------------------------------------------------
+
+AdaptiveSequencedExecutor::Self::Self()
+ : cond(),
+ state(State::OPEN),
+ waiting_tasks(0),
+ pending_tasks(0)
+{
+}
+
+AdaptiveSequencedExecutor::Self::~Self()
+{
+ assert(state == State::CLOSED);
+ assert(waiting_tasks == 0);
+ assert(pending_tasks == 0);
+}
+
+//-----------------------------------------------------------------------------
+
+AdaptiveSequencedExecutor::ThreadTools::ThreadTools(AdaptiveSequencedExecutor &parent_in)
+ : parent(parent_in),
+ pool(std::make_unique<FastOS_ThreadPool>(STACK_SIZE)),
+ allow_worker_exit()
+{
+}
+
+AdaptiveSequencedExecutor::ThreadTools::~ThreadTools()
+{
+ assert(pool->isClosed());
+}
+
+void
+AdaptiveSequencedExecutor::ThreadTools::Run(FastOS_ThreadInterface *, void *)
+{
+ parent.worker_main();
+}
+
+void
+AdaptiveSequencedExecutor::ThreadTools::start(size_t num_threads)
+{
+ for (size_t i = 0; i < num_threads; ++i) {
+ FastOS_ThreadInterface *thread = pool->NewThread(this);
+ assert(thread != nullptr);
+ (void)thread;
+ }
+}
+
+void
+AdaptiveSequencedExecutor::ThreadTools::close()
+{
+ allow_worker_exit.countDown();
+ pool->Close();
+}
+
+//-----------------------------------------------------------------------------
+
+void
+AdaptiveSequencedExecutor::maybe_block_self(std::unique_lock<std::mutex> &lock)
+{
+ while (_self.state == Self::State::BLOCKED) {
+ _self.cond.wait(lock);
+ }
+ while ((_self.state == Self::State::OPEN) && (_self.pending_tasks >= _cfg.max_pending)) {
+ _self.state = Self::State::BLOCKED;
+ while (_self.state == Self::State::BLOCKED) {
+ _self.cond.wait(lock);
+ }
+ }
+}
+
+bool
+AdaptiveSequencedExecutor::maybe_unblock_self(const std::unique_lock<std::mutex> &)
+{
+ if ((_self.state == Self::State::BLOCKED) && (_self.pending_tasks < _cfg.wakeup_limit)) {
+ _self.state = Self::State::OPEN;
+ return true;
+ }
+ return false;
+}
+
+AdaptiveSequencedExecutor::Worker *
+AdaptiveSequencedExecutor::get_worker_to_wake(const std::unique_lock<std::mutex> &)
+{
+ if ((_self.waiting_tasks > _cfg.max_waiting) && (!_worker_stack.empty())) {
+ assert(!_wait_queue.empty());
+ Worker *worker = _worker_stack.back();
+ _worker_stack.popBack();
+ assert(worker->state == Worker::State::BLOCKED);
+ assert(worker->strand == nullptr);
+ worker->state = Worker::State::RUNNING;
+ worker->strand = _wait_queue.front();
+ _wait_queue.pop();
+ assert(worker->strand->state == Strand::State::WAITING);
+ assert(!worker->strand->queue.empty());
+ worker->strand->state = Strand::State::ACTIVE;
+ assert(_self.waiting_tasks >= worker->strand->queue.size());
+ _self.waiting_tasks -= worker->strand->queue.size();
+ return worker;
+ }
+ return nullptr;
+}
+
+bool
+AdaptiveSequencedExecutor::obtain_strand(Worker &worker, std::unique_lock<std::mutex> &lock)
+{
+ assert(worker.strand == nullptr);
+ if (!_wait_queue.empty()) {
+ worker.strand = _wait_queue.front();
+ _wait_queue.pop();
+ assert(worker.strand->state == Strand::State::WAITING);
+ assert(!worker.strand->queue.empty());
+ worker.strand->state = Strand::State::ACTIVE;
+ assert(_self.waiting_tasks >= worker.strand->queue.size());
+ _self.waiting_tasks -= worker.strand->queue.size();
+ } else if (_self.state == Self::State::CLOSED) {
+ worker.state = Worker::State::DONE;
+ } else {
+ worker.state = Worker::State::BLOCKED;
+ _worker_stack.push(&worker);
+ while (worker.state == Worker::State::BLOCKED) {
+ worker.cond.wait(lock);
+ }
+ }
+ return (worker.state == Worker::State::RUNNING);
+}
+
+bool
+AdaptiveSequencedExecutor::exchange_strand(Worker &worker, std::unique_lock<std::mutex> &lock)
+{
+ if (worker.strand == nullptr) {
+ return obtain_strand(worker, lock);
+ }
+ if (worker.strand->queue.empty()) {
+ worker.strand->state = Strand::State::IDLE;
+ worker.strand = nullptr;
+ return obtain_strand(worker, lock);
+ }
+ if (!_wait_queue.empty()) {
+ worker.strand->state = Strand::State::WAITING;
+ _self.waiting_tasks += worker.strand->queue.size();
+ _wait_queue.push(worker.strand);
+ worker.strand = nullptr;
+ return obtain_strand(worker, lock);
+ }
+ return true;
+}
+
+AdaptiveSequencedExecutor::Task::UP
+AdaptiveSequencedExecutor::next_task(Worker &worker)
+{
+ Task::UP task;
+ Worker *worker_to_wake = nullptr;
+ auto guard = std::unique_lock(_mutex);
+ if (exchange_strand(worker, guard)) {
+ assert(worker.state == Worker::State::RUNNING);
+ assert(worker.strand != nullptr);
+ assert(!worker.strand->queue.empty());
+ task = std::move(worker.strand->queue.front());
+ worker.strand->queue.pop();
+ _stats.queueSize.add(--_self.pending_tasks);
+ worker_to_wake = get_worker_to_wake(guard);
+ } else {
+ assert(worker.state == Worker::State::DONE);
+ assert(worker.strand == nullptr);
+ }
+ bool signal_self = maybe_unblock_self(guard);
+ guard.unlock(); // UNLOCK
+ if (worker_to_wake != nullptr) {
+ worker_to_wake->cond.notify_one();
+ }
+ if (signal_self) {
+ _self.cond.notify_all();
+ }
+ return task;
+}
+
+void
+AdaptiveSequencedExecutor::worker_main()
+{
+ Worker worker;
+ while (Task::UP my_task = next_task(worker)) {
+ my_task->run();
+ }
+ _thread_tools->allow_worker_exit.await();
+}
+
+AdaptiveSequencedExecutor::AdaptiveSequencedExecutor(size_t num_strands, size_t num_threads,
+ size_t max_waiting, size_t max_pending)
+ : ISequencedTaskExecutor(num_strands),
+ _thread_tools(std::make_unique<ThreadTools>(*this)),
+ _mutex(),
+ _strands(num_strands),
+ _wait_queue(num_strands),
+ _worker_stack(num_threads),
+ _self(),
+ _stats(),
+ _cfg(num_threads, max_waiting, max_pending)
+{
+ _stats.queueSize.add(_self.pending_tasks);
+ _thread_tools->start(num_threads);
+}
+
+AdaptiveSequencedExecutor::~AdaptiveSequencedExecutor()
+{
+ sync();
+ {
+ auto guard = std::unique_lock(_mutex);
+ assert(_self.state == Self::State::OPEN);
+ _self.state = Self::State::CLOSED;
+ while (!_worker_stack.empty()) {
+ Worker *worker = _worker_stack.back();
+ _worker_stack.popBack();
+ assert(worker->state == Worker::State::BLOCKED);
+ assert(worker->strand == nullptr);
+ worker->state = Worker::State::DONE;
+ worker->cond.notify_one();
+ }
+ _self.cond.notify_all();
+ }
+ _thread_tools->close();
+ assert(_wait_queue.empty());
+ assert(_worker_stack.empty());
+}
+
+void
+AdaptiveSequencedExecutor::executeTask(ExecutorId id, Task::UP task)
+{
+ assert(id.getId() < _strands.size());
+ Strand &strand = _strands[id.getId()];
+ auto guard = std::unique_lock(_mutex);
+ maybe_block_self(guard);
+ assert(_self.state != Self::State::CLOSED);
+ strand.queue.push(std::move(task));
+ _stats.queueSize.add(++_self.pending_tasks);
+ ++_stats.acceptedTasks;
+ if (strand.state == Strand::State::WAITING) {
+ ++_self.waiting_tasks;
+ } else if (strand.state == Strand::State::IDLE) {
+ if (_worker_stack.size() < _cfg.num_threads) {
+ strand.state = Strand::State::WAITING;
+ _wait_queue.push(&strand);
+ _self.waiting_tasks += strand.queue.size();
+ } else {
+ strand.state = Strand::State::ACTIVE;
+ assert(_wait_queue.empty());
+ Worker *worker = _worker_stack.back();
+ _worker_stack.popBack();
+ assert(worker->state == Worker::State::BLOCKED);
+ assert(worker->strand == nullptr);
+ worker->state = Worker::State::RUNNING;
+ worker->strand = &strand;
+ guard.unlock(); // UNLOCK
+ worker->cond.notify_one();
+ }
+ }
+}
+
+void
+AdaptiveSequencedExecutor::sync()
+{
+ vespalib::CountDownLatch latch(_strands.size());
+ for (size_t i = 0; i < _strands.size(); ++i) {
+ execute(ExecutorId(i), [&](){ latch.countDown(); });
+ }
+ latch.await();
+}
+
+void
+AdaptiveSequencedExecutor::setTaskLimit(uint32_t task_limit)
+{
+ auto guard = std::unique_lock(_mutex);
+ _cfg.set_max_pending(task_limit);
+ bool signal_self = maybe_unblock_self(guard);
+ guard.unlock(); // UNLOCK
+ if (signal_self) {
+ _self.cond.notify_all();
+ }
+}
+
+AdaptiveSequencedExecutor::Stats
+AdaptiveSequencedExecutor::getStats()
+{
+ auto guard = std::lock_guard(_mutex);
+ Stats stats = _stats;
+ _stats = Stats();
+ _stats.queueSize.add(_self.pending_tasks);
+ return stats;
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.h b/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.h
new file mode 100644
index 00000000000..3abc095e9df
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/common/adaptive_sequenced_executor.h
@@ -0,0 +1,126 @@
+// Copyright 2020 Oath Inc. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "isequencedtaskexecutor.h"
+#include <vespa/vespalib/util/arrayqueue.hpp>
+#include <vespa/vespalib/util/gate.h>
+#include <vespa/fastos/thread.h>
+#include <mutex>
+#include <condition_variable>
+#include <cassert>
+
+namespace search {
+
+/**
+ * Sequenced executor that balances the number of active threads in
+ * order to optimize for throughput over latency by minimizing the
+ * number of critical-path wakeups.
+ **/
+class AdaptiveSequencedExecutor : public ISequencedTaskExecutor
+{
+private:
+ using Stats = vespalib::ExecutorStats;
+ using Task = vespalib::Executor::Task;
+
+ /**
+ * Values used to configure the executor.
+ **/
+ struct Config {
+ size_t num_threads;
+ size_t max_waiting;
+ size_t max_pending;
+ size_t wakeup_limit;
+ void set_max_pending(size_t max_pending_in) {
+ max_pending = std::max(1uL, max_pending_in);
+ wakeup_limit = std::max(1uL, size_t(max_pending * 0.9));
+ assert(wakeup_limit > 0);
+ assert(wakeup_limit <= max_pending);
+ }
+ Config(size_t num_threads_in, size_t max_waiting_in, size_t max_pending_in)
+ : num_threads(num_threads_in), max_waiting(max_waiting_in), max_pending(1000), wakeup_limit(900)
+ {
+ assert(num_threads > 0);
+ set_max_pending(max_pending_in);
+ }
+ };
+
+ /**
+ * Tasks that need to be sequenced are handled by a single strand.
+ **/
+ struct Strand {
+ enum class State { IDLE, WAITING, ACTIVE };
+ State state;
+ vespalib::ArrayQueue<Task::UP> queue;
+ Strand();
+ ~Strand();
+ };
+
+ /**
+ * The state of a single worker thread.
+ **/
+ struct Worker {
+ enum class State { RUNNING, BLOCKED, DONE };
+ std::condition_variable cond;
+ State state;
+ Strand *strand;
+ Worker();
+ ~Worker();
+ };
+
+ /**
+ * State related to the executor itself.
+ **/
+ struct Self {
+ enum class State { OPEN, BLOCKED, CLOSED };
+ std::condition_variable cond;
+ State state;
+ size_t waiting_tasks;
+ size_t pending_tasks;
+ Self();
+ ~Self();
+ };
+
+ /**
+ * Stuff related to worker thread startup and shutdown.
+ **/
+ struct ThreadTools : FastOS_Runnable {
+ static constexpr size_t STACK_SIZE = (256 * 1024);
+ AdaptiveSequencedExecutor &parent;
+ std::unique_ptr<FastOS_ThreadPool> pool;
+ vespalib::Gate allow_worker_exit;
+ ThreadTools(AdaptiveSequencedExecutor &parent_in);
+ ~ThreadTools();
+ void Run(FastOS_ThreadInterface *, void *) override;
+ void start(size_t num_threads);
+ void close();
+ };
+
+ std::unique_ptr<ThreadTools> _thread_tools;
+ std::mutex _mutex;
+ std::vector<Strand> _strands;
+ vespalib::ArrayQueue<Strand*> _wait_queue;
+ vespalib::ArrayQueue<Worker*> _worker_stack;
+ Self _self;
+ Stats _stats;
+ Config _cfg;
+
+ void maybe_block_self(std::unique_lock<std::mutex> &lock);
+ bool maybe_unblock_self(const std::unique_lock<std::mutex> &lock);
+
+ Worker *get_worker_to_wake(const std::unique_lock<std::mutex> &lock);
+ bool obtain_strand(Worker &worker, std::unique_lock<std::mutex> &lock);
+ bool exchange_strand(Worker &worker, std::unique_lock<std::mutex> &lock);
+ Task::UP next_task(Worker &worker);
+ void worker_main();
+public:
+ AdaptiveSequencedExecutor(size_t num_strands, size_t num_threads,
+ size_t max_waiting, size_t max_pending);
+ ~AdaptiveSequencedExecutor() override;
+ void executeTask(ExecutorId id, Task::UP task) override;
+ void sync() override;
+ void setTaskLimit(uint32_t task_limit) override;
+ vespalib::ExecutorStats getStats() override;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/common/foregroundtaskexecutor.cpp b/searchlib/src/vespa/searchlib/common/foregroundtaskexecutor.cpp
index 4c501defeea..a93eb1ff4bc 100644
--- a/searchlib/src/vespa/searchlib/common/foregroundtaskexecutor.cpp
+++ b/searchlib/src/vespa/searchlib/common/foregroundtaskexecutor.cpp
@@ -39,7 +39,7 @@ void ForegroundTaskExecutor::setTaskLimit(uint32_t) {
}
vespalib::ExecutorStats ForegroundTaskExecutor::getStats() {
- return vespalib::ExecutorStats(0, _accepted.load(std::memory_order_relaxed), 0);
+ return vespalib::ExecutorStats(vespalib::ExecutorStats::QueueSizeT(0) , _accepted.load(std::memory_order_relaxed), 0);
}
} // namespace search
diff --git a/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp b/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp
index ec31bcb5117..a8737a19eec 100644
--- a/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp
+++ b/searchlib/src/vespa/searchlib/features/dotproductfeature.cpp
@@ -224,25 +224,27 @@ private:
template <typename A>
class SingleDotProductExecutorByValue final : public fef::FeatureExecutor {
public:
- SingleDotProductExecutorByValue(const A * attribute, multivalue::WeightedValue<typename A::BaseType> keyValue)
+ SingleDotProductExecutorByValue(const A * attribute, typename A::BaseType key, feature_t value)
: _attribute(attribute),
- _keyValue(keyValue)
+ _key(key),
+ _value(value)
{}
void execute(uint32_t docId) override {
const multivalue::WeightedValue<typename A::BaseType> *values(nullptr);
uint32_t sz = _attribute->getRawValues(docId, values);
for (size_t i = 0; i < sz; ++i) {
- if (values[i].value() == _keyValue.value()) {
- outputs().set_number(0, values[i].weight()*_keyValue.weight());
+ if (values[i].value() == _key) {
+ outputs().set_number(0, values[i].weight() * _value);
return;
}
}
outputs().set_number(0, 0);
}
private:
- const A * _attribute;
- multivalue::WeightedValue<typename A::BaseType> _keyValue;
+ const A * _attribute;
+ typename A::BaseType _key;
+ feature_t _value;
};
}
@@ -628,9 +630,9 @@ size_t extractSize(const dotproduct::wset::IntegerVectorT<T> & v) {
}
template<typename T>
-multivalue::WeightedValue<T> extractElem(const dotproduct::wset::IntegerVectorT<T> & v, size_t idx) {
+std::pair<T, feature_t> extractElem(const dotproduct::wset::IntegerVectorT<T> & v, size_t idx) {
const auto & pair = v.getVector()[idx];
- return multivalue::WeightedValue<T>(pair.first, pair.second);
+ return std::pair<T, feature_t>(pair.first, pair.second);
}
template<typename T>
@@ -639,7 +641,7 @@ size_t extractSize(const std::unique_ptr<dotproduct::wset::IntegerVectorT<T>> &
}
template<typename T>
-multivalue::WeightedValue<T> extractElem(const std::unique_ptr<dotproduct::wset::IntegerVectorT<T>> & v, size_t idx) {
+std::pair<T, feature_t> extractElem(const std::unique_ptr<dotproduct::wset::IntegerVectorT<T>> & v, size_t idx) {
return extractElem(*v, idx);
}
@@ -656,7 +658,8 @@ createForDirectWSetImpl(const IAttributeVector * attribute, V && vector, vespali
auto * exactA = dynamic_cast<const ExactA *>(iattr);
if (exactA != nullptr) {
if (extractSize(vector) == 1) {
- return stash.create<SingleDotProductExecutorByValue<ExactA>>(exactA, extractElem(vector, 0ul));
+ auto elem = extractElem(vector, 0ul);
+ return stash.create<SingleDotProductExecutorByValue<ExactA>>(exactA, elem.first, elem.second);
}
return stash.create<DotProductExecutor<ExactA>>(exactA, std::forward<V>(vector));
}
diff --git a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
index 7090158c773..0f106f693f8 100644
--- a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
@@ -9,11 +9,15 @@ vespa_add_library(searchlib_tensor OBJECT
generic_tensor_attribute.cpp
generic_tensor_attribute_saver.cpp
generic_tensor_store.cpp
+ hnsw_graph.cpp
hnsw_index.cpp
+ hnsw_index_loader.cpp
+ hnsw_index_saver.cpp
imported_tensor_attribute_vector.cpp
imported_tensor_attribute_vector_read_guard.cpp
inv_log_level_generator.cpp
nearest_neighbor_index.cpp
+ nearest_neighbor_index_saver.cpp
tensor_attribute.cpp
tensor_store.cpp
DEPENDS
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
index 627f7f0dfa9..68ce0c1bb00 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute.cpp
@@ -3,16 +3,19 @@
#include "dense_tensor_attribute.h"
#include "dense_tensor_attribute_saver.h"
#include "nearest_neighbor_index.h"
+#include "nearest_neighbor_index_saver.h"
#include "tensor_attribute.hpp"
#include <vespa/eval/tensor/dense/mutable_dense_tensor_view.h>
#include <vespa/eval/tensor/tensor.h>
#include <vespa/fastlib/io/bufferedfile.h>
+#include <vespa/searchlib/attribute/load_utils.h>
#include <vespa/searchlib/attribute/readerbase.h>
#include <vespa/vespalib/data/slime/inserter.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.tensor.dense_tensor_attribute");
+using search::attribute::LoadUtils;
using vespalib::eval::ValueType;
using vespalib::slime::ObjectInserter;
using vespalib::tensor::MutableDenseTensorView;
@@ -148,6 +151,8 @@ DenseTensorAttribute::onLoad()
if (!tensorReader.hasData()) {
return false;
}
+ bool has_index_file = LoadUtils::file_exists(*this, DenseTensorAttributeSaver::index_file_suffix());
+
setCreateSerialNum(tensorReader.getCreateSerialNum());
assert(tensorReader.getVersion() == DENSE_TENSOR_ATTRIBUTE_VERSION);
assert(getConfig().tensorType().to_spec() ==
@@ -160,7 +165,7 @@ DenseTensorAttribute::onLoad()
auto raw = _denseTensorStore.allocRawBuffer();
tensorReader.readTensor(raw.data, _denseTensorStore.getBufSize());
_refVector.push_back(raw.ref);
- if (_index) {
+ if (_index && !has_index_file) {
// This ensures that get_vector() (via getTensor()) is able to find the newly added tensor.
setCommittedDocIdLimit(lid + 1);
_index->add_document(lid);
@@ -171,6 +176,12 @@ DenseTensorAttribute::onLoad()
}
setNumDocs(numDocs);
setCommittedDocIdLimit(numDocs);
+ if (_index && has_index_file) {
+ auto buffer = LoadUtils::loadFile(*this, DenseTensorAttributeSaver::index_file_suffix());
+ if (!_index->load(*buffer)) {
+ return false;
+ }
+ }
return true;
}
@@ -180,11 +191,13 @@ DenseTensorAttribute::onInitSave(vespalib::stringref fileName)
{
vespalib::GenerationHandler::Guard guard(getGenerationHandler().
takeGuard());
+ auto index_saver = (_index ? _index->make_saver() : std::unique_ptr<NearestNeighborIndexSaver>());
return std::make_unique<DenseTensorAttributeSaver>
(std::move(guard),
this->createAttributeHeader(fileName),
getRefCopy(),
- _denseTensorStore);
+ _denseTensorStore,
+ std::move(index_saver));
}
void
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.cpp
index d78adab81b5..fd8d6162f01 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.cpp
@@ -1,20 +1,19 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "dense_tensor_attribute_saver.h"
-#include <vespa/vespalib/util/bufferwriter.h>
#include "dense_tensor_store.h"
+#include "nearest_neighbor_index_saver.h"
+#include <vespa/vespalib/util/bufferwriter.h>
#include <vespa/searchlib/attribute/iattributesavetarget.h>
using vespalib::GenerationHandler;
-namespace search {
-
-namespace tensor {
+namespace search::tensor {
namespace {
-static const uint8_t tensorIsNotPresent = 0;
-static const uint8_t tensorIsPresent = 1;
+constexpr uint8_t tensorIsNotPresent = 0;
+constexpr uint8_t tensorIsPresent = 1;
}
@@ -22,42 +21,60 @@ DenseTensorAttributeSaver::
DenseTensorAttributeSaver(GenerationHandler::Guard &&guard,
const attribute::AttributeHeader &header,
RefCopyVector &&refs,
- const DenseTensorStore &tensorStore)
+ const DenseTensorStore &tensorStore,
+ IndexSaverUP index_saver)
: AttributeSaver(std::move(guard), header),
_refs(std::move(refs)),
- _tensorStore(tensorStore)
+ _tensorStore(tensorStore),
+ _index_saver(std::move(index_saver))
{
}
+DenseTensorAttributeSaver::~DenseTensorAttributeSaver() = default;
-DenseTensorAttributeSaver::~DenseTensorAttributeSaver()
+vespalib::string
+DenseTensorAttributeSaver::index_file_suffix()
{
+ return "nnidx";
}
-
bool
DenseTensorAttributeSaver::onSave(IAttributeSaveTarget &saveTarget)
{
- std::unique_ptr<BufferWriter>
- datWriter(saveTarget.datWriter().allocBufferWriter());
+ if (_index_saver) {
+ if (!saveTarget.setup_writer(index_file_suffix(), "Binary data file for nearest neighbor index")) {
+ return false;
+ }
+ }
+
+ auto dat_writer = saveTarget.datWriter().allocBufferWriter();
+ save_tensor_store(*dat_writer);
+
+ if (_index_saver) {
+ auto index_writer = saveTarget.get_writer(index_file_suffix()).allocBufferWriter();
+ // Note: Implementation of save() is responsible to call BufferWriter::flush().
+ _index_saver->save(*index_writer);
+ }
+ return true;
+}
+
+void
+DenseTensorAttributeSaver::save_tensor_store(BufferWriter& writer) const
+{
const uint32_t docIdLimit(_refs.size());
const uint32_t cellSize = _tensorStore.getCellSize();
for (uint32_t lid = 0; lid < docIdLimit; ++lid) {
if (_refs[lid].valid()) {
auto raw = _tensorStore.getRawBuffer(_refs[lid]);
- datWriter->write(&tensorIsPresent, sizeof(tensorIsPresent));
+ writer.write(&tensorIsPresent, sizeof(tensorIsPresent));
size_t numCells = _tensorStore.getNumCells();
size_t rawLen = numCells * cellSize;
- datWriter->write(static_cast<const char *>(raw), rawLen);
+ writer.write(static_cast<const char *>(raw), rawLen);
} else {
- datWriter->write(&tensorIsNotPresent, sizeof(tensorIsNotPresent));
+ writer.write(&tensorIsNotPresent, sizeof(tensorIsNotPresent));
}
}
- datWriter->flush();
- return true;
+ writer.flush();
}
-
-} // namespace search::tensor
-
-} // namespace search
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.h b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.h
index 1f6596e82f5..895e2951cea 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.h
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_attribute_saver.h
@@ -5,28 +5,41 @@
#include "tensor_attribute.h"
#include <vespa/searchlib/attribute/attributesaver.h>
+namespace search { class BufferWriter; }
+
namespace search::tensor {
class DenseTensorStore;
+class NearestNeighborIndexSaver;
-/*
- * Class for saving a tensor attribute.
+/**
+ * Class for saving a dense tensor attribute.
+ * Will also save the nearest neighbor index if existing.
*/
-class DenseTensorAttributeSaver : public AttributeSaver
-{
+class DenseTensorAttributeSaver : public AttributeSaver {
public:
using RefCopyVector = TensorAttribute::RefCopyVector;
private:
+ using GenerationHandler = vespalib::GenerationHandler;
+ using IndexSaverUP = std::unique_ptr<NearestNeighborIndexSaver>;
+
RefCopyVector _refs;
const DenseTensorStore &_tensorStore;
- using GenerationHandler = vespalib::GenerationHandler;
+ IndexSaverUP _index_saver;
bool onSave(IAttributeSaveTarget &saveTarget) override;
+ void save_tensor_store(BufferWriter& writer) const;
+
public:
- DenseTensorAttributeSaver(GenerationHandler::Guard &&guard, const attribute::AttributeHeader &header,
- RefCopyVector &&refs, const DenseTensorStore &tensorStore);
+ DenseTensorAttributeSaver(GenerationHandler::Guard &&guard,
+ const attribute::AttributeHeader &header,
+ RefCopyVector &&refs,
+ const DenseTensorStore &tensorStore,
+ IndexSaverUP index_saver);
~DenseTensorAttributeSaver() override;
+
+ static vespalib::string index_file_suffix();
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_graph.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_graph.cpp
new file mode 100644
index 00000000000..6f902a30861
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_graph.cpp
@@ -0,0 +1,72 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "hnsw_graph.h"
+#include "hnsw_index.h"
+#include <vespa/vespalib/datastore/array_store.hpp>
+#include <vespa/vespalib/util/rcuvector.hpp>
+
+namespace search::tensor {
+
+HnswGraph::HnswGraph()
+ : node_refs(),
+ nodes(HnswIndex::make_default_node_store_config()),
+ links(HnswIndex::make_default_link_store_config()),
+ entry_docid(0), // Note that docid 0 is reserved and never used
+ entry_level(-1)
+{}
+
+HnswGraph::~HnswGraph() {}
+
+void
+HnswGraph::make_node_for_document(uint32_t docid, uint32_t num_levels)
+{
+ node_refs.ensure_size(docid + 1, AtomicEntryRef());
+ // A document cannot be added twice.
+ assert(!node_refs[docid].load_acquire().valid());
+ // Note: The level array instance lives as long as the document is present in the index.
+ vespalib::Array<AtomicEntryRef> levels(num_levels, AtomicEntryRef());
+ auto node_ref = nodes.add(levels);
+ node_refs[docid].store_release(node_ref);
+}
+
+void
+HnswGraph::remove_node_for_document(uint32_t docid)
+{
+ auto node_ref = node_refs[docid].load_acquire();
+ nodes.remove(node_ref);
+ search::datastore::EntryRef invalid;
+ node_refs[docid].store_release(invalid);
+}
+
+void
+HnswGraph::set_link_array(uint32_t docid, uint32_t level, const LinkArrayRef& new_links)
+{
+ auto new_links_ref = links.add(new_links);
+ auto node_ref = node_refs[docid].load_acquire();
+ assert(node_ref.valid());
+ auto levels = nodes.get_writable(node_ref);
+ auto old_links_ref = levels[level].load_acquire();
+ levels[level].store_release(new_links_ref);
+ links.remove(old_links_ref);
+}
+
+std::vector<uint32_t>
+HnswGraph::level_histogram() const
+{
+ std::vector<uint32_t> result;
+ size_t num_nodes = node_refs.size();
+ for (size_t i = 0; i < num_nodes; ++i) {
+ uint32_t levels = 0;
+ auto node_ref = node_refs[i].load_acquire();
+ if (node_ref.valid()) {
+ levels = nodes.get(node_ref).size();
+ }
+ while (result.size() <= levels) {
+ result.push_back(0);
+ }
+ ++result[levels];
+ }
+ return result;
+}
+
+} // namespace
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_graph.h b/searchlib/src/vespa/searchlib/tensor/hnsw_graph.h
new file mode 100644
index 00000000000..233b9087af7
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_graph.h
@@ -0,0 +1,76 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <vespa/vespalib/datastore/array_store.h>
+#include <vespa/vespalib/datastore/atomic_entry_ref.h>
+#include <vespa/vespalib/datastore/entryref.h>
+#include <vespa/vespalib/util/rcuvector.h>
+
+namespace search::tensor {
+
+/**
+ * Stroage of a hierarchical navigable small world graph (HNSW)
+ * that is used for approximate K-nearest neighbor search.
+ */
+struct HnswGraph {
+ using AtomicEntryRef = search::datastore::AtomicEntryRef;
+
+ // This uses 10 bits for buffer id -> 1024 buffers.
+ // As we have very short arrays we get less fragmentation with fewer and larger buffers.
+ using EntryRefType = search::datastore::EntryRefT<22>;
+
+ // Provides mapping from document id -> node reference.
+ // The reference is used to lookup the node data in NodeStore.
+ using NodeRefVector = vespalib::RcuVector<AtomicEntryRef>;
+
+ // This stores the level arrays for all nodes.
+ // Each node consists of an array of levels (from level 0 to n) where each entry is a reference to the link array at that level.
+ using NodeStore = search::datastore::ArrayStore<AtomicEntryRef, EntryRefType>;
+ using StoreConfig = search::datastore::ArrayStoreConfig;
+ using LevelArrayRef = NodeStore::ConstArrayRef;
+
+ // This stores all link arrays.
+ // A link array consists of the document ids of the nodes a particular node is linked to.
+ using LinkStore = search::datastore::ArrayStore<uint32_t, EntryRefType>;
+ using LinkArrayRef = LinkStore::ConstArrayRef;
+
+ NodeRefVector node_refs;
+ NodeStore nodes;
+ LinkStore links;
+ uint32_t entry_docid;
+ int32_t entry_level;
+
+ HnswGraph();
+
+ ~HnswGraph();
+
+ void make_node_for_document(uint32_t docid, uint32_t num_levels);
+
+ void remove_node_for_document(uint32_t docid);
+
+ LevelArrayRef get_level_array(uint32_t docid) const {
+ auto node_ref = node_refs[docid].load_acquire();
+ assert(node_ref.valid());
+ return nodes.get(node_ref);
+ }
+
+ LinkArrayRef get_link_array(uint32_t docid, uint32_t level) const {
+ auto levels = get_level_array(docid);
+ assert(level < levels.size());
+ return links.get(levels[level].load_acquire());
+ }
+
+ void set_link_array(uint32_t docid, uint32_t level, const LinkArrayRef& new_links);
+
+ void set_entry_node(uint32_t docid, int32_t level) {
+ entry_docid = docid;
+ entry_level = level;
+ }
+
+ size_t size() const { return node_refs.size(); }
+
+ std::vector<uint32_t> level_histogram() const;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
index 988264c0455..b08d862ae6d 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
@@ -2,6 +2,8 @@
#include "distance_function.h"
#include "hnsw_index.h"
+#include "hnsw_index_loader.h"
+#include "hnsw_index_saver.h"
#include "random_level_generator.h"
#include <vespa/searchlib/util/state_explorer_utils.h>
#include <vespa/eval/tensor/dense/typed_cells.h>
@@ -66,54 +68,6 @@ HnswIndex::max_links_for_level(uint32_t level) const
return (level == 0) ? _cfg.max_links_at_level_0() : _cfg.max_links_on_inserts();
}
-void
-HnswIndex::make_node_for_document(uint32_t docid, uint32_t num_levels)
-{
- _node_refs.ensure_size(docid + 1, AtomicEntryRef());
- // A document cannot be added twice.
- assert(!_node_refs[docid].load_acquire().valid());
-
- // Note: The level array instance lives as long as the document is present in the index.
- LevelArray levels(num_levels, AtomicEntryRef());
- auto node_ref = _nodes.add(levels);
- _node_refs[docid].store_release(node_ref);
-}
-
-void
-HnswIndex::remove_node_for_document(uint32_t docid)
-{
- auto node_ref = _node_refs[docid].load_acquire();
- _nodes.remove(node_ref);
- EntryRef invalid;
- _node_refs[docid].store_release(invalid);
-}
-
-HnswIndex::LevelArrayRef
-HnswIndex::get_level_array(uint32_t docid) const
-{
- auto node_ref = _node_refs[docid].load_acquire();
- return _nodes.get(node_ref);
-}
-
-HnswIndex::LinkArrayRef
-HnswIndex::get_link_array(uint32_t docid, uint32_t level) const
-{
- auto levels = get_level_array(docid);
- assert(level < levels.size());
- return _links.get(levels[level].load_acquire());
-}
-
-void
-HnswIndex::set_link_array(uint32_t docid, uint32_t level, const LinkArrayRef& links)
-{
- auto new_links_ref = _links.add(links);
- auto node_ref = _node_refs[docid].load_acquire();
- auto levels = _nodes.get_writable(node_ref);
- auto old_links_ref = levels[level].load_acquire();
- levels[level].store_release(new_links_ref);
- _links.remove(old_links_ref);
-}
-
bool
HnswIndex::have_closer_distance(HnswCandidate candidate, const LinkArrayRef& result) const
{
@@ -182,7 +136,7 @@ HnswIndex::select_neighbors(const HnswCandidateVector& neighbors, uint32_t max_l
void
HnswIndex::shrink_if_needed(uint32_t docid, uint32_t level)
{
- auto old_links = get_link_array(docid, level);
+ auto old_links = _graph.get_link_array(docid, level);
uint32_t max_links = max_links_for_level(level);
if (old_links.size() > max_links) {
HnswCandidateVector neighbors;
@@ -191,7 +145,7 @@ HnswIndex::shrink_if_needed(uint32_t docid, uint32_t level)
neighbors.emplace_back(neighbor_docid, dist);
}
auto split = select_neighbors(neighbors, max_links);
- set_link_array(docid, level, split.used);
+ _graph.set_link_array(docid, level, split.used);
for (uint32_t removed_docid : split.unused) {
remove_link_to(removed_docid, docid, level);
}
@@ -201,9 +155,9 @@ HnswIndex::shrink_if_needed(uint32_t docid, uint32_t level)
void
HnswIndex::connect_new_node(uint32_t docid, const LinkArrayRef &neighbors, uint32_t level)
{
- set_link_array(docid, level, neighbors);
+ _graph.set_link_array(docid, level, neighbors);
for (uint32_t neighbor_docid : neighbors) {
- auto old_links = get_link_array(neighbor_docid, level);
+ auto old_links = _graph.get_link_array(neighbor_docid, level);
add_link_to(neighbor_docid, level, old_links, docid);
}
for (uint32_t neighbor_docid : neighbors) {
@@ -215,11 +169,11 @@ void
HnswIndex::remove_link_to(uint32_t remove_from, uint32_t remove_id, uint32_t level)
{
LinkArray new_links;
- auto old_links = get_link_array(remove_from, level);
+ auto old_links = _graph.get_link_array(remove_from, level);
for (uint32_t id : old_links) {
if (id != remove_id) new_links.push_back(id);
}
- set_link_array(remove_from, level, new_links);
+ _graph.set_link_array(remove_from, level, new_links);
}
@@ -244,7 +198,7 @@ HnswIndex::find_nearest_in_layer(const TypedCells& input, const HnswCandidate& e
bool keep_searching = true;
while (keep_searching) {
keep_searching = false;
- for (uint32_t neighbor_docid : get_link_array(nearest.docid, level)) {
+ for (uint32_t neighbor_docid : _graph.get_link_array(nearest.docid, level)) {
double dist = calc_distance(input, neighbor_docid);
if (dist < nearest.distance) {
nearest = HnswCandidate(neighbor_docid, dist);
@@ -259,7 +213,7 @@ void
HnswIndex::search_layer(const TypedCells& input, uint32_t neighbors_to_find, FurthestPriQ& best_neighbors, uint32_t level) const
{
NearestPriQ candidates;
- uint32_t doc_id_limit = _node_refs.size();
+ uint32_t doc_id_limit = _graph.node_refs.size();
auto visited = _visited_set_pool.get(doc_id_limit);
for (const auto &entry : best_neighbors.peek()) {
assert(entry.docid < doc_id_limit);
@@ -274,7 +228,7 @@ HnswIndex::search_layer(const TypedCells& input, uint32_t neighbors_to_find, Fur
break;
}
candidates.pop();
- for (uint32_t neighbor_docid : get_link_array(cand.docid, level)) {
+ for (uint32_t neighbor_docid : _graph.get_link_array(cand.docid, level)) {
if ((neighbor_docid >= doc_id_limit) || visited.is_marked(neighbor_docid)) {
continue;
}
@@ -294,15 +248,12 @@ HnswIndex::search_layer(const TypedCells& input, uint32_t neighbors_to_find, Fur
HnswIndex::HnswIndex(const DocVectorAccess& vectors, DistanceFunction::UP distance_func,
RandomLevelGenerator::UP level_generator, const Config& cfg)
- : _vectors(vectors),
+ :
+ _graph(),
+ _vectors(vectors),
_distance_func(std::move(distance_func)),
_level_generator(std::move(level_generator)),
- _cfg(cfg),
- _node_refs(),
- _nodes(make_default_node_store_config()),
- _links(make_default_link_store_config()),
- _entry_docid(0), // Note that docid 0 is reserved and never used
- _entry_level(-1)
+ _cfg(cfg)
{
}
@@ -314,16 +265,16 @@ HnswIndex::add_document(uint32_t docid)
auto input = get_vector(docid);
// TODO: Add capping on num_levels
int level = _level_generator->max_level();
- make_node_for_document(docid, level + 1);
- if (_entry_docid == 0) {
- _entry_docid = docid;
- _entry_level = level;
+ _graph.make_node_for_document(docid, level + 1);
+ uint32_t entry_docid = get_entry_docid();
+ if (entry_docid == 0) {
+ _graph.set_entry_node(docid, level);
return;
}
- int search_level = _entry_level;
- double entry_dist = calc_distance(input, _entry_docid);
- HnswCandidate entry_point(_entry_docid, entry_dist);
+ int search_level = get_entry_level();
+ double entry_dist = calc_distance(input, entry_docid);
+ HnswCandidate entry_point(entry_docid, entry_dist);
while (search_level > level) {
entry_point = find_nearest_in_layer(input, entry_point, search_level);
--search_level;
@@ -331,7 +282,7 @@ HnswIndex::add_document(uint32_t docid)
FurthestPriQ best_neighbors;
best_neighbors.push(entry_point);
- search_level = std::min(level, _entry_level);
+ search_level = std::min(level, search_level);
// Insert the added document in each level it should exist in.
while (search_level >= 0) {
@@ -341,9 +292,8 @@ HnswIndex::add_document(uint32_t docid)
connect_new_node(docid, neighbors.used, search_level);
--search_level;
}
- if (level > _entry_level) {
- _entry_docid = docid;
- _entry_level = level;
+ if (level > get_entry_level()) {
+ _graph.set_entry_node(docid, level);
}
}
@@ -353,7 +303,7 @@ HnswIndex::mutual_reconnect(const LinkArrayRef &cluster, uint32_t level)
std::vector<PairDist> pairs;
for (uint32_t i = 0; i + 1 < cluster.size(); ++i) {
uint32_t n_id_1 = cluster[i];
- LinkArrayRef n_list_1 = get_link_array(n_id_1, level);
+ LinkArrayRef n_list_1 = _graph.get_link_array(n_id_1, level);
for (uint32_t j = i + 1; j < cluster.size(); ++j) {
uint32_t n_id_2 = cluster[j];
if (has_link_to(n_list_1, n_id_2)) continue;
@@ -362,10 +312,10 @@ HnswIndex::mutual_reconnect(const LinkArrayRef &cluster, uint32_t level)
}
std::sort(pairs.begin(), pairs.end());
for (const PairDist & pair : pairs) {
- LinkArrayRef old_links_1 = get_link_array(pair.id_first, level);
+ LinkArrayRef old_links_1 = _graph.get_link_array(pair.id_first, level);
if (old_links_1.size() >= _cfg.max_links_on_inserts()) continue;
- LinkArrayRef old_links_2 = get_link_array(pair.id_second, level);
+ LinkArrayRef old_links_2 = _graph.get_link_array(pair.id_second, level);
if (old_links_2.size() >= _cfg.max_links_on_inserts()) continue;
add_link_to(pair.id_first, level, old_links_1, pair.id_second);
@@ -376,27 +326,25 @@ HnswIndex::mutual_reconnect(const LinkArrayRef &cluster, uint32_t level)
void
HnswIndex::remove_document(uint32_t docid)
{
- bool need_new_entrypoint = (docid == _entry_docid);
+ bool need_new_entrypoint = (docid == get_entry_docid());
LinkArray empty;
- LevelArrayRef node_levels = get_level_array(docid);
+ LevelArrayRef node_levels = _graph.get_level_array(docid);
for (int level = node_levels.size(); level-- > 0; ) {
- LinkArrayRef my_links = get_link_array(docid, level);
+ LinkArrayRef my_links = _graph.get_link_array(docid, level);
for (uint32_t neighbor_id : my_links) {
if (need_new_entrypoint) {
- _entry_docid = neighbor_id;
- _entry_level = level;
+ _graph.set_entry_node(neighbor_id, level);
need_new_entrypoint = false;
}
remove_link_to(neighbor_id, docid, level);
}
mutual_reconnect(my_links, level);
- set_link_array(docid, level, empty);
+ _graph.set_link_array(docid, level, empty);
}
if (need_new_entrypoint) {
- _entry_docid = 0;
- _entry_level = -1;
+ _graph.set_entry_node(0, -1);
}
- remove_node_for_document(docid);
+ _graph.remove_node_for_document(docid);
}
void
@@ -404,26 +352,26 @@ HnswIndex::transfer_hold_lists(generation_t current_gen)
{
// Note: RcuVector transfers hold lists as part of reallocation based on current generation.
// We need to set the next generation here, as it is incremented on a higher level right after this call.
- _node_refs.setGeneration(current_gen + 1);
- _nodes.transferHoldLists(current_gen);
- _links.transferHoldLists(current_gen);
+ _graph.node_refs.setGeneration(current_gen + 1);
+ _graph.nodes.transferHoldLists(current_gen);
+ _graph.links.transferHoldLists(current_gen);
}
void
HnswIndex::trim_hold_lists(generation_t first_used_gen)
{
- _node_refs.removeOldGenerations(first_used_gen);
- _nodes.trimHoldLists(first_used_gen);
- _links.trimHoldLists(first_used_gen);
+ _graph.node_refs.removeOldGenerations(first_used_gen);
+ _graph.nodes.trimHoldLists(first_used_gen);
+ _graph.links.trimHoldLists(first_used_gen);
}
vespalib::MemoryUsage
HnswIndex::memory_usage() const
{
vespalib::MemoryUsage result;
- result.merge(_node_refs.getMemoryUsage());
- result.merge(_nodes.getMemoryUsage());
- result.merge(_links.getMemoryUsage());
+ result.merge(_graph.node_refs.getMemoryUsage());
+ result.merge(_graph.nodes.getMemoryUsage());
+ result.merge(_graph.links.getMemoryUsage());
result.merge(_visited_set_pool.memory_usage());
return result;
}
@@ -433,6 +381,34 @@ HnswIndex::get_state(const vespalib::slime::Inserter& inserter) const
{
auto& object = inserter.insertObject();
StateExplorerUtils::memory_usage_to_slime(memory_usage(), object.setObject("memory_usage"));
+ object.setLong("nodes", _graph.size());
+ auto& histogram_array = object.setArray("level_histogram");
+ auto level_histogram = _graph.level_histogram();
+ for (uint32_t hist_val : level_histogram) {
+ histogram_array.addLong(hist_val);
+ }
+ uint32_t reachable = count_reachable_nodes();
+ uint32_t unreachable = _graph.size() - reachable;
+ if (level_histogram.size() > 0) {
+ unreachable -= level_histogram[0];
+ }
+ object.setLong("unreachable_nodes", unreachable);
+ object.setLong("entry_docid", _graph.entry_docid);
+ object.setLong("entry_level", _graph.entry_level);
+}
+
+std::unique_ptr<NearestNeighborIndexSaver>
+HnswIndex::make_saver() const
+{
+ return std::make_unique<HnswIndexSaver>(_graph);
+}
+
+bool
+HnswIndex::load(const fileutil::LoadedBuffer& buf)
+{
+ assert(get_entry_docid() == 0); // cannot load after index has data
+ HnswIndexLoader loader(_graph);
+ return loader.load(buf);
}
struct NeighborsByDocId {
@@ -463,12 +439,13 @@ FurthestPriQ
HnswIndex::top_k_candidates(const TypedCells &vector, uint32_t k) const
{
FurthestPriQ best_neighbors;
- if (_entry_level < 0) {
+ if (get_entry_level() < 0) {
return best_neighbors;
}
- double entry_dist = calc_distance(vector, _entry_docid);
- HnswCandidate entry_point(_entry_docid, entry_dist);
- int search_level = _entry_level;
+ uint32_t entry_docid = get_entry_docid();
+ int search_level = get_entry_level();
+ double entry_dist = calc_distance(vector, entry_docid);
+ HnswCandidate entry_point(entry_docid, entry_dist);
while (search_level > 0) {
entry_point = find_nearest_in_layer(vector, entry_point, search_level);
--search_level;
@@ -481,14 +458,14 @@ HnswIndex::top_k_candidates(const TypedCells &vector, uint32_t k) const
HnswNode
HnswIndex::get_node(uint32_t docid) const
{
- auto node_ref = _node_refs[docid].load_acquire();
+ auto node_ref = _graph.node_refs[docid].load_acquire();
if (!node_ref.valid()) {
return HnswNode();
}
- auto levels = _nodes.get(node_ref);
+ auto levels = _graph.nodes.get(node_ref);
HnswNode::LevelArray result;
for (const auto& links_ref : levels) {
- auto links = _links.get(links_ref.load_acquire());
+ auto links = _graph.links.get(links_ref.load_acquire());
HnswNode::LinkArray result_links(links.begin(), links.end());
std::sort(result_links.begin(), result_links.end());
result.push_back(result_links);
@@ -501,14 +478,13 @@ HnswIndex::set_node(uint32_t docid, const HnswNode &node)
{
size_t num_levels = node.size();
assert(num_levels > 0);
- make_node_for_document(docid, num_levels);
+ _graph.make_node_for_document(docid, num_levels);
for (size_t level = 0; level < num_levels; ++level) {
connect_new_node(docid, node.level(level), level);
}
int max_level = num_levels - 1;
- if (_entry_level < max_level) {
- _entry_docid = docid;
- _entry_level = max_level;
+ if (get_entry_level() < max_level) {
+ _graph.set_entry_node(docid, max_level);
}
}
@@ -516,15 +492,15 @@ bool
HnswIndex::check_link_symmetry() const
{
bool all_sym = true;
- for (size_t docid = 0; docid < _node_refs.size(); ++docid) {
- auto node_ref = _node_refs[docid].load_acquire();
+ for (size_t docid = 0; docid < _graph.node_refs.size(); ++docid) {
+ auto node_ref = _graph.node_refs[docid].load_acquire();
if (node_ref.valid()) {
- auto levels = _nodes.get(node_ref);
+ auto levels = _graph.nodes.get(node_ref);
uint32_t level = 0;
for (const auto& links_ref : levels) {
- auto links = _links.get(links_ref.load_acquire());
+ auto links = _graph.links.get(links_ref.load_acquire());
for (auto neighbor_docid : links) {
- auto neighbor_links = get_link_array(neighbor_docid, level);
+ auto neighbor_links = _graph.get_link_array(neighbor_docid, level);
if (! has_link_to(neighbor_links, docid)) {
all_sym = false;
}
@@ -536,4 +512,31 @@ HnswIndex::check_link_symmetry() const
return all_sym;
}
+uint32_t
+HnswIndex::count_reachable_nodes() const
+{
+ int search_level = get_entry_level();
+ if (search_level < 0) {
+ return 0;
+ }
+ auto visited = _visited_set_pool.get(_graph.size());
+ uint32_t entry_id = get_entry_docid();
+ LinkArray found_links;
+ found_links.push_back(entry_id);
+ visited.mark(entry_id);
+ while (search_level >= 0) {
+ for (uint32_t idx = 0; idx < found_links.size(); ++idx) {
+ uint32_t docid = found_links[idx];
+ auto neighbors = _graph.get_link_array(docid, search_level);
+ for (uint32_t neighbor : neighbors) {
+ if (visited.is_marked(neighbor)) continue;
+ visited.mark(neighbor);
+ found_links.push_back(neighbor);
+ }
+ }
+ --search_level;
+ }
+ return found_links.size();
+}
+
} // namespace
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
index 130c012effe..95001853710 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
@@ -8,6 +8,7 @@
#include "hnsw_node.h"
#include "nearest_neighbor_index.h"
#include "random_level_generator.h"
+#include "hnsw_graph.h"
#include <vespa/eval/tensor/dense/typed_cells.h>
#include <vespa/searchlib/common/bitvector.h>
#include <vespa/vespalib/datastore/array_store.h>
@@ -57,54 +58,30 @@ public:
};
protected:
- using AtomicEntryRef = search::datastore::AtomicEntryRef;
+ using AtomicEntryRef = HnswGraph::AtomicEntryRef;
+ using NodeStore = HnswGraph::NodeStore;
- // This uses 10 bits for buffer id -> 1024 buffers.
- // As we have very short arrays we get less fragmentation with fewer and larger buffers.
- using EntryRefType = search::datastore::EntryRefT<22>;
-
- // Provides mapping from document id -> node reference.
- // The reference is used to lookup the node data in NodeStore.
- using NodeRefVector = vespalib::RcuVector<AtomicEntryRef>;
+ using LinkStore = HnswGraph::LinkStore;
+ using LinkArrayRef = HnswGraph::LinkArrayRef;
+ using LinkArray = vespalib::Array<uint32_t>;
- // This stores the level arrays for all nodes.
- // Each node consists of an array of levels (from level 0 to n) where each entry is a reference to the link array at that level.
- using NodeStore = search::datastore::ArrayStore<AtomicEntryRef, EntryRefType>;
- using LevelArrayRef = NodeStore::ConstArrayRef;
+ using LevelArrayRef = HnswGraph::LevelArrayRef;
using LevelArray = vespalib::Array<AtomicEntryRef>;
- // This stores all link arrays.
- // A link array consists of the document ids of the nodes a particular node is linked to.
- using LinkStore = search::datastore::ArrayStore<uint32_t, EntryRefType>;
- using LinkArrayRef = LinkStore::ConstArrayRef;
- using LinkArray = vespalib::Array<uint32_t>;
-
using TypedCells = vespalib::tensor::TypedCells;
+ HnswGraph _graph;
const DocVectorAccess& _vectors;
DistanceFunction::UP _distance_func;
RandomLevelGenerator::UP _level_generator;
Config _cfg;
- NodeRefVector _node_refs;
- NodeStore _nodes;
- LinkStore _links;
mutable vespalib::ReusableSetPool _visited_set_pool;
- uint32_t _entry_docid;
- int _entry_level;
-
- static search::datastore::ArrayStoreConfig make_default_node_store_config();
- static search::datastore::ArrayStoreConfig make_default_link_store_config();
uint32_t max_links_for_level(uint32_t level) const;
- void make_node_for_document(uint32_t docid, uint32_t num_levels);
- void remove_node_for_document(uint32_t docid);
- LevelArrayRef get_level_array(uint32_t docid) const;
- LinkArrayRef get_link_array(uint32_t docid, uint32_t level) const;
- void set_link_array(uint32_t docid, uint32_t level, const LinkArrayRef& links);
void add_link_to(uint32_t docid, uint32_t level, const LinkArrayRef& old_links, uint32_t new_link) {
LinkArray new_links(old_links.begin(), old_links.end());
new_links.push_back(new_link);
- set_link_array(docid, level, new_links);
+ _graph.set_link_array(docid, level, new_links);
}
/**
@@ -155,18 +132,25 @@ public:
vespalib::MemoryUsage memory_usage() const override;
void get_state(const vespalib::slime::Inserter& inserter) const override;
+ std::unique_ptr<NearestNeighborIndexSaver> make_saver() const override;
+ bool load(const fileutil::LoadedBuffer& buf) override;
+
std::vector<Neighbor> find_top_k(uint32_t k, TypedCells vector, uint32_t explore_k) const override;
const DistanceFunction *distance_function() const override { return _distance_func.get(); }
FurthestPriQ top_k_candidates(const TypedCells &vector, uint32_t k) const;
- uint32_t get_entry_docid() const { return _entry_docid; }
- uint32_t get_entry_level() const { return _entry_level; }
+ uint32_t get_entry_docid() const { return _graph.entry_docid; }
+ int32_t get_entry_level() const { return _graph.entry_level; }
// Should only be used by unit tests.
HnswNode get_node(uint32_t docid) const;
void set_node(uint32_t docid, const HnswNode &node);
bool check_link_symmetry() const;
+ uint32_t count_reachable_nodes() const;
+
+ static search::datastore::ArrayStoreConfig make_default_node_store_config();
+ static search::datastore::ArrayStoreConfig make_default_link_store_config();
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.cpp
new file mode 100644
index 00000000000..f02ead86a8d
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.cpp
@@ -0,0 +1,47 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "hnsw_index_loader.h"
+#include "hnsw_graph.h"
+#include <vespa/searchlib/util/fileutil.h>
+
+namespace search::tensor {
+
+HnswIndexLoader::~HnswIndexLoader() {}
+
+HnswIndexLoader::HnswIndexLoader(HnswGraph &graph)
+ : _graph(graph), _ptr(nullptr), _end(nullptr), _failed(false)
+{
+}
+
+bool
+HnswIndexLoader::load(const fileutil::LoadedBuffer& buf)
+{
+ size_t num_readable = buf.size(sizeof(uint32_t));
+ _ptr = static_cast<const uint32_t *>(buf.buffer());
+ _end = _ptr + num_readable;
+ uint32_t entry_docid = next_int();
+ int32_t entry_level = next_int();
+ uint32_t num_nodes = next_int();
+ std::vector<uint32_t> link_array;
+ for (uint32_t docid = 0; docid < num_nodes; ++docid) {
+ uint32_t num_levels = next_int();
+ if (num_levels > 0) {
+ _graph.make_node_for_document(docid, num_levels);
+ for (uint32_t level = 0; level < num_levels; ++level) {
+ uint32_t num_links = next_int();
+ link_array.clear();
+ while (num_links-- > 0) {
+ link_array.push_back(next_int());
+ }
+ _graph.set_link_array(docid, level, link_array);
+ }
+ }
+ }
+ if (_failed) return false;
+ _graph.node_refs.ensure_size(num_nodes);
+ _graph.set_entry_node(entry_docid, entry_level);
+ return true;
+}
+
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.h b/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.h
new file mode 100644
index 00000000000..174f66b95ec
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index_loader.h
@@ -0,0 +1,35 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <cstdint>
+
+namespace search::fileutil { class LoadedBuffer; }
+
+namespace search::tensor {
+
+class HnswGraph;
+
+/**
+ * Implements loading of HNSW graph structure from binary format.
+ **/
+class HnswIndexLoader {
+public:
+ HnswIndexLoader(HnswGraph &graph);
+ ~HnswIndexLoader();
+ bool load(const fileutil::LoadedBuffer& buf);
+private:
+ HnswGraph &_graph;
+ const uint32_t *_ptr;
+ const uint32_t *_end;
+ bool _failed;
+ uint32_t next_int() {
+ if (__builtin_expect((_ptr == _end), false)) {
+ _failed = true;
+ return 0;
+ }
+ return *_ptr++;
+ }
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.cpp
new file mode 100644
index 00000000000..acff30f8cbf
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.cpp
@@ -0,0 +1,57 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "hnsw_index_saver.h"
+#include "hnsw_graph.h"
+#include <vespa/vespalib/util/bufferwriter.h>
+
+namespace search::tensor {
+
+HnswIndexSaver::~HnswIndexSaver() {}
+
+HnswIndexSaver::HnswIndexSaver(const HnswGraph &graph)
+ : _graph_links(graph.links), _meta_data()
+{
+ _meta_data.entry_docid = graph.entry_docid;
+ _meta_data.entry_level = graph.entry_level;
+ size_t num_nodes = graph.node_refs.size();
+ _meta_data.nodes.reserve(num_nodes);
+ for (size_t i = 0; i < num_nodes; ++i) {
+ LevelVector node;
+ auto node_ref = graph.node_refs[i].load_acquire();
+ if (node_ref.valid()) {
+ auto levels = graph.nodes.get(node_ref);
+ for (const auto& links_ref : levels) {
+ auto level = links_ref.load_acquire();
+ node.push_back(level);
+ }
+ }
+ _meta_data.nodes.emplace_back(std::move(node));
+ }
+}
+
+void
+HnswIndexSaver::save(BufferWriter& writer) const
+{
+ writer.write(&_meta_data.entry_docid, sizeof(uint32_t));
+ writer.write(&_meta_data.entry_level, sizeof(int32_t));
+ uint32_t num_nodes = _meta_data.nodes.size();
+ writer.write(&num_nodes, sizeof(uint32_t));
+ for (const auto &node : _meta_data.nodes) {
+ uint32_t num_levels = node.size();
+ writer.write(&num_levels, sizeof(uint32_t));
+ for (auto links_ref : node) {
+ if (links_ref.valid()) {
+ vespalib::ConstArrayRef<uint32_t> link_array = _graph_links.get(links_ref);
+ uint32_t num_links = link_array.size();
+ writer.write(&num_links, sizeof(uint32_t));
+ writer.write(link_array.cbegin(), sizeof(uint32_t)*num_links);
+ } else {
+ uint32_t num_links = 0;
+ writer.write(&num_links, sizeof(uint32_t));
+ }
+ }
+ }
+ writer.flush();
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.h b/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.h
new file mode 100644
index 00000000000..d1d8e0db19d
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index_saver.h
@@ -0,0 +1,37 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "nearest_neighbor_index_saver.h"
+#include "hnsw_graph.h"
+#include <vespa/vespalib/datastore/entryref.h>
+#include <vector>
+
+namespace search::tensor {
+
+/**
+ * Implements saving of HNSW graph structure in binary format.
+ * The constructor takes a snapshot of all meta-data, but
+ * the links will be fetched from the graph in the save()
+ * method.
+ **/
+class HnswIndexSaver : public NearestNeighborIndexSaver {
+public:
+ using LevelVector = std::vector<search::datastore::EntryRef>;
+
+ HnswIndexSaver(const HnswGraph &graph);
+ ~HnswIndexSaver();
+ void save(BufferWriter& writer) const override;
+
+private:
+ struct MetaData {
+ uint32_t entry_docid;
+ int32_t entry_level;
+ std::vector<LevelVector> nodes;
+ MetaData() : entry_docid(0), entry_level(-1), nodes() {}
+ };
+ const HnswGraph::LinkStore &_graph_links;
+ MetaData _meta_data;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
index e7302028996..aca2ce2af66 100644
--- a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
@@ -2,17 +2,22 @@
#pragma once
-#include <cstdint>
-#include <vector>
+#include "distance_function.h"
#include <vespa/eval/tensor/dense/typed_cells.h>
#include <vespa/vespalib/util/generationhandler.h>
#include <vespa/vespalib/util/memoryusage.h>
-#include "distance_function.h"
+#include <cstdint>
+#include <memory>
+#include <vector>
namespace vespalib::slime { struct Inserter; }
+namespace search::fileutil { class LoadedBuffer; }
+
namespace search::tensor {
+class NearestNeighborIndexSaver;
+
/**
* Interface for an index that is used for (approximate) nearest neighbor search.
*/
@@ -35,6 +40,15 @@ public:
virtual vespalib::MemoryUsage memory_usage() const = 0;
virtual void get_state(const vespalib::slime::Inserter& inserter) const = 0;
+ /**
+ * Creates a saver that is used to save the index to binary form.
+ *
+ * This function is always called by the attribute write thread,
+ * and the caller ensures that an attribute read guard is held during the lifetime of the saver.
+ */
+ virtual std::unique_ptr<NearestNeighborIndexSaver> make_saver() const = 0;
+ virtual bool load(const fileutil::LoadedBuffer& buf) = 0;
+
virtual std::vector<Neighbor> find_top_k(uint32_t k,
vespalib::tensor::TypedCells vector,
uint32_t explore_k) const = 0;
diff --git a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.cpp b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.cpp
new file mode 100644
index 00000000000..4b293488737
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.cpp
@@ -0,0 +1,3 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "nearest_neighbor_index_saver.h"
diff --git a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.h b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.h
new file mode 100644
index 00000000000..99d8960ae10
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index_saver.h
@@ -0,0 +1,33 @@
+// Copyright Verizon Media. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+namespace search { class BufferWriter; }
+
+namespace search::tensor {
+
+/**
+ * Interface that is used to save a nearest neighbor index to binary form.
+ *
+ * An instance of this interface must hold a snapshot of the index from the
+ * point in time the instance was created, and then save this to binary form in the save() function.
+ *
+ * The instance is always created by the attribute write thread,
+ * and the caller ensures that an attribute read guard is held during the lifetime of the saver.
+ * Data that might change later must be copied in the constructor.
+ *
+ * A flush thread is calling save() at a later point in time.
+ */
+class NearestNeighborIndexSaver {
+public:
+ virtual ~NearestNeighborIndexSaver() {}
+
+ /**
+ * Saves the index in binary form using the given writer.
+ *
+ * It is the responsibility of the implementer to call BufferWriter::flush() at the end.
+ */
+ virtual void save(BufferWriter& writer) const = 0;
+};
+
+}
diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/duper/InfraApplication.java b/service-monitor/src/main/java/com/yahoo/vespa/service/duper/InfraApplication.java
index 299ea47b257..d113b34f3c6 100644
--- a/service-monitor/src/main/java/com/yahoo/vespa/service/duper/InfraApplication.java
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/duper/InfraApplication.java
@@ -32,6 +32,7 @@ import java.util.stream.Collectors;
* @author freva
*/
public abstract class InfraApplication implements InfraApplicationApi {
+
private static final TenantName TENANT_NAME = TenantName.from("hosted-vespa");
private final ApplicationId applicationId;
diff --git a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/InfraApplicationApi.java b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/InfraApplicationApi.java
index b4b61682a33..4e7a557ffc7 100644
--- a/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/InfraApplicationApi.java
+++ b/service-monitor/src/main/java/com/yahoo/vespa/service/monitor/InfraApplicationApi.java
@@ -14,7 +14,9 @@ import java.util.Optional;
* @author hakonhall
*/
public interface InfraApplicationApi {
+
ApplicationId getApplicationId();
Capacity getCapacity();
ClusterSpec getClusterSpecWithVersion(Version version);
+
}
diff --git a/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.h b/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.h
index 777230566f2..ea5ccf0659b 100644
--- a/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.h
+++ b/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.h
@@ -108,7 +108,7 @@ public:
/**
* This fetches the object without modifying the lru list.
*/
- const V & get(const K & key) { return HashTable::find(key)->second._value; }
+ const V & get(const K & key) const { return HashTable::find(key)->second._value; }
/**
* This simply erases the object.
@@ -133,13 +133,11 @@ public:
insert_result insert(const K & key, V && value);
/**
- * Return the object with the given key. If it does not exist an empty one will be created.
- * This can be used as an insert.
- * Object is then put at head of LRU list.
+ * Return pointer to the object with the given key.
+ * Object is then put at head of LRU list if found.
+ * If not found nullptr is returned.
*/
- const V & operator [] (const K & key) const {
- return const_cast<lrucache_map<P> *>(this)->findAndRef(key).second._value;
- }
+ V * findAndRef(const K & key);
/**
* Return the object with the given key. If it does not exist an empty one will be created.
@@ -183,7 +181,6 @@ private:
* Implements the resize of the hashtable
*/
void move(NodeStore && oldStore) override;
- internal_iterator findAndRef(const K & key);
void ref(const internal_iterator & it);
insert_result insert(value_type && value);
void removeOld();
diff --git a/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.hpp b/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.hpp
index d8d55c9b8c4..839a93cc5ca 100644
--- a/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.hpp
+++ b/staging_vespalib/src/vespa/vespalib/stllike/lrucache_map.hpp
@@ -263,14 +263,17 @@ lrucache_map<P>::operator [] (const K & key)
}
template< typename P >
-typename lrucache_map<P>::internal_iterator
+typename P::Value *
lrucache_map<P>::findAndRef(const K & key)
{
internal_iterator found = HashTable::find(key);
if (found != HashTable::end()) {
- ref(found);
+ if (size()*2 > capacity()) {
+ ref(found);
+ }
+ return &found->second._value;
}
- return found;
+ return nullptr;
}
}
diff --git a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
index 73612452b09..90eb18c23ef 100644
--- a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
+++ b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.cpp
@@ -15,14 +15,16 @@ SingleExecutor::SingleExecutor(uint32_t taskLimit)
_producerCondition(),
_thread(*this),
_lastAccepted(0),
- _maxPending(0),
+ _queueSize(),
_wakeupConsumerAt(0),
_producerNeedWakeupAt(0),
- _wp(0)
+ _wp(0),
+ _closed(false)
{
_thread.start();
}
SingleExecutor::~SingleExecutor() {
+ shutdown();
sync();
_thread.stop().join();
}
@@ -32,16 +34,6 @@ SingleExecutor::getNumThreads() const {
return 1;
}
-uint64_t
-SingleExecutor::addTask(Task::UP task) {
- Lock guard(_mutex);
- wait_for_room(guard);
- uint64_t wp = _wp.load(std::memory_order_relaxed);
- _tasks[index(wp)] = std::move(task);
- _wp.store(wp + 1, std::memory_order_release);
- return wp;
-}
-
void
SingleExecutor::sleepProducer(Lock & lock, duration maxWaitTime, uint64_t wakeupAt) {
_producerNeedWakeupAt.store(wakeupAt, std::memory_order_relaxed);
@@ -51,7 +43,17 @@ SingleExecutor::sleepProducer(Lock & lock, duration maxWaitTime, uint64_t wakeup
Executor::Task::UP
SingleExecutor::execute(Task::UP task) {
- uint64_t wp = addTask(std::move(task));
+ uint64_t wp;
+ {
+ Lock guard(_mutex);
+ if (_closed) {
+ return task;
+ }
+ wait_for_room(guard);
+ wp = _wp.load(std::memory_order_relaxed);
+ _tasks[index(wp)] = std::move(task);
+ _wp.store(wp + 1, std::memory_order_release);
+ }
if (wp == _wakeupConsumerAt.load(std::memory_order_relaxed)) {
_consumerCondition.notify_one();
}
@@ -88,6 +90,13 @@ SingleExecutor::sync() {
return *this;
}
+SingleExecutor &
+SingleExecutor::shutdown() {
+ Lock lock(_mutex);
+ _closed = true;
+ return *this;
+}
+
void
SingleExecutor::run() {
while (!_thread.stopped()) {
@@ -112,10 +121,6 @@ SingleExecutor::drain_tasks() {
void
SingleExecutor::run_tasks_till(uint64_t available) {
uint64_t consumed = _rp.load(std::memory_order_relaxed);
- uint64_t left = available - consumed;
- if (_maxPending.load(std::memory_order_relaxed) < left) {
- _maxPending.store(left, std::memory_order_relaxed);
- }
uint64_t wakeupLimit = _producerNeedWakeupAt.load(std::memory_order_relaxed);
while (consumed < available) {
Task::UP task = std::move(_tasks[index(consumed)]);
@@ -137,6 +142,7 @@ SingleExecutor::wait_for_room(Lock & lock) {
_taskLimit = _wantedTaskLimit.load();
taskLimit = _taskLimit;
}
+ _queueSize.add(numTasks());
while (numTasks() >= _taskLimit.load(std::memory_order_relaxed)) {
sleepProducer(lock, 10ms, wp - taskLimit/4);
}
@@ -144,10 +150,11 @@ SingleExecutor::wait_for_room(Lock & lock) {
ThreadExecutor::Stats
SingleExecutor::getStats() {
+ Lock lock(_mutex);
uint64_t accepted = _wp.load(std::memory_order_relaxed);
- Stats stats(_maxPending, (accepted - _lastAccepted), 0);
+ Stats stats(_queueSize, (accepted - _lastAccepted), 0);
_lastAccepted = accepted;
- _maxPending = 0;
+ _queueSize = Stats::QueueSizeT() ;
return stats;
}
diff --git a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
index 9c3ebb4caf7..3d759769ea3 100644
--- a/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
+++ b/staging_vespalib/src/vespa/vespalib/util/singleexecutor.h
@@ -27,9 +27,9 @@ public:
size_t getNumThreads() const override;
uint32_t getTaskLimit() const { return _taskLimit.load(std::memory_order_relaxed); }
Stats getStats() override;
+ SingleExecutor & shutdown() override;
private:
using Lock = std::unique_lock<std::mutex>;
- uint64_t addTask(Task::UP task);
void drain(Lock & lock);
void run() override;
void drain_tasks();
@@ -52,10 +52,11 @@ private:
std::condition_variable _producerCondition;
vespalib::Thread _thread;
uint64_t _lastAccepted;
- std::atomic<uint64_t> _maxPending;
+ Stats::QueueSizeT _queueSize;
std::atomic<uint64_t> _wakeupConsumerAt;
std::atomic<uint64_t> _producerNeedWakeupAt;
std::atomic<uint64_t> _wp;
+ bool _closed;
};
}
diff --git a/standalone-container/src/main/sh/standalone-container.sh b/standalone-container/src/main/sh/standalone-container.sh
index b49744ebe49..b8025b9629b 100755
--- a/standalone-container/src/main/sh/standalone-container.sh
+++ b/standalone-container/src/main/sh/standalone-container.sh
@@ -112,13 +112,13 @@ StartCommand() {
local service_regex='^[0-9a-zA-Z_-]+$'
if ! [[ "$service" =~ $service_regex ]]; then
- Fail "Service must match regex '$service_regex'"
+ Fail "Service must match regex '$service_regex'"
fi
local pidfile="$VESPA_HOME/var/run/$service.pid"
if [ "$force" = false ] && test -r "$pidfile"; then
- echo "$service is already running as PID $(< "$pidfile") according to $pidfile"
- return
+ echo "$service is already running as PID $(< "$pidfile") according to $pidfile"
+ return
fi
# common setup
@@ -199,71 +199,71 @@ Kill() {
local -i now
if ! now=$(date +%s); then
- Fail "Failed to get the current date in seconds since epoch"
+ Fail "Failed to get the current date in seconds since epoch"
fi
local -i timeout=$(( now + 300 ))
local has_killed=false
while true; do
- local ps_output=""
- if ! ps_output=$(ps -p "$pid" -o user= -o comm=); then
- # success
- return
- fi
-
- local user comm
- read -r user comm <<< "$ps_output"
-
- if test "$user" != "$expected_user"; then
- echo "Warning: Pid collision ($pid): Expected user $expected_user but found $user."
- echo "Will assume original process has died."
- return
- fi
-
- if test "$comm" != "$expected_comm"; then
- echo "Warning: Pid collision ($pid): Expected program $expected_comm but found $comm."
- echo "Will assume original process has died."
- return
- fi
-
- if ! "$has_killed"; then
- if $force; then
- if ! kill -KILL "$pid"; then
- Fail "Failed to kill $pid"
- fi
- else
- if ! kill "$pid"; then
- Fail "Failed to kill $pid"
- fi
- fi
-
- has_killed=true
- fi
-
- sleep 1
-
- now=$(date +%s)
- if (( now >= timeout )); then
- Fail "Process $pid still exists after $timeout seconds, giving up"
- fi
+ local ps_output=""
+ if ! ps_output=$(ps -p "$pid" -o user= -o comm=); then
+ # success
+ return
+ fi
+
+ local user comm
+ read -r user comm <<< "$ps_output"
+
+ if test "$user" != "$expected_user"; then
+ echo "Warning: Pid collision ($pid): Expected user $expected_user but found $user."
+ echo "Will assume original process has died."
+ return
+ fi
+
+ if test "$comm" != "$expected_comm"; then
+ echo "Warning: Pid collision ($pid): Expected program $expected_comm but found $comm."
+ echo "Will assume original process has died."
+ return
+ fi
+
+ if ! "$has_killed"; then
+ if $force; then
+ if ! kill -KILL "$pid"; then
+ Fail "Failed to kill $pid"
+ fi
+ else
+ if ! kill "$pid"; then
+ Fail "Failed to kill $pid"
+ fi
+ fi
+
+ has_killed=true
+ fi
+
+ sleep 1
+
+ now=$(date +%s)
+ if (( now >= timeout )); then
+ Fail "Process $pid still exists after $timeout seconds, giving up"
+ fi
done
}
StopCommand() {
local user="$1"
- local force="$2"
- local service="$3"
+ local service="$2"
+ local force="$3"
local pidfile="$VESPA_HOME/var/run/$service.pid"
if ! test -r "$pidfile"; then
- echo "$service is not running"
- return
+ echo "$service is not running"
+ return
fi
local pid=$(< "$pidfile")
if ! [[ "$pid" =~ ^[0-9]+$ ]]; then
- Fail "Pid file '$pidfile' does not contain a valid pid: $pid"
+ Fail "Pid file '$pidfile' does not contain a valid pid: $pid"
fi
Kill "$force" "$user" java "$pid"
@@ -272,7 +272,7 @@ StopCommand() {
Main() {
if (( $# == 0 )); then
- Usage
+ Usage
fi
local command="$1"
@@ -284,49 +284,49 @@ Main() {
local -a jvm_arguments=()
while (( $# > 0 )); do
- case "$1" in
- --help|-h) Usage ;;
- --service|-s)
- service="$2"
- shift 2
- ;;
- --user|-u)
- user="$2"
- shift 2
- ;;
- --force|-f)
- force=true
- shift
- ;;
- --)
- shift
- jvm_arguments=("$@")
- break
- ;;
- *) break ;;
- esac
+ case "$1" in
+ --help|-h) Usage ;;
+ --service|-s)
+ service="$2"
+ shift 2
+ ;;
+ --user|-u)
+ user="$2"
+ shift 2
+ ;;
+ --force|-f)
+ force=true
+ shift
+ ;;
+ --)
+ shift
+ jvm_arguments=("$@")
+ break
+ ;;
+ *) break ;;
+ esac
done
# Service name will be included in paths and possibly environment variable
# names, so be restrictive.
local service_regex='^[a-zA-Z0-9_-]+$'
if test -z "$service"; then
- Fail "SERVICE not specified"
+ Fail "SERVICE not specified"
elif ! [[ "$service" =~ $service_regex ]]; then
- Fail "Service must math the regex '$service_regex'"
+ Fail "Service must math the regex '$service_regex'"
fi
if ! getent passwd "$user" &> /dev/null; then
- Fail "Bad user ($user): not found in passwd"
+ Fail "Bad user ($user): not found in passwd"
elif test "$(id -un)" != "$user"; then
- Fail "${0##*/} must be started by $user"
+ Fail "${0##*/} must be started by $user"
fi
case "$command" in
- help) Usage ;;
- start) StartCommand "$service" "$force" "${jvm_arguments[@]}" ;;
- stop) StopCommand "$user" "$service" "$force" "$@" ;;
- *) Fail "Unknown command '$command'" ;;
+ help) Usage ;;
+ start) StartCommand "$service" "$force" "${jvm_arguments[@]}" ;;
+ stop) StopCommand "$user" "$service" "$force" "$@" ;;
+ *) Fail "Unknown command '$command'" ;;
esac
}
diff --git a/storage/src/tests/distributor/btree_bucket_database_test.cpp b/storage/src/tests/distributor/btree_bucket_database_test.cpp
index 43d74ca2fb5..a2518272a7f 100644
--- a/storage/src/tests/distributor/btree_bucket_database_test.cpp
+++ b/storage/src/tests/distributor/btree_bucket_database_test.cpp
@@ -9,8 +9,8 @@ using namespace ::testing;
namespace storage::distributor {
-INSTANTIATE_TEST_CASE_P(BTreeDatabase, BucketDatabaseTest,
- ::testing::Values(std::make_shared<BTreeBucketDatabase>()));
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(BTreeDatabase, BucketDatabaseTest,
+ ::testing::Values(std::make_shared<BTreeBucketDatabase>()));
using document::BucketId;
diff --git a/storage/src/tests/distributor/mapbucketdatabasetest.cpp b/storage/src/tests/distributor/mapbucketdatabasetest.cpp
index 0ae4a49530e..2c000f6b5db 100644
--- a/storage/src/tests/distributor/mapbucketdatabasetest.cpp
+++ b/storage/src/tests/distributor/mapbucketdatabasetest.cpp
@@ -5,7 +5,7 @@
namespace storage::distributor {
-INSTANTIATE_TEST_CASE_P(MapDatabase, BucketDatabaseTest,
- ::testing::Values(std::make_shared<MapBucketDatabase>()));
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(MapDatabase, BucketDatabaseTest,
+ ::testing::Values(std::make_shared<MapBucketDatabase>()));
}
diff --git a/storage/src/vespa/storage/config/stor-communicationmanager.def b/storage/src/vespa/storage/config/stor-communicationmanager.def
index 2a2a840dd4e..8f5b22aa7fa 100644
--- a/storage/src/vespa/storage/config/stor-communicationmanager.def
+++ b/storage/src/vespa/storage/config/stor-communicationmanager.def
@@ -33,6 +33,8 @@ mbus.rpctargetcache.ttl double default = 600
## Any value below 1 will be 1.
mbus.num_threads int default=4
+mbus.optimize_for enum {LATENCY, THROUGHPUT} default = LATENCY
+
## Enable to use above thread pool for encoding replies
## False will use network(fnet) thread
mbus.dispatch_on_encode bool default=true
diff --git a/storage/src/vespa/storage/persistence/persistencethread.cpp b/storage/src/vespa/storage/persistence/persistencethread.cpp
index dd44c96555b..4bcd92293d3 100644
--- a/storage/src/vespa/storage/persistence/persistencethread.cpp
+++ b/storage/src/vespa/storage/persistence/persistencethread.cpp
@@ -36,8 +36,8 @@ PersistenceThread::PersistenceThread(ServiceLayerComponentRegister& compReg,
{
std::ostringstream threadName;
threadName << "Disk " << _env._partition << " thread " << _stripeId;
- _component.reset(new ServiceLayerComponent(compReg, threadName.str()));
- _bucketOwnershipNotifier.reset(new BucketOwnershipNotifier(*_component, filestorHandler));
+ _component = std::make_unique<ServiceLayerComponent>(compReg, threadName.str());
+ _bucketOwnershipNotifier = std::make_unique<BucketOwnershipNotifier>(*_component, filestorHandler);
framework::MilliSecTime maxProcessingTime(60 * 1000);
framework::MilliSecTime waitTime(1000);
_thread = _component->startThread(*this, maxProcessingTime, waitTime);
@@ -473,11 +473,10 @@ PersistenceThread::handleSplitBucket(api::SplitBucketCommand& cmd)
const document::Bucket &target(i == 0 ? target1 : target2);
uint16_t disk(i == 0 ? lock1.disk : lock2.disk);
assert(target.getBucketId().getRawId() != 0);
- targets.push_back(TargetInfo(
- _env.getBucketDatabase(target.getBucketSpace()).get(
+ targets.emplace_back(_env.getBucketDatabase(target.getBucketSpace()).get(
target.getBucketId(), "PersistenceThread::handleSplitBucket - Target",
StorBucketDatabase::CREATE_IF_NONEXISTING),
- FileStorHandler::RemapInfo(target, disk)));
+ FileStorHandler::RemapInfo(target, disk));
targets.back().first->setBucketInfo(_env.getBucketInfo(target, disk));
targets.back().first->disk = disk;
}
@@ -795,7 +794,7 @@ PersistenceThread::handleCommand(api::StorageCommand& msg)
{
_context = spi::Context(msg.getLoadType(), msg.getPriority(), msg.getTrace().getLevel());
MessageTracker::UP mtracker(handleCommandSplitByType(msg));
- if (mtracker) {
+ if (mtracker && ! _context.getTrace().getRoot().isEmpty()) {
if (mtracker->getReply()) {
mtracker->getReply()->getTrace().getRoot().addChild(_context.getTrace().getRoot());
} else {
@@ -844,11 +843,11 @@ PersistenceThread::processMessage(api::StorageMessage& msg)
LOG(debug, "Handling command: %s", msg.toString().c_str());
LOG(spam, "Message content: %s", msg.toString(true).c_str());
auto tracker(handleCommand(initiatingCommand));
- if (!tracker.get()) {
+ if (!tracker) {
LOG(debug, "Received unsupported command %s", msg.getType().getName().c_str());
} else {
tracker->generateReply(initiatingCommand);
- if ((tracker->getReply().get()
+ if ((tracker->getReply()
&& tracker->getReply()->getResult().failed())
|| tracker->getResult().failed())
{
diff --git a/storage/src/vespa/storage/storageserver/communicationmanager.cpp b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
index 978d434847e..fa2b0cda018 100644
--- a/storage/src/vespa/storage/storageserver/communicationmanager.cpp
+++ b/storage/src/vespa/storage/storageserver/communicationmanager.cpp
@@ -415,6 +415,7 @@ void CommunicationManager::configure(std::unique_ptr<CommunicationManagerConfig>
params.setNumThreads(std::max(1, config->mbus.numThreads));
params.setDispatchOnDecode(config->mbus.dispatchOnDecode);
params.setDispatchOnEncode(config->mbus.dispatchOnEncode);
+ params.setTcpNoDelay(config->mbus.optimizeFor == CommunicationManagerConfig::Mbus::OptimizeFor::LATENCY);
params.setIdentity(mbus::Identity(_component.getIdentity()));
if (config->mbusport != -1) {
diff --git a/storageapi/src/tests/mbusprot/storageprotocoltest.cpp b/storageapi/src/tests/mbusprot/storageprotocoltest.cpp
index 2e5eb115844..0f628f59aac 100644
--- a/storageapi/src/tests/mbusprot/storageprotocoltest.cpp
+++ b/storageapi/src/tests/mbusprot/storageprotocoltest.cpp
@@ -20,7 +20,7 @@
#include <iomanip>
#include <sstream>
-#include <gtest/gtest.h>
+#include <vespa/vespalib/gtest/gtest.h>
using namespace ::testing;
@@ -105,11 +105,10 @@ std::string version_as_gtest_string(TestParamInfo<vespalib::Version> info) {
}
-// TODO replace with INSTANTIATE_TEST_SUITE_P on newer gtest versions
-INSTANTIATE_TEST_CASE_P(MultiVersionTest, StorageProtocolTest,
- Values(vespalib::Version(6, 240, 0),
- vespalib::Version(7, 41, 19)),
- version_as_gtest_string);
+VESPA_GTEST_INSTANTIATE_TEST_SUITE_P(MultiVersionTest, StorageProtocolTest,
+ Values(vespalib::Version(6, 240, 0),
+ vespalib::Version(7, 41, 19)),
+ version_as_gtest_string);
namespace {
mbus::Message::UP lastCommand;
diff --git a/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java b/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java
index 82d859b08bd..55b3af93050 100644
--- a/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java
+++ b/tenant-auth/src/main/java/ai/vespa/hosted/auth/ApiAuthenticator.java
@@ -13,10 +13,12 @@ public class ApiAuthenticator implements ai.vespa.hosted.api.ApiAuthenticator {
.map(certificateFile -> ControllerHttpClient.withKeyAndCertificate(Properties.apiEndpoint(),
Properties.apiKeyFile(),
certificateFile))
- .orElseGet(() ->
- ControllerHttpClient.withSignatureKey(Properties.apiEndpoint(),
- Properties.apiKeyFile(),
- Properties.application()));
+ .or(() -> Properties.apiKey().map(apiKey -> ControllerHttpClient.withSignatureKey(Properties.apiEndpoint(),
+ apiKey,
+ Properties.application())))
+ .orElseGet(() -> ControllerHttpClient.withSignatureKey(Properties.apiEndpoint(),
+ Properties.apiKeyFile(),
+ Properties.application()));
}
}
diff --git a/vespa-hadoop/src/main/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperation.java b/vespa-hadoop/src/main/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperation.java
index 219996ee9aa..94176bbb658 100644
--- a/vespa-hadoop/src/main/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperation.java
+++ b/vespa-hadoop/src/main/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperation.java
@@ -6,7 +6,6 @@ import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
import com.yahoo.vespa.hadoop.mapreduce.util.TupleTools;
import com.yahoo.vespa.hadoop.mapreduce.util.VespaConfiguration;
-import org.apache.commons.lang.exception.ExceptionUtils;
import org.apache.pig.EvalFunc;
import org.apache.pig.PigWarning;
import org.apache.pig.data.DataBag;
@@ -19,6 +18,9 @@ import org.joda.time.DateTime;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.io.UncheckedIOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.*;
@@ -125,14 +127,11 @@ public class VespaDocumentOperation extends EvalFunc<String> {
if (statusReporter != null) {
statusReporter.incrCounter("Vespa Document Operation Counters", "Document operation failed", 1);
}
- warn("No valid document id template found. Skipping.", PigWarning.UDF_WARNING_1);
+ warnLog("No valid document id template found. Skipping.", PigWarning.UDF_WARNING_1);
return null;
}
if (operation == null) {
- if (statusReporter != null) {
- statusReporter.incrCounter("Vespa Document Operation Counters", "Document operation failed", 1);
- }
- warn("No valid operation found. Skipping.", PigWarning.UDF_WARNING_1);
+ warnLog("No valid operation found. Skipping.", PigWarning.UDF_WARNING_2);
return null;
}
@@ -149,7 +148,7 @@ public class VespaDocumentOperation extends EvalFunc<String> {
// create json
json = create(operation, docId, fields, properties, inputSchema);
if (json == null || json.length() == 0) {
- warn("No valid document operation could be created.", PigWarning.UDF_WARNING_1);
+ warnLog("No valid document operation could be created.", PigWarning.UDF_WARNING_3);
return null;
}
@@ -162,8 +161,8 @@ public class VespaDocumentOperation extends EvalFunc<String> {
sb.append("Caught exception processing input row: \n");
sb.append(tuple.toString());
sb.append("\nException: ");
- sb.append(ExceptionUtils.getStackTrace(e));
- warn(sb.toString(), PigWarning.UDF_WARNING_1);
+ sb.append(getStackTraceAsString(e));
+ warnLog(sb.toString(), PigWarning.UDF_WARNING_4);
return null;
}
if (statusReporter != null) {
@@ -644,4 +643,21 @@ public class VespaDocumentOperation extends EvalFunc<String> {
}
g.writeEndArray();
}
+
+ // copied from vespajlib for reducing dependency and building with JDK 8
+ private static String getStackTraceAsString(Throwable throwable) {
+ try (StringWriter stringWriter = new StringWriter();
+ PrintWriter printWriter = new PrintWriter(stringWriter, true)) {
+ throwable.printStackTrace(printWriter);
+ return stringWriter.getBuffer().toString();
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }
+
+ // wrapper to emit logs
+ private void warnLog(String msg, PigWarning warning) {
+ warn(msg, warning);
+ System.err.println(msg);
+ }
}
diff --git a/vespa-hadoop/src/test/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperationTest.java b/vespa-hadoop/src/test/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperationTest.java
index 72d0a2ec069..67003273cac 100644
--- a/vespa-hadoop/src/test/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperationTest.java
+++ b/vespa-hadoop/src/test/java/com/yahoo/vespa/hadoop/pig/VespaDocumentOperationTest.java
@@ -86,7 +86,6 @@ public class VespaDocumentOperationTest {
@Test
public void requireThatUDFCorrectlyGeneratesRemoveBagAsMapOperation() throws Exception {
-
DataBag bag = BagFactory.getInstance().newDefaultBag();
Schema innerObjectSchema = new Schema();
@@ -249,6 +248,26 @@ public class VespaDocumentOperationTest {
}
@Test
+ public void requireThatUDFReturnsNullWhenExceptionHappens() throws IOException {
+ Schema schema = new Schema();
+ Tuple tuple = TupleFactory.getInstance().newTuple();
+
+ // broken DELTA format that would throw internally
+ Map<String, Double> tensor = new HashMap<String, Double>() {{
+ put("xlabel1", 2.0); // missing : between 'x' and 'label1'
+ }};
+
+ addToTuple("id", DataType.CHARARRAY, "123", schema, tuple);
+ addToTuple("tensor", DataType.MAP, tensor, schema, tuple);
+
+ VespaDocumentOperation docOp = new VespaDocumentOperation("docid=empty", "create-tensor-fields=tensor");
+ docOp.setInputSchema(schema);
+ String json = docOp.exec(tuple);
+
+ assertNull(json);
+ }
+
+ @Test
public void requireThatUDFCorrectlyGeneratesRemoveOperation() throws Exception {
String json = getDocumentOperationJson("operation=remove", "docid=id:<application>:metrics::<name>-<date>");
ObjectMapper m = new ObjectMapper();
diff --git a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/DeployMojo.java b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/DeployMojo.java
index cc714f38290..a798c2ad6df 100644
--- a/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/DeployMojo.java
+++ b/vespa-maven-plugin/src/main/java/ai/vespa/hosted/plugin/DeployMojo.java
@@ -62,15 +62,16 @@ public class DeployMojo extends AbstractVespaDeploymentMojo {
private void tailLogs(ApplicationId id, ZoneId zone, long run) throws MojoFailureException, MojoExecutionException {
DeploymentLog log = controller.followDeploymentUntilDone(id, zone, run, this::print);
switch (log.status()) {
- case success: return;
- case error: throw new MojoExecutionException("Unexpected error during deployment; see log for details");
- case aborted: throw new MojoFailureException("Deployment was aborted, probably by a newer deployment");
- case outOfCapacity: throw new MojoFailureException("No capacity left in zone; please contact the Vespa team");
- case deploymentFailed: throw new MojoFailureException("Deployment failed; see log for details");
- case installationFailed: throw new MojoFailureException("Installation failed; see Vespa log for details");
- case running: throw new MojoFailureException("Deployment not completed");
- case testFailure: throw new IllegalStateException("Unexpected status; tests are not run for manual deployments");
- default: throw new IllegalArgumentException("Unexpected status '" + log.status() + "'");
+ case success: return;
+ case error: throw new MojoExecutionException("Unexpected error during deployment; see log for details");
+ case aborted: throw new MojoFailureException("Deployment was aborted, probably by a newer deployment");
+ case outOfCapacity: throw new MojoFailureException("No capacity left in zone; please contact the Vespa team");
+ case deploymentFailed: throw new MojoFailureException("Deployment failed; see log for details");
+ case installationFailed: throw new MojoFailureException("Installation failed; see Vespa log for details");
+ case running: throw new MojoFailureException("Deployment not completed");
+ case endpointCertificateTimeout: throw new MojoFailureException("Endpoint certificate not ready in time; please contact Vespa team");
+ case testFailure: throw new IllegalStateException("Unexpected status; tests are not run for manual deployments");
+ default: throw new IllegalArgumentException("Unexpected status '" + log.status() + "'");
}
}
diff --git a/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/FeederParams.java b/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/FeederParams.java
index 98394a56694..c1e164f7fe8 100644
--- a/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/FeederParams.java
+++ b/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/FeederParams.java
@@ -34,7 +34,8 @@ class FeederParams {
private boolean benchmarkMode = false;
private int numDispatchThreads = 1;
private int maxPending = 0;
- private int numConnectionsPerTarget = 2;
+ private int numConnectionsPerTarget = 1;
+ private long numMessagesToSend = Long.MAX_VALUE;
private List<InputStream> inputStreams = new ArrayList<>();
FeederParams() {
@@ -84,10 +85,9 @@ class FeederParams {
}
int getNumConnectionsPerTarget() { return numConnectionsPerTarget; }
- FeederParams setNumConnectionsPerTarget(int numConnectionsPerTarget) {
- this.numConnectionsPerTarget = numConnectionsPerTarget;
- return this;
- }
+
+ long getNumMessagesToSend() { return numMessagesToSend; }
+
boolean isSerialTransferEnabled() {
return maxPending == 1;
}
@@ -116,6 +116,7 @@ class FeederParams {
opts.addOption("b", "mode", true, "Mode for benchmarking.");
opts.addOption("o", "output", true, "File to write to. Extensions gives format (.xml, .json, .vespa) json will be produced if no extension.");
opts.addOption("c", "numconnections", true, "Number of connections per host.");
+ opts.addOption("l", "nummessages", true, "Number of messages to send (all is default).");
CommandLine cmd = new DefaultParser().parse(opts, args);
@@ -142,6 +143,9 @@ class FeederParams {
if (cmd.hasOption('s')) {
setSerialTransfer();
}
+ if (cmd.hasOption('l')) {
+ numMessagesToSend = Long.valueOf(cmd.getOptionValue('l').trim());
+ }
if ( !cmd.getArgList().isEmpty()) {
inputStreams.clear();
diff --git a/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/SimpleFeeder.java b/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/SimpleFeeder.java
index 2925ea08de9..556d9bd60c7 100644
--- a/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/SimpleFeeder.java
+++ b/vespa_feed_perf/src/main/java/com/yahoo/vespa/feed/perf/SimpleFeeder.java
@@ -65,6 +65,7 @@ public class SimpleFeeder implements ReplyHandler {
private final RPCMessageBus mbus;
private final SourceSession session;
private final int numThreads;
+ private final long numMessagesToSend;
private final Destination destination;
private final boolean benchmarkMode;
private final static long REPORT_INTERVAL = TimeUnit.SECONDS.toMillis(10);
@@ -81,18 +82,20 @@ public class SimpleFeeder implements ReplyHandler {
private final Destination destination;
private final FeedReader reader;
private final Executor executor;
- AtomicReference<Throwable> failure;
+ private final long messagesToSend;
+ private final AtomicReference<Throwable> failure;
- Metrics(Destination destination, FeedReader reader, Executor executor, AtomicReference<Throwable> failure) {
+ Metrics(Destination destination, FeedReader reader, Executor executor, AtomicReference<Throwable> failure, long messagesToSend) {
this.destination = destination;
this.reader = reader;
this.executor = executor;
+ this.messagesToSend = messagesToSend;
this.failure = failure;
}
long feed() throws Throwable {
long numMessages = 0;
- while (failure.get() == null) {
+ while ((failure.get() == null) && (numMessages < messagesToSend)) {
FeedOperation op = reader.read();
if (op.getType() == FeedOperation.Type.INVALID) {
break;
@@ -341,6 +344,7 @@ public class SimpleFeeder implements ReplyHandler {
inputStreams = params.getInputStreams();
out = params.getStdOut();
numThreads = params.getNumDispatchThreads();
+ numMessagesToSend = params.getNumMessagesToSend();
mbus = newMessageBus(docTypeMgr, params);
session = newSession(mbus, this, params.getMaxPending());
docTypeMgr.configure(params.getConfigId());
@@ -380,7 +384,7 @@ public class SimpleFeeder implements ReplyHandler {
printHeader(out);
long numMessagesSent = 0;
for (InputStream in : inputStreams) {
- Metrics m = new Metrics(destination, createFeedReader(in), executor, failure);
+ Metrics m = new Metrics(destination, createFeedReader(in), executor, failure, numMessagesToSend);
numMessagesSent += m.feed();
}
while (failure.get() == null && numReplies.get() < numMessagesSent) {
diff --git a/vespa_feed_perf/src/main/sh/vespa-feed-perf b/vespa_feed_perf/src/main/sh/vespa-feed-perf
index 466cd2ee98c..d6ccf0e4fc5 100755
--- a/vespa_feed_perf/src/main/sh/vespa-feed-perf
+++ b/vespa_feed_perf/src/main/sh/vespa-feed-perf
@@ -74,4 +74,4 @@ findhost
# END environment bootstrap section
-exec java -jar $VESPA_HOME/lib/jars/vespa_feed_perf-jar-with-dependencies.jar "$@"
+exec java -XX:+UseParallelGC -XX:ParallelGCThreads=4 -jar $VESPA_HOME/lib/jars/vespa_feed_perf-jar-with-dependencies.jar "$@"
diff --git a/vespa_feed_perf/src/test/java/com/yahoo/vespa/feed/perf/FeederParamsTest.java b/vespa_feed_perf/src/test/java/com/yahoo/vespa/feed/perf/FeederParamsTest.java
index 8682adc0935..13e307d9973 100644
--- a/vespa_feed_perf/src/test/java/com/yahoo/vespa/feed/perf/FeederParamsTest.java
+++ b/vespa_feed_perf/src/test/java/com/yahoo/vespa/feed/perf/FeederParamsTest.java
@@ -88,12 +88,19 @@ public class FeederParamsTest {
}
@Test
public void requireThatNumConnectionsAreParsed() throws ParseException, FileNotFoundException {
- assertEquals(2, new FeederParams().getNumConnectionsPerTarget());
- assertEquals(17, new FeederParams().parseArgs("-c 17").getNumConnectionsPerTarget());
+ assertEquals(1, new FeederParams().getNumConnectionsPerTarget());
+ assertEquals(16, new FeederParams().parseArgs("-c 16").getNumConnectionsPerTarget());
assertEquals(17, new FeederParams().parseArgs("--numconnections", "17").getNumConnectionsPerTarget());
}
@Test
+ public void requireThatNumMessagesToSendAreParsed() throws ParseException, FileNotFoundException {
+ assertEquals(Long.MAX_VALUE, new FeederParams().getNumMessagesToSend());
+ assertEquals(18, new FeederParams().parseArgs("-l 18").getNumMessagesToSend());
+ assertEquals(19, new FeederParams().parseArgs("--nummessages", "19").getNumMessagesToSend());
+ }
+
+ @Test
public void requireThatDumpStreamAreParsed() throws ParseException, IOException {
assertNull(new FeederParams().getDumpStream());
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
index d537e873600..e6bc2211bea 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/vespa/http/server/ClientFeederV3.java
@@ -53,7 +53,6 @@ class ClientFeederV3 {
private final Metric metric;
private Instant prevOpsPerSecTime = Instant.now();
private double operationsForOpsPerSec = 0d;
-
private final Object monitor = new Object();
private final StreamReaderV3 streamReaderV3;
private final AtomicInteger ongoingRequests = new AtomicInteger(0);
diff --git a/vespajlib/src/main/java/com/yahoo/system/ProcessExecuter.java b/vespajlib/src/main/java/com/yahoo/system/ProcessExecuter.java
index cceac7e84bb..c455929bf51 100644
--- a/vespajlib/src/main/java/com/yahoo/system/ProcessExecuter.java
+++ b/vespajlib/src/main/java/com/yahoo/system/ProcessExecuter.java
@@ -16,6 +16,14 @@ import com.yahoo.collections.Pair;
*/
public class ProcessExecuter {
+ private final boolean override_log_control;
+ public ProcessExecuter(boolean override_log_control) {
+ this.override_log_control = override_log_control;
+ }
+ public ProcessExecuter() {
+ this(false);
+ }
+
/**
* Executes the given command synchronously without timeout.
*
@@ -39,6 +47,10 @@ public class ProcessExecuter {
ProcessBuilder pb = new ProcessBuilder(command);
StringBuilder ret = new StringBuilder();
pb.environment().remove("VESPA_LOG_TARGET");
+ if (override_log_control) {
+ pb.environment().remove("VESPA_LOG_CONTROL_FILE");
+ pb.environment().put("VESPA_SERVICE_NAME", "exec-" + command[0]);
+ }
pb.redirectErrorStream(true);
Process p = pb.start();
InputStream is = p.getInputStream();
diff --git a/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp b/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
index 88a5a05738b..e4631e28625 100644
--- a/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
@@ -159,7 +159,7 @@ template <>
std::vector<double> TestBase<DoubleUniqueStore>::values{ 10.0, 20.0, 30.0, 10.0 };
using UniqueStoreTestTypes = ::testing::Types<NumberUniqueStore, StringUniqueStore, CStringUniqueStore, DoubleUniqueStore>;
-TYPED_TEST_CASE(TestBase, UniqueStoreTestTypes);
+VESPA_GTEST_TYPED_TEST_SUITE(TestBase, UniqueStoreTestTypes);
// Disable warnings emitted by gtest generated files when using typed tests
#pragma GCC diagnostic push
diff --git a/vespalib/src/tests/executor/executor_test.cpp b/vespalib/src/tests/executor/executor_test.cpp
index 9015391beaa..942b425be72 100644
--- a/vespalib/src/tests/executor/executor_test.cpp
+++ b/vespalib/src/tests/executor/executor_test.cpp
@@ -3,6 +3,7 @@
#include <vespa/vespalib/testkit/testapp.h>
#include <vespa/vespalib/util/closuretask.h>
#include <vespa/vespalib/util/lambdatask.h>
+#include <vespa/vespalib/util/executor_stats.h>
using namespace vespalib;
@@ -24,4 +25,29 @@ TEST("require that lambdas can be wrapped as tasks") {
EXPECT_TRUE(called);
}
+template<typename T>
+void verify(const AggregatedAverage<T> & avg, size_t expCount, T expTotal, T expMin, T expMax, double expAvg) {
+ EXPECT_EQUAL(expCount, avg.count());
+ EXPECT_EQUAL(expTotal, avg.total());
+ EXPECT_EQUAL(expMin, avg.min());
+ EXPECT_EQUAL(expMax, avg.max());
+ EXPECT_EQUAL(expAvg, avg.average());
+}
+
+TEST("test that aggregated averages") {
+ TEST_DO(verify(AggregatedAverage<size_t>(), 0ul, 0ul, std::numeric_limits<size_t>::max(), std::numeric_limits<size_t>::min(), 0.0));
+ AggregatedAverage<size_t> avg;
+ avg.add(9);
+ TEST_DO(verify(avg, 1ul, 9ul, 9ul, 9ul, 9.0));
+ avg.add(8);
+ TEST_DO(verify(avg, 2ul, 17ul, 8ul, 9ul, 8.5));
+ avg.add(3, 17, 4,17);
+ TEST_DO(verify(avg, 5ul, 34ul, 4ul, 17ul, 6.8));
+ AggregatedAverage<size_t> avg2;
+ avg2.add(avg);
+ TEST_DO(verify(avg2, 5ul, 34ul, 4ul, 17ul, 6.8));
+ avg2 += avg;
+ TEST_DO(verify(avg2, 10ul, 68ul, 4ul, 17ul, 6.8));
+}
+
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/executor/threadstackexecutor_test.cpp b/vespalib/src/tests/executor/threadstackexecutor_test.cpp
index 987b10526a3..9d69adcd96a 100644
--- a/vespalib/src/tests/executor/threadstackexecutor_test.cpp
+++ b/vespalib/src/tests/executor/threadstackexecutor_test.cpp
@@ -78,10 +78,10 @@ struct MyState {
EXPECT_EQUAL(expect_rejected, stats.rejectedTasks);
EXPECT_TRUE(!(gate.getCount() == 1) || (expect_deleted == 0));
if (expect_deleted == 0) {
- EXPECT_EQUAL(expect_queue + expect_running, stats.maxPendingTasks);
+ EXPECT_EQUAL(expect_queue + expect_running, stats.queueSize.max());
}
stats = executor.getStats();
- EXPECT_EQUAL(expect_queue + expect_running, stats.maxPendingTasks);
+ EXPECT_EQUAL(expect_queue + expect_running, stats.queueSize.max());
EXPECT_EQUAL(0u, stats.acceptedTasks);
EXPECT_EQUAL(0u, stats.rejectedTasks);
return *this;
@@ -187,12 +187,18 @@ TEST_F("require that executor thread stack tag can be set", ThreadStackExecutor(
}
TEST("require that stats can be accumulated") {
- ThreadStackExecutor::Stats stats(1,2,3);
- EXPECT_EQUAL(1u, stats.maxPendingTasks);
+ ThreadStackExecutor::Stats stats(ThreadExecutor::Stats::QueueSizeT(1) ,2,3);
+ EXPECT_EQUAL(1u, stats.queueSize.max());
EXPECT_EQUAL(2u, stats.acceptedTasks);
EXPECT_EQUAL(3u, stats.rejectedTasks);
- stats += ThreadStackExecutor::Stats(7,8,9);
- EXPECT_EQUAL(8u, stats.maxPendingTasks);
+ stats += ThreadStackExecutor::Stats(ThreadExecutor::Stats::QueueSizeT(7),8,9);
+ EXPECT_EQUAL(2u, stats.queueSize.count());
+ EXPECT_EQUAL(8u, stats.queueSize.total());
+ EXPECT_EQUAL(8u, stats.queueSize.max());
+ EXPECT_EQUAL(8u, stats.queueSize.min());
+ EXPECT_EQUAL(8u, stats.queueSize.max());
+ EXPECT_EQUAL(4.0, stats.queueSize.average());
+
EXPECT_EQUAL(10u, stats.acceptedTasks);
EXPECT_EQUAL(12u, stats.rejectedTasks);
diff --git a/vespalib/src/tests/stllike/hash_test.cpp b/vespalib/src/tests/stllike/hash_test.cpp
index b39b6859623..d23c2c6b68c 100644
--- a/vespalib/src/tests/stllike/hash_test.cpp
+++ b/vespalib/src/tests/stllike/hash_test.cpp
@@ -32,7 +32,7 @@ namespace {
TEST("test that hashValue gives expected response")
{
const char * s("abcdefghi");
- EXPECT_EQUAL(7045194595191919248ul, vespalib::hashValue(s));
+ EXPECT_EQUAL(2878261200250560019ul, vespalib::hashValue(s));
EXPECT_EQUAL(vespalib::hashValue(s), vespalib::hashValue(s, strlen(s)));
EXPECT_NOT_EQUAL(vespalib::hashValue(s), vespalib::hashValue(s, strlen(s)-1));
}
diff --git a/vespalib/src/tests/stllike/lookup_benchmark.cpp b/vespalib/src/tests/stllike/lookup_benchmark.cpp
index acde9ea8f9a..b3ce8c29a18 100644
--- a/vespalib/src/tests/stllike/lookup_benchmark.cpp
+++ b/vespalib/src/tests/stllike/lookup_benchmark.cpp
@@ -5,7 +5,6 @@
#include <set>
#include <unordered_set>
#include <vector>
-//#define XXH_INLINE_ALL
#include <xxhash.h>
#include <vespa/vespalib/stllike/hash_set.hpp>
#include <vespa/vespalib/stllike/hash_map.hpp>
diff --git a/vespalib/src/vespa/vespalib/gtest/gtest.h b/vespalib/src/vespa/vespalib/gtest/gtest.h
index e5bfcf2ae55..87362687103 100644
--- a/vespalib/src/vespa/vespalib/gtest/gtest.h
+++ b/vespalib/src/vespa/vespalib/gtest/gtest.h
@@ -14,3 +14,15 @@ main(int argc, char* argv[]) \
::testing::InitGoogleTest(&argc, argv); \
return RUN_ALL_TESTS(); \
}
+
+#ifdef INSTANTIATE_TEST_SUITE_P
+#define VESPA_GTEST_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_SUITE_P
+#else
+#define VESPA_GTEST_INSTANTIATE_TEST_SUITE_P INSTANTIATE_TEST_CASE_P
+#endif
+
+#ifdef TYPED_TEST_SUITE
+#define VESPA_GTEST_TYPED_TEST_SUITE TYPED_TEST_SUITE
+#else
+#define VESPA_GTEST_TYPED_TEST_SUITE TYPED_TEST_CASE
+#endif
diff --git a/vespalib/src/vespa/vespalib/stllike/hash_fun.cpp b/vespalib/src/vespa/vespalib/stllike/hash_fun.cpp
index d8c6c87ecda..5f4fee06c4a 100644
--- a/vespalib/src/vespa/vespalib/stllike/hash_fun.cpp
+++ b/vespalib/src/vespa/vespalib/stllike/hash_fun.cpp
@@ -1,25 +1,20 @@
// Copyright 2018 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "hash_fun.h"
+#include <xxhash.h>
namespace vespalib {
size_t
hashValue(const char *str)
{
- size_t res = 0;
- unsigned const char *pt = (unsigned const char *) str;
- while (*pt != 0) {
- res = (res << 7) + (res >> 25) + *pt++;
- }
- return res;
+ return hashValue(str, strlen(str));
}
/**
* @brief Calculate hash value.
*
- * This is the hash function used by the HashMap class.
- * The hash function is inherited from Fastserver4 / FastLib / pandora.
+ * The hash function XXH64 from xxhash library.
* @param buf input buffer
* @param sz input buffer size
* @return hash value of input
@@ -27,12 +22,7 @@ hashValue(const char *str)
size_t
hashValue(const void * buf, size_t sz)
{
- size_t res = 0;
- unsigned const char *pt = (unsigned const char *) buf;
- for (size_t i(0); i < sz; i++) {
- res = (res << 7) + (res >> 25) + pt[i];
- }
- return res;
+ return XXH64(buf, sz, 0);
}
}
diff --git a/vespalib/src/vespa/vespalib/util/executor_stats.h b/vespalib/src/vespa/vespalib/util/executor_stats.h
index 771435fbabf..9b941095c27 100644
--- a/vespalib/src/vespa/vespalib/util/executor_stats.h
+++ b/vespalib/src/vespa/vespalib/util/executor_stats.h
@@ -2,21 +2,69 @@
#pragma once
+#include <limits>
+
namespace vespalib {
/**
+ * Used for aggregating values, preserving min, max, sum and count.
+ */
+template <typename T>
+class AggregatedAverage {
+public:
+ AggregatedAverage() : AggregatedAverage(0ul, T(0), std::numeric_limits<T>::max(), std::numeric_limits<T>::min()) { }
+ explicit AggregatedAverage(T value) : AggregatedAverage(1, value, value, value) { }
+ AggregatedAverage(size_t count_in, T total_in, T min_in, T max_in)
+ : _count(count_in),
+ _total(total_in),
+ _min(min_in),
+ _max(max_in)
+ { }
+ AggregatedAverage & operator += (const AggregatedAverage & rhs) {
+ add(rhs);
+ return *this;
+ }
+ void add(const AggregatedAverage & rhs) {
+ add(rhs._count, rhs._total, rhs._min, rhs._max);
+ }
+ void add(T value) {
+ add(1, value, value, value);
+ }
+ void add(size_t count_in, T total_in, T min_in, T max_in) {
+ _count += count_in;
+ _total += total_in;
+ if (min_in < _min) _min = min_in;
+ if (max_in > _max) _max = max_in;
+ }
+ size_t count() const { return _count; }
+ T total() const { return _total; }
+ T min() const { return _min; }
+ T max() const { return _max; }
+ double average() const { return (_count > 0) ? (double(_total) / _count) : 0; }
+private:
+ size_t _count;
+ T _total;
+ T _min;
+ T _max;
+};
+
+/**
* Struct representing stats for an executor.
**/
struct ExecutorStats {
- size_t maxPendingTasks;
+ using QueueSizeT = AggregatedAverage<size_t>;
+ QueueSizeT queueSize;
size_t acceptedTasks;
size_t rejectedTasks;
- ExecutorStats() : ExecutorStats(0, 0, 0) {}
- ExecutorStats(size_t maxPending, size_t accepted, size_t rejected)
- : maxPendingTasks(maxPending), acceptedTasks(accepted), rejectedTasks(rejected)
+ ExecutorStats() : ExecutorStats(QueueSizeT(), 0, 0) {}
+ ExecutorStats(QueueSizeT queueSize_in, size_t accepted, size_t rejected)
+ : queueSize(queueSize_in), acceptedTasks(accepted), rejectedTasks(rejected)
{}
ExecutorStats & operator += (const ExecutorStats & rhs) {
- maxPendingTasks += rhs.maxPendingTasks;
+ queueSize = QueueSizeT(queueSize.count() + rhs.queueSize.count(),
+ queueSize.total() + rhs.queueSize.total(),
+ queueSize.min() + rhs.queueSize.min(),
+ queueSize.max() + rhs.queueSize.max());
acceptedTasks += rhs.acceptedTasks;
rejectedTasks += rhs.rejectedTasks;
return *this;
diff --git a/vespalib/src/vespa/vespalib/util/signalhandler.cpp b/vespalib/src/vespa/vespalib/util/signalhandler.cpp
index 21543ef10d8..0a5eea0d327 100644
--- a/vespalib/src/vespa/vespalib/util/signalhandler.cpp
+++ b/vespalib/src/vespa/vespalib/util/signalhandler.cpp
@@ -19,9 +19,6 @@ public:
}
-// Clear SignalHandler::_handlers in a slightly less unsafe manner.
-Shutdown shutdown;
-
SignalHandler SignalHandler::HUP(SIGHUP);
SignalHandler SignalHandler::INT(SIGINT);
SignalHandler SignalHandler::TERM(SIGTERM);
@@ -36,6 +33,9 @@ SignalHandler SignalHandler::FPE(SIGFPE);
SignalHandler SignalHandler::QUIT(SIGQUIT);
SignalHandler SignalHandler::USR1(SIGUSR1);
+// Clear SignalHandler::_handlers in a slightly less unsafe manner.
+Shutdown shutdown;
+
void
SignalHandler::handleSignal(int signal)
{
@@ -112,8 +112,14 @@ SignalHandler::shutdown()
it = _handlers.begin(), ite = _handlers.end();
it != ite;
++it) {
- if (*it != nullptr)
- (*it)->unhook();
+ if (*it != nullptr) {
+ // Ignore SIGTERM at shutdown in case valgrind is used.
+ if ((*it)->_signal == SIGTERM) {
+ (*it)->ignore();
+ } else {
+ (*it)->unhook();
+ }
+ }
}
std::vector<SignalHandler *>().swap(_handlers);
}
diff --git a/vespalib/src/vespa/vespalib/util/threadexecutor.h b/vespalib/src/vespa/vespalib/util/threadexecutor.h
index 202e516bc60..61a5d9d5ac7 100644
--- a/vespalib/src/vespa/vespalib/util/threadexecutor.h
+++ b/vespalib/src/vespa/vespalib/util/threadexecutor.h
@@ -40,6 +40,7 @@ public:
class SyncableThreadExecutor : public ThreadExecutor, public Syncable
{
public:
+ virtual SyncableThreadExecutor & shutdown() = 0;
};
} // namespace vespalib
diff --git a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.cpp b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.cpp
index 9ab465841d6..efb1dbf4054 100644
--- a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.cpp
+++ b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.cpp
@@ -196,7 +196,7 @@ ThreadStackExecutorBase::getStats()
LockGuard lock(_monitor);
Stats stats = _stats;
_stats = Stats();
- _stats.maxPendingTasks = _taskCount;
+ _stats.queueSize.add(_taskCount);
return stats;
}
@@ -208,8 +208,7 @@ ThreadStackExecutorBase::execute(Task::UP task)
TaggedTask taggedTask(std::move(task), _barrier.startEvent());
++_taskCount;
++_stats.acceptedTasks;
- _stats.maxPendingTasks = (_taskCount > _stats.maxPendingTasks)
- ?_taskCount : _stats.maxPendingTasks;
+ _stats.queueSize.add(_taskCount);
if (!_workers.empty()) {
Worker *worker = _workers.back();
_workers.popBack();
diff --git a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
index 2c0bc56d6df..6333a8fc66e 100644
--- a/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
+++ b/vespalib/src/vespa/vespalib/util/threadstackexecutorbase.h
@@ -226,7 +226,7 @@ public:
*
* @return this object; for chaining
**/
- ThreadStackExecutorBase &shutdown();
+ ThreadStackExecutorBase &shutdown() override;
/**
* Will invoke shutdown then sync.
diff --git a/vespalog/abi-spec.json b/vespalog/abi-spec.json
index 09ac3fa75d3..996cc0259a0 100644
--- a/vespalog/abi-spec.json
+++ b/vespalog/abi-spec.json
@@ -186,19 +186,6 @@
],
"fields": []
},
- "com.yahoo.log.MappedLevelControllerRepo": {
- "superClass": "java.lang.Object",
- "interfaces": [],
- "attributes": [
- "public"
- ],
- "methods": [
- "public void <init>(java.nio.MappedByteBuffer, int, int, java.lang.String)",
- "public com.yahoo.log.LevelController getLevelController(java.lang.String)",
- "public void checkBack()"
- ],
- "fields": []
- },
"com.yahoo.log.RejectFilter": {
"superClass": "java.lang.Object",
"interfaces": [],
@@ -300,15 +287,10 @@
"public"
],
"methods": [
- "public void <init>(java.lang.String, java.lang.String, java.lang.String)",
- "public com.yahoo.log.LevelController getLevelControl(java.lang.String)",
"public com.yahoo.log.LevelController getLevelController(java.lang.String)",
"public void close()"
],
- "fields": [
- "public static final int controlFileHeaderLength",
- "public static final int numLevels"
- ]
+ "fields": []
},
"com.yahoo.log.event.Collection": {
"superClass": "com.yahoo.log.event.Event",
diff --git a/vespalog/src/main/java/com/yahoo/log/LevelController.java b/vespalog/src/main/java/com/yahoo/log/LevelController.java
index ccd18f126d6..0efe0d4e7c1 100644
--- a/vespalog/src/main/java/com/yahoo/log/LevelController.java
+++ b/vespalog/src/main/java/com/yahoo/log/LevelController.java
@@ -1,4 +1,8 @@
// Copyright 2017 Yahoo Holdings. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.log;
+
+import java.util.logging.Level;
+
/**
* This is the interface for controlling the log level of a
* component logger. This hides the actual controlling
@@ -7,32 +11,24 @@
* @author arnej27959
*
*/
-
-/**
- * @author arnej27959
- **/
-package com.yahoo.log;
-
-import java.util.logging.Level;
-
public interface LevelController {
/**
* should we actually publish a log message with the given Level now?
- **/
- public boolean shouldLog(Level level);
+ */
+ boolean shouldLog(Level level);
/**
* return a string suitable for printing in a logctl file.
* the string must be be 4 * 8 characters, where each group
* of 4 characters is either " ON" or " OFF".
- **/
- public String getOnOffString();
+ */
+ String getOnOffString();
/**
* check the current state of logging and reflect it into the
* associated Logger instance, if available.
- **/
- public void checkBack();
- public Level getLevelLimit();
+ */
+ void checkBack();
+ Level getLevelLimit();
}
diff --git a/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java b/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java
index f02d8793b23..53f4de4f264 100644
--- a/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java
+++ b/vespalog/src/main/java/com/yahoo/log/MappedLevelControllerRepo.java
@@ -13,14 +13,14 @@ import java.util.Map;
* @author Ulf Lilleengen
* @since 5.1
*/
-public class MappedLevelControllerRepo {
+class MappedLevelControllerRepo {
private final Map<String, LevelController> levelControllerMap = new HashMap<>();
private final MappedByteBuffer mapBuf;
private final int controlFileHeaderLength;
private final int numLevels;
private final String logControlFilename;
- public MappedLevelControllerRepo(MappedByteBuffer mapBuf, int controlFileHeaderLength, int numLevels, String logControlFilename) {
+ MappedLevelControllerRepo(MappedByteBuffer mapBuf, int controlFileHeaderLength, int numLevels, String logControlFilename) {
this.mapBuf = mapBuf;
this.controlFileHeaderLength = controlFileHeaderLength;
this.numLevels = numLevels;
@@ -101,12 +101,12 @@ public class MappedLevelControllerRepo {
return MappedLevelController.checkOnOff(mapBuf, levstart);
}
- public LevelController getLevelController(String suffix) {
+ LevelController getLevelController(String suffix) {
return levelControllerMap.get(suffix);
}
- public void checkBack() {
+ void checkBack() {
for (LevelController ctrl : levelControllerMap.values()) {
ctrl.checkBack();
}
diff --git a/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java b/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java
index 85d92075827..2cc88855deb 100644
--- a/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java
+++ b/vespalog/src/main/java/com/yahoo/log/VespaLevelControllerRepo.java
@@ -30,12 +30,12 @@ public class VespaLevelControllerRepo implements LevelControllerRepo {
/**
* length of fixed header content of a control file, constant:
**/
- public static final int controlFileHeaderLength;
+ static final int controlFileHeaderLength;
/**
* number of distinctly controlled levels (in logctl files),
* must be compatible with C++ Vespa logging
**/
- public static final int numLevels = 8;
+ static final int numLevels = 8;
static {
controlFileHeaderLength = CFHEADER.length()
@@ -50,7 +50,7 @@ public class VespaLevelControllerRepo implements LevelControllerRepo {
**/
private LevelController defaultLevelCtrl;
- public VespaLevelControllerRepo(String logCtlFn, String logLevel, String applicationPrefix) {
+ VespaLevelControllerRepo(String logCtlFn, String logLevel, String applicationPrefix) {
this.logControlFilename = logCtlFn;
this.appPrefix = applicationPrefix;
defaultLevelCtrl = new DefaultLevelController(logLevel);
@@ -142,7 +142,7 @@ public class VespaLevelControllerRepo implements LevelControllerRepo {
levelControllerRepo = new MappedLevelControllerRepo(mapBuf, controlFileHeaderLength, numLevels, logControlFilename);
}
- public LevelController getLevelControl(String suffix) {
+ private LevelController getLevelControl(String suffix) {
LevelController ctrl = null;
if (levelControllerRepo != null) {
if (suffix == null || suffix.equals("default")) {
diff --git a/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java b/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java
index 331780f226b..32b1003c20c 100644
--- a/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java
+++ b/vespalog/src/main/java/com/yahoo/log/VespaLogHandler.java
@@ -44,7 +44,7 @@ class VespaLogHandler extends StreamHandler {
/**
* Publish a log record into the Vespa log target.
*/
- public synchronized void publish (LogRecord record) {
+ public synchronized void publish(LogRecord record) {
Level level = record.getLevel();
String component = record.getLoggerName();
diff --git a/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java b/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java
index c0dd856b634..220e5e9271e 100644
--- a/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java
+++ b/vespalog/src/test/java/com/yahoo/log/VespaLogHandlerTestCase.java
@@ -13,6 +13,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
+import java.nio.charset.StandardCharsets;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
@@ -32,20 +33,20 @@ import static org.junit.Assert.fail;
* @author Bjorn Borud
*/
public class VespaLogHandlerTestCase {
- protected static String hostname;
- protected static String pid;
+ private static String hostname;
+ private static String pid;
- protected static LogRecord record1;
- protected static String record1String;
+ static LogRecord record1;
+ static String record1String;
- protected static LogRecord record2;
- protected static String record2String;
+ static LogRecord record2;
+ private static String record2String;
- protected static LogRecord record3;
- protected static String record3String;
+ private static LogRecord record3;
+ private static String record3String;
- protected static LogRecord record4;
- protected static String record4String;
+ private static LogRecord record4;
+ private static String record4String;
static {
hostname = Util.getHostName();
@@ -139,7 +140,7 @@ public class VespaLogHandlerTestCase {
}
@Test
- public void testFallback() throws FileNotFoundException {
+ public void testFallback() {
File file = new File("mydir2");
file.delete();
assertTrue(file.mkdir());
@@ -157,7 +158,7 @@ public class VespaLogHandlerTestCase {
* Perform simple test
*/
@Test
- public void testLogCtl () throws InterruptedException, FileNotFoundException {
+ public void testLogCtl () {
MockLevelController ctl = new MockLevelController();
MockLevelControllerRepo ctlRepo = new MockLevelControllerRepo(ctl);
MockLogTarget target = new MockLogTarget();
@@ -203,7 +204,7 @@ public class VespaLogHandlerTestCase {
@Test
public void testRotate () throws IOException {
// Doesn't work in Windows. TODO: Fix the logging stuff
- if (System.getProperty("os.name").toLowerCase().indexOf("win")>=0)
+ if (System.getProperty("os.name").toLowerCase().contains("win"))
return;
try {
VespaLogHandler h
@@ -269,10 +270,8 @@ public class VespaLogHandlerTestCase {
);
class LogRacer implements Runnable {
- private int n;
- public LogRacer (int n) {
- this.n = n;
+ private LogRacer() {
}
public void run () {
@@ -285,7 +284,7 @@ public class VespaLogHandlerTestCase {
}
}
- public void logLikeCrazy () {
+ void logLikeCrazy() {
for (int j = 0; j < numLogEntries; j++) {
try {
h.publish(record1);
@@ -299,7 +298,7 @@ public class VespaLogHandlerTestCase {
}
for (int i = 0; i < numThreads; i++) {
- t[i] = new Thread(new LogRacer(i));
+ t[i] = new Thread(new LogRacer());
t[i].start();
}
@@ -361,35 +360,23 @@ public class VespaLogHandlerTestCase {
*
*/
protected static String[] readFile (String fileName) {
- BufferedReader br = null;
- List<String> lines = new LinkedList<String>();
- try {
- br = new BufferedReader(
- new InputStreamReader(new FileInputStream(new File(fileName)), "UTF-8"));
+ List<String> lines = new LinkedList<>();
+ try (BufferedReader br = new BufferedReader(
+ new InputStreamReader(new FileInputStream(new File(fileName)), StandardCharsets.UTF_8))) {
for (String line = br.readLine();
line != null;
- line = br.readLine())
- {
+ line = br.readLine()) {
lines.add(line);
}
- return lines.toArray(new String[lines.size()]);
- }
- catch (Throwable e) {
+ return lines.toArray(new String[0]);
+ } catch (Throwable e) {
return new String[0];
}
- finally {
- if (br != null) {
- try {
- br.close();
- }
- catch (IOException e) {}
- }
- }
}
private static class MockLevelControllerRepo implements LevelControllerRepo {
private LevelController levelController;
- public MockLevelControllerRepo(LevelController controller) {
+ MockLevelControllerRepo(LevelController controller) {
this.levelController = controller;
}
@@ -411,7 +398,7 @@ public class VespaLogHandlerTestCase {
return (level.equals(logLevel));
}
- public void setShouldLog(Level level) {
+ void setShouldLog(Level level) {
this.logLevel = level;
}
@@ -431,7 +418,7 @@ public class VespaLogHandlerTestCase {
private static class MockLogTarget implements LogTarget {
private final ByteArrayOutputStream baos = new ByteArrayOutputStream();
- public String[] getLines() {
+ String[] getLines() {
return baos.toString().split("\n");
}
@Override
diff --git a/vespalog/src/vespa/log/llparser.cpp b/vespalog/src/vespa/log/llparser.cpp
index 8e44f36c7ae..ae1af3e6416 100644
--- a/vespalog/src/vespa/log/llparser.cpp
+++ b/vespalog/src/vespa/log/llparser.cpp
@@ -361,8 +361,7 @@ LLParser::makeMessage(const char *tmf, const char *hsf, const char *pdf,
if ((c == '\\' && src[0] == 't')
|| (c >= 32 && c < '\\')
|| (c > '\\' && c < 128)
- || c == 0
- || c > 160)
+ || c == 0)
{
*dst++ = static_cast<char>(c);
} else {
diff --git a/vsm/src/tests/textutil/textutil.cpp b/vsm/src/tests/textutil/textutil.cpp
index e71b95f22f9..581419fc80e 100644
--- a/vsm/src/tests/textutil/textutil.cpp
+++ b/vsm/src/tests/textutil/textutil.cpp
@@ -60,10 +60,10 @@ void
TextUtilTest::assertSkipSeparators(const char * input, size_t len, const UCS4V & expdstbuf, const SizeV & expoffsets)
{
const byte * srcbuf = reinterpret_cast<const byte *>(input);
- ucs4_t dstbuf[len];
- size_t offsets[len];
+ auto dstbuf = std::make_unique<ucs4_t[]>(len + 1);
+ auto offsets = std::make_unique<size_t[]>(len + 1);
UTF8StrChrFieldSearcher fs;
- BW bw(dstbuf, offsets);
+ BW bw(dstbuf.get(), offsets.get());
size_t dstlen = fs.skipSeparators(srcbuf, len, bw);
EXPECT_EQUAL(dstlen, expdstbuf.size());
ASSERT_TRUE(dstlen == expdstbuf.size());