aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--airlift-zstd/src/test/java/ai/vespa/airlift/zstd/TestCompressor.java1
-rw-r--r--ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp3
-rw-r--r--application-model/src/main/java/com/yahoo/vespa/applicationmodel/InfrastructureApplication.java1
-rw-r--r--client/go/go.mod6
-rw-r--r--client/go/go.sum12
-rw-r--r--client/go/internal/admin/envvars/env_vars.go1
-rw-r--r--client/go/internal/admin/prog/common_env.go43
-rw-r--r--client/go/internal/admin/prog/hugepages.go2
-rw-r--r--client/go/internal/admin/prog/madvise.go2
-rw-r--r--client/go/internal/admin/prog/spec_env.go10
-rw-r--r--client/go/internal/admin/prog/valgrind.go26
-rw-r--r--client/go/internal/admin/prog/valgrind_test.go5
-rw-r--r--client/go/internal/admin/prog/vespamalloc.go10
-rw-r--r--client/go/internal/admin/vespa-wrapper/configserver/logd.go4
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/common_env.go73
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/numactl.go72
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/numactl_test.go89
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/progspec.go123
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/progspec_test.go38
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/startcbinary.go40
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/tuning.go2
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/valgrind.go82
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go92
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/vespamalloc.go57
-rw-r--r--client/go/internal/cli/auth/auth0/auth0.go38
-rw-r--r--client/go/internal/cli/auth/zts/zts.go28
-rw-r--r--client/go/internal/cli/auth/zts/zts_test.go7
-rw-r--r--client/go/internal/cli/cmd/cert.go9
-rw-r--r--client/go/internal/cli/cmd/config.go101
-rw-r--r--client/go/internal/cli/cmd/config_test.go120
-rw-r--r--client/go/internal/cli/cmd/curl.go12
-rw-r--r--client/go/internal/cli/cmd/feed.go156
-rw-r--r--client/go/internal/cli/cmd/feed_test.go52
-rw-r--r--client/go/internal/cli/cmd/prod.go14
-rw-r--r--client/go/internal/cli/cmd/root.go98
-rw-r--r--client/go/internal/cli/cmd/test.go2
-rw-r--r--client/go/internal/cli/cmd/testutil_test.go21
-rw-r--r--client/go/internal/cli/cmd/visit.go31
-rw-r--r--client/go/internal/cli/cmd/visit_test.go5
-rw-r--r--client/go/internal/mock/http.go4
-rw-r--r--client/go/internal/util/http.go77
-rw-r--r--client/go/internal/vespa/crypto.go2
-rw-r--r--client/go/internal/vespa/deploy.go24
-rw-r--r--client/go/internal/vespa/deploy_test.go4
-rw-r--r--client/go/internal/vespa/document/dispatcher.go208
-rw-r--r--client/go/internal/vespa/document/dispatcher_test.go50
-rw-r--r--client/go/internal/vespa/document/document.go45
-rw-r--r--client/go/internal/vespa/document/feeder.go7
-rw-r--r--client/go/internal/vespa/document/http.go181
-rw-r--r--client/go/internal/vespa/document/http_test.go139
-rw-r--r--client/go/internal/vespa/document/queue.go43
-rw-r--r--client/go/internal/vespa/document/queue_test.go29
-rw-r--r--client/go/internal/vespa/document/throttler.go41
-rw-r--r--client/go/internal/vespa/document/throttler_test.go6
-rw-r--r--client/go/internal/vespa/target.go78
-rw-r--r--client/go/internal/vespa/target_cloud.go109
-rw-r--r--client/go/internal/vespa/target_custom.go25
-rw-r--r--client/go/internal/vespa/target_test.go46
-rw-r--r--client/pom.xml6
-rw-r--r--cloud-tenant-base-dependencies-enforcer/pom.xml7
-rw-r--r--clustercontroller-core/pom.xml5
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterInfo.java11
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterStateVersionSpecificRequest.java2
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ContentCluster.java52
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisiting.java32
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisitingAdapter.java37
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeInfo.java17
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java153
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateGatherer.java38
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateChangeHandler.java173
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java2
-rw-r--r--clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/SetNodeStateRequest.java38
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java440
-rw-r--r--clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java3
-rw-r--r--clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java3
-rw-r--r--clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java2
-rw-r--r--config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java1
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java2
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/deploy/TestProperties.java7
-rw-r--r--config-model/src/main/java/com/yahoo/schema/Schema.java11
-rw-r--r--config-model/src/main/java/com/yahoo/schema/derived/VsmFields.java74
-rw-r--r--config-model/src/main/java/com/yahoo/schema/document/Attribute.java2
-rw-r--r--config-model/src/main/java/com/yahoo/schema/expressiontransforms/InputRecorder.java6
-rw-r--r--config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedFields.java1
-rw-r--r--config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java25
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java2
-rw-r--r--config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/LogForwarder.java21
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerConfigurer.java8
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidator.java19
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/StreamingValidator.java3
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java1
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java25
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV4Builder.java2
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/IdentityProvider.java9
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/container/xml/ContainerModelBuilder.java4
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java218
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java18
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelInfo.java15
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/search/NodeResourcesTuning.java10
-rw-r--r--config-model/src/main/javacc/SchemaParser.jj7
-rw-r--r--config-model/src/main/resources/schema/admin.rnc3
-rw-r--r--config-model/src/main/resources/schema/content.rnc3
-rw-r--r--config-model/src/test/derived/hnsw_index/attributes.cfg2
-rw-r--r--config-model/src/test/derived/hnsw_index/test.sd2
-rw-r--r--config-model/src/test/derived/indexswitches/ilscripts.cfg2
-rw-r--r--config-model/src/test/derived/multiplesummaries/index-info.cfg2
-rw-r--r--config-model/src/test/derived/multiplesummaries/multiplesummaries.sd5
-rw-r--r--config-model/src/test/derived/nearestneighbor_streaming/test.sd24
-rw-r--r--config-model/src/test/derived/nearestneighbor_streaming/vsmfields.cfg31
-rw-r--r--config-model/src/test/java/com/yahoo/config/model/provision/ModelProvisioningTest.java33
-rw-r--r--config-model/src/test/java/com/yahoo/schema/derived/NearestNeighborTestCase.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java4
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidatorTest.java60
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java2
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java3
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java50
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java39
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java2
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/search/NodeResourcesTuningTest.java12
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java7
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/utils/VespaModelCreatorWithMockPkg.java2
-rw-r--r--config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/FileReferencesAndDownloadsMaintainer.java6
-rw-r--r--configdefinitions/src/vespa/attributes.def2
-rw-r--r--configdefinitions/src/vespa/fleetcontroller.def15
-rw-r--r--configdefinitions/src/vespa/logforwarder.def1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/ApplicationRepository.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java12
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java36
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java17
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java2
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java4
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ReindexingMaintainer.java2
-rw-r--r--configserver/src/main/sh/start-logd2
-rwxr-xr-xconfigserver/src/main/sh/stop-configserver2
-rw-r--r--configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java39
-rw-r--r--container-disc/abi-spec.json3
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/AthenzIdentityProviderProvider.java3
-rw-r--r--container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java2
-rw-r--r--container-search/abi-spec.json3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/IndexFacts.java98
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java42
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/cluster/SchemaResolver.java58
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java21
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/MultiRangeItem.java2
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/MultiTermItem.java4
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java3
-rw-r--r--container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java14
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/result/BucketGroupId.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/result/HitRenderer.java24
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/result/RawBucketId.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/result/RawId.java6
-rw-r--r--container-search/src/main/java/com/yahoo/search/grouping/vespa/ResultBuilder.java25
-rw-r--r--container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java2
-rw-r--r--container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java25
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java22
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/query/parser/test/TokenizerTestCase.java15
-rw-r--r--container-search/src/test/java/com/yahoo/prelude/test/IndexFactsTestCase.java8
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/result/GroupIdTestCase.java11
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/result/HitRendererTestCase.java4
-rw-r--r--container-search/src/test/java/com/yahoo/search/grouping/vespa/ResultBuilderTestCase.java92
-rw-r--r--container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java22
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java33
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeFilter.java44
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java5
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java34
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java18
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java8
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java6
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/HostAction.java1
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VcmrReport.java63
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/PathGroup.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java9
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java76
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java48
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java22
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java239
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java88
-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/JobController.java56
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java5
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java6
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java17
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java18
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java12
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java10
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java2
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java3
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java33
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java4
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java20
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java102
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java51
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java7
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java19
-rw-r--r--controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/ControllerTest.java79
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java26
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java38
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/integration/NodeRepositoryMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java14
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java16
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java20
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java4
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java12
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java82
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview-2.json8
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json37
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json56
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java3
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java7
-rw-r--r--controller-server/src/test/resources/application-packages/with-certificate.zipbin0 -> 1640 bytes
-rw-r--r--default_build_settings.cmake18
-rw-r--r--docprocs/src/test/cfg/ilscripts.cfg1
-rw-r--r--document/abi-spec.json6
-rw-r--r--document/src/main/java/com/yahoo/document/DocumentPut.java14
-rw-r--r--document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java7
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/DocumentUpdateFlags.java2
-rw-r--r--document/src/main/java/com/yahoo/document/serialization/XmlSerializationHelper.java6
-rw-r--r--document/src/test/java/com/yahoo/document/DocumentTestCase.java2
-rw-r--r--document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java2
-rw-r--r--document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java2
-rw-r--r--document/src/test/java/com/yahoo/document/json/JsonWriterTestCase.java2
-rw-r--r--document/src/vespa/document/select/parse_utils.cpp2
-rw-r--r--documentapi/abi-spec.json92
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java14
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentSummaryMessage.java27
-rwxr-xr-xdocumentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java11
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java65
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/SearchResultMessage.java27
-rw-r--r--documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java177
-rwxr-xr-xdocumentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java25
-rw-r--r--documentapi/src/tests/messages/messages60test.cpp244
-rw-r--r--documentapi/src/tests/messages/messages60test.h2
-rw-r--r--documentapi/src/tests/messages/testbase.cpp24
-rw-r--r--documentapi/src/tests/messages/testbase.h9
-rw-r--r--documentapi/src/vespa/documentapi/documentapi.h2
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/documentprotocol.cpp4
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/documentprotocol.h10
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/CMakeLists.txt2
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.cpp42
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.h38
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/putdocumentmessage.h5
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.cpp42
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.h26
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/testandsetcondition.h4
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp72
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/routablefactories60.h20
-rw-r--r--documentapi/test/crosslanguagefiles/.gitattributes1
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-1.datbin17 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-2.datbin57 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-3.datbin57 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryReply.datbin5 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-pad.datbin0 -> 56 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-truncate.datbin0 -> 54 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create.datbin0 -> 55 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage.datbin80 -> 81 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-1.datbin29 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-2.datbin60 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-3.datbin60 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-4.datbin113 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-5.datbin113 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultReply.datbin5 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-DocumentSummaryReply.datbin5 -> 0 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-pad.datbin0 -> 56 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-truncate.datbin0 -> 54 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create.datbin0 -> 55 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage.datbin80 -> 81 bytes
-rw-r--r--documentapi/test/crosslanguagefiles/6.221-java-SearchResultReply.datbin5 -> 0 bytes
-rw-r--r--fat-model-dependencies/pom.xml4
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java6
-rw-r--r--filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceCompressor.java6
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/Flags.java44
-rw-r--r--flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java6
-rw-r--r--functions.cmake8
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionConverter.java121
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ValueTransformProvider.java2
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java7
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java8
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpression.java4
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GuardExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java10
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisExpression.java6
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java10
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java9
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java10
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java12
-rw-r--r--indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/VerificationContext.java2
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java35
-rw-r--r--indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceTestCase.java25
-rw-r--r--jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesIT.java15
-rw-r--r--jdisc_core/src/test/resources/exportPackages.properties4
-rw-r--r--model-integration/abi-spec.json51
-rw-r--r--model-integration/src/main/java/ai/vespa/embedding/BertBaseEmbedder.java18
-rw-r--r--model-integration/src/main/java/ai/vespa/llm/generation/Generator.java (renamed from model-integration/src/main/java/ai/vespa/llm/Generator.java)4
-rw-r--r--model-integration/src/main/java/ai/vespa/llm/generation/GeneratorOptions.java (renamed from model-integration/src/main/java/ai/vespa/llm/GeneratorOptions.java)5
-rw-r--r--model-integration/src/main/java/ai/vespa/llm/generation/package-info.java11
-rw-r--r--model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluatorOptions.java14
-rw-r--r--model-integration/src/main/java/ai/vespa/rankingexpression/importer/OrderedTensorType.java4
-rw-r--r--model-integration/src/main/resources/configdefinitions/embedding.bert-base-embedder.def4
-rw-r--r--model-integration/src/test/java/ai/vespa/llm/generation/GeneratorTest.java (renamed from model-integration/src/test/java/ai/vespa/llm/GeneratorTest.java)4
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java51
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/DropDocumentsReport.java55
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroup.java90
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1.java129
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2.java108
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/Container.java10
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java2
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java18
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/ContainerWireguardTask.java3
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java9
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java157
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfo.java7
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java35
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddressesImpl.java3
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java2
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java7
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1Test.java100
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2Test.java14
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java14
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java23
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfoTest.java6
-rw-r--r--node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java55
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/AutoscalingMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DiskReplacer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostDeprovisioner.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/MetricsReporter.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java18
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java2
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java20
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ApplicationFilter.java10
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java11
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java29
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java9
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java24
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java4
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java61
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java10
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java44
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainerTest.java2
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisioningTest.java17
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java14
-rw-r--r--node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java43
-rw-r--r--openai-client/abi-spec.json29
-rw-r--r--openai-client/pom.xml67
-rw-r--r--openai-client/src/main/java/ai/vespa/llm/client/openai/OpenAiClient.java84
-rw-r--r--openai-client/src/main/java/ai/vespa/llm/client/openai/package-info.java11
-rw-r--r--parent/pom.xml10
-rw-r--r--pom.xml3
-rw-r--r--predicate-search/src/main/java/com/yahoo/search/predicate/benchmarks/ResultMetrics.java4
-rw-r--r--predicate-search/src/main/java/com/yahoo/search/predicate/index/CachedPostingListCounter.java5
-rwxr-xr-xscrewdriver/update-vespa-version-in-sample-apps.sh6
-rw-r--r--searchcore/src/tests/proton/attribute/attribute_initializer/attribute_initializer_test.cpp32
-rw-r--r--searchcore/src/tests/proton/common/attribute_updater/attribute_updater_test.cpp6
-rw-r--r--searchcore/src/tests/proton/matching/matching_test.cpp3
-rw-r--r--searchcore/src/tests/proton/server/shared_threading_service/shared_threading_service_test.cpp7
-rw-r--r--searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp1
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.cpp9
-rw-r--r--searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.h2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.h4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/extract_features.cpp4
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/extract_features.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/match_master.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/matcher.cpp2
-rw-r--r--searchcore/src/vespa/searchcore/proton/matching/matcher.h6
-rw-r--r--searchcore/src/vespa/searchcore/proton/server/shared_threading_service_config.cpp8
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java6
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java8
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java6
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java6
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java88
-rw-r--r--searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java14
-rw-r--r--searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java58
-rw-r--r--searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp1
-rw-r--r--searchlib/src/tests/attribute/attribute_test.cpp56
-rw-r--r--searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp21
-rw-r--r--searchlib/src/tests/attribute/enumstore/enumstore_test.cpp77
-rw-r--r--searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp18
-rw-r--r--searchlib/src/tests/attribute/imported_search_context/imported_search_context_test.cpp15
-rw-r--r--searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp24
-rw-r--r--searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp3
-rw-r--r--searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp17
-rw-r--r--searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp4
-rw-r--r--searchlib/src/tests/expression/attributenode/attribute_node_test.cpp19
-rw-r--r--searchlib/src/tests/features/prod_features.cpp5
-rw-r--r--searchlib/src/tests/memoryindex/field_index/field_index_test.cpp36
-rw-r--r--searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp4
-rw-r--r--searchlib/src/tests/predicate/document_features_store_test.cpp14
-rw-r--r--searchlib/src/tests/query/streaming_query_test.cpp37
-rw-r--r--searchlib/src/tests/queryeval/nearest_neighbor/nearest_neighbor_test.cpp12
-rw-r--r--searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp451
-rw-r--r--searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp25
-rw-r--r--searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp8
-rw-r--r--searchlib/src/vespa/searchcommon/attribute/distance_metric.h2
-rw-r--r--searchlib/src/vespa/searchcommon/attribute/i_search_context.h5
-rw-r--r--searchlib/src/vespa/searchcommon/common/undefinedvalues.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attribute_header.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.cpp25
-rw-r--r--searchlib/src/vespa/searchlib/attribute/attributevector.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/configconverter.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/empty_search_context.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/empty_search_context.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enum_store_loaders.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enum_store_loaders.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumattribute.h3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumattribute.hpp14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumhintsearchcontext.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumstore.h10
-rw-r--r--searchlib/src/vespa/searchlib/attribute/enumstore.hpp46
-rw-r--r--searchlib/src/vespa/searchlib/attribute/extendableattributes.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/extendableattributes.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/i_enum_store.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h5
-rw-r--r--searchlib/src/vespa/searchlib/attribute/imported_search_context.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/imported_search_context.h6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h14
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp36
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp1
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postinglistattribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/postingstore.cpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp11
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_enum_search_context.h7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_enum_search_context.hpp9
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.h6
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.hpp9
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.cpp11
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleenumattribute.h4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp22
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp5
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp13
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp13
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp12
-rw-r--r--searchlib/src/vespa/searchlib/attribute/stringbase.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/attribute/stringbase.h4
-rw-r--r--searchlib/src/vespa/searchlib/common/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/engine/searchreply.h3
-rw-r--r--searchlib/src/vespa/searchlib/expression/attribute_map_lookup_node.cpp16
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/feature_store.h3
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/field_index.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/memoryindex/word_store.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.h2
-rw-r--r--searchlib/src/vespa/searchlib/predicate/document_features_store.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/predicate/simple_index.hpp6
-rw-r--r--searchlib/src/vespa/searchlib/query/query_term_simple.h4
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/CMakeLists.txt1
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp52
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h57
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/querynode.cpp22
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/querynode.h3
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/queryterm.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/queryterm.h5
-rw-r--r--searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h6
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/nearest_neighbor_blueprint.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.h2
-rw-r--r--searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_search.cpp34
-rw-r--r--searchlib/src/vespa/searchlib/tensor/CMakeLists.txt3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/angular_distance.cpp71
-rw-r--r--searchlib/src/vespa/searchlib/tensor/angular_distance.h19
-rw-r--r--searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/bound_distance_function.h38
-rw-r--r--searchlib/src/vespa/searchlib/tensor/default_nearest_neighbor_index_factory.cpp4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp10
-rw-r--r--searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp6
-rw-r--r--searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_calculator.cpp29
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_calculator.h21
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_function.h19
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp83
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_function_factory.h19
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_functions.h1
-rw-r--r--searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp69
-rw-r--r--searchlib/src/vespa/searchlib/tensor/euclidean_distance.h12
-rw-r--r--searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.cpp61
-rw-r--r--searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.h8
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp60
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hamming_distance.h11
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp90
-rw-r--r--searchlib/src/vespa/searchlib/tensor/hnsw_index.h40
-rw-r--r--searchlib/src/vespa/searchlib/tensor/i_tensor_attribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/imported_tensor_attribute_vector_read_guard.h3
-rw-r--r--searchlib/src/vespa/searchlib/tensor/inner_product_distance.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp22
-rw-r--r--searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.h8
-rw-r--r--searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h7
-rw-r--r--searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp81
-rw-r--r--searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.h27
-rw-r--r--searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp30
-rw-r--r--searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.h8
-rw-r--r--searchlib/src/vespa/searchlib/tensor/temporary_vector_store.cpp57
-rw-r--r--searchlib/src/vespa/searchlib/tensor/temporary_vector_store.h32
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp8
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_attribute.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.cpp1
-rw-r--r--searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.h7
-rw-r--r--searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.cpp2
-rw-r--r--searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.h2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h3
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp2
-rw-r--r--searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp3
-rw-r--r--security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java2
-rw-r--r--security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java6
-rw-r--r--storage/src/tests/distributor/CMakeLists.txt4
-rw-r--r--storage/src/tests/distributor/getoperationtest.cpp137
-rw-r--r--storage/src/tests/distributor/gtest_runner.cpp8
-rw-r--r--storage/src/tests/distributor/newest_replica_test.cpp24
-rw-r--r--storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp5
-rw-r--r--storage/src/tests/persistence/persistencetestutils.h12
-rw-r--r--storage/src/tests/persistence/testandsettest.cpp81
-rw-r--r--storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp19
-rw-r--r--storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp2
-rw-r--r--storage/src/vespa/storage/distributor/node_supported_features.h1
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/getoperation.cpp9
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/newest_replica.cpp1
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/newest_replica.h11
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/putoperation.cpp50
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/putoperation.h5
-rw-r--r--storage/src/vespa/storage/distributor/operations/external/removeoperation.cpp9
-rw-r--r--storage/src/vespa/storage/distributor/pendingclusterstate.cpp1
-rw-r--r--storage/src/vespa/storage/persistence/asynchandler.cpp6
-rw-r--r--storage/src/vespa/storage/persistence/filestorage/filestorhandlerimpl.cpp4
-rw-r--r--storage/src/vespa/storage/persistence/persistencehandler.cpp2
-rw-r--r--storage/src/vespa/storage/persistence/simplemessagehandler.cpp33
-rw-r--r--storage/src/vespa/storage/persistence/simplemessagehandler.h13
-rw-r--r--storage/src/vespa/storage/persistence/testandsethelper.cpp84
-rw-r--r--storage/src/vespa/storage/persistence/testandsethelper.h52
-rw-r--r--storage/src/vespa/storage/storageserver/documentapiconverter.cpp14
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto2
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto1
-rw-r--r--storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp11
-rw-r--r--storage/src/vespa/storageapi/message/CMakeLists.txt2
-rw-r--r--storage/src/vespa/storageapi/message/bucket.h1
-rw-r--r--storage/src/vespa/storageapi/message/documentsummary.cpp43
-rw-r--r--storage/src/vespa/storageapi/message/documentsummary.h39
-rw-r--r--storage/src/vespa/storageapi/message/persistence.cpp18
-rw-r--r--storage/src/vespa/storageapi/message/persistence.h15
-rw-r--r--storage/src/vespa/storageapi/message/searchresult.cpp47
-rw-r--r--storage/src/vespa/storageapi/message/searchresult.h35
-rw-r--r--storage/src/vespa/storageapi/messageapi/messagehandler.h8
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagemessage.cpp4
-rw-r--r--storage/src/vespa/storageapi/messageapi/storagemessage.h12
-rw-r--r--streamingvisitors/CMakeLists.txt10
-rw-r--r--streamingvisitors/src/tests/hitcollector/hitcollector_test.cpp2
-rw-r--r--streamingvisitors/src/tests/matching_elements_filler/CMakeLists.txt1
-rw-r--r--streamingvisitors/src/tests/matching_elements_filler/matching_elements_filler_test.cpp12
-rw-r--r--streamingvisitors/src/tests/nearest_neighbor_field_searcher/CMakeLists.txt12
-rw-r--r--streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp185
-rw-r--r--streamingvisitors/src/tests/rank_processor/CMakeLists.txt9
-rw-r--r--streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp101
-rw-r--r--streamingvisitors/src/tests/searcher/CMakeLists.txt2
-rw-r--r--streamingvisitors/src/tests/searcher/searcher_test.cpp30
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/hitcollector.cpp2
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/hitcollector.h8
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/indexenvironment.cpp5
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/indexenvironment.h4
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp13
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp64
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/rankprocessor.h6
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp52
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchvisitor.h9
-rw-r--r--streamingvisitors/src/vespa/vsm/common/document.h2
-rw-r--r--streamingvisitors/src/vespa/vsm/config/vsmfields.def2
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/CMakeLists.txt1
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.cpp7
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp59
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h20
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp10
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp10
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp10
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/mock_field_searcher_env.h48
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp160
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h58
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp16
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8exactstringfieldsearcher.cpp5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8flexiblestringfieldsearcher.cpp4
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8strchrfieldsearcher.cpp9
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.cpp7
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.h5
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8substringsearcher.cpp7
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/utf8substringsnippetmodifier.cpp7
-rw-r--r--streamingvisitors/src/vespa/vsm/vsm/fieldsearchspec.cpp19
-rw-r--r--streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.cpp10
-rw-r--r--streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.h9
-rw-r--r--streamingvisitors/src/vespa/vsm/vsm/vsm-adapter.h10
-rw-r--r--vdslib/src/main/java/com/yahoo/vdslib/distribution/Distribution.java4
-rw-r--r--vdslib/src/tests/distribution/distributiontest.cpp6
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/DefaultSignedIdentityDocument.java14
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java124
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java54
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocumentClient.java7
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/LegacySignedIdentityDocument.java6
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java52
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/DefaultSignedIdentityDocumentEntity.java12
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java51
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/LegacySignedIdentityDocumentEntity.java57
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java124
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java18
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java117
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderProvider.java38
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java16
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java50
-rw-r--r--vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImpl.java392
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapperTest.java43
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java50
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java68
-rw-r--r--vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImplTest.java160
-rw-r--r--vespa-dependencies-enforcer/allowed-maven-dependencies.txt40
-rw-r--r--vespa-documentgen-plugin/etc/music/music.sd13
-rw-r--r--vespa-documentgen-plugin/src/test/java/com/yahoo/vespa/DocumentGenTest.java4
-rw-r--r--vespa-feed-client-api/pom.xml6
-rw-r--r--vespa-feed-client-cli/pom.xml6
-rw-r--r--vespa-feed-client/pom.xml6
-rw-r--r--vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java16
-rw-r--r--vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpRequestStrategy.java7
-rw-r--r--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java110
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java9
-rw-r--r--vespajlib/abi-spec.json108
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/LanguageModel.java20
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/completion/Completion.java41
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/completion/Prompt.java23
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/completion/StringPrompt.java43
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/completion/package-info.java11
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/package-info.java11
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/test/MockLanguageModel.java44
-rw-r--r--vespajlib/src/main/java/ai/vespa/llm/test/package-info.java11
-rw-r--r--vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java26
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/BinaryEncoder.java2
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/BinaryView.java99
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/DecodeIndex.java63
-rw-r--r--vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java95
-rw-r--r--vespajlib/src/main/java/com/yahoo/tensor/functions/ReduceJoin.java2
-rw-r--r--vespajlib/src/test/java/ai/vespa/llm/completion/CompletionTest.java40
-rw-r--r--vespajlib/src/test/java/com/yahoo/io/FatalErrorHandlerTestCase.java58
-rw-r--r--vespajlib/src/test/java/com/yahoo/slime/BinaryViewTest.java64
-rw-r--r--vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java217
-rw-r--r--vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp2
-rw-r--r--vespalib/src/tests/btree/btree_test.cpp2
-rw-r--r--vespalib/src/tests/datastore/array_store/array_store_test.cpp68
-rw-r--r--vespalib/src/tests/datastore/array_store_config/array_store_config_test.cpp22
-rw-r--r--vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp14
-rw-r--r--vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp160
-rw-r--r--vespalib/src/tests/datastore/datastore/datastore_test.cpp128
-rw-r--r--vespalib/src/tests/datastore/free_list/free_list_test.cpp12
-rw-r--r--vespalib/src/tests/datastore/unique_store/unique_store_test.cpp41
-rw-r--r--vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp18
-rw-r--r--vespalib/src/tests/signalhandler/CMakeLists.txt5
-rw-r--r--vespalib/src/vespa/vespalib/btree/btree.h4
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodeallocator.h4
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp8
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodestore.h16
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreenodestore.hpp10
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreestore.h10
-rw-r--r--vespalib/src/vespa/vespalib/btree/btreestore.hpp26
-rw-r--r--vespalib/src/vespa/vespalib/datastore/allocator.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/allocator.hpp26
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store.h20
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store.hpp105
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store_config.cpp14
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store_config.h28
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store_simple_type_mapper.h6
-rw-r--r--vespalib/src/vespa/vespalib/datastore/array_store_type_mapper.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_free_list.cpp7
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_free_list.h7
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp40
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_stats.h50
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_type.cpp155
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_type.h108
-rw-r--r--vespalib/src/vespa/vespalib/datastore/buffer_type.hpp74
-rw-r--r--vespalib/src/vespa/vespalib/datastore/bufferstate.cpp122
-rw-r--r--vespalib/src/vespa/vespalib/datastore/bufferstate.h39
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastore.h14
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastore.hpp14
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastorebase.cpp125
-rw-r--r--vespalib/src/vespa/vespalib/datastore/datastorebase.h57
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list.h1
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list_allocator.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list_allocator.hpp10
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.h2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.hpp10
-rw-r--r--vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h8
-rw-r--r--vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp22
-rw-r--r--vespalib/src/vespa/vespalib/datastore/memory_stats.cpp16
-rw-r--r--vespalib/src/vespa/vespalib/datastore/memory_stats.h8
-rw-r--r--vespalib/src/vespa/vespalib/datastore/raw_allocator.h6
-rw-r--r--vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp14
-rw-r--r--vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.h4
-rw-r--r--vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.hpp14
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store.hpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_enumerator.hpp2
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.cpp18
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h8
-rw-r--r--vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp9
-rw-r--r--vespalib/src/vespa/vespalib/gtest/matchers/elements_are_distinct.h35
-rw-r--r--vespalib/src/vespa/vespalib/util/CMakeLists.txt5
-rw-r--r--vespalib/src/vespa/vespalib/util/featureset.cpp (renamed from searchlib/src/vespa/searchlib/common/featureset.cpp)4
-rw-r--r--vespalib/src/vespa/vespalib/util/featureset.h (renamed from searchlib/src/vespa/searchlib/common/featureset.h)5
-rw-r--r--vespalib/src/vespa/vespalib/util/time.cpp2
-rw-r--r--vespalib/src/vespa/vespalib/util/time.h2
778 files changed, 11773 insertions, 7108 deletions
diff --git a/airlift-zstd/src/test/java/ai/vespa/airlift/zstd/TestCompressor.java b/airlift-zstd/src/test/java/ai/vespa/airlift/zstd/TestCompressor.java
index d6f13b98c71..4aa00f91ffc 100644
--- a/airlift-zstd/src/test/java/ai/vespa/airlift/zstd/TestCompressor.java
+++ b/airlift-zstd/src/test/java/ai/vespa/airlift/zstd/TestCompressor.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static sun.misc.Unsafe.ARRAY_BYTE_BASE_OFFSET;
+@SuppressWarnings("proprietary")
public class TestCompressor
{
@Test
diff --git a/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp b/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp
index fe7fd9d25fb..a52e0850b7d 100644
--- a/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp
+++ b/ann_benchmark/src/vespa/ann_benchmark/vespa_ann_benchmark.cpp
@@ -207,7 +207,8 @@ HnswIndex::find_top_k(uint32_t k, const std::vector<float>& value, uint32_t expl
TopKResult result;
std::vector<float> normalized_value;
auto typed_cells = get_typed_cells(value, normalized_value);
- auto raw_result = _nearest_neighbor_index->find_top_k(k, typed_cells, explore_k, std::numeric_limits<double>::max());
+ auto df = _nearest_neighbor_index->distance_function_factory().for_query_vector(typed_cells);
+ auto raw_result = _nearest_neighbor_index->find_top_k(k, *df, explore_k, std::numeric_limits<double>::max());
result.reserve(raw_result.size());
switch (_hnsw_index_params.distance_metric()) {
case DistanceMetric::Euclidean:
diff --git a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/InfrastructureApplication.java b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/InfrastructureApplication.java
index ad73905c395..e54f952c056 100644
--- a/application-model/src/main/java/com/yahoo/vespa/applicationmodel/InfrastructureApplication.java
+++ b/application-model/src/main/java/com/yahoo/vespa/applicationmodel/InfrastructureApplication.java
@@ -13,6 +13,7 @@ import java.util.stream.Stream;
* @author hakonhall
*/
public enum InfrastructureApplication {
+
CONTROLLER_HOST("controller-host", NodeType.controllerhost),
CONTROLLER("controller", NodeType.controller),
CONFIG_SERVER_HOST("configserver-host", NodeType.confighost),
diff --git a/client/go/go.mod b/client/go/go.mod
index 18e3853868d..94f69c8286a 100644
--- a/client/go/go.mod
+++ b/client/go/go.mod
@@ -13,8 +13,8 @@ require (
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.7.0
github.com/zalando/go-keyring v0.1.1
- golang.org/x/net v0.8.0
- golang.org/x/sys v0.6.0
+ golang.org/x/net v0.9.0
+ golang.org/x/sys v0.7.0
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b
)
@@ -28,7 +28,7 @@ require (
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/stretchr/objx v0.1.1 // indirect
- golang.org/x/text v0.8.0 // indirect
+ golang.org/x/text v0.9.0 // indirect
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.4.0 // indirect
)
diff --git a/client/go/go.sum b/client/go/go.sum
index 2af8bb1e4c0..ac662c9fd43 100644
--- a/client/go/go.sum
+++ b/client/go/go.sum
@@ -47,16 +47,16 @@ github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5Cc
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/zalando/go-keyring v0.1.1 h1:w2V9lcx/Uj4l+dzAf1m9s+DJ1O8ROkEHnynonHjTcYE=
github.com/zalando/go-keyring v0.1.1/go.mod h1:OIC+OZ28XbmwFxU/Rp9V7eKzZjamBJwRzC8UFJH9+L8=
-golang.org/x/net v0.8.0 h1:Zrh2ngAOFYneWTAIAPethzeaQLuHwhuBkuV6ZiRnUaQ=
-golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
+golang.org/x/net v0.9.0 h1:aWJ/m6xSmxWBx+V0XRHTlrYrPG56jKsLdTFmsSsCzOM=
+golang.org/x/net v0.9.0/go.mod h1:d48xBJpPfHeWQsugry2m+kC02ZBRGRgulfHnEXEuWns=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.6.0 h1:MVltZSvRTcU2ljQOhs94SXPftV6DCNnZViHeQps87pQ=
-golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/text v0.8.0 h1:57P1ETyNKtuIjB4SRd15iJxuhj8Gc416Y78H3qgMh68=
-golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
+golang.org/x/sys v0.7.0 h1:3jlCCIQZPdOYu1h8BkNvLz8Kgwtae2cagcG/VamtZRU=
+golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
+golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
diff --git a/client/go/internal/admin/envvars/env_vars.go b/client/go/internal/admin/envvars/env_vars.go
index ad58338254c..a2a6415f484 100644
--- a/client/go/internal/admin/envvars/env_vars.go
+++ b/client/go/internal/admin/envvars/env_vars.go
@@ -7,7 +7,6 @@ package envvars
const (
ADDR_CONFIGSERVER = "addr_configserver"
- CLOUDCONFIG_SERVER_MULTITENANT = "cloudconfig_server__multitenant"
CONFIGPROXY_RPC_PORT = "port_configproxy_rpc"
CONFIGSERVER_RPC_PORT = "port_configserver_rpc"
DEBUG_JVM_STARTUP = "DEBUG_JVM_STARTUP"
diff --git a/client/go/internal/admin/prog/common_env.go b/client/go/internal/admin/prog/common_env.go
deleted file mode 100644
index f743716a64e..00000000000
--- a/client/go/internal/admin/prog/common_env.go
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-// Author: arnej
-
-package prog
-
-import (
- "os"
- "strings"
-
- "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/vespa"
-)
-
-func (spec *Spec) configureCommonEnv() {
- os.Unsetenv(envvars.LD_PRELOAD)
- spec.Setenv(envvars.STD_THREAD_PREVENT_TRY_CATCH, "true")
- spec.Setenv(envvars.GLIBCXX_FORCE_NEW, "1")
- spec.Setenv(envvars.LD_LIBRARY_PATH, vespa.FindHome()+"/lib64:/opt/vespa-deps/lib64")
- spec.Setenv(envvars.MALLOC_ARENA_MAX, "1")
-
- // fallback from old env.vars:
- spec.considerEnvFallback(envvars.VESPA_USE_HUGEPAGES_LIST, envvars.HUGEPAGES_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_MADVISE_LIST, envvars.MADVISE_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC, envvars.VESPAMALLOC_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_D, envvars.VESPAMALLOCD_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_DST, envvars.VESPAMALLOCDST_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_NO_VESPAMALLOC, envvars.NO_VESPAMALLOC_LIST)
- // other fallbacks:
- spec.considerFallback(envvars.ROOT, vespa.FindHome())
- spec.considerFallback(envvars.VESPA_USER, vespa.FindVespaUser())
- spec.considerFallback(envvars.VESPA_USE_HUGEPAGES_LIST, "all")
- spec.considerFallback(envvars.VESPA_USE_VESPAMALLOC, "all")
- spec.considerFallback(envvars.VESPA_USE_NO_VESPAMALLOC, strings.Join([]string{
- "vespa-rpc-invoke",
- "vespa-get-config",
- "vespa-sentinel-cmd",
- "vespa-route",
- "vespa-proton-cmd",
- "vespa-configproxy-cmd",
- "vespa-config-status",
- }, " "))
-
-}
diff --git a/client/go/internal/admin/prog/hugepages.go b/client/go/internal/admin/prog/hugepages.go
index c6f019937ff..b66d512d4c9 100644
--- a/client/go/internal/admin/prog/hugepages.go
+++ b/client/go/internal/admin/prog/hugepages.go
@@ -9,7 +9,7 @@ import (
)
func (spec *Spec) ConfigureHugePages() {
- if spec.matchesListEnv(envvars.VESPA_USE_HUGEPAGES_LIST) {
+ if spec.MatchesListEnv(envvars.VESPA_USE_HUGEPAGES_LIST) {
trace.Debug("setting", envvars.VESPA_USE_HUGEPAGES, "= 'yes'")
spec.Setenv(envvars.VESPA_USE_HUGEPAGES, "yes")
}
diff --git a/client/go/internal/admin/prog/madvise.go b/client/go/internal/admin/prog/madvise.go
index 48986a12182..967823d956b 100644
--- a/client/go/internal/admin/prog/madvise.go
+++ b/client/go/internal/admin/prog/madvise.go
@@ -9,7 +9,7 @@ import (
)
func (spec *Spec) ConfigureUseMadvise() {
- limit := spec.valueFromListEnv(envvars.VESPA_USE_MADVISE_LIST)
+ limit := spec.ValueFromListEnv(envvars.VESPA_USE_MADVISE_LIST)
if limit != "" {
trace.Trace("shall use madvise with limit", limit, "as set in", envvars.VESPA_USE_MADVISE_LIST)
spec.Setenv(envvars.VESPA_MALLOC_MADVISE_LIMIT, limit)
diff --git a/client/go/internal/admin/prog/spec_env.go b/client/go/internal/admin/prog/spec_env.go
index 4fa40695acb..c88ec963812 100644
--- a/client/go/internal/admin/prog/spec_env.go
+++ b/client/go/internal/admin/prog/spec_env.go
@@ -50,17 +50,17 @@ func (spec *Spec) EffectiveEnv() []string {
return envVec
}
-func (spec *Spec) considerFallback(varName, varValue string) {
+func (spec *Spec) ConsiderFallback(varName, varValue string) {
if spec.Getenv(varName) == "" && varValue != "" {
spec.Setenv(varName, varValue)
}
}
-func (spec *Spec) considerEnvFallback(targetVar, fallbackVar string) {
- spec.considerFallback(targetVar, spec.Getenv(fallbackVar))
+func (spec *Spec) ConsiderEnvFallback(targetVar, fallbackVar string) {
+ spec.ConsiderFallback(targetVar, spec.Getenv(fallbackVar))
}
-func (p *Spec) matchesListEnv(envVarName string) bool {
+func (p *Spec) MatchesListEnv(envVarName string) bool {
return p.matchesListString(p.Getenv(envVarName))
}
@@ -80,7 +80,7 @@ func (p *Spec) matchesListString(env string) bool {
return false
}
-func (p *Spec) valueFromListEnv(envVarName string) string {
+func (p *Spec) ValueFromListEnv(envVarName string) string {
return p.valueFromListString(p.Getenv(envVarName))
}
diff --git a/client/go/internal/admin/prog/valgrind.go b/client/go/internal/admin/prog/valgrind.go
index 7d3fb059f8f..b949102d6bd 100644
--- a/client/go/internal/admin/prog/valgrind.go
+++ b/client/go/internal/admin/prog/valgrind.go
@@ -21,24 +21,18 @@ const (
func (p *Spec) ConfigureValgrind() {
p.shouldUseValgrind = false
p.shouldUseCallgrind = false
- env := p.Getenv(envvars.VESPA_USE_VALGRIND)
- parts := strings.Split(env, " ")
- for _, part := range parts {
- if p.BaseName == part {
- trace.Trace("using valgrind as", p.Program, "has basename in", envvars.VESPA_USE_VALGRIND, "=>", env)
- backticks := util.BackTicksWithStderr
- out, err := backticks.Run(VALGRIND_PROG, "--help")
- if err != nil {
- trace.Trace("trial run of valgrind fails:", err, "=>", out)
- return
- }
- if opts := p.Getenv(envvars.VESPA_VALGRIND_OPT); strings.Contains(opts, "callgrind") {
- p.shouldUseCallgrind = true
- }
- p.shouldUseValgrind = true
+ if p.MatchesListEnv(envvars.VESPA_USE_VALGRIND) {
+ trace.Trace("using valgrind as", p.Program, "has basename in", envvars.VESPA_USE_VALGRIND)
+ backticks := util.BackTicksWithStderr
+ out, err := backticks.Run(VALGRIND_PROG, "--help")
+ if err != nil {
+ trace.Trace("trial run of valgrind fails:", err, "=>", out)
return
}
- trace.Debug("checking", envvars.VESPA_USE_VALGRIND, ":", p.BaseName, "!=", part)
+ if opts := p.Getenv(envvars.VESPA_VALGRIND_OPT); strings.Contains(opts, "callgrind") {
+ p.shouldUseCallgrind = true
+ }
+ p.shouldUseValgrind = true
}
}
diff --git a/client/go/internal/admin/prog/valgrind_test.go b/client/go/internal/admin/prog/valgrind_test.go
index 6ec622277c6..11d9424405f 100644
--- a/client/go/internal/admin/prog/valgrind_test.go
+++ b/client/go/internal/admin/prog/valgrind_test.go
@@ -52,6 +52,11 @@ func TestValgrindDetection(t *testing.T) {
assert.Equal(t, false, spec.shouldUseValgrind)
assert.Equal(t, false, spec.shouldUseCallgrind)
+ t.Setenv("VESPA_USE_VALGRIND", "all")
+ spec.ConfigureValgrind()
+ assert.Equal(t, true, spec.shouldUseValgrind)
+ assert.Equal(t, false, spec.shouldUseCallgrind)
+
t.Setenv("VESPA_USE_VALGRIND", "foo bar")
spec.ConfigureValgrind()
assert.Equal(t, false, spec.shouldUseValgrind)
diff --git a/client/go/internal/admin/prog/vespamalloc.go b/client/go/internal/admin/prog/vespamalloc.go
index 439935770d7..e66c9e5d966 100644
--- a/client/go/internal/admin/prog/vespamalloc.go
+++ b/client/go/internal/admin/prog/vespamalloc.go
@@ -27,7 +27,7 @@ func vespaMallocLib(suf string) string {
func (p *Spec) ConfigureVespaMalloc() {
p.shouldUseVespaMalloc = false
- if p.matchesListEnv(envvars.VESPA_USE_NO_VESPAMALLOC) {
+ if p.MatchesListEnv(envvars.VESPA_USE_NO_VESPAMALLOC) {
trace.Trace("use no vespamalloc:", p.BaseName)
return
}
@@ -36,11 +36,11 @@ func (p *Spec) ConfigureVespaMalloc() {
return
}
var useFile string
- if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_DST) {
+ if p.MatchesListEnv(envvars.VESPA_USE_VESPAMALLOC_DST) {
useFile = vespaMallocLib("libvespamallocdst16.so")
- } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_D) {
+ } else if p.MatchesListEnv(envvars.VESPA_USE_VESPAMALLOC_D) {
useFile = vespaMallocLib("libvespamallocd.so")
- } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC) {
+ } else if p.MatchesListEnv(envvars.VESPA_USE_VESPAMALLOC) {
useFile = vespaMallocLib("libvespamalloc.so")
}
trace.Trace("use file:", useFile)
@@ -51,7 +51,7 @@ func (p *Spec) ConfigureVespaMalloc() {
otherFile := vespaMallocLib("libvespa_load_as_huge.so")
useFile = fmt.Sprintf("%s:%s", useFile, otherFile)
}
- p.considerEnvFallback(envvars.VESPA_MALLOC_HUGEPAGES, envvars.VESPA_USE_HUGEPAGES)
+ p.ConsiderEnvFallback(envvars.VESPA_MALLOC_HUGEPAGES, envvars.VESPA_USE_HUGEPAGES)
p.vespaMallocPreload = useFile
p.shouldUseVespaMalloc = true
}
diff --git a/client/go/internal/admin/vespa-wrapper/configserver/logd.go b/client/go/internal/admin/vespa-wrapper/configserver/logd.go
index 39a2100b20f..8e1fb09548e 100644
--- a/client/go/internal/admin/vespa-wrapper/configserver/logd.go
+++ b/client/go/internal/admin/vespa-wrapper/configserver/logd.go
@@ -12,9 +12,7 @@ import (
)
func maybeStartLogd() {
- v1 := os.Getenv(envvars.CLOUDCONFIG_SERVER_MULTITENANT)
- v2 := os.Getenv(envvars.VESPA_CONFIGSERVER_MULTITENANT)
- if v1 == "true" || v2 == "true" {
+ if os.Getenv(envvars.VESPA_CONFIGSERVER_MULTITENANT) == "true" {
backticks := util.BackTicksForwardStderr
out, err := backticks.Run("libexec/vespa/start-logd")
if err != nil {
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/common_env.go b/client/go/internal/admin/vespa-wrapper/startcbinary/common_env.go
index 6bc730b5119..07ec19bf7e5 100644
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/common_env.go
+++ b/client/go/internal/admin/vespa-wrapper/startcbinary/common_env.go
@@ -8,40 +8,30 @@ import (
"strings"
"github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/prog"
"github.com/vespa-engine/vespa/client/go/internal/vespa"
)
-func (spec *ProgSpec) considerFallback(varName, varValue string) {
- if spec.getenv(varName) == "" && varValue != "" {
- spec.setenv(varName, varValue)
- }
-}
-
-func (spec *ProgSpec) considerEnvFallback(targetVar, fallbackVar string) {
- spec.considerFallback(targetVar, spec.getenv(fallbackVar))
-}
-
-func (spec *ProgSpec) configureCommonEnv() {
+func configureCommonEnv(spec *prog.Spec) {
os.Unsetenv(envvars.LD_PRELOAD)
- spec.setenv(envvars.STD_THREAD_PREVENT_TRY_CATCH, "true")
- spec.setenv(envvars.GLIBCXX_FORCE_NEW, "1")
- spec.setenv(envvars.LD_LIBRARY_PATH, vespa.FindHome()+"/lib64:/opt/vespa-deps/lib64")
- spec.setenv(envvars.MALLOC_ARENA_MAX, "1")
+ spec.Setenv(envvars.STD_THREAD_PREVENT_TRY_CATCH, "true")
+ spec.Setenv(envvars.GLIBCXX_FORCE_NEW, "1")
+ spec.Setenv(envvars.LD_LIBRARY_PATH, vespa.FindHome()+"/lib64:/opt/vespa-deps/lib64")
+ spec.Setenv(envvars.MALLOC_ARENA_MAX, "1")
// fallback from old env.vars:
- spec.considerEnvFallback(envvars.VESPA_USE_HUGEPAGES_LIST, envvars.HUGEPAGES_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_MADVISE_LIST, envvars.MADVISE_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC, envvars.VESPAMALLOC_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_D, envvars.VESPAMALLOCD_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_VESPAMALLOC_DST, envvars.VESPAMALLOCDST_LIST)
- spec.considerEnvFallback(envvars.VESPA_USE_NO_VESPAMALLOC, envvars.NO_VESPAMALLOC_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_HUGEPAGES_LIST, envvars.HUGEPAGES_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_MADVISE_LIST, envvars.MADVISE_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_VESPAMALLOC, envvars.VESPAMALLOC_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_VESPAMALLOC_D, envvars.VESPAMALLOCD_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_VESPAMALLOC_DST, envvars.VESPAMALLOCDST_LIST)
+ spec.ConsiderEnvFallback(envvars.VESPA_USE_NO_VESPAMALLOC, envvars.NO_VESPAMALLOC_LIST)
// other fallbacks:
- spec.considerFallback(envvars.ROOT, vespa.FindHome())
- spec.considerFallback(envvars.VESPA_USER, vespa.FindVespaUser())
- spec.considerFallback(envvars.VESPA_USE_HUGEPAGES_LIST, "all")
- spec.considerFallback(envvars.VESPA_USE_VESPAMALLOC, "all")
- spec.considerFallback(envvars.VESPA_USE_NO_VESPAMALLOC, strings.Join([]string{
+ spec.ConsiderFallback(envvars.ROOT, vespa.FindHome())
+ spec.ConsiderFallback(envvars.VESPA_USER, vespa.FindVespaUser())
+ spec.ConsiderFallback(envvars.VESPA_USE_HUGEPAGES_LIST, "all")
+ spec.ConsiderFallback(envvars.VESPA_USE_VESPAMALLOC, "all")
+ spec.ConsiderFallback(envvars.VESPA_USE_NO_VESPAMALLOC, strings.Join([]string{
"vespa-rpc-invoke",
"vespa-get-config",
"vespa-sentinel-cmd",
@@ -53,31 +43,16 @@ func (spec *ProgSpec) configureCommonEnv() {
}
-func (spec *ProgSpec) configureHugePages() {
- if spec.matchesListEnv(envvars.VESPA_USE_HUGEPAGES_LIST) {
- spec.setenv(envvars.VESPA_USE_HUGEPAGES, "yes")
- }
-}
-
-func (spec *ProgSpec) configureUseMadvise() {
- limit := spec.valueFromListEnv(envvars.VESPA_USE_MADVISE_LIST)
- if limit != "" {
- trace.Trace("shall use madvise with limit", limit, "as set in", envvars.VESPA_USE_MADVISE_LIST)
- spec.setenv(envvars.VESPA_MALLOC_MADVISE_LIMIT, limit)
- return
- }
-}
-
-func (spec *ProgSpec) configurePath() {
+func configurePath(spec *prog.Spec) {
// Prefer newer gdb and pstack:
- spec.prependPath("/opt/rh/gcc-toolset-12/root/usr/bin")
+ prependPath("/opt/rh/gcc-toolset-12/root/usr/bin", spec)
// Maven is needed for tester applications:
- spec.prependPath(vespa.FindHome() + "/local/maven/bin")
- spec.prependPath(vespa.FindHome() + "/bin64")
- spec.prependPath(vespa.FindHome() + "/bin")
+ prependPath(vespa.FindHome()+"/local/maven/bin", spec)
+ prependPath(vespa.FindHome()+"/bin64", spec)
+ prependPath(vespa.FindHome()+"/bin", spec)
// how to find the "java" program?
// should be available in $VESPA_HOME/bin or JAVA_HOME
- if javaHome := spec.getenv(envvars.JAVA_HOME); javaHome != "" {
- spec.prependPath(javaHome + "/bin")
+ if javaHome := spec.Getenv(envvars.JAVA_HOME); javaHome != "" {
+ prependPath(javaHome+"/bin", spec)
}
}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/numactl.go b/client/go/internal/admin/vespa-wrapper/startcbinary/numactl.go
deleted file mode 100644
index fe091dedba9..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/numactl.go
+++ /dev/null
@@ -1,72 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-// Author: arnej
-
-package startcbinary
-
-import (
- "fmt"
- "strconv"
- "strings"
-
- "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
- "github.com/vespa-engine/vespa/client/go/internal/util"
-)
-
-func (p *ProgSpec) configureNumaCtl() {
- p.shouldUseNumaCtl = false
- p.numaSocket = -1
- if p.getenv(envvars.VESPA_NO_NUMACTL) != "" {
- return
- }
- backticks := util.BackTicksIgnoreStderr
- out, err := backticks.Run("numactl", "--hardware")
- trace.Debug("numactl --hardware says:", out)
- if err != nil {
- trace.Trace("numactl error:", err)
- return
- }
- outfoo, errfoo := backticks.Run("numactl", "--interleave", "all", "echo", "foo")
- if errfoo != nil {
- trace.Trace("cannot run with numactl:", errfoo)
- return
- }
- if outfoo != "foo\n" {
- trace.Trace("bad numactl output:", outfoo)
- return
- }
- p.shouldUseNumaCtl = true
- if affinity := p.getenv(envvars.VESPA_AFFINITY_CPU_SOCKET); affinity != "" {
- wantSocket, _ := strconv.Atoi(affinity)
- trace.Debug("want socket:", wantSocket)
- parts := strings.Fields(out)
- for idx := 0; idx+2 < len(parts); idx++ {
- if parts[idx] == "available:" && parts[idx+2] == "nodes" {
- numSockets, _ := strconv.Atoi(parts[idx+1])
- trace.Debug("numSockets:", numSockets)
- if numSockets > 1 {
- p.numaSocket = (wantSocket % numSockets)
- return
- }
- }
- }
- }
-}
-
-func (p *ProgSpec) numaCtlBinary() string {
- return "numactl"
-}
-
-func (p *ProgSpec) prependNumaCtl(args []string) []string {
- v := util.NewArrayList[string](5 + len(args))
- v.Append("numactl")
- if p.numaSocket >= 0 {
- v.Append(fmt.Sprintf("--cpunodebind=%d", p.numaSocket))
- v.Append(fmt.Sprintf("--membind=%d", p.numaSocket))
- } else {
- v.Append("--interleave")
- v.Append("all")
- }
- v.AppendAll(args...)
- return v
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/numactl_test.go b/client/go/internal/admin/vespa-wrapper/startcbinary/numactl_test.go
deleted file mode 100644
index 65f52be988e..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/numactl_test.go
+++ /dev/null
@@ -1,89 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package startcbinary
-
-import (
- "fmt"
- "os"
- "runtime"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
-)
-
-func setup(t *testing.T, testFileName string) {
- trace.AdjustVerbosity(1)
- mockBinParent = strings.TrimSuffix(testFileName, "/numactl_test.go")
- tmpBin = t.TempDir() + "/mock.bin.numactl_test"
- err := os.MkdirAll(tmpBin, 0755)
- assert.Nil(t, err)
- t.Setenv("PATH", fmt.Sprintf("%s:%s", tmpBin, os.Getenv("PATH")))
-}
-
-func TestNumaCtlDetection(t *testing.T) {
- if runtime.GOOS == "windows" {
- return
- }
- _, tfn, _, _ := runtime.Caller(0)
- setup(t, tfn)
- orig := []string{"/bin/myprog", "-c", "cfgid"}
- spec := NewProgSpec(orig)
-
- useMock("no-numactl", "numactl")
- spec.configureNumaCtl()
- assert.Equal(t, false, spec.shouldUseNumaCtl)
-
- useMock("bad-numactl", "numactl")
- spec.configureNumaCtl()
- assert.Equal(t, false, spec.shouldUseNumaCtl)
-
- t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "")
- useMock("good-numactl", "numactl")
- spec.configureNumaCtl()
- assert.Equal(t, true, spec.shouldUseNumaCtl)
- assert.Equal(t, -1, spec.numaSocket)
- argv := spec.prependNumaCtl(orig)
- trace.Trace("argv:", argv)
- assert.Equal(t, 6, len(argv))
- assert.Equal(t, "numactl", argv[0])
- assert.Equal(t, "--interleave", argv[1])
- assert.Equal(t, "all", argv[2])
- assert.Equal(t, "/bin/myprog-bin", argv[3])
- assert.Equal(t, "-c", argv[4])
- assert.Equal(t, "cfgid", argv[5])
-
- t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "0")
- spec.configureNumaCtl()
- assert.Equal(t, true, spec.shouldUseNumaCtl)
- assert.Equal(t, 0, spec.numaSocket)
- argv = spec.prependNumaCtl(orig)
- trace.Trace("argv:", argv)
- assert.Equal(t, 6, len(argv))
- assert.Equal(t, "numactl", argv[0])
- assert.Equal(t, "--cpunodebind=0", argv[1])
- assert.Equal(t, "--membind=0", argv[2])
- assert.Equal(t, "/bin/myprog-bin", argv[3])
- assert.Equal(t, "-c", argv[4])
- assert.Equal(t, "cfgid", argv[5])
-
- t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "1")
- spec.configureNumaCtl()
- assert.Equal(t, true, spec.shouldUseNumaCtl)
- assert.Equal(t, 1, spec.numaSocket)
- argv = spec.prependNumaCtl(orig)
- trace.Trace("argv:", argv)
- assert.Equal(t, 6, len(argv))
- assert.Equal(t, "numactl", argv[0])
- assert.Equal(t, "--cpunodebind=1", argv[1])
- assert.Equal(t, "--membind=1", argv[2])
- assert.Equal(t, "/bin/myprog-bin", argv[3])
- assert.Equal(t, "-c", argv[4])
- assert.Equal(t, "cfgid", argv[5])
-
- t.Setenv("VESPA_AFFINITY_CPU_SOCKET", "2")
- spec.configureNumaCtl()
- assert.Equal(t, true, spec.shouldUseNumaCtl)
- assert.Equal(t, 0, spec.numaSocket)
-
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/progspec.go b/client/go/internal/admin/vespa-wrapper/startcbinary/progspec.go
index 9975f6c3c90..b0dcc402893 100644
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/progspec.go
+++ b/client/go/internal/admin/vespa-wrapper/startcbinary/progspec.go
@@ -8,56 +8,21 @@ import (
"strings"
"github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/prog"
)
-type ProgSpec struct {
- Program string
- Args []string
- BaseName string
- Env map[string]string
- numaSocket int
- shouldUseCallgrind bool
- shouldUseValgrind bool
- shouldUseNumaCtl bool
- shouldUseVespaMalloc bool
- vespaMallocPreload string
-}
-
-func NewProgSpec(argv []string) *ProgSpec {
+func NewProgSpec(argv []string) *prog.Spec {
progName := argv[0]
binProg := progName + "-bin"
- p := ProgSpec{
- Program: binProg,
- Args: argv,
- BaseName: baseNameOf(progName),
- Env: make(map[string]string),
- numaSocket: -1,
- }
+ p := prog.NewSpec(argv)
+ p.Program = binProg
p.Args[0] = binProg
- return &p
-}
-
-func baseNameOf(s string) string {
- idx := strings.LastIndex(s, "/")
- idx++
- return s[idx:]
-}
-
-func (p *ProgSpec) setenv(k, v string) {
- p.Env[k] = v
-}
-
-func (p *ProgSpec) getenv(k string) string {
- if v, ok := p.Env[k]; ok {
- return v
- }
- return os.Getenv(k)
+ return p
}
-func (p *ProgSpec) prependPath(dirName string) {
+func prependPath(dirName string, p *prog.Spec) {
pathList := []string{dirName}
- oldPath := p.getenv(envvars.PATH)
+ oldPath := p.Getenv(envvars.PATH)
if oldPath == "" {
oldPath = "/usr/bin"
}
@@ -67,78 +32,6 @@ func (p *ProgSpec) prependPath(dirName string) {
}
}
newPath := strings.Join(pathList, ":")
- p.setenv(envvars.PATH, newPath)
+ p.Setenv(envvars.PATH, newPath)
os.Setenv(envvars.PATH, newPath)
}
-
-func (p *ProgSpec) matchesListEnv(envVarName string) bool {
- return p.matchesListString(p.getenv(envVarName))
-}
-
-func (p *ProgSpec) matchesListString(env string) bool {
- if env == "all" {
- trace.Debug(p.Program, "always matching in:", env)
- return true
- }
- parts := strings.Fields(env)
- for _, part := range parts {
- if p.BaseName == part {
- trace.Debug(p.Program, "has basename matching in:", env)
- return true
- }
- trace.Debug("checking matching:", p.BaseName, "!=", part)
- }
- return false
-}
-
-func (p *ProgSpec) valueFromListEnv(envVarName string) string {
- return p.valueFromListString(p.getenv(envVarName))
-}
-
-func (p *ProgSpec) valueFromListString(env string) string {
- parts := strings.Fields(env)
- for _, part := range parts {
- idx := strings.Index(part, "=")
- if idx <= 0 {
- trace.Trace("expected key=value, but got:", part)
- continue
- }
- partName := part[:idx]
- idx++
- partValue := part[idx:]
- if p.BaseName == partName || partName == "all" {
- trace.Debug(p.Program, "has basename matching in:", env)
- return partValue
- }
- trace.Debug("checking matching:", p.BaseName, "!=", part)
- }
- return ""
-}
-
-func (spec *ProgSpec) effectiveEnv() []string {
- env := make(map[string]string)
- for _, entry := range os.Environ() {
- addInMap := func(kv string) bool {
- for idx, elem := range kv {
- if elem == '=' {
- k := kv[:idx]
- env[k] = kv
- return true
- }
- }
- return false
- }
- if !addInMap(entry) {
- env[entry] = ""
- }
- }
- for k, v := range spec.Env {
- trace.Trace("add to environment:", k, "=", v)
- env[k] = k + "=" + v
- }
- envv := make([]string, 0, len(env))
- for _, v := range env {
- envv = append(envv, v)
- }
- return envv
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/progspec_test.go b/client/go/internal/admin/vespa-wrapper/startcbinary/progspec_test.go
deleted file mode 100644
index be113e4e350..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/progspec_test.go
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package startcbinary
-
-import (
- "testing"
-
- "github.com/stretchr/testify/assert"
-)
-
-func TestProgSpec(t *testing.T) {
- spec := NewProgSpec([]string{"/opt/vespa/bin/foobar"})
- var b bool
-
- b = spec.matchesListString("")
- assert.Equal(t, false, b)
- b = spec.matchesListString("foobar")
- assert.Equal(t, true, b)
- b = spec.matchesListString("foo bar")
- assert.Equal(t, false, b)
- b = spec.matchesListString("one foobar")
- assert.Equal(t, true, b)
- b = spec.matchesListString("foobar two")
- assert.Equal(t, true, b)
- b = spec.matchesListString("one foobar two")
- assert.Equal(t, true, b)
- b = spec.matchesListString("all")
- assert.Equal(t, true, b)
-
- var s string
- s = spec.valueFromListString("")
- assert.Equal(t, "", s)
- s = spec.valueFromListString("foobar=123")
- assert.Equal(t, "123", s)
- s = spec.valueFromListString("one=1 foobar=123 two=2")
- assert.Equal(t, "123", s)
- s = spec.valueFromListString("one=1 all=123")
- assert.Equal(t, "123", s)
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/startcbinary.go b/client/go/internal/admin/vespa-wrapper/startcbinary/startcbinary.go
index f5e58e59808..a062f631b2c 100644
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/startcbinary.go
+++ b/client/go/internal/admin/vespa-wrapper/startcbinary/startcbinary.go
@@ -7,20 +7,19 @@ import (
"fmt"
"os"
- "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/util"
+ "github.com/vespa-engine/vespa/client/go/internal/admin/prog"
)
-func startCbinary(spec *ProgSpec) int {
- spec.configureCommonEnv()
- spec.configurePath()
- spec.configureTuning()
- spec.configureValgrind()
- spec.configureNumaCtl()
- spec.configureHugePages()
- spec.configureUseMadvise()
- spec.configureVespaMalloc()
- err := spec.run()
+func startCbinary(spec *prog.Spec) int {
+ configureCommonEnv(spec)
+ configurePath(spec)
+ configureTuning()
+ spec.ConfigureValgrind()
+ spec.ConfigureNumaCtl()
+ spec.ConfigureHugePages()
+ spec.ConfigureUseMadvise()
+ spec.ConfigureVespaMalloc()
+ err := spec.Run()
if err != nil {
fmt.Fprintln(os.Stderr, err)
return 1
@@ -28,20 +27,3 @@ func startCbinary(spec *ProgSpec) int {
return 0
}
}
-
-func (spec *ProgSpec) run() error {
- prog := spec.Program
- args := spec.Args
- if spec.shouldUseValgrind {
- args = spec.prependValgrind(args)
- prog = spec.valgrindBinary()
- } else if spec.shouldUseNumaCtl {
- args = spec.prependNumaCtl(args)
- prog = spec.numaCtlBinary()
- }
- if spec.shouldUseVespaMalloc {
- spec.setenv(envvars.LD_PRELOAD, spec.vespaMallocPreload)
- }
- envv := spec.effectiveEnv()
- return util.Execvpe(prog, args, envv)
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/tuning.go b/client/go/internal/admin/vespa-wrapper/startcbinary/tuning.go
index 57230629d7a..f839d6a0946 100644
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/tuning.go
+++ b/client/go/internal/admin/vespa-wrapper/startcbinary/tuning.go
@@ -7,7 +7,7 @@ import (
"github.com/vespa-engine/vespa/client/go/internal/util"
)
-func (spec *ProgSpec) configureTuning() {
+func configureTuning() {
util.OptionallyReduceTimerFrequency()
util.TuneResourceLimits()
}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind.go b/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind.go
deleted file mode 100644
index 43a1ed602bd..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind.go
+++ /dev/null
@@ -1,82 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-// Author: arnej
-
-package startcbinary
-
-import (
- "fmt"
- "os"
- "strings"
-
- "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
- "github.com/vespa-engine/vespa/client/go/internal/util"
- "github.com/vespa-engine/vespa/client/go/internal/vespa"
-)
-
-func (p *ProgSpec) configureValgrind() {
- p.shouldUseValgrind = false
- p.shouldUseCallgrind = false
- env := p.getenv(envvars.VESPA_USE_VALGRIND)
- parts := strings.Split(env, " ")
- for _, part := range parts {
- if p.BaseName == part {
- trace.Trace("using valgrind as", p.Program, "has basename in", envvars.VESPA_USE_VALGRIND, "=>", env)
- backticks := util.BackTicksWithStderr
- out, err := backticks.Run("which", "valgrind")
- if err != nil {
- trace.Trace("no valgrind, 'which' fails:", err, "=>", out)
- return
- }
- if opts := p.getenv(envvars.VESPA_VALGRIND_OPT); strings.Contains(opts, "callgrind") {
- p.shouldUseCallgrind = true
- }
- p.shouldUseValgrind = true
- return
- }
- trace.Debug("checking", envvars.VESPA_USE_VALGRIND, ":", p.BaseName, "!=", part)
- }
-}
-
-func (p *ProgSpec) valgrindBinary() string {
- return "valgrind"
-}
-
-func (p *ProgSpec) valgrindOptions() []string {
- env := p.getenv(envvars.VESPA_VALGRIND_OPT)
- if env != "" {
- return strings.Fields(env)
- }
- result := []string{
- "--num-callers=32",
- "--run-libc-freeres=yes",
- "--track-origins=yes",
- "--freelist-vol=1000000000",
- "--leak-check=full",
- "--show-reachable=yes",
- }
- result = addValgrindSuppression(result, "etc/vespa/valgrind-suppressions.txt")
- result = addValgrindSuppression(result, "etc/vespa/suppressions.txt")
- return result
-}
-
-func addValgrindSuppression(r []string, fn string) []string {
- existsOk, fileName := vespa.HasFileUnderVespaHome(fn)
- if existsOk {
- r = append(r, fmt.Sprintf("--suppressions=%s", fileName))
- }
- return r
-}
-
-func (p *ProgSpec) valgrindLogOption() string {
- return fmt.Sprintf("--log-file=%s/tmp/valgrind.%s.log.%d", vespa.FindHome(), p.BaseName, os.Getpid())
-}
-
-func (p *ProgSpec) prependValgrind(args []string) []string {
- v := util.NewArrayList[string](15 + len(args))
- v.Append(p.valgrindBinary())
- v.AppendAll(p.valgrindOptions()...)
- v.Append(p.valgrindLogOption())
- v.AppendAll(args...)
- return v
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go b/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go
deleted file mode 100644
index 48cc78474ed..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go
+++ /dev/null
@@ -1,92 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package startcbinary
-
-import (
- "fmt"
- "os"
- "runtime"
- "strings"
- "testing"
-
- "github.com/stretchr/testify/assert"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
- "github.com/vespa-engine/vespa/client/go/internal/util"
-)
-
-var tmpBin string
-var mockBinParent string
-
-func useMock(prog, target string) {
- mock := fmt.Sprintf("%s/mockbin/%s", mockBinParent, prog)
- symlink := fmt.Sprintf("%s/%s", tmpBin, target)
- os.Remove(symlink)
- err := os.Symlink(mock, symlink)
- if err != nil {
- util.JustExitWith(err)
- }
-}
-
-func setupValgrind(t *testing.T, testFileName string) {
- trace.AdjustVerbosity(1)
- t.Setenv("VESPA_HOME", mockBinParent+"/mock_vespahome")
- mockBinParent = strings.TrimSuffix(testFileName, "/valgrind_test.go")
- tmpBin = t.TempDir() + "/mock.bin.valgrind_test"
- err := os.MkdirAll(tmpBin, 0755)
- assert.Nil(t, err)
- t.Setenv("PATH", fmt.Sprintf("%s:%s", tmpBin, os.Getenv("PATH")))
-}
-
-func TestValgrindDetection(t *testing.T) {
- if runtime.GOOS == "windows" {
- return
- }
- _, tfn, _, _ := runtime.Caller(0)
- setupValgrind(t, tfn)
- spec := NewProgSpec([]string{"/opt/vespa/bin/foobar"})
- var argv []string
-
- useMock("has-valgrind", "which")
-
- t.Setenv("VESPA_USE_VALGRIND", "")
- spec.configureValgrind()
- assert.Equal(t, false, spec.shouldUseValgrind)
- assert.Equal(t, false, spec.shouldUseCallgrind)
-
- t.Setenv("VESPA_USE_VALGRIND", "foo bar")
- spec.configureValgrind()
- assert.Equal(t, false, spec.shouldUseValgrind)
- assert.Equal(t, false, spec.shouldUseCallgrind)
-
- t.Setenv("VESPA_USE_VALGRIND", "foobar")
- spec.configureValgrind()
- assert.Equal(t, true, spec.shouldUseValgrind)
- assert.Equal(t, false, spec.shouldUseCallgrind)
-
- argv = spec.prependValgrind([]string{"/bin/myprog", "-c", "cfgid"})
- trace.Trace("argv:", argv)
- assert.Equal(t, 11, len(argv))
- assert.Equal(t, "valgrind", argv[0])
- assert.Equal(t, "/bin/myprog", argv[8])
-
- t.Setenv("VESPA_USE_VALGRIND", "another foobar yetmore")
- spec.configureValgrind()
- assert.Equal(t, true, spec.shouldUseValgrind)
- assert.Equal(t, false, spec.shouldUseCallgrind)
-
- t.Setenv("VESPA_VALGRIND_OPT", "--tool=callgrind")
- spec.configureValgrind()
- assert.Equal(t, true, spec.shouldUseValgrind)
- assert.Equal(t, true, spec.shouldUseCallgrind)
-
- argv = spec.prependValgrind([]string{"/bin/myprog", "-c", "cfgid"})
- trace.Trace("argv:", argv)
- assert.Equal(t, 6, len(argv))
- assert.Equal(t, "valgrind", argv[0])
- assert.Equal(t, "--tool=callgrind", argv[1])
- assert.Equal(t, "/bin/myprog", argv[3])
-
- useMock("no-valgrind", "which")
- spec.configureValgrind()
- assert.Equal(t, false, spec.shouldUseValgrind)
- assert.Equal(t, false, spec.shouldUseCallgrind)
-}
diff --git a/client/go/internal/admin/vespa-wrapper/startcbinary/vespamalloc.go b/client/go/internal/admin/vespa-wrapper/startcbinary/vespamalloc.go
deleted file mode 100644
index c6d53e1d03c..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/vespamalloc.go
+++ /dev/null
@@ -1,57 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-// Author: arnej
-
-package startcbinary
-
-import (
- "fmt"
-
- "github.com/vespa-engine/vespa/client/go/internal/admin/envvars"
- "github.com/vespa-engine/vespa/client/go/internal/admin/trace"
- "github.com/vespa-engine/vespa/client/go/internal/vespa"
-)
-
-func vespaMallocLib(suf string) string {
- prefixes := []string{"lib64", "lib"}
- for _, pre := range prefixes {
- fn := fmt.Sprintf("%s/vespa/malloc/%s", pre, suf)
- existsOk, fileName := vespa.HasFileUnderVespaHome(fn)
- if existsOk {
- trace.Debug("found library:", fileName)
- return fileName
- }
- trace.Debug("bad or missing library:", fn)
- }
- return ""
-}
-
-func (p *ProgSpec) configureVespaMalloc() {
- p.shouldUseVespaMalloc = false
- if p.matchesListEnv(envvars.VESPA_USE_NO_VESPAMALLOC) {
- trace.Trace("use no vespamalloc:", p.BaseName)
- return
- }
- if p.shouldUseValgrind && !p.shouldUseCallgrind {
- trace.Trace("use valgrind, so no vespamalloc:", p.BaseName)
- return
- }
- var useFile string
- if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_DST) {
- useFile = vespaMallocLib("libvespamallocdst16.so")
- } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC_D) {
- useFile = vespaMallocLib("libvespamallocd.so")
- } else if p.matchesListEnv(envvars.VESPA_USE_VESPAMALLOC) {
- useFile = vespaMallocLib("libvespamalloc.so")
- }
- trace.Trace("use file:", useFile)
- if useFile == "" {
- return
- }
- if loadAsHuge := p.getenv(envvars.VESPA_LOAD_CODE_AS_HUGEPAGES); loadAsHuge != "" {
- otherFile := vespaMallocLib("libvespa_load_as_huge.so")
- useFile = fmt.Sprintf("%s:%s", useFile, otherFile)
- }
- p.considerEnvFallback(envvars.VESPA_MALLOC_HUGEPAGES, envvars.VESPA_USE_HUGEPAGES)
- p.vespaMallocPreload = useFile
- p.shouldUseVespaMalloc = true
-}
diff --git a/client/go/internal/cli/auth/auth0/auth0.go b/client/go/internal/cli/auth/auth0/auth0.go
index 5f7612d4d2e..6fcd3f7680e 100644
--- a/client/go/internal/cli/auth/auth0/auth0.go
+++ b/client/go/internal/cli/auth/auth0/auth0.go
@@ -110,28 +110,40 @@ func (a *Client) getDeviceFlowConfig() (flowConfig, error) {
}
r, err := a.httpClient.Do(req, time.Second*30)
if err != nil {
- return flowConfig{}, fmt.Errorf("failed to get device flow config: %w", err)
+ return flowConfig{}, fmt.Errorf("auth0: failed to get device flow config: %w", err)
}
defer r.Body.Close()
if r.StatusCode/100 != 2 {
- return flowConfig{}, fmt.Errorf("failed to get device flow config: got response code %d from %s", r.StatusCode, url)
+ return flowConfig{}, fmt.Errorf("auth0: failed to get device flow config: got response code %d from %s", r.StatusCode, url)
}
var cfg flowConfig
if err := json.NewDecoder(r.Body).Decode(&cfg); err != nil {
- return flowConfig{}, fmt.Errorf("failed to decode response: %w", err)
+ return flowConfig{}, fmt.Errorf("auth0: failed to decode response: %w", err)
}
return cfg, nil
}
+func (a *Client) Authenticate(request *http.Request) error {
+ accessToken, err := a.AccessToken()
+ if err != nil {
+ return err
+ }
+ if request.Header == nil {
+ request.Header = make(http.Header)
+ }
+ request.Header.Set("Authorization", "Bearer "+accessToken)
+ return nil
+}
+
// AccessToken returns an access token for the configured system, refreshing it if necessary.
func (a *Client) AccessToken() (string, error) {
creds, ok := a.provider.Systems[a.options.SystemName]
if !ok {
- return "", fmt.Errorf("system %s is not configured", a.options.SystemName)
+ return "", fmt.Errorf("auth0: system %s is not configured: %s", a.options.SystemName, reauthMessage)
} else if creds.AccessToken == "" {
- return "", fmt.Errorf("access token missing: %s", reauthMessage)
+ return "", fmt.Errorf("auth0: access token missing: %s", reauthMessage)
} else if scopesChanged(creds) {
- return "", fmt.Errorf("authentication scopes changed: %s", reauthMessage)
+ return "", fmt.Errorf("auth0: authentication scopes changed: %s", reauthMessage)
} else if isExpired(creds.ExpiresAt, accessTokenExpiry) {
// check if the stored access token is expired:
// use the refresh token to get a new access token:
@@ -142,7 +154,7 @@ func (a *Client) AccessToken() (string, error) {
}
resp, err := tr.Refresh(cancelOnInterrupt(), a.options.SystemName)
if err != nil {
- return "", fmt.Errorf("failed to renew access token: %w: %s", err, reauthMessage)
+ return "", fmt.Errorf("auth0: failed to renew access token: %w: %s", err, reauthMessage)
} else {
// persist the updated system with renewed access token
creds.AccessToken = resp.AccessToken
@@ -173,12 +185,6 @@ func scopesChanged(s Credentials) bool {
return false
}
-// HasCredentials returns true if this client has retrived credentials for the configured system.
-func (a *Client) HasCredentials() bool {
- _, ok := a.provider.Systems[a.options.SystemName]
- return ok
-}
-
// WriteCredentials writes given credentials to the configuration file.
func (a *Client) WriteCredentials(credentials Credentials) error {
if a.provider.Systems == nil {
@@ -186,7 +192,7 @@ func (a *Client) WriteCredentials(credentials Credentials) error {
}
a.provider.Systems[a.options.SystemName] = credentials
if err := writeConfig(a.provider, a.options.ConfigPath); err != nil {
- return fmt.Errorf("failed to write config: %w", err)
+ return fmt.Errorf("auth0: failed to write config: %w", err)
}
return nil
}
@@ -195,11 +201,11 @@ func (a *Client) WriteCredentials(credentials Credentials) error {
func (a *Client) RemoveCredentials() error {
tr := &auth.TokenRetriever{Secrets: &auth.Keyring{}}
if err := tr.Delete(a.options.SystemName); err != nil {
- return fmt.Errorf("failed to remove system %s from secret storage: %w", a.options.SystemName, err)
+ return fmt.Errorf("auth0: failed to remove system %s from secret storage: %w", a.options.SystemName, err)
}
delete(a.provider.Systems, a.options.SystemName)
if err := writeConfig(a.provider, a.options.ConfigPath); err != nil {
- return fmt.Errorf("failed to write config: %w", err)
+ return fmt.Errorf("auth0: failed to write config: %w", err)
}
return nil
}
diff --git a/client/go/internal/cli/auth/zts/zts.go b/client/go/internal/cli/auth/zts/zts.go
index 1e84912a271..2c66ff13e8b 100644
--- a/client/go/internal/cli/auth/zts/zts.go
+++ b/client/go/internal/cli/auth/zts/zts.go
@@ -1,7 +1,6 @@
package zts
import (
- "crypto/tls"
"encoding/json"
"fmt"
"net/http"
@@ -18,26 +17,39 @@ const DefaultURL = "https://zts.athenz.ouroath.com:4443"
type Client struct {
client util.HTTPClient
tokenURL *url.URL
+ domain string
}
// NewClient creates a new client for an Athenz ZTS service located at serviceURL.
-func NewClient(client util.HTTPClient, serviceURL string) (*Client, error) {
+func NewClient(client util.HTTPClient, domain, serviceURL string) (*Client, error) {
tokenURL, err := url.Parse(serviceURL)
if err != nil {
return nil, err
}
tokenURL.Path = "/zts/v1/oauth2/token"
- return &Client{tokenURL: tokenURL, client: client}, nil
+ return &Client{tokenURL: tokenURL, client: client, domain: domain}, nil
}
-// AccessToken returns an access token within the given domain, using certificate to authenticate with ZTS.
-func (c *Client) AccessToken(domain string, certificate tls.Certificate) (string, error) {
- data := fmt.Sprintf("grant_type=client_credentials&scope=%s:domain", domain)
+func (c *Client) Authenticate(request *http.Request) error {
+ accessToken, err := c.AccessToken()
+ if err != nil {
+ return err
+ }
+ if request.Header == nil {
+ request.Header = make(http.Header)
+ }
+ request.Header.Add("Authorization", "Bearer "+accessToken)
+ return nil
+}
+
+// AccessToken returns an access token within the domain configured in client c.
+func (c *Client) AccessToken() (string, error) {
+ // TODO(mpolden): This should cache and re-use tokens until expiry
+ data := fmt.Sprintf("grant_type=client_credentials&scope=%s:domain", c.domain)
req, err := http.NewRequest("POST", c.tokenURL.String(), strings.NewReader(data))
if err != nil {
return "", err
}
- util.SetCertificate(c.client, []tls.Certificate{certificate})
response, err := c.client.Do(req, 10*time.Second)
if err != nil {
return "", err
@@ -45,7 +57,7 @@ func (c *Client) AccessToken(domain string, certificate tls.Certificate) (string
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
- return "", fmt.Errorf("got status %d from %s", response.StatusCode, c.tokenURL.String())
+ return "", fmt.Errorf("zts: got status %d from %s", response.StatusCode, c.tokenURL.String())
}
var ztsResponse struct {
AccessToken string `json:"access_token"`
diff --git a/client/go/internal/cli/auth/zts/zts_test.go b/client/go/internal/cli/auth/zts/zts_test.go
index d0cc7ea9f9d..1c75a94ee03 100644
--- a/client/go/internal/cli/auth/zts/zts_test.go
+++ b/client/go/internal/cli/auth/zts/zts_test.go
@@ -1,7 +1,6 @@
package zts
import (
- "crypto/tls"
"testing"
"github.com/vespa-engine/vespa/client/go/internal/mock"
@@ -9,17 +8,17 @@ import (
func TestAccessToken(t *testing.T) {
httpClient := mock.HTTPClient{}
- client, err := NewClient(&httpClient, "http://example.com")
+ client, err := NewClient(&httpClient, "vespa.vespa", "http://example.com")
if err != nil {
t.Fatal(err)
}
httpClient.NextResponseString(400, `{"message": "bad request"}`)
- _, err = client.AccessToken("vespa.vespa", tls.Certificate{})
+ _, err = client.AccessToken()
if err == nil {
t.Fatal("want error for non-ok response status")
}
httpClient.NextResponseString(200, `{"access_token": "foo bar"}`)
- token, err := client.AccessToken("vespa.vespa", tls.Certificate{})
+ token, err := client.AccessToken()
if err != nil {
t.Fatal(err)
}
diff --git a/client/go/internal/cli/cmd/cert.go b/client/go/internal/cli/cmd/cert.go
index 7f79a9db358..48bad974c3f 100644
--- a/client/go/internal/cli/cmd/cert.go
+++ b/client/go/internal/cli/cmd/cert.go
@@ -34,13 +34,18 @@ package specified as an argument to this command (default '.').
It's possible to override the private key and certificate used through
environment variables. This can be useful in continuous integration systems.
-Example of setting the certificate and key in-line:
+It's also possible override the CA certificate which can be useful when using self-signed certificates with a
+self-hosted Vespa service. See https://docs.vespa.ai/en/mtls.html for more information.
+Example of setting the CA certificate, certificate and key in-line:
+
+ export VESPA_CLI_DATA_PLANE_CA_CERT="my CA cert"
export VESPA_CLI_DATA_PLANE_CERT="my cert"
export VESPA_CLI_DATA_PLANE_KEY="my private key"
-Example of loading certificate and key from custom paths:
+Example of loading CA certificate, certificate and key from custom paths:
+ export VESPA_CLI_DATA_PLANE_CA_CERT_FILE=/path/to/cacert
export VESPA_CLI_DATA_PLANE_CERT_FILE=/path/to/cert
export VESPA_CLI_DATA_PLANE_KEY_FILE=/path/to/key
diff --git a/client/go/internal/cli/cmd/config.go b/client/go/internal/cli/cmd/config.go
index fd049864096..e2132814386 100644
--- a/client/go/internal/cli/cmd/config.go
+++ b/client/go/internal/cli/cmd/config.go
@@ -19,7 +19,6 @@ import (
"github.com/fatih/color"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
- "github.com/vespa-engine/vespa/client/go/internal/cli/auth/auth0"
"github.com/vespa-engine/vespa/client/go/internal/cli/config"
"github.com/vespa-engine/vespa/client/go/internal/vespa"
)
@@ -250,9 +249,10 @@ type Config struct {
}
type KeyPair struct {
- KeyPair tls.Certificate
- CertificateFile string
- PrivateKeyFile string
+ KeyPair tls.Certificate
+ RootCertificates []byte
+ CertificateFile string
+ PrivateKeyFile string
}
func loadConfig(environment map[string]string, flags map[string]*pflag.Flag) (*Config, error) {
@@ -392,6 +392,10 @@ func (c *Config) deploymentIn(system vespa.System) (vespa.Deployment, error) {
return vespa.Deployment{System: system, Application: app, Zone: zone}, nil
}
+func (c *Config) caCertificatePath() string {
+ return c.environment["VESPA_CLI_DATA_PLANE_CA_CERT_FILE"]
+}
+
func (c *Config) certificatePath(app vespa.ApplicationID, targetType string) (string, error) {
if override, ok := c.environment["VESPA_CLI_DATA_PLANE_CERT_FILE"]; ok {
return override, nil
@@ -412,50 +416,68 @@ func (c *Config) privateKeyPath(app vespa.ApplicationID, targetType string) (str
return c.applicationFilePath(app, "data-plane-private-key.pem")
}
-func (c *Config) x509KeyPair(app vespa.ApplicationID, targetType string) (KeyPair, error) {
+func (c *Config) readTLSOptions(app vespa.ApplicationID, targetType string) (vespa.TLSOptions, error) {
+ _, trustAll := c.environment["VESPA_CLI_DATA_PLANE_TRUST_ALL"]
cert, certOk := c.environment["VESPA_CLI_DATA_PLANE_CERT"]
key, keyOk := c.environment["VESPA_CLI_DATA_PLANE_KEY"]
- var (
- kp tls.Certificate
- err error
- certFile string
- keyFile string
- )
+ caCertText, caCertOk := c.environment["VESPA_CLI_DATA_PLANE_CA_CERT"]
+ options := vespa.TLSOptions{TrustAll: trustAll}
+ // CA certificate
+ if caCertOk {
+ options.CACertificate = []byte(caCertText)
+ } else {
+ caCertFile := c.caCertificatePath()
+ if caCertFile != "" {
+ b, err := os.ReadFile(caCertFile)
+ if err != nil {
+ return options, err
+ }
+ options.CACertificate = b
+ options.CACertificateFile = caCertFile
+ }
+ }
+ // Certificate and private key
if certOk && keyOk {
- // Use key pair from environment
- kp, err = tls.X509KeyPair([]byte(cert), []byte(key))
+ kp, err := tls.X509KeyPair([]byte(cert), []byte(key))
+ if err != nil {
+ return vespa.TLSOptions{}, err
+ }
+ options.KeyPair = []tls.Certificate{kp}
} else {
- keyFile, err = c.privateKeyPath(app, targetType)
+ keyFile, err := c.privateKeyPath(app, targetType)
if err != nil {
- return KeyPair{}, err
+ return vespa.TLSOptions{}, err
}
- certFile, err = c.certificatePath(app, targetType)
+ certFile, err := c.certificatePath(app, targetType)
if err != nil {
- return KeyPair{}, err
+ return vespa.TLSOptions{}, err
+ }
+ kp, err := tls.LoadX509KeyPair(certFile, keyFile)
+ if err == nil {
+ options.KeyPair = []tls.Certificate{kp}
+ options.PrivateKeyFile = keyFile
+ options.CertificateFile = certFile
+ } else if err != nil && !os.IsNotExist(err) {
+ return vespa.TLSOptions{}, err
}
- kp, err = tls.LoadX509KeyPair(certFile, keyFile)
- }
- if err != nil {
- return KeyPair{}, err
}
- if targetType == vespa.TargetHosted {
- cert, err := x509.ParseCertificate(kp.Certificate[0])
+ if options.KeyPair != nil {
+ cert, err := x509.ParseCertificate(options.KeyPair[0].Certificate[0])
if err != nil {
- return KeyPair{}, err
+ return vespa.TLSOptions{}, err
}
now := time.Now()
expiredAt := cert.NotAfter
if expiredAt.Before(now) {
delta := now.Sub(expiredAt).Truncate(time.Second)
- return KeyPair{}, fmt.Errorf("certificate %s expired at %s (%s ago)", certFile, cert.NotAfter, delta)
+ source := options.CertificateFile
+ if source == "" {
+ source = "environment"
+ }
+ return vespa.TLSOptions{}, fmt.Errorf("certificate in %s expired at %s (%s ago)", source, cert.NotAfter, delta)
}
- return KeyPair{KeyPair: kp, CertificateFile: certFile, PrivateKeyFile: keyFile}, nil
}
- return KeyPair{
- KeyPair: kp,
- CertificateFile: certFile,
- PrivateKeyFile: keyFile,
- }, nil
+ return options, nil
}
func (c *Config) apiKeyFileFromEnv() (string, bool) {
@@ -490,11 +512,10 @@ func (c *Config) readAPIKey(cli *CLI, system vespa.System, tenantName string) ([
return nil, nil // Vespa Cloud CI only talks to data plane and does not have an API key
}
if !cli.isCI() {
- client, err := cli.auth0Factory(cli.httpClient, auth0.Options{ConfigPath: c.authConfigPath(), SystemName: system.Name, SystemURL: system.URL})
- if err == nil && client.HasCredentials() {
- return nil, nil // use Auth0
+ if _, err := os.Stat(c.authConfigPath()); err == nil {
+ return nil, nil // We have auth config, so we should prefer Auth0 over API key
}
- cli.printWarning("Authenticating with API key. This is discouraged in non-CI environments", "Authenticate with 'vespa auth login'")
+ cli.printWarning("Authenticating with API key. This is discouraged in non-CI environments", "Authenticate with 'vespa auth login' instead")
}
return os.ReadFile(c.apiKeyPath(tenantName))
}
@@ -544,12 +565,12 @@ func (c *Config) list(includeUnset bool) []string {
}
// flagValue returns the set value and default value of the named flag.
-func (c *Config) flagValue(name string) (string, string) {
+func (c *Config) flagValue(name string) (string, string, bool) {
f, ok := c.flags[name]
if !ok {
- return "", ""
+ return "", "", ok
}
- return f.Value.String(), f.DefValue
+ return f.Value.String(), f.DefValue, f.Changed
}
// getNonEmpty returns value of given option, if that value is non-empty
@@ -564,9 +585,9 @@ func (c *Config) getNonEmpty(option string) (string, bool) {
// get returns the value associated with option, from the most preferred source in the following order: flag > local
// config > global config.
func (c *Config) get(option string) (string, bool) {
- flagValue, flagDefault := c.flagValue(option)
+ flagValue, flagDefault, changed := c.flagValue(option)
// explicit flag value always takes precedence over everything else
- if flagValue != flagDefault {
+ if changed {
return flagValue, true
}
// ... then local config, if option is explicitly defined there
diff --git a/client/go/internal/cli/cmd/config_test.go b/client/go/internal/cli/cmd/config_test.go
index 612904061de..66b65bf402b 100644
--- a/client/go/internal/cli/cmd/config_test.go
+++ b/client/go/internal/cli/cmd/config_test.go
@@ -2,15 +2,21 @@
package cmd
import (
+ "crypto/rand"
+ "crypto/rsa"
+ "crypto/tls"
+ "crypto/x509"
+ "crypto/x509/pkix"
+ "encoding/pem"
+ "math/big"
"os"
"path/filepath"
"testing"
+ "time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
- "github.com/vespa-engine/vespa/client/go/internal/cli/auth/auth0"
"github.com/vespa-engine/vespa/client/go/internal/mock"
- "github.com/vespa-engine/vespa/client/go/internal/util"
"github.com/vespa-engine/vespa/client/go/internal/vespa"
)
@@ -28,6 +34,7 @@ func TestConfig(t *testing.T) {
assertConfigCommand(t, configHome, "", "config", "set", "target", "http://127.0.0.1:8080")
assertConfigCommand(t, configHome, "", "config", "set", "target", "https://127.0.0.1")
assertConfigCommand(t, configHome, "target = https://127.0.0.1\n", "config", "get", "target")
+ assertConfigCommand(t, configHome, "target = local\n", "config", "get", "-t", "local", "target")
// application
assertConfigCommandErr(t, configHome, "Error: invalid application: \"foo\"\n", "config", "set", "application", "foo")
@@ -165,7 +172,7 @@ func TestReadAPIKey(t *testing.T) {
require.Nil(t, err)
assert.Equal(t, []byte("foo"), key)
- // Cloud CI does not read key from disk as it's not expected to have any
+ // Cloud CI never reads key from disk as it's not expected to have any
cli, _, _ = newTestCLI(t, "VESPA_CLI_CLOUD_CI=true")
key, err = cli.config.readAPIKey(cli, vespa.PublicSystem, "t1")
require.Nil(t, err)
@@ -185,12 +192,111 @@ func TestReadAPIKey(t *testing.T) {
require.Nil(t, err)
assert.Equal(t, []byte("baz"), key)
- // Auth0 is preferred when configured
+ // Prefer Auth0 if we have auth config
cli, _, _ = newTestCLI(t)
- cli.auth0Factory = func(httpClient util.HTTPClient, options auth0.Options) (auth0Client, error) {
- return &mockAuth0{hasCredentials: true}, nil
- }
+ require.Nil(t, os.WriteFile(filepath.Join(cli.config.homeDir, "auth.json"), []byte("foo"), 0600))
key, err = cli.config.readAPIKey(cli, vespa.PublicSystem, "t1")
require.Nil(t, err)
assert.Nil(t, key)
}
+
+func TestConfigReadTLSOptions(t *testing.T) {
+ app := vespa.ApplicationID{Tenant: "t1", Application: "a1", Instance: "i1"}
+ homeDir := t.TempDir()
+
+ // No environment variables, and no files on disk
+ assertTLSOptions(t, homeDir, app, vespa.TargetLocal, vespa.TLSOptions{})
+
+ // A single environment variable is set
+ assertTLSOptions(t, homeDir, app, vespa.TargetLocal, vespa.TLSOptions{TrustAll: true}, "VESPA_CLI_DATA_PLANE_TRUST_ALL=true")
+
+ // Key pair is provided in-line in environment variables
+ pemCert, pemKey, keyPair := createKeyPair(t)
+ assertTLSOptions(t, homeDir, app,
+ vespa.TargetLocal,
+ vespa.TLSOptions{
+ TrustAll: true,
+ CACertificate: []byte("cacert"),
+ KeyPair: []tls.Certificate{keyPair},
+ },
+ "VESPA_CLI_DATA_PLANE_TRUST_ALL=true",
+ "VESPA_CLI_DATA_PLANE_CA_CERT=cacert",
+ "VESPA_CLI_DATA_PLANE_CERT="+string(pemCert),
+ "VESPA_CLI_DATA_PLANE_KEY="+string(pemKey),
+ )
+
+ // Key pair is provided as file paths through environment variables
+ certFile := filepath.Join(homeDir, "cert")
+ keyFile := filepath.Join(homeDir, "key")
+ caCertFile := filepath.Join(homeDir, "cacert")
+ require.Nil(t, os.WriteFile(certFile, pemCert, 0600))
+ require.Nil(t, os.WriteFile(keyFile, pemKey, 0600))
+ require.Nil(t, os.WriteFile(caCertFile, []byte("cacert"), 0600))
+ assertTLSOptions(t, homeDir, app,
+ vespa.TargetLocal,
+ vespa.TLSOptions{
+ KeyPair: []tls.Certificate{keyPair},
+ CACertificate: []byte("cacert"),
+ CACertificateFile: caCertFile,
+ CertificateFile: certFile,
+ PrivateKeyFile: keyFile,
+ },
+ "VESPA_CLI_DATA_PLANE_CERT_FILE="+certFile,
+ "VESPA_CLI_DATA_PLANE_KEY_FILE="+keyFile,
+ "VESPA_CLI_DATA_PLANE_CA_CERT_FILE="+caCertFile,
+ )
+
+ // Key pair resides in default paths
+ defaultCertFile := filepath.Join(homeDir, app.String(), "data-plane-public-cert.pem")
+ defaultKeyFile := filepath.Join(homeDir, app.String(), "data-plane-private-key.pem")
+ require.Nil(t, os.WriteFile(defaultCertFile, pemCert, 0600))
+ require.Nil(t, os.WriteFile(defaultKeyFile, pemKey, 0600))
+ assertTLSOptions(t, homeDir, app,
+ vespa.TargetLocal,
+ vespa.TLSOptions{
+ KeyPair: []tls.Certificate{keyPair},
+ CertificateFile: defaultCertFile,
+ PrivateKeyFile: defaultKeyFile,
+ },
+ )
+}
+
+func assertTLSOptions(t *testing.T, homeDir string, app vespa.ApplicationID, target string, want vespa.TLSOptions, envVars ...string) {
+ t.Helper()
+ envVars = append(envVars, "VESPA_CLI_HOME="+homeDir)
+ cli, _, _ := newTestCLI(t, envVars...)
+ require.Nil(t, cli.Run("config", "set", "application", app.String()))
+ config, err := cli.config.readTLSOptions(app, vespa.TargetLocal)
+ require.Nil(t, err)
+ assert.Equal(t, want, config)
+}
+
+func createKeyPair(t *testing.T) ([]byte, []byte, tls.Certificate) {
+ privateKey, err := rsa.GenerateKey(rand.Reader, 2048)
+ if err != nil {
+ t.Fatal(err)
+ }
+ notBefore := time.Now()
+ notAfter := notBefore.Add(24 * time.Hour)
+ template := x509.Certificate{
+ SerialNumber: big.NewInt(1),
+ Subject: pkix.Name{CommonName: "example.com"},
+ NotBefore: notBefore,
+ NotAfter: notAfter,
+ }
+ certificateDER, err := x509.CreateCertificate(rand.Reader, &template, &template, &privateKey.PublicKey, privateKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ privateKeyDER, err := x509.MarshalPKCS8PrivateKey(privateKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ pemCert := pem.EncodeToMemory(&pem.Block{Type: "CERTIFICATE", Bytes: certificateDER})
+ pemKey := pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privateKeyDER})
+ kp, err := tls.X509KeyPair(pemCert, pemKey)
+ if err != nil {
+ t.Fatal(err)
+ }
+ return pemCert, pemKey, kp
+}
diff --git a/client/go/internal/cli/cmd/curl.go b/client/go/internal/cli/cmd/curl.go
index 8fcd1fa6ef7..3d5aaff24dc 100644
--- a/client/go/internal/cli/cmd/curl.go
+++ b/client/go/internal/cli/cmd/curl.go
@@ -4,7 +4,6 @@ package cmd
import (
"fmt"
"log"
- "net/http"
"os"
"strings"
@@ -54,6 +53,7 @@ $ vespa curl -- -v --data-urlencode "yql=select * from music where album contain
return err
}
case vespa.DocumentService, vespa.QueryService:
+ c.CaCertificate = service.TLSOptions.CACertificateFile
c.PrivateKey = service.TLSOptions.PrivateKeyFile
c.Certificate = service.TLSOptions.CertificateFile
default:
@@ -79,15 +79,7 @@ func addAccessToken(cmd *curl.Command, target vespa.Target) error {
if target.Type() != vespa.TargetCloud {
return nil
}
- req := http.Request{}
- if err := target.SignRequest(&req, ""); err != nil {
- return err
- }
- headerValue := req.Header.Get("Authorization")
- if headerValue == "" {
- return fmt.Errorf("no authorization header added when signing request")
- }
- cmd.Header("Authorization", headerValue)
+ cmd.Header("Authorization", "secret")
return nil
}
diff --git a/client/go/internal/cli/cmd/feed.go b/client/go/internal/cli/cmd/feed.go
index c8e032929b8..a6447ef8d2e 100644
--- a/client/go/internal/cli/cmd/feed.go
+++ b/client/go/internal/cli/cmd/feed.go
@@ -6,77 +6,161 @@ import (
"io"
"math"
"os"
+ "runtime/pprof"
"time"
"github.com/spf13/cobra"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
"github.com/vespa-engine/vespa/client/go/internal/vespa/document"
)
-func addFeedFlags(cmd *cobra.Command, concurrency *int) {
- // TOOD(mpolden): Remove this flag
- cmd.PersistentFlags().IntVarP(concurrency, "concurrency", "T", 64, "Number of goroutines to use for dispatching")
+func addFeedFlags(cmd *cobra.Command, options *feedOptions) {
+ cmd.PersistentFlags().IntVar(&options.connections, "connections", 8, "The number of connections to use")
+ cmd.PersistentFlags().StringVar(&options.compression, "compression", "auto", `Compression mode to use. Default is "auto" which compresses large documents. Must be "auto", "gzip" or "none"`)
+ cmd.PersistentFlags().IntVar(&options.timeoutSecs, "timeout", 0, "Invididual feed operation timeout in seconds. 0 to disable")
+ cmd.PersistentFlags().IntVar(&options.doomSecs, "max-failure-seconds", 0, "Exit if given number of seconds elapse without any successful operations. 0 to disable")
+ cmd.PersistentFlags().BoolVar(&options.verbose, "verbose", false, "Verbose mode. Print successful operations in addition to errors")
+ cmd.PersistentFlags().StringVar(&options.route, "route", "", "Target Vespa route for feed operations")
+ cmd.PersistentFlags().IntVar(&options.traceLevel, "trace", 0, "The trace level of network traffic. 0 to disable")
+ memprofile := "memprofile"
+ cpuprofile := "cpuprofile"
+ cmd.PersistentFlags().StringVar(&options.memprofile, memprofile, "", "Write a heap profile to given file")
+ cmd.PersistentFlags().StringVar(&options.cpuprofile, cpuprofile, "", "Write a CPU profile to given file")
+ // Hide these flags as they are intended for internal use
+ cmd.PersistentFlags().MarkHidden(memprofile)
+ cmd.PersistentFlags().MarkHidden(cpuprofile)
+}
+
+type feedOptions struct {
+ connections int
+ compression string
+ route string
+ verbose bool
+ traceLevel int
+ timeoutSecs int
+ doomSecs int
+
+ memprofile string
+ cpuprofile string
}
func newFeedCmd(cli *CLI) *cobra.Command {
- var (
- concurrency int
- )
+ var options feedOptions
cmd := &cobra.Command{
- Use: "feed FILE",
+ Use: "feed FILE [FILE]...",
Short: "Feed documents to a Vespa cluster",
Long: `Feed documents to a Vespa cluster.
-A high performance feeding client. This can be used to feed large amounts of
-documents to Vespa cluster efficiently.
+This command can be used to feed large amounts of documents to a Vespa cluster
+efficiently.
The contents of FILE must be either a JSON array or JSON objects separated by
newline (JSONL).
+
+If FILE is a single dash ('-'), documents will be read from standard input.
`,
- Example: `$ vespa feed documents.jsonl
-`,
- Args: cobra.ExactArgs(1),
+ Example: `$ vespa feed docs.jsonl moredocs.json
+$ cat docs.jsonl | vespa feed -`,
+ Args: cobra.MinimumNArgs(1),
DisableAutoGenTag: true,
SilenceUsage: true,
Hidden: true, // TODO(mpolden): Remove when ready for public use
RunE: func(cmd *cobra.Command, args []string) error {
- f, err := os.Open(args[0])
- if err != nil {
- return err
+ if options.cpuprofile != "" {
+ f, err := os.Create(options.cpuprofile)
+ if err != nil {
+ return err
+ }
+ pprof.StartCPUProfile(f)
+ defer pprof.StopCPUProfile()
+ }
+ err := feed(args, options, cli)
+ if options.memprofile != "" {
+ f, err := os.Create(options.memprofile)
+ if err != nil {
+ return err
+ }
+ defer f.Close()
+ pprof.WriteHeapProfile(f)
}
- defer f.Close()
- return feed(f, cli, concurrency)
+ return err
},
}
- addFeedFlags(cmd, &concurrency)
+ addFeedFlags(cmd, &options)
return cmd
}
-func feed(r io.Reader, cli *CLI, concurrency int) error {
+func createServiceClients(service *vespa.Service, n int) []util.HTTPClient {
+ clients := make([]util.HTTPClient, 0, n)
+ for i := 0; i < n; i++ {
+ client := service.Client().Clone()
+ // Feeding should always use HTTP/2
+ util.ForceHTTP2(client, service.TLSOptions.KeyPair, service.TLSOptions.CACertificate, service.TLSOptions.TrustAll)
+ clients = append(clients, client)
+ }
+ return clients
+}
+
+func (opts feedOptions) compressionMode() (document.Compression, error) {
+ switch opts.compression {
+ case "auto":
+ return document.CompressionAuto, nil
+ case "none":
+ return document.CompressionNone, nil
+ case "gzip":
+ return document.CompressionGzip, nil
+ }
+ return 0, errHint(fmt.Errorf("invalid compression mode: %s", opts.compression), `Must be "auto", "gzip" or "none"`)
+}
+
+func feed(files []string, options feedOptions, cli *CLI) error {
service, err := documentService(cli)
if err != nil {
return err
}
+ clients := createServiceClients(service, options.connections)
+ compression, err := options.compressionMode()
+ if err != nil {
+ return err
+ }
client := document.NewClient(document.ClientOptions{
- BaseURL: service.BaseURL,
- }, service)
- throttler := document.NewThrottler()
- // TODO(mpolden): Make doom duration configurable
- circuitBreaker := document.NewCircuitBreaker(10*time.Second, 0)
- dispatcher := document.NewDispatcher(client, throttler, circuitBreaker)
- dec := document.NewDecoder(r)
-
+ Compression: compression,
+ Timeout: time.Duration(options.timeoutSecs) * time.Second,
+ Route: options.route,
+ TraceLevel: options.traceLevel,
+ BaseURL: service.BaseURL,
+ NowFunc: cli.now,
+ }, clients)
+ throttler := document.NewThrottler(options.connections)
+ circuitBreaker := document.NewCircuitBreaker(10*time.Second, time.Duration(options.doomSecs)*time.Second)
+ dispatcher := document.NewDispatcher(client, throttler, circuitBreaker, cli.Stderr, options.verbose)
start := cli.now()
- for {
- doc, err := dec.Decode()
- if err == io.EOF {
- break
- }
- if err != nil {
- cli.printErr(fmt.Errorf("failed to decode document: %w", err))
+ for _, name := range files {
+ var r io.ReadCloser
+ if len(files) == 1 && name == "-" {
+ r = io.NopCloser(cli.Stdin)
+ } else {
+ f, err := os.Open(name)
+ if err != nil {
+ return err
+ }
+ r = f
}
- if err := dispatcher.Enqueue(doc); err != nil {
- cli.printErr(err)
+ dec := document.NewDecoder(r)
+ for {
+ doc, err := dec.Decode()
+ if err == io.EOF {
+ break
+ }
+ if err != nil {
+ cli.printErr(fmt.Errorf("failed to decode document: %w", err))
+ }
+ if err := dispatcher.Enqueue(doc); err != nil {
+ cli.printErr(err)
+ }
}
+ r.Close()
}
if err := dispatcher.Close(); err != nil {
return err
diff --git a/client/go/internal/cli/cmd/feed_test.go b/client/go/internal/cli/cmd/feed_test.go
index 1bf1ef6ab9b..467d55a0a6e 100644
--- a/client/go/internal/cli/cmd/feed_test.go
+++ b/client/go/internal/cli/cmd/feed_test.go
@@ -1,6 +1,7 @@
package cmd
import (
+ "bytes"
"os"
"path/filepath"
"testing"
@@ -30,38 +31,51 @@ func TestFeed(t *testing.T) {
cli.now = clock.now
td := t.TempDir()
- jsonFile := filepath.Join(td, "docs.jsonl")
- err := os.WriteFile(jsonFile, []byte(`{
+ doc := []byte(`{
"put": "id:ns:type::doc1",
"fields": {"foo": "123"}
-}`), 0644)
-
- require.Nil(t, err)
+}`)
+ jsonFile1 := filepath.Join(td, "docs1.jsonl")
+ jsonFile2 := filepath.Join(td, "docs2.jsonl")
+ require.Nil(t, os.WriteFile(jsonFile1, doc, 0644))
+ require.Nil(t, os.WriteFile(jsonFile2, doc, 0644))
httpClient.NextResponseString(200, `{"message":"OK"}`)
- require.Nil(t, cli.Run("feed", jsonFile))
+ httpClient.NextResponseString(200, `{"message":"OK"}`)
+ require.Nil(t, cli.Run("feed", jsonFile1, jsonFile2))
assert.Equal(t, "", stderr.String())
- assert.Equal(t, `{
- "feeder.seconds": 1.000,
- "feeder.ok.count": 1,
- "feeder.ok.rate": 1.000,
+ want := `{
+ "feeder.seconds": 5.000,
+ "feeder.ok.count": 2,
+ "feeder.ok.rate": 0.400,
"feeder.error.count": 0,
"feeder.inflight.count": 0,
- "http.request.count": 1,
- "http.request.bytes": 25,
+ "http.request.count": 2,
+ "http.request.bytes": 50,
"http.request.MBps": 0.000,
"http.exception.count": 0,
- "http.response.count": 1,
- "http.response.bytes": 16,
+ "http.response.count": 2,
+ "http.response.bytes": 32,
"http.response.MBps": 0.000,
"http.response.error.count": 0,
- "http.response.latency.millis.min": 0,
- "http.response.latency.millis.avg": 0,
- "http.response.latency.millis.max": 0,
+ "http.response.latency.millis.min": 1000,
+ "http.response.latency.millis.avg": 1000,
+ "http.response.latency.millis.max": 1000,
"http.response.code.counts": {
- "200": 1
+ "200": 2
}
}
-`, stdout.String())
+`
+ assert.Equal(t, want, stdout.String())
+
+ stdout.Reset()
+ var stdinBuf bytes.Buffer
+ stdinBuf.Write(doc)
+ stdinBuf.Write(doc)
+ cli.Stdin = &stdinBuf
+ httpClient.NextResponseString(200, `{"message":"OK"}`)
+ httpClient.NextResponseString(200, `{"message":"OK"}`)
+ require.Nil(t, cli.Run("feed", "-"))
+ assert.Equal(t, want, stdout.String())
}
diff --git a/client/go/internal/cli/cmd/prod.go b/client/go/internal/cli/cmd/prod.go
index 0e00597221a..f322bd667df 100644
--- a/client/go/internal/cli/cmd/prod.go
+++ b/client/go/internal/cli/cmd/prod.go
@@ -105,21 +105,17 @@ https://cloud.vespa.ai/en/reference/deployment`,
func newProdSubmitCmd(cli *CLI) *cobra.Command {
return &cobra.Command{
Use: "submit",
- Short: "Submit your application for production deployment",
- Long: `Submit your application for production deployment.
+ Short: "Submit an application for production deployment",
+ Long: `Submit an application for production deployment.
-This commands uploads your application package to Vespa Cloud and deploys it to
+This commands uploads an application package to Vespa Cloud and deploys it to
the production zones specified in deployment.xml.
-Nodes are allocated to your application according to resources specified in
+Nodes are allocated to the application according to resources specified in
services.xml.
-While submitting an application from a local development environment is
-supported, it's strongly recommended that production deployments are performed
-by a continuous build system.
-
For more information about production deployments in Vespa Cloud see:
-https://cloud.vespa.ai/en/getting-to-production
+https://cloud.vespa.ai/en/production-deployment
https://cloud.vespa.ai/en/automated-deployments`,
DisableAutoGenTag: true,
SilenceUsage: true,
diff --git a/client/go/internal/cli/cmd/root.go b/client/go/internal/cli/cmd/root.go
index 58e940d59ef..88d43411983 100644
--- a/client/go/internal/cli/cmd/root.go
+++ b/client/go/internal/cli/cmd/root.go
@@ -2,7 +2,6 @@
package cmd
import (
- "crypto/tls"
"encoding/json"
"fmt"
"io"
@@ -88,18 +87,9 @@ func (c *execSubprocess) Run(name string, args ...string) ([]byte, error) {
return exec.Command(name, args...).Output()
}
-type ztsClient interface {
- AccessToken(domain string, certficiate tls.Certificate) (string, error)
-}
-
-type auth0Client interface {
- AccessToken() (string, error)
- HasCredentials() bool
-}
-
-type auth0Factory func(httpClient util.HTTPClient, options auth0.Options) (auth0Client, error)
+type auth0Factory func(httpClient util.HTTPClient, options auth0.Options) (vespa.Authenticator, error)
-type ztsFactory func(httpClient util.HTTPClient, url string) (ztsClient, error)
+type ztsFactory func(httpClient util.HTTPClient, domain, url string) (vespa.Authenticator, error)
// New creates the Vespa CLI, writing output to stdout and stderr, and reading environment variables from environment.
func New(stdout, stderr io.Writer, environment []string) (*CLI, error) {
@@ -143,11 +133,11 @@ For detailed description of flags and configuration, see 'vespa help config'.
httpClient: util.CreateClient(time.Second * 10),
exec: &execSubprocess{},
now: time.Now,
- auth0Factory: func(httpClient util.HTTPClient, options auth0.Options) (auth0Client, error) {
+ auth0Factory: func(httpClient util.HTTPClient, options auth0.Options) (vespa.Authenticator, error) {
return auth0.NewClient(httpClient, options)
},
- ztsFactory: func(httpClient util.HTTPClient, url string) (ztsClient, error) {
- return zts.NewClient(httpClient, url)
+ ztsFactory: func(httpClient util.HTTPClient, domain, url string) (vespa.Authenticator, error) {
+ return zts.NewClient(httpClient, domain, url)
},
}
cli.isTerminal = func() bool { return isTerminal(cli.Stdout) && isTerminal(cli.Stderr) }
@@ -321,16 +311,34 @@ func (c *CLI) createTarget(opts targetOptions) (vespa.Target, error) {
if err != nil {
return nil, err
}
+ customURL := ""
if strings.HasPrefix(targetType, "http") {
- return vespa.CustomTarget(c.httpClient, targetType), nil
+ customURL = targetType
+ targetType = vespa.TargetCustom
}
switch targetType {
- case vespa.TargetLocal:
- return vespa.LocalTarget(c.httpClient), nil
+ case vespa.TargetLocal, vespa.TargetCustom:
+ return c.createCustomTarget(targetType, customURL)
case vespa.TargetCloud, vespa.TargetHosted:
return c.createCloudTarget(targetType, opts)
+ default:
+ return nil, errHint(fmt.Errorf("invalid target: %s", targetType), "Valid targets are 'local', 'cloud', 'hosted' or an URL")
+ }
+}
+
+func (c *CLI) createCustomTarget(targetType, customURL string) (vespa.Target, error) {
+ tlsOptions, err := c.config.readTLSOptions(vespa.DefaultApplication, targetType)
+ if err != nil {
+ return nil, err
+ }
+ switch targetType {
+ case vespa.TargetLocal:
+ return vespa.LocalTarget(c.httpClient, tlsOptions), nil
+ case vespa.TargetCustom:
+ return vespa.CustomTarget(c.httpClient, customURL, tlsOptions), nil
+ default:
+ return nil, fmt.Errorf("invalid custom target: %s", targetType)
}
- return nil, errHint(fmt.Errorf("invalid target: %s", targetType), "Valid targets are 'local', 'cloud', 'hosted' or an URL")
}
func (c *CLI) createCloudTarget(targetType string, opts targetOptions) (vespa.Target, error) {
@@ -347,48 +355,53 @@ func (c *CLI) createCloudTarget(targetType string, opts targetOptions) (vespa.Ta
return nil, err
}
var (
- apiKey []byte
- authConfigPath string
+ apiAuth vespa.Authenticator
+ deploymentAuth vespa.Authenticator
apiTLSOptions vespa.TLSOptions
deploymentTLSOptions vespa.TLSOptions
)
switch targetType {
case vespa.TargetCloud:
- apiKey, err = c.config.readAPIKey(c, system, deployment.Application.Tenant)
+ apiKey, err := c.config.readAPIKey(c, system, deployment.Application.Tenant)
if err != nil {
return nil, err
}
- authConfigPath = c.config.authConfigPath()
+ if apiKey == nil {
+ authConfigPath := c.config.authConfigPath()
+ auth0, err := c.auth0Factory(c.httpClient, auth0.Options{ConfigPath: authConfigPath, SystemName: system.Name, SystemURL: system.URL})
+ if err != nil {
+ return nil, err
+ }
+ apiAuth = auth0
+ } else {
+ apiAuth = vespa.NewRequestSigner(deployment.Application.SerializedForm(), apiKey)
+ }
deploymentTLSOptions = vespa.TLSOptions{}
if !opts.noCertificate {
- kp, err := c.config.x509KeyPair(deployment.Application, targetType)
+ kp, err := c.config.readTLSOptions(deployment.Application, targetType)
if err != nil {
- return nil, errHint(err, "Deployment to cloud requires a certificate. Try 'vespa auth cert'")
- }
- deploymentTLSOptions = vespa.TLSOptions{
- KeyPair: &kp.KeyPair,
- CertificateFile: kp.CertificateFile,
- PrivateKeyFile: kp.PrivateKeyFile,
+ return nil, errHint(err, "Deployment to cloud requires a certificate", "Try 'vespa auth cert' to create a self-signed certificate")
}
+ deploymentTLSOptions = kp
}
case vespa.TargetHosted:
- kp, err := c.config.x509KeyPair(deployment.Application, targetType)
+ kp, err := c.config.readTLSOptions(deployment.Application, targetType)
if err != nil {
return nil, errHint(err, "Deployment to hosted requires an Athenz certificate", "Try renewing certificate with 'athenz-user-cert'")
}
- apiTLSOptions = vespa.TLSOptions{
- KeyPair: &kp.KeyPair,
- CertificateFile: kp.CertificateFile,
- PrivateKeyFile: kp.PrivateKeyFile,
+ zts, err := c.ztsFactory(c.httpClient, system.AthenzDomain, zts.DefaultURL)
+ if err != nil {
+ return nil, err
}
- deploymentTLSOptions = apiTLSOptions
+ deploymentAuth = zts
+ apiTLSOptions = kp
+ deploymentTLSOptions = kp
default:
return nil, fmt.Errorf("invalid cloud target: %s", targetType)
}
apiOptions := vespa.APIOptions{
System: system,
TLSOptions: apiTLSOptions,
- APIKey: apiKey,
}
deploymentOptions := vespa.CloudDeploymentOptions{
Deployment: deployment,
@@ -403,15 +416,7 @@ func (c *CLI) createCloudTarget(targetType string, opts targetOptions) (vespa.Ta
Writer: c.Stdout,
Level: vespa.LogLevel(logLevel),
}
- auth0, err := c.auth0Factory(c.httpClient, auth0.Options{ConfigPath: authConfigPath, SystemName: apiOptions.System.Name, SystemURL: apiOptions.System.URL})
- if err != nil {
- return nil, err
- }
- zts, err := c.ztsFactory(c.httpClient, zts.DefaultURL)
- if err != nil {
- return nil, err
- }
- return vespa.CloudTarget(c.httpClient, zts, auth0, apiOptions, deploymentOptions, logOptions)
+ return vespa.CloudTarget(c.httpClient, apiAuth, deploymentAuth, apiOptions, deploymentOptions, logOptions)
}
// system returns the appropiate system for the target configured in this CLI.
@@ -460,7 +465,6 @@ func (c *CLI) createDeploymentOptions(pkg vespa.ApplicationPackage, target vespa
ApplicationPackage: pkg,
Target: target,
Timeout: timeout,
- HTTPClient: c.httpClient,
}, nil
}
diff --git a/client/go/internal/cli/cmd/test.go b/client/go/internal/cli/cmd/test.go
index 4a53fe6bed3..8c4501e2870 100644
--- a/client/go/internal/cli/cmd/test.go
+++ b/client/go/internal/cli/cmd/test.go
@@ -263,7 +263,7 @@ func verify(step step, defaultCluster string, defaultParameters map[string]strin
var response *http.Response
if externalEndpoint {
- util.SetCertificate(context.cli.httpClient, []tls.Certificate{})
+ util.ConfigureTLS(context.cli.httpClient, []tls.Certificate{}, nil, false)
response, err = context.cli.httpClient.Do(request, 60*time.Second)
} else {
response, err = service.Do(request, 600*time.Second) // Vespa should provide a response within the given request timeout
diff --git a/client/go/internal/cli/cmd/testutil_test.go b/client/go/internal/cli/cmd/testutil_test.go
index 61f8dab2264..492e40d8855 100644
--- a/client/go/internal/cli/cmd/testutil_test.go
+++ b/client/go/internal/cli/cmd/testutil_test.go
@@ -3,13 +3,14 @@ package cmd
import (
"bytes"
- "crypto/tls"
+ "net/http"
"path/filepath"
"testing"
"github.com/vespa-engine/vespa/client/go/internal/cli/auth/auth0"
"github.com/vespa-engine/vespa/client/go/internal/mock"
"github.com/vespa-engine/vespa/client/go/internal/util"
+ "github.com/vespa-engine/vespa/client/go/internal/vespa"
)
func newTestCLI(t *testing.T, envVars ...string) (*CLI, *bytes.Buffer, *bytes.Buffer) {
@@ -29,21 +30,15 @@ func newTestCLI(t *testing.T, envVars ...string) (*CLI, *bytes.Buffer, *bytes.Bu
httpClient := &mock.HTTPClient{}
cli.httpClient = httpClient
cli.exec = &mock.Exec{}
- cli.auth0Factory = func(httpClient util.HTTPClient, options auth0.Options) (auth0Client, error) {
- return &mockAuth0{}, nil
+ cli.auth0Factory = func(httpClient util.HTTPClient, options auth0.Options) (vespa.Authenticator, error) {
+ return &mockAuthenticator{}, nil
}
- cli.ztsFactory = func(httpClient util.HTTPClient, url string) (ztsClient, error) {
- return &mockZTS{}, nil
+ cli.ztsFactory = func(httpClient util.HTTPClient, domain, url string) (vespa.Authenticator, error) {
+ return &mockAuthenticator{}, nil
}
return cli, &stdout, &stderr
}
-type mockZTS struct{}
+type mockAuthenticator struct{}
-func (z *mockZTS) AccessToken(domain string, cert tls.Certificate) (string, error) { return "", nil }
-
-type mockAuth0 struct{ hasCredentials bool }
-
-func (a *mockAuth0) AccessToken() (string, error) { return "", nil }
-
-func (a *mockAuth0) HasCredentials() bool { return a.hasCredentials }
+func (a *mockAuthenticator) Authenticate(request *http.Request) error { return nil }
diff --git a/client/go/internal/cli/cmd/visit.go b/client/go/internal/cli/cmd/visit.go
index 6a5b936434c..10fb2743c63 100644
--- a/client/go/internal/cli/cmd/visit.go
+++ b/client/go/internal/cli/cmd/visit.go
@@ -33,6 +33,8 @@ type visitArgs struct {
to string
slices int
sliceId int
+ bucketSpace string
+ bucketSpaces []string
cli *CLI
}
@@ -132,6 +134,7 @@ $ vespa visit --field-set "[id]" # list document IDs
cmd.Flags().StringVar(&vArgs.to, "to", "", `Timestamp to visit up to, in seconds`)
cmd.Flags().IntVar(&vArgs.sliceId, "slice-id", -1, `The number of the slice this visit invocation should fetch`)
cmd.Flags().IntVar(&vArgs.slices, "slices", -1, `Split the document corpus into this number of independent slices`)
+ cmd.Flags().StringSliceVar(&vArgs.bucketSpaces, "bucket-space", []string{"global", "default"}, `"default" or "global" bucket space`)
return cmd
}
@@ -157,6 +160,16 @@ func checkArguments(vArgs visitArgs) (res util.OperationResult) {
return util.Failure("Invalid 'to' argument: '" + vArgs.to + "': " + err.Error())
}
}
+ for _, b := range vArgs.bucketSpaces {
+ switch b {
+ case
+ "default",
+ "global":
+ // Do nothing
+ default:
+ return util.Failure("Invalid 'bucket-space' argument '" + b + "', must be 'default' or 'global'")
+ }
+ }
return util.Success("")
}
@@ -226,13 +239,16 @@ func visitClusters(vArgs *visitArgs, service *vespa.Service) (res util.Operation
if vArgs.makeFeed {
vArgs.writeString("[\n")
}
- for _, c := range clusters {
- vArgs.contentCluster = c
- res = runVisit(vArgs, service)
- if !res.Success {
- return res
+ for _, b := range vArgs.bucketSpaces {
+ for _, c := range clusters {
+ vArgs.bucketSpace = b
+ vArgs.contentCluster = c
+ res = runVisit(vArgs, service)
+ if !res.Success {
+ return res
+ }
+ vArgs.debugPrint("Success: " + res.Message)
}
- vArgs.debugPrint("Success: " + res.Message)
}
if vArgs.makeFeed {
vArgs.writeString("{}\n]\n")
@@ -330,6 +346,9 @@ func runOneVisit(vArgs *visitArgs, service *vespa.Service, contToken string) (*V
if vArgs.slices > 0 {
urlPath = urlPath + fmt.Sprintf("&slices=%d&sliceId=%d", vArgs.slices, vArgs.sliceId)
}
+ if vArgs.bucketSpace != "" {
+ urlPath = urlPath + "&bucketSpace=" + vArgs.bucketSpace
+ }
url, urlParseError := url.Parse(urlPath)
if urlParseError != nil {
return nil, util.Failure("Invalid request path: '" + urlPath + "': " + urlParseError.Error())
diff --git a/client/go/internal/cli/cmd/visit_test.go b/client/go/internal/cli/cmd/visit_test.go
index 4302680b9d9..9bb8f61554a 100644
--- a/client/go/internal/cli/cmd/visit_test.go
+++ b/client/go/internal/cli/cmd/visit_test.go
@@ -47,7 +47,7 @@ func TestQuoteFunc(t *testing.T) {
res := quoteArgForUrl(s)
if i < 32 || i > 127 {
assert.Equal(t, "a+z", res)
- } else {
+ } else if testing.Verbose() { // go test -v
fmt.Printf("res %3d => '%s'\n", i, res)
}
}
@@ -105,6 +105,7 @@ func TestVisitCommand(t *testing.T) {
assertVisitResults(
[]string{
"visit",
+ "--bucket-space", "default",
"--json-lines",
},
t,
@@ -118,7 +119,7 @@ func TestVisitCommand(t *testing.T) {
document3 +
`],"documentCount":2}`,
},
- "cluster=fooCC&continuation=CAFE&wantedDocumentCount=1000",
+ "cluster=fooCC&continuation=CAFE&wantedDocumentCount=1000&bucketSpace=default",
document1+"\n"+
document2+"\n"+
document3+"\n")
diff --git a/client/go/internal/mock/http.go b/client/go/internal/mock/http.go
index 9c55f2e79bf..58614d7e5bd 100644
--- a/client/go/internal/mock/http.go
+++ b/client/go/internal/mock/http.go
@@ -6,6 +6,8 @@ import (
"net/http"
"strconv"
"time"
+
+ "github.com/vespa-engine/vespa/client/go/internal/util"
)
type HTTPClient struct {
@@ -58,3 +60,5 @@ func (c *HTTPClient) Do(request *http.Request, timeout time.Duration) (*http.Res
},
nil
}
+
+func (c *HTTPClient) Clone() util.HTTPClient { return c }
diff --git a/client/go/internal/util/http.go b/client/go/internal/util/http.go
index cb35932c8e7..30874153510 100644
--- a/client/go/internal/util/http.go
+++ b/client/go/internal/util/http.go
@@ -2,9 +2,11 @@
package util
import (
- "bytes"
+ "context"
"crypto/tls"
+ "crypto/x509"
"fmt"
+ "net"
"net/http"
"time"
@@ -14,6 +16,7 @@ import (
type HTTPClient interface {
Do(request *http.Request, timeout time.Duration) (response *http.Response, error error)
+ Clone() HTTPClient
}
type defaultHTTPClient struct {
@@ -31,50 +34,58 @@ func (c *defaultHTTPClient) Do(request *http.Request, timeout time.Duration) (re
return c.client.Do(request)
}
-func SetCertificate(client HTTPClient, certificates []tls.Certificate) {
+func (c *defaultHTTPClient) Clone() HTTPClient { return CreateClient(c.client.Timeout) }
+
+func ConfigureTLS(client HTTPClient, certificates []tls.Certificate, caCertificate []byte, trustAll bool) {
c, ok := client.(*defaultHTTPClient)
if !ok {
return
}
- // Use HTTP/2 transport explicitly. Connection reuse does not work properly when using regular http.Transport, even
- // though it upgrades to HTTP/2 automatically
- // https://github.com/golang/go/issues/16582
- // https://github.com/golang/go/issues/22091
- var transport *http2.Transport
- if _, ok := c.client.Transport.(*http.Transport); ok {
- transport = &http2.Transport{}
- c.client.Transport = transport
- } else if t, ok := c.client.Transport.(*http2.Transport); ok {
- transport = t
+ var tlsConfig *tls.Config = nil
+ if certificates != nil {
+ tlsConfig = &tls.Config{
+ Certificates: certificates,
+ MinVersion: tls.VersionTLS12,
+ InsecureSkipVerify: trustAll,
+ }
+ if caCertificate != nil {
+ certs := x509.NewCertPool()
+ certs.AppendCertsFromPEM(caCertificate)
+ tlsConfig.RootCAs = certs
+ }
+ }
+ if tr, ok := c.client.Transport.(*http.Transport); ok {
+ tr.TLSClientConfig = tlsConfig
+ } else if tr, ok := c.client.Transport.(*http2.Transport); ok {
+ tr.TLSClientConfig = tlsConfig
} else {
panic(fmt.Sprintf("unknown transport type: %T", c.client.Transport))
}
- if ok && !c.hasCertificates(transport.TLSClientConfig, certificates) {
- transport.TLSClientConfig = &tls.Config{
- Certificates: certificates,
- MinVersion: tls.VersionTLS12,
- }
- }
}
-func (c *defaultHTTPClient) hasCertificates(tlsConfig *tls.Config, certs []tls.Certificate) bool {
- if tlsConfig == nil {
- return false
- }
- if len(tlsConfig.Certificates) != len(certs) {
- return false
+func ForceHTTP2(client HTTPClient, certificates []tls.Certificate, caCertificate []byte, trustAll bool) {
+ c, ok := client.(*defaultHTTPClient)
+ if !ok {
+ return
}
- for i := 0; i < len(certs); i++ {
- if len(tlsConfig.Certificates[i].Certificate) != len(certs[i].Certificate) {
- return false
- }
- for j := 0; j < len(certs[i].Certificate); j++ {
- if !bytes.Equal(tlsConfig.Certificates[i].Certificate[j], certs[i].Certificate[j]) {
- return false
- }
+ var dialFunc func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error)
+ if certificates == nil {
+ // No certificate, so force H2C (HTTP/2 over clear-text) by using a non-TLS Dialer
+ dialer := net.Dialer{}
+ dialFunc = func(ctx context.Context, network, addr string, cfg *tls.Config) (net.Conn, error) {
+ return dialer.DialContext(ctx, network, addr)
}
}
- return true
+ // Use HTTP/2 transport explicitly. Connection reuse does not work properly when using regular http.Transport, even
+ // though it upgrades to HTTP/2 automatically
+ // https://github.com/golang/go/issues/16582
+ // https://github.com/golang/go/issues/22091
+ c.client.Transport = &http2.Transport{
+ AllowHTTP: true,
+ DialTLSContext: dialFunc,
+ StrictMaxConcurrentStreams: true,
+ }
+ ConfigureTLS(client, certificates, caCertificate, trustAll)
}
func CreateClient(timeout time.Duration) HTTPClient {
diff --git a/client/go/internal/vespa/crypto.go b/client/go/internal/vespa/crypto.go
index 9621d0c1180..5e273538869 100644
--- a/client/go/internal/vespa/crypto.go
+++ b/client/go/internal/vespa/crypto.go
@@ -111,6 +111,8 @@ func NewRequestSigner(keyID string, pemPrivateKey []byte) *RequestSigner {
}
}
+func (rs *RequestSigner) Authenticate(request *http.Request) error { return rs.SignRequest(request) }
+
// SignRequest signs the given HTTP request using the private key in rs
func (rs *RequestSigner) SignRequest(request *http.Request) error {
timestamp := rs.now().UTC().Format(time.RFC3339)
diff --git a/client/go/internal/vespa/deploy.go b/client/go/internal/vespa/deploy.go
index 687bfc46124..f633c8ed9ee 100644
--- a/client/go/internal/vespa/deploy.go
+++ b/client/go/internal/vespa/deploy.go
@@ -45,7 +45,6 @@ type DeploymentOptions struct {
ApplicationPackage ApplicationPackage
Timeout time.Duration
Version version.Version
- HTTPClient util.HTTPClient
}
type LogLinePrepareResponse struct {
@@ -130,7 +129,7 @@ func Prepare(deployment DeploymentOptions) (PrepareResult, error) {
return PrepareResult{}, err
}
serviceDescription := "Deploy service"
- response, err := deployment.HTTPClient.Do(req, time.Second*30)
+ response, err := deployServiceDo(req, time.Second*30, deployment)
if err != nil {
return PrepareResult{}, err
}
@@ -171,7 +170,7 @@ func Activate(sessionID int64, deployment DeploymentOptions) error {
return err
}
serviceDescription := "Deploy service"
- response, err := deployment.HTTPClient.Do(req, time.Second*30)
+ response, err := deployServiceDo(req, time.Second*30, deployment)
if err != nil {
return err
}
@@ -263,11 +262,7 @@ func Submit(opts DeploymentOptions) error {
}
request.Header.Set("Content-Type", writer.FormDataContentType())
serviceDescription := "Submit service"
- sigKeyId := opts.Target.Deployment().Application.SerializedForm()
- if err := opts.Target.SignRequest(request, sigKeyId); err != nil {
- return fmt.Errorf("failed to sign api request: %w", err)
- }
- response, err := opts.HTTPClient.Do(request, time.Minute*10)
+ response, err := deployServiceDo(request, time.Minute*10, opts)
if err != nil {
return err
}
@@ -275,6 +270,14 @@ func Submit(opts DeploymentOptions) error {
return checkResponse(request, response, serviceDescription)
}
+func deployServiceDo(request *http.Request, timeout time.Duration, opts DeploymentOptions) (*http.Response, error) {
+ s, err := opts.Target.Service(DeployService, 0, 0, "")
+ if err != nil {
+ return nil, err
+ }
+ return s.Do(request, timeout)
+}
+
func checkDeploymentOpts(opts DeploymentOptions) error {
if opts.Target.Type() == TargetCloud && !opts.ApplicationPackage.HasCertificate() {
return fmt.Errorf("%s: missing certificate in package", opts)
@@ -334,11 +337,6 @@ func uploadApplicationPackage(url *url.URL, opts DeploymentOptions) (PrepareResu
if err != nil {
return PrepareResult{}, err
}
-
- keyID := opts.Target.Deployment().Application.SerializedForm()
- if err := opts.Target.SignRequest(request, keyID); err != nil {
- return PrepareResult{}, err
- }
response, err := service.Do(request, time.Minute*10)
if err != nil {
return PrepareResult{}, err
diff --git a/client/go/internal/vespa/deploy_test.go b/client/go/internal/vespa/deploy_test.go
index 3e74e9ab3b6..da2604282c0 100644
--- a/client/go/internal/vespa/deploy_test.go
+++ b/client/go/internal/vespa/deploy_test.go
@@ -19,12 +19,11 @@ import (
func TestDeploy(t *testing.T) {
httpClient := mock.HTTPClient{}
- target := LocalTarget(&httpClient)
+ target := LocalTarget(&httpClient, TLSOptions{})
appDir, _ := mock.ApplicationPackageDir(t, false, false)
opts := DeploymentOptions{
Target: target,
ApplicationPackage: ApplicationPackage{Path: appDir},
- HTTPClient: &httpClient,
}
_, err := Deploy(opts)
assert.Nil(t, err)
@@ -47,7 +46,6 @@ func TestDeployCloud(t *testing.T) {
opts := DeploymentOptions{
Target: target,
ApplicationPackage: ApplicationPackage{Path: appDir},
- HTTPClient: &httpClient,
}
_, err := Deploy(opts)
require.Nil(t, err)
diff --git a/client/go/internal/vespa/document/dispatcher.go b/client/go/internal/vespa/document/dispatcher.go
index 7011ae7a9b6..b87dfaf55eb 100644
--- a/client/go/internal/vespa/document/dispatcher.go
+++ b/client/go/internal/vespa/document/dispatcher.go
@@ -1,7 +1,10 @@
package document
import (
+ "container/list"
"fmt"
+ "io"
+ "strings"
"sync"
"sync/atomic"
"time"
@@ -16,79 +19,101 @@ type Dispatcher struct {
circuitBreaker CircuitBreaker
stats Stats
- closed bool
- ready chan Id
- results chan Result
+ started bool
+ results chan Result
+ msgs chan string
+
inflight map[string]*documentGroup
inflightCount int64
+ output io.Writer
+ verbose bool
+ listPool sync.Pool
mu sync.RWMutex
- wg sync.WaitGroup
+ workerWg sync.WaitGroup
resultWg sync.WaitGroup
}
-// documentGroup holds document operations which share their ID, and must be dispatched in order.
-type documentGroup struct {
- id Id
- operations []documentOp
- mu sync.Mutex
-}
-
+// documentOp represents a document operation and the number of times it has been attempted.
type documentOp struct {
document Document
attempts int
}
-func (g *documentGroup) append(op documentOp) {
+// documentGroup holds document operations which share an ID, and must be dispatched in order.
+type documentGroup struct {
+ q *Queue[documentOp]
+ mu sync.Mutex
+}
+
+func (g *documentGroup) add(op documentOp, first bool) {
g.mu.Lock()
defer g.mu.Unlock()
- g.operations = append(g.operations, op)
+ g.q.Add(op, first)
}
-func NewDispatcher(feeder Feeder, throttler Throttler, breaker CircuitBreaker) *Dispatcher {
+func NewDispatcher(feeder Feeder, throttler Throttler, breaker CircuitBreaker, output io.Writer, verbose bool) *Dispatcher {
d := &Dispatcher{
feeder: feeder,
throttler: throttler,
circuitBreaker: breaker,
inflight: make(map[string]*documentGroup),
+ output: output,
+ verbose: verbose,
}
d.start()
return d
}
-func (d *Dispatcher) dispatchAll(g *documentGroup) {
- g.mu.Lock()
- defer g.mu.Unlock()
- for i := 0; i < len(g.operations); i++ {
- op := g.operations[i]
- ok := false
- for !ok {
- op.attempts++
- result := d.feeder.Send(op.document)
- d.results <- result
- ok = result.Status.Success()
- if !d.shouldRetry(op, result) {
- break
- }
- }
- d.releaseSlot()
+func (d *Dispatcher) sendDocumentIn(group *documentGroup) {
+ group.mu.Lock()
+ op, ok := group.q.Poll()
+ if !ok {
+ panic("sending from empty document group, this should not happen")
+ }
+ op.attempts++
+ result := d.feeder.Send(op.document)
+ d.results <- result
+ d.releaseSlot()
+ group.mu.Unlock()
+ if d.shouldRetry(op, result) {
+ d.enqueue(op)
}
- g.operations = nil
}
func (d *Dispatcher) shouldRetry(op documentOp, result Result) bool {
if result.HTTPStatus/100 == 2 || result.HTTPStatus == 404 || result.HTTPStatus == 412 {
+ if d.verbose {
+ d.msgs <- fmt.Sprintf("feed: successfully fed %s with status %d", op.document.Id, result.HTTPStatus)
+ }
d.throttler.Success()
d.circuitBreaker.Success()
return false
}
if result.HTTPStatus == 429 || result.HTTPStatus == 503 {
+ d.msgs <- fmt.Sprintf("feed: %s was throttled with status %d: retrying\n", op.document, result.HTTPStatus)
d.throttler.Throttled(atomic.LoadInt64(&d.inflightCount))
return true
}
- if result.HTTPStatus == 500 || result.HTTPStatus == 502 || result.HTTPStatus == 504 {
+ if result.Err != nil || result.HTTPStatus == 500 || result.HTTPStatus == 502 || result.HTTPStatus == 504 {
+ retry := op.attempts <= maxAttempts
+ var msg strings.Builder
+ msg.WriteString("feed: ")
+ msg.WriteString(op.document.String())
+ if result.Err != nil {
+ msg.WriteString("error ")
+ msg.WriteString(result.Err.Error())
+ } else {
+ msg.WriteString(fmt.Sprintf("status %d", result.HTTPStatus))
+ }
+ if retry {
+ msg.WriteString(": retrying")
+ } else {
+ msg.WriteString(fmt.Sprintf(": giving up after %d attempts", maxAttempts))
+ }
+ d.msgs <- msg.String()
d.circuitBreaker.Error(fmt.Errorf("request failed with status %d", result.HTTPStatus))
- if op.attempts <= maxAttempts {
+ if retry {
return true
}
}
@@ -98,70 +123,76 @@ func (d *Dispatcher) shouldRetry(op documentOp, result Result) bool {
func (d *Dispatcher) start() {
d.mu.Lock()
defer d.mu.Unlock()
- d.ready = make(chan Id, 4096)
+ if d.started {
+ return
+ }
+ d.listPool.New = func() any { return list.New() }
d.results = make(chan Result, 4096)
- d.closed = false
- d.wg.Add(1)
- go func() {
- defer d.wg.Done()
- d.readDocuments()
- }()
- d.resultWg.Add(1)
- go func() {
- defer d.resultWg.Done()
- d.readResults()
- }()
+ d.msgs = make(chan string, 4096)
+ d.started = true
+ d.resultWg.Add(2)
+ go d.sumStats()
+ go d.printMessages()
}
-func (d *Dispatcher) readDocuments() {
- for id := range d.ready {
- d.mu.RLock()
- group := d.inflight[id.String()]
- d.mu.RUnlock()
- if group != nil {
- d.wg.Add(1)
- go func() {
- defer d.wg.Done()
- d.dispatchAll(group)
- }()
- }
+func (d *Dispatcher) sumStats() {
+ defer d.resultWg.Done()
+ for result := range d.results {
+ d.stats.Add(result.Stats)
}
}
-func (d *Dispatcher) readResults() {
- for result := range d.results {
- d.stats.Add(result.Stats)
+func (d *Dispatcher) printMessages() {
+ defer d.resultWg.Done()
+ for msg := range d.msgs {
+ fmt.Fprintln(d.output, msg)
}
}
-func (d *Dispatcher) Enqueue(doc Document) error {
+func (d *Dispatcher) enqueue(op documentOp) error {
d.mu.Lock()
- if d.closed {
+ if !d.started {
return fmt.Errorf("dispatcher is closed")
}
- group, ok := d.inflight[doc.Id.String()]
- if ok {
- group.append(documentOp{document: doc})
- } else {
- group = &documentGroup{
- id: doc.Id,
- operations: []documentOp{{document: doc}},
- }
- d.inflight[doc.Id.String()] = group
+ key := op.document.Id.String()
+ group, ok := d.inflight[key]
+ if !ok {
+ group = &documentGroup{q: NewQueue[documentOp](&d.listPool)}
+ d.inflight[key] = group
}
d.mu.Unlock()
- d.enqueueWithSlot(doc.Id)
+ group.add(op, op.attempts > 0)
+ d.dispatch(op.document.Id, group)
return nil
}
-func (d *Dispatcher) Stats() Stats { return d.stats }
-
-func (d *Dispatcher) enqueueWithSlot(id Id) {
+func (d *Dispatcher) dispatch(id Id, group *documentGroup) {
+ if !d.canDispatch() {
+ d.msgs <- fmt.Sprintf("refusing to dispatch document %s: too many errors", id)
+ return
+ }
d.acquireSlot()
- d.ready <- id
+ d.workerWg.Add(1)
+ go func() {
+ defer d.workerWg.Done()
+ d.sendDocumentIn(group)
+ }()
d.throttler.Sent()
}
+func (d *Dispatcher) canDispatch() bool {
+ switch d.circuitBreaker.State() {
+ case CircuitClosed:
+ return true
+ case CircuitHalfOpen:
+ time.Sleep(time.Second)
+ return true
+ case CircuitOpen:
+ return false
+ }
+ panic("invalid circuit state")
+}
+
func (d *Dispatcher) acquireSlot() {
for atomic.LoadInt64(&d.inflightCount) >= d.throttler.TargetInflight() {
time.Sleep(time.Millisecond)
@@ -171,21 +202,20 @@ func (d *Dispatcher) acquireSlot() {
func (d *Dispatcher) releaseSlot() { atomic.AddInt64(&d.inflightCount, -1) }
-func closeAndWait[T any](ch chan T, wg *sync.WaitGroup, d *Dispatcher, markClosed bool) {
- d.mu.Lock()
- if !d.closed {
- close(ch)
- if markClosed {
- d.closed = true
- }
- }
- d.mu.Unlock()
- wg.Wait()
-}
+func (d *Dispatcher) Enqueue(doc Document) error { return d.enqueue(documentOp{document: doc}) }
+
+func (d *Dispatcher) Stats() Stats { return d.stats }
// Close closes the dispatcher and waits for all inflight operations to complete.
func (d *Dispatcher) Close() error {
- closeAndWait(d.ready, &d.wg, d, false)
- closeAndWait(d.results, &d.resultWg, d, true)
+ d.workerWg.Wait() // Wait for all inflight operations to complete
+ d.mu.Lock()
+ if d.started {
+ close(d.results)
+ close(d.msgs)
+ d.started = false
+ }
+ d.mu.Unlock()
+ d.resultWg.Wait() // Wait for results
return nil
}
diff --git a/client/go/internal/vespa/document/dispatcher_test.go b/client/go/internal/vespa/document/dispatcher_test.go
index 8a6d8c6117c..2e2e9a5abbd 100644
--- a/client/go/internal/vespa/document/dispatcher_test.go
+++ b/client/go/internal/vespa/document/dispatcher_test.go
@@ -1,6 +1,7 @@
package document
import (
+ "io"
"sync"
"testing"
"time"
@@ -29,18 +30,24 @@ func (f *mockFeeder) Send(doc Document) Result {
} else {
f.documents = append(f.documents, doc)
}
- if !result.Status.Success() {
+ if !result.Success() {
result.Stats.Errors = 1
}
return result
}
+type mockCircuitBreaker struct{ state CircuitState }
+
+func (c *mockCircuitBreaker) Success() {}
+func (c *mockCircuitBreaker) Error(err error) {}
+func (c *mockCircuitBreaker) State() CircuitState { return c.state }
+
func TestDispatcher(t *testing.T) {
feeder := &mockFeeder{}
clock := &manualClock{tick: time.Second}
- throttler := newThrottler(clock.now)
+ throttler := newThrottler(8, clock.now)
breaker := NewCircuitBreaker(time.Second, 0)
- dispatcher := NewDispatcher(feeder, throttler, breaker)
+ dispatcher := NewDispatcher(feeder, throttler, breaker, io.Discard, false)
docs := []Document{
{Id: mustParseId("id:ns:type::doc1"), Operation: OperationPut, Body: []byte(`{"fields":{"foo": "123"}}`)},
{Id: mustParseId("id:ns:type::doc2"), Operation: OperationPut, Body: []byte(`{"fields":{"bar": "456"}}`)},
@@ -71,9 +78,9 @@ func TestDispatcherOrdering(t *testing.T) {
{Id: mustParseId("id:ns:type::doc9"), Operation: OperationPut},
}
clock := &manualClock{tick: time.Second}
- throttler := newThrottler(clock.now)
+ throttler := newThrottler(8, clock.now)
breaker := NewCircuitBreaker(time.Second, 0)
- dispatcher := NewDispatcher(feeder, throttler, breaker)
+ dispatcher := NewDispatcher(feeder, throttler, breaker, io.Discard, false)
for _, d := range docs {
dispatcher.Enqueue(d)
}
@@ -107,9 +114,9 @@ func TestDispatcherOrderingWithFailures(t *testing.T) {
}
feeder.failAfterN(2)
clock := &manualClock{tick: time.Second}
- throttler := newThrottler(clock.now)
+ throttler := newThrottler(8, clock.now)
breaker := NewCircuitBreaker(time.Second, 0)
- dispatcher := NewDispatcher(feeder, throttler, breaker)
+ dispatcher := NewDispatcher(feeder, throttler, breaker, io.Discard, false)
for _, d := range docs {
dispatcher.Enqueue(d)
}
@@ -129,3 +136,32 @@ func TestDispatcherOrderingWithFailures(t *testing.T) {
assert.Equal(t, int64(2), dispatcher.Stats().Errors)
assert.Equal(t, 6, len(feeder.documents))
}
+
+func TestDispatcherOpenCircuit(t *testing.T) {
+ feeder := &mockFeeder{}
+ doc := Document{Id: mustParseId("id:ns:type::doc1"), Operation: OperationPut}
+ clock := &manualClock{tick: time.Second}
+ throttler := newThrottler(8, clock.now)
+ breaker := &mockCircuitBreaker{}
+ dispatcher := NewDispatcher(feeder, throttler, breaker, io.Discard, false)
+ dispatcher.Enqueue(doc)
+ breaker.state = CircuitOpen
+ dispatcher.Enqueue(doc)
+ dispatcher.Close()
+ assert.Equal(t, 1, len(feeder.documents))
+}
+
+func BenchmarkDocumentDispatching(b *testing.B) {
+ feeder := &mockFeeder{}
+ clock := &manualClock{tick: time.Second}
+ throttler := newThrottler(8, clock.now)
+ breaker := NewCircuitBreaker(time.Second, 0)
+ dispatcher := NewDispatcher(feeder, throttler, breaker, io.Discard, false)
+ doc := Document{Id: mustParseId("id:ns:type::doc1"), Operation: OperationPut, Body: []byte(`{"fields":{"foo": "123"}}`)}
+ b.ResetTimer() // ignore setup time
+
+ for n := 0; n < b.N; n++ {
+ dispatcher.enqueue(documentOp{document: doc})
+ dispatcher.workerWg.Wait()
+ }
+}
diff --git a/client/go/internal/vespa/document/document.go b/client/go/internal/vespa/document/document.go
index 98cb2d1b6c6..214d1dc4797 100644
--- a/client/go/internal/vespa/document/document.go
+++ b/client/go/internal/vespa/document/document.go
@@ -14,13 +14,15 @@ var asciiSpace = [256]uint8{'\t': 1, '\n': 1, '\v': 1, '\f': 1, '\r': 1, ' ': 1}
type Operation int
const (
- OperationPut = iota
+ OperationPut Operation = iota
OperationUpdate
OperationRemove
)
// Id represents a Vespa document ID.
type Id struct {
+ id string
+
Type string
Namespace string
Number *int64
@@ -36,24 +38,7 @@ func (d Id) Equal(o Id) bool {
d.UserSpecific == o.UserSpecific
}
-func (d Id) String() string {
- var sb strings.Builder
- sb.WriteString("id:")
- sb.WriteString(d.Namespace)
- sb.WriteString(":")
- sb.WriteString(d.Type)
- sb.WriteString(":")
- if d.Number != nil {
- sb.WriteString("n=")
- sb.WriteString(strconv.FormatInt(*d.Number, 10))
- } else if d.Group != "" {
- sb.WriteString("g=")
- sb.WriteString(d.Group)
- }
- sb.WriteString(":")
- sb.WriteString(d.UserSpecific)
- return sb.String()
-}
+func (d Id) String() string { return d.id }
// ParseId parses a serialized document ID string.
func ParseId(serialized string) (Id, error) {
@@ -95,6 +80,7 @@ func ParseId(serialized string) (Id, error) {
return Id{}, parseError(serialized)
}
return Id{
+ id: serialized,
Namespace: namespace,
Type: docType,
Number: number,
@@ -130,6 +116,27 @@ type Decoder struct {
jsonl bool
}
+func (d Document) String() string {
+ var sb strings.Builder
+ switch d.Operation {
+ case OperationPut:
+ sb.WriteString("put ")
+ case OperationUpdate:
+ sb.WriteString("update ")
+ case OperationRemove:
+ sb.WriteString("remove ")
+ }
+ sb.WriteString(d.Id.String())
+ if d.Condition != "" {
+ sb.WriteString(", condition=")
+ sb.WriteString(d.Condition)
+ }
+ if d.Create {
+ sb.WriteString(", create=true")
+ }
+ return sb.String()
+}
+
func (d *Decoder) guessMode() error {
for !d.array && !d.jsonl {
b, err := d.buf.ReadByte()
diff --git a/client/go/internal/vespa/document/feeder.go b/client/go/internal/vespa/document/feeder.go
index 8bdd5bca5ba..9e6768d0eb4 100644
--- a/client/go/internal/vespa/document/feeder.go
+++ b/client/go/internal/vespa/document/feeder.go
@@ -17,8 +17,6 @@ const (
// StatusTransportFailure indicates that there was failure in the transport layer error while sending the document
// operation to Vespa.
StatusTransportFailure
- // StatusError is a catch-all status for any other error that might occur.
- StatusError
)
// Result represents the result of a feeding operation.
@@ -32,8 +30,9 @@ type Result struct {
Stats Stats
}
-// Success returns whether status s is considered a success.
-func (s Status) Success() bool { return s == StatusSuccess || s == StatusConditionNotMet }
+func (r Result) Success() bool {
+ return r.Err == nil && (r.Status == StatusSuccess || r.Status == StatusConditionNotMet)
+}
// Stats represents feeding operation statistics.
type Stats struct {
diff --git a/client/go/internal/vespa/document/http.go b/client/go/internal/vespa/document/http.go
index 4dadcd1d05c..877bcc5edce 100644
--- a/client/go/internal/vespa/document/http.go
+++ b/client/go/internal/vespa/document/http.go
@@ -2,31 +2,59 @@ package document
import (
"bytes"
+ "compress/gzip"
"encoding/json"
"fmt"
"io"
+ "math"
"net/http"
"net/url"
"strconv"
"strings"
+ "sync"
+ "sync/atomic"
"time"
"github.com/vespa-engine/vespa/client/go/internal/util"
)
+type Compression int
+
+const (
+ CompressionAuto Compression = iota
+ CompressionNone
+ CompressionGzip
+)
+
// Client represents a HTTP client for the /document/v1/ API.
type Client struct {
- options ClientOptions
- httpClient util.HTTPClient
- now func() time.Time
+ options ClientOptions
+ httpClients []countingHTTPClient
+ now func() time.Time
+ sendCount int32
+ gzippers sync.Pool
}
// ClientOptions specifices the configuration options of a feed client.
type ClientOptions struct {
- BaseURL string
- Timeout time.Duration
- Route string
- TraceLevel *int
+ BaseURL string
+ Timeout time.Duration
+ Route string
+ TraceLevel int
+ Compression Compression
+ NowFunc func() time.Time
+}
+
+type countingHTTPClient struct {
+ client util.HTTPClient
+ inflight int64
+}
+
+func (c *countingHTTPClient) addInflight(n int64) { atomic.AddInt64(&c.inflight, n) }
+
+func (c *countingHTTPClient) Do(req *http.Request, timeout time.Duration) (*http.Response, error) {
+ defer c.addInflight(-1)
+ return c.client.Do(req, timeout)
}
type countingReader struct {
@@ -40,25 +68,41 @@ func (r *countingReader) Read(p []byte) (int, error) {
return n, err
}
-func NewClient(options ClientOptions, httpClient util.HTTPClient) *Client {
+func NewClient(options ClientOptions, httpClients []util.HTTPClient) *Client {
+ if len(httpClients) < 1 {
+ panic("need at least one HTTP client")
+ }
+ countingClients := make([]countingHTTPClient, 0, len(httpClients))
+ for _, client := range httpClients {
+ countingClients = append(countingClients, countingHTTPClient{client: client})
+ }
+ nowFunc := options.NowFunc
+ if nowFunc == nil {
+ nowFunc = time.Now
+ }
c := &Client{
- options: options,
- httpClient: httpClient,
- now: time.Now,
+ options: options,
+ httpClients: countingClients,
+ now: nowFunc,
}
+ c.gzippers.New = func() any { return gzip.NewWriter(io.Discard) }
return c
}
func (c *Client) queryParams() url.Values {
params := url.Values{}
- if c.options.Timeout > 0 {
- params.Set("timeout", strconv.FormatInt(c.options.Timeout.Milliseconds(), 10)+"ms")
+ timeout := c.options.Timeout
+ if timeout == 0 {
+ timeout = 200 * time.Second
+ } else {
+ timeout = timeout*11/10 + 1000
}
+ params.Set("timeout", strconv.FormatInt(timeout.Milliseconds(), 10)+"ms")
if c.options.Route != "" {
params.Set("route", c.options.Route)
}
- if c.options.TraceLevel != nil {
- params.Set("tracelevel", strconv.Itoa(*c.options.TraceLevel))
+ if c.options.TraceLevel > 0 {
+ params.Set("tracelevel", strconv.Itoa(c.options.TraceLevel))
}
return params
}
@@ -109,45 +153,90 @@ func (c *Client) feedURL(d Document, queryParams url.Values) (string, *url.URL,
return httpMethod, u, nil
}
-// Send given document the URL configured in this client.
+func (c *Client) leastBusyClient() *countingHTTPClient {
+ leastBusy := c.httpClients[0]
+ min := int64(math.MaxInt64)
+ next := atomic.AddInt32(&c.sendCount, 1)
+ start := int(next) % len(c.httpClients)
+ for i := range c.httpClients {
+ j := (i + start) % len(c.httpClients)
+ client := c.httpClients[j]
+ inflight := atomic.LoadInt64(&client.inflight)
+ if inflight < min {
+ leastBusy = client
+ min = inflight
+ }
+ }
+ leastBusy.addInflight(1)
+ return &leastBusy
+}
+
+func (c *Client) gzipWriter(w io.Writer) *gzip.Writer {
+ gzipWriter := c.gzippers.Get().(*gzip.Writer)
+ gzipWriter.Reset(w)
+ return gzipWriter
+}
+
+func (c *Client) createRequest(method, url string, body []byte) (*http.Request, error) {
+ var r io.Reader
+ useGzip := c.options.Compression == CompressionGzip || (c.options.Compression == CompressionAuto && len(body) > 512)
+ if useGzip {
+ var buf bytes.Buffer
+ w := c.gzipWriter(&buf)
+ if _, err := w.Write(body); err != nil {
+ return nil, err
+ }
+ if err := w.Close(); err != nil {
+ return nil, err
+ }
+ c.gzippers.Put(w)
+ r = &buf
+ } else {
+ r = bytes.NewReader(body)
+ }
+ req, err := http.NewRequest(method, url, r)
+ if err != nil {
+ return nil, err
+ }
+ if useGzip {
+ req.Header.Set("Content-Encoding", "gzip")
+ }
+ req.Header.Set("Content-Type", "application/json; charset=utf-8")
+ return req, nil
+}
+
+// Send given document to the endpoint configured in this client.
func (c *Client) Send(document Document) Result {
start := c.now()
- result := Result{Id: document.Id}
- result.Stats.Requests = 1
+ result := Result{Id: document.Id, Stats: Stats{Requests: 1}}
method, url, err := c.feedURL(document, c.queryParams())
if err != nil {
- result.Stats.Errors = 1
- result.Err = err
- return result
+ return resultWithErr(result, err)
}
- req, err := http.NewRequest(method, url.String(), bytes.NewReader(document.Body))
+ req, err := c.createRequest(method, url.String(), document.Body)
if err != nil {
- result.Stats.Errors = 1
- result.Status = StatusError
- result.Err = err
- return result
+ return resultWithErr(result, err)
}
- resp, err := c.httpClient.Do(req, 190*time.Second)
+ resp, err := c.leastBusyClient().Do(req, 190*time.Second)
if err != nil {
- result.Stats.Errors = 1
- result.Status = StatusTransportFailure
- result.Err = err
- return result
+ return resultWithErr(result, err)
}
defer resp.Body.Close()
- result.Stats.Responses = 1
- result.Stats.ResponsesByCode = map[int]int64{
- resp.StatusCode: 1,
- }
- result.Stats.BytesSent = int64(len(document.Body))
elapsed := c.now().Sub(start)
- result.Stats.TotalLatency = elapsed
- result.Stats.MinLatency = elapsed
- result.Stats.MaxLatency = elapsed
- return c.resultWithResponse(resp, result)
+ return resultWithResponse(resp, result, document, elapsed)
}
-func (c *Client) resultWithResponse(resp *http.Response, result Result) Result {
+func resultWithErr(result Result, err error) Result {
+ result.Stats.Errors++
+ result.Status = StatusTransportFailure
+ result.Err = err
+ return result
+}
+
+func resultWithResponse(resp *http.Response, result Result, document Document, elapsed time.Duration) Result {
+ result.HTTPStatus = resp.StatusCode
+ result.Stats.Responses++
+ result.Stats.ResponsesByCode = map[int]int64{resp.StatusCode: 1}
switch resp.StatusCode {
case 200:
result.Status = StatusSuccess
@@ -165,14 +254,18 @@ func (c *Client) resultWithResponse(resp *http.Response, result Result) Result {
cr := countingReader{reader: resp.Body}
jsonDec := json.NewDecoder(&cr)
if err := jsonDec.Decode(&body); err != nil {
- result.Status = StatusError
+ result.Status = StatusVespaFailure
result.Err = fmt.Errorf("failed to decode json response: %w", err)
}
result.Message = body.Message
result.Trace = string(body.Trace)
+ result.Stats.BytesSent = int64(len(document.Body))
result.Stats.BytesRecv = cr.bytesRead
- if !result.Status.Success() {
- result.Stats.Errors = 1
+ if !result.Success() {
+ result.Stats.Errors++
}
+ result.Stats.TotalLatency = elapsed
+ result.Stats.MinLatency = elapsed
+ result.Stats.MaxLatency = elapsed
return result
}
diff --git a/client/go/internal/vespa/document/http_test.go b/client/go/internal/vespa/document/http_test.go
index 311668fa16e..f67368b5128 100644
--- a/client/go/internal/vespa/document/http_test.go
+++ b/client/go/internal/vespa/document/http_test.go
@@ -7,10 +7,12 @@ import (
"net/http"
"net/url"
"reflect"
+ "strings"
"testing"
"time"
"github.com/vespa-engine/vespa/client/go/internal/mock"
+ "github.com/vespa-engine/vespa/client/go/internal/util"
)
type manualClock struct {
@@ -25,6 +27,37 @@ func (c *manualClock) now() time.Time {
func (c *manualClock) advance(d time.Duration) { c.t = c.t.Add(d) }
+type mockHTTPClient struct {
+ id int
+ *mock.HTTPClient
+}
+
+func TestLeastBusyClient(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ var httpClients []util.HTTPClient
+ for i := 0; i < 4; i++ {
+ httpClients = append(httpClients, &mockHTTPClient{i, &httpClient})
+ }
+ client := NewClient(ClientOptions{}, httpClients)
+ client.httpClients[0].addInflight(1)
+ client.httpClients[1].addInflight(1)
+ assertLeastBusy(t, 2, client)
+ assertLeastBusy(t, 2, client)
+ assertLeastBusy(t, 3, client)
+ client.httpClients[3].addInflight(1)
+ client.httpClients[1].addInflight(-1)
+ assertLeastBusy(t, 1, client)
+}
+
+func assertLeastBusy(t *testing.T, id int, client *Client) {
+ t.Helper()
+ leastBusy := client.leastBusyClient()
+ got := leastBusy.client.(*mockHTTPClient).id
+ if got != id {
+ t.Errorf("got client.id=%d, want %d", got, id)
+ }
+}
+
func TestClientSend(t *testing.T) {
docs := []Document{
{Create: true, Id: mustParseId("id:ns:type::doc1"), Operation: OperationUpdate, Body: []byte(`{"fields":{"foo": "123"}}`)},
@@ -35,26 +68,48 @@ func TestClientSend(t *testing.T) {
client := NewClient(ClientOptions{
BaseURL: "https://example.com:1337",
Timeout: time.Duration(5 * time.Second),
- }, &httpClient)
+ }, []util.HTTPClient{&httpClient})
clock := manualClock{t: time.Now(), tick: time.Second}
client.now = clock.now
var stats Stats
for i, doc := range docs {
+ wantRes := Result{
+ Id: doc.Id,
+ Stats: Stats{
+ Requests: 1,
+ Responses: 1,
+ TotalLatency: time.Second,
+ MinLatency: time.Second,
+ MaxLatency: time.Second,
+ BytesSent: 25,
+ },
+ }
if i < 2 {
httpClient.NextResponseString(200, `{"message":"All good!"}`)
+ wantRes.Status = StatusSuccess
+ wantRes.HTTPStatus = 200
+ wantRes.Message = "All good!"
+ wantRes.Stats.ResponsesByCode = map[int]int64{200: 1}
+ wantRes.Stats.BytesRecv = 23
} else {
httpClient.NextResponseString(502, `{"message":"Good bye, cruel world!"}`)
+ wantRes.Status = StatusVespaFailure
+ wantRes.HTTPStatus = 502
+ wantRes.Message = "Good bye, cruel world!"
+ wantRes.Stats.ResponsesByCode = map[int]int64{502: 1}
+ wantRes.Stats.Errors = 1
+ wantRes.Stats.BytesRecv = 36
}
res := client.Send(doc)
- stats.Add(res.Stats)
- if res.Err != nil {
- t.Fatalf("got unexpected error %q", res.Err)
+ if !reflect.DeepEqual(res, wantRes) {
+ t.Fatalf("got result %+v, want %+v", res, wantRes)
}
+ stats.Add(res.Stats)
r := httpClient.LastRequest
if r.Method != http.MethodPut {
t.Errorf("got r.Method = %q, want %q", r.Method, http.MethodPut)
}
- wantURL := fmt.Sprintf("https://example.com:1337/document/v1/ns/type/docid/%s?create=true&timeout=5000ms", doc.Id.UserSpecific)
+ wantURL := fmt.Sprintf("https://example.com:1337/document/v1/ns/type/docid/%s?create=true&timeout=5500ms", doc.Id.UserSpecific)
if r.URL.String() != wantURL {
t.Errorf("got r.URL = %q, want %q", r.URL, wantURL)
}
@@ -87,6 +142,55 @@ func TestClientSend(t *testing.T) {
}
}
+func TestClientSendCompressed(t *testing.T) {
+ httpClient := mock.HTTPClient{}
+ client := NewClient(ClientOptions{
+ BaseURL: "https://example.com:1337",
+ Timeout: time.Duration(5 * time.Second),
+ }, []util.HTTPClient{&httpClient})
+
+ bigBody := fmt.Sprintf(`{"fields":{"foo": "%s"}}`, strings.Repeat("s", 512+1))
+ bigDoc := Document{Create: true, Id: mustParseId("id:ns:type::doc1"), Operation: OperationUpdate, Body: []byte(bigBody)}
+ smallDoc := Document{Create: true, Id: mustParseId("id:ns:type::doc2"), Operation: OperationUpdate, Body: []byte(`{"fields":{"foo": "s"}}`)}
+
+ client.options.Compression = CompressionNone
+ _ = client.Send(bigDoc)
+ assertCompressedRequest(t, false, httpClient.LastRequest)
+ _ = client.Send(smallDoc)
+ assertCompressedRequest(t, false, httpClient.LastRequest)
+
+ client.options.Compression = CompressionAuto
+ _ = client.Send(bigDoc)
+ assertCompressedRequest(t, true, httpClient.LastRequest)
+ _ = client.Send(smallDoc)
+ assertCompressedRequest(t, false, httpClient.LastRequest)
+
+ client.options.Compression = CompressionGzip
+ _ = client.Send(bigDoc)
+ assertCompressedRequest(t, true, httpClient.LastRequest)
+ _ = client.Send(smallDoc)
+ assertCompressedRequest(t, true, httpClient.LastRequest)
+}
+
+func assertCompressedRequest(t *testing.T, want bool, request *http.Request) {
+ wantEnc := ""
+ if want {
+ wantEnc = "gzip"
+ }
+ gotEnc := request.Header.Get("Content-Encoding")
+ if gotEnc != wantEnc {
+ t.Errorf("got Content-Encoding=%q, want %q", gotEnc, wantEnc)
+ }
+ body, err := io.ReadAll(request.Body)
+ if err != nil {
+ t.Fatal(err)
+ }
+ compressed := bytes.HasPrefix(body, []byte{0x1f, 0x8b})
+ if compressed != want {
+ t.Errorf("got compressed=%t, want %t", compressed, want)
+ }
+}
+
func TestURLPath(t *testing.T) {
tests := []struct {
in Id
@@ -176,7 +280,7 @@ func TestClientFeedURL(t *testing.T) {
httpClient := mock.HTTPClient{}
client := NewClient(ClientOptions{
BaseURL: "https://example.com",
- }, &httpClient)
+ }, []util.HTTPClient{&httpClient})
for i, tt := range tests {
moreParams := url.Values{}
moreParams.Set("foo", "ba/r")
@@ -189,3 +293,26 @@ func TestClientFeedURL(t *testing.T) {
}
}
}
+
+func benchmarkClientSend(b *testing.B, compression Compression, document Document) {
+ httpClient := mock.HTTPClient{}
+ client := NewClient(ClientOptions{
+ Compression: compression,
+ BaseURL: "https://example.com:1337",
+ Timeout: time.Duration(5 * time.Second),
+ }, []util.HTTPClient{&httpClient})
+ b.ResetTimer() // ignore setup
+ for n := 0; n < b.N; n++ {
+ client.Send(document)
+ }
+}
+
+func BenchmarkClientSend(b *testing.B) {
+ doc := Document{Create: true, Id: mustParseId("id:ns:type::doc1"), Operation: OperationUpdate, Body: []byte(`{"fields":{"foo": "my document"}}`)}
+ benchmarkClientSend(b, CompressionNone, doc)
+}
+
+func BenchmarkClientSendCompressed(b *testing.B) {
+ doc := Document{Create: true, Id: mustParseId("id:ns:type::doc1"), Operation: OperationUpdate, Body: []byte(`{"fields":{"foo": "my document"}}`)}
+ benchmarkClientSend(b, CompressionGzip, doc)
+}
diff --git a/client/go/internal/vespa/document/queue.go b/client/go/internal/vespa/document/queue.go
new file mode 100644
index 00000000000..2e5a1976d58
--- /dev/null
+++ b/client/go/internal/vespa/document/queue.go
@@ -0,0 +1,43 @@
+package document
+
+import (
+ "container/list"
+ "sync"
+)
+
+// Queue wraps a doubly linked list. It attempts to re-use lists through a sync.Pool to reduce GC pressure.
+type Queue[T any] struct {
+ items *list.List
+ listPool *sync.Pool
+}
+
+func NewQueue[T any](listPool *sync.Pool) *Queue[T] {
+ if listPool.New == nil {
+ listPool.New = func() any { return list.New() }
+ }
+ return &Queue[T]{listPool: listPool}
+}
+
+func (q *Queue[T]) Add(item T, front bool) {
+ if q.items == nil {
+ q.items = q.listPool.Get().(*list.List)
+ }
+ if front {
+ q.items.PushFront(item)
+ } else {
+ q.items.PushBack(item)
+ }
+}
+
+func (q *Queue[T]) Poll() (T, bool) {
+ if q.items == nil || q.items.Front() == nil {
+ var empty T
+ return empty, false
+ }
+ item := q.items.Remove(q.items.Front()).(T)
+ if q.items.Front() == nil { // Emptied queue, release list back to pool
+ q.listPool.Put(q.items)
+ q.items = nil
+ }
+ return item, true
+}
diff --git a/client/go/internal/vespa/document/queue_test.go b/client/go/internal/vespa/document/queue_test.go
new file mode 100644
index 00000000000..992e7410053
--- /dev/null
+++ b/client/go/internal/vespa/document/queue_test.go
@@ -0,0 +1,29 @@
+package document
+
+import (
+ "sync"
+ "testing"
+)
+
+func TestQueue(t *testing.T) {
+ q := NewQueue[int](&sync.Pool{})
+ assertPoll(t, q, 0, false)
+ q.Add(1, false)
+ q.Add(2, false)
+ assertPoll(t, q, 1, true)
+ assertPoll(t, q, 2, true)
+ q.Add(3, false)
+ q.Add(4, true)
+ assertPoll(t, q, 4, true)
+ assertPoll(t, q, 3, true)
+}
+
+func assertPoll(t *testing.T, q *Queue[int], want int, wantOk bool) {
+ got, ok := q.Poll()
+ if ok != wantOk {
+ t.Fatalf("got ok=%t, want %t", ok, wantOk)
+ }
+ if got != want {
+ t.Fatalf("got v=%d, want %d", got, want)
+ }
+}
diff --git a/client/go/internal/vespa/document/throttler.go b/client/go/internal/vespa/document/throttler.go
index 69bb7c8d7ac..5b0aab6174e 100644
--- a/client/go/internal/vespa/document/throttler.go
+++ b/client/go/internal/vespa/document/throttler.go
@@ -7,13 +7,7 @@ import (
"time"
)
-const (
- throttlerWeight = 0.7
- // TODO(mpolden): Multiply this by connections per endpoint, and number of endpoints when this becomes configurable
- // for local target
- throttlerMinInflight = 16
- throttlerMaxInflight = 256 * throttlerMinInflight // 4096 max streams per connection on the server side
-)
+const throttlerWeight = 0.7
type Throttler interface {
// Sent notifies the the throttler that a document has been sent.
@@ -27,29 +21,38 @@ type Throttler interface {
}
type dynamicThrottler struct {
- ok int64
+ minInflight int64
+ maxInflight int64
targetInflight int64
targetTimesTen int64
throughputs []float64
+ ok int64
sent int64
start time.Time
now func() time.Time
}
-func newThrottler(nowFunc func() time.Time) *dynamicThrottler {
+func newThrottler(connections int, nowFunc func() time.Time) *dynamicThrottler {
+ var (
+ minInflight = 16 * int64(connections)
+ maxInflight = 256 * minInflight // 4096 max streams per connection on the server side
+ )
return &dynamicThrottler{
+ minInflight: minInflight,
+ maxInflight: maxInflight,
+ targetInflight: 8 * minInflight,
+ targetTimesTen: 10 * maxInflight,
+
throughputs: make([]float64, 128),
- start: nowFunc(),
- now: nowFunc,
- targetInflight: 8 * throttlerMinInflight,
- targetTimesTen: 10 * throttlerMaxInflight,
+ start: nowFunc(),
+ now: nowFunc,
}
}
-func NewThrottler() Throttler { return newThrottler(time.Now) }
+func NewThrottler(connections int) Throttler { return newThrottler(connections, time.Now) }
func (t *dynamicThrottler) Sent() {
currentInflight := atomic.LoadInt64(&t.targetInflight)
@@ -64,7 +67,7 @@ func (t *dynamicThrottler) Sent() {
currentThroughput := float64(atomic.SwapInt64(&t.ok, 0)) / float64(elapsed)
// Use buckets for throughput over inflight, along the log-scale, in [minInflight, maxInflight).
- index := int(float64(len(t.throughputs)) * math.Log(max(1, min(255, float64(currentInflight)/throttlerMinInflight))) / math.Log(256))
+ index := int(float64(len(t.throughputs)) * math.Log(max(1, min(255, float64(currentInflight)/float64(t.minInflight)))) / math.Log(256))
t.throughputs[index] = currentThroughput
// Loop over throughput measurements and pick the one which optimises throughput and latency.
@@ -74,7 +77,7 @@ func (t *dynamicThrottler) Sent() {
if t.throughputs[i] == 0 {
continue // Skip unknown values
}
- inflight := float64(throttlerMinInflight) * math.Pow(256, (float64(i)+0.5)/float64(len(t.throughputs)))
+ inflight := float64(t.minInflight) * math.Pow(256, (float64(i)+0.5)/float64(len(t.throughputs)))
objective := t.throughputs[i] * math.Pow(inflight, throttlerWeight-1) // Optimise throughput (weight), but also latency (1 - weight)
if objective > maxObjective {
maxObjective = objective
@@ -82,7 +85,7 @@ func (t *dynamicThrottler) Sent() {
}
}
target := int64((rand.Float64()*0.20 + 0.92) * choice) // Random walk, skewed towards increase
- atomic.StoreInt64(&t.targetInflight, max(throttlerMinInflight, min(throttlerMaxInflight, target)))
+ atomic.StoreInt64(&t.targetInflight, max(t.minInflight, min(t.maxInflight, target)))
}
func (t *dynamicThrottler) Success() {
@@ -91,11 +94,11 @@ func (t *dynamicThrottler) Success() {
}
func (t *dynamicThrottler) Throttled(inflight int64) {
- atomic.StoreInt64(&t.targetTimesTen, max(inflight*5, throttlerMinInflight*10))
+ atomic.StoreInt64(&t.targetTimesTen, max(inflight*5, t.minInflight*10))
}
func (t *dynamicThrottler) TargetInflight() int64 {
- staticTargetInflight := min(throttlerMaxInflight, atomic.LoadInt64(&t.targetTimesTen)/10)
+ staticTargetInflight := min(t.maxInflight, atomic.LoadInt64(&t.targetTimesTen)/10)
targetInflight := atomic.LoadInt64(&t.targetInflight)
return min(staticTargetInflight, targetInflight)
}
diff --git a/client/go/internal/vespa/document/throttler_test.go b/client/go/internal/vespa/document/throttler_test.go
index 2fd1e73a45a..a22f059207f 100644
--- a/client/go/internal/vespa/document/throttler_test.go
+++ b/client/go/internal/vespa/document/throttler_test.go
@@ -7,15 +7,15 @@ import (
func TestThrottler(t *testing.T) {
clock := &manualClock{tick: time.Second}
- tr := newThrottler(clock.now)
+ tr := newThrottler(8, clock.now)
for i := 0; i < 100; i++ {
tr.Sent()
}
- if got, want := tr.TargetInflight(), int64(128); got != want {
+ if got, want := tr.TargetInflight(), int64(1024); got != want {
t.Errorf("got TargetInflight() = %d, but want %d", got, want)
}
tr.Throttled(5)
- if got, want := tr.TargetInflight(), int64(16); got != want {
+ if got, want := tr.TargetInflight(), int64(128); got != want {
t.Errorf("got TargetInflight() = %d, but want %d", got, want)
}
}
diff --git a/client/go/internal/vespa/target.go b/client/go/internal/vespa/target.go
index 51861eb12ab..9f3fd7f5c65 100644
--- a/client/go/internal/vespa/target.go
+++ b/client/go/internal/vespa/target.go
@@ -7,6 +7,7 @@ import (
"fmt"
"io"
"net/http"
+ "sync"
"time"
"github.com/vespa-engine/vespa/client/go/internal/util"
@@ -17,7 +18,7 @@ const (
// A target for a local Vespa service
TargetLocal = "local"
- // A target for a custom URL
+ // A target for a Vespa service at a custom URL
TargetCustom = "custom"
// A Vespa Cloud target
@@ -38,13 +39,19 @@ const (
retryInterval = 2 * time.Second
)
+// Authenticator authenticates the given HTTP request.
+type Authenticator interface {
+ Authenticate(request *http.Request) error
+}
+
// Service represents a Vespa service.
type Service struct {
BaseURL string
Name string
TLSOptions TLSOptions
- zts zts
+ once sync.Once
+ auth Authenticator
httpClient util.HTTPClient
}
@@ -65,19 +72,19 @@ type Target interface {
// PrintLog writes the logs of this deployment using given options to control output.
PrintLog(options LogOptions) error
- // SignRequest signs request with given keyID as required by the implementation of this target.
- SignRequest(request *http.Request, keyID string) error
-
// CheckVersion verifies whether clientVersion is compatible with this target.
CheckVersion(clientVersion version.Version) error
}
-// TLSOptions configures the client certificate to use for cloud API or service requests.
+// TLSOptions holds the client certificate to use for cloud API or service requests.
type TLSOptions struct {
- KeyPair *tls.Certificate
- CertificateFile string
- PrivateKeyFile string
- AthenzDomain string
+ CACertificate []byte
+ KeyPair []tls.Certificate
+ TrustAll bool
+
+ CACertificateFile string
+ CertificateFile string
+ PrivateKeyFile string
}
// LogOptions configures the log output to produce when writing log messages.
@@ -90,21 +97,21 @@ type LogOptions struct {
Level int
}
-// Do sends request to this service. Any required authentication happens automatically.
+// Do sends request to this service. Authentication of the request happens automatically.
func (s *Service) Do(request *http.Request, timeout time.Duration) (*http.Response, error) {
- if s.TLSOptions.AthenzDomain != "" && s.TLSOptions.KeyPair != nil {
- accessToken, err := s.zts.AccessToken(s.TLSOptions.AthenzDomain, *s.TLSOptions.KeyPair)
- if err != nil {
+ s.once.Do(func() {
+ util.ConfigureTLS(s.httpClient, s.TLSOptions.KeyPair, s.TLSOptions.CACertificate, s.TLSOptions.TrustAll)
+ })
+ if s.auth != nil {
+ if err := s.auth.Authenticate(request); err != nil {
return nil, err
}
- if request.Header == nil {
- request.Header = make(http.Header)
- }
- request.Header.Add("Authorization", "Bearer "+accessToken)
}
return s.httpClient.Do(request, timeout)
}
+func (s *Service) Client() util.HTTPClient { return s.httpClient }
+
// Wait polls the health check of this service until it succeeds or timeout passes.
func (s *Service) Wait(timeout time.Duration) (int, error) {
url := s.BaseURL
@@ -116,7 +123,7 @@ func (s *Service) Wait(timeout time.Duration) (int, error) {
default:
return 0, fmt.Errorf("invalid service: %s", s.Name)
}
- return waitForOK(s.httpClient, url, s.TLSOptions.KeyPair, timeout)
+ return waitForOK(s, url, timeout)
}
func (s *Service) Description() string {
@@ -131,27 +138,40 @@ func (s *Service) Description() string {
return fmt.Sprintf("No description of service %s", s.Name)
}
-func isOK(status int) bool { return status/100 == 2 }
+func isOK(status int) (bool, error) {
+ class := status / 100
+ switch class {
+ case 2: // success
+ return true, nil
+ case 4: // client error
+ return false, fmt.Errorf("request failed with status %d", status)
+ default: // retry
+ return false, nil
+ }
+}
type responseFunc func(status int, response []byte) (bool, error)
type requestFunc func() *http.Request
-// waitForOK queries url and returns its status code. If the url returns a non-200 status code, it is repeatedly queried
+// waitForOK queries url and returns its status code. If response status is not 2xx or 4xx, it is repeatedly queried
// until timeout elapses.
-func waitForOK(client util.HTTPClient, url string, certificate *tls.Certificate, timeout time.Duration) (int, error) {
+func waitForOK(service *Service, url string, timeout time.Duration) (int, error) {
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return 0, err
}
- okFunc := func(status int, response []byte) (bool, error) { return isOK(status), nil }
- return wait(client, okFunc, func() *http.Request { return req }, certificate, timeout)
+ okFunc := func(status int, response []byte) (bool, error) {
+ ok, err := isOK(status)
+ if err != nil {
+ return false, fmt.Errorf("failed to query %s at %s: %w", service.Description(), url, err)
+ }
+ return ok, err
+ }
+ return wait(service, okFunc, func() *http.Request { return req }, timeout)
}
-func wait(client util.HTTPClient, fn responseFunc, reqFn requestFunc, certificate *tls.Certificate, timeout time.Duration) (int, error) {
- if certificate != nil {
- util.SetCertificate(client, []tls.Certificate{*certificate})
- }
+func wait(service *Service, fn responseFunc, reqFn requestFunc, timeout time.Duration) (int, error) {
var (
httpErr error
response *http.Response
@@ -161,7 +181,7 @@ func wait(client util.HTTPClient, fn responseFunc, reqFn requestFunc, certificat
loopOnce := timeout == 0
for time.Now().Before(deadline) || loopOnce {
req := reqFn()
- response, httpErr = client.Do(req, 10*time.Second)
+ response, httpErr = service.Do(req, 10*time.Second)
if httpErr == nil {
statusCode = response.StatusCode
body, err := io.ReadAll(response.Body)
diff --git a/client/go/internal/vespa/target_cloud.go b/client/go/internal/vespa/target_cloud.go
index 2335d4f3432..928bb788494 100644
--- a/client/go/internal/vespa/target_cloud.go
+++ b/client/go/internal/vespa/target_cloud.go
@@ -2,7 +2,6 @@ package vespa
import (
"bytes"
- "crypto/tls"
"encoding/json"
"fmt"
"math"
@@ -35,8 +34,8 @@ type cloudTarget struct {
deploymentOptions CloudDeploymentOptions
logOptions LogOptions
httpClient util.HTTPClient
- zts zts
- auth0 auth0
+ apiAuth Authenticator
+ deploymentAuth Authenticator
}
type deploymentEndpoint struct {
@@ -62,23 +61,15 @@ type logMessage struct {
Message string `json:"message"`
}
-type zts interface {
- AccessToken(domain string, certficiate tls.Certificate) (string, error)
-}
-
-type auth0 interface {
- AccessToken() (string, error)
-}
-
// CloudTarget creates a Target for the Vespa Cloud or hosted Vespa platform.
-func CloudTarget(httpClient util.HTTPClient, ztsClient zts, auth0Client auth0, apiOptions APIOptions, deploymentOptions CloudDeploymentOptions, logOptions LogOptions) (Target, error) {
+func CloudTarget(httpClient util.HTTPClient, apiAuth Authenticator, deploymentAuth Authenticator, apiOptions APIOptions, deploymentOptions CloudDeploymentOptions, logOptions LogOptions) (Target, error) {
return &cloudTarget{
httpClient: httpClient,
apiOptions: apiOptions,
deploymentOptions: deploymentOptions,
logOptions: logOptions,
- zts: ztsClient,
- auth0: auth0Client,
+ apiAuth: apiAuth,
+ deploymentAuth: deploymentAuth,
}, nil
}
@@ -118,25 +109,25 @@ func (t *cloudTarget) IsCloud() bool { return true }
func (t *cloudTarget) Deployment() Deployment { return t.deploymentOptions.Deployment }
func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64, cluster string) (*Service, error) {
- var service *Service
switch name {
case DeployService:
- service = &Service{
+ service := &Service{
Name: name,
BaseURL: t.apiOptions.System.URL,
TLSOptions: t.apiOptions.TLSOptions,
- zts: t.zts,
httpClient: t.httpClient,
+ auth: t.apiAuth,
}
if timeout > 0 {
status, err := service.Wait(timeout)
if err != nil {
return nil, err
}
- if !isOK(status) {
+ if ok, _ := isOK(status); !ok {
return nil, fmt.Errorf("got status %d from deploy service at %s", status, service.BaseURL)
}
}
+ return service, nil
case QueryService, DocumentService:
if t.deploymentOptions.ClusterURLs == nil {
if err := t.waitForEndpoints(timeout, runID); err != nil {
@@ -147,38 +138,15 @@ func (t *cloudTarget) Service(name string, timeout time.Duration, runID int64, c
if err != nil {
return nil, err
}
- t.deploymentOptions.TLSOptions.AthenzDomain = t.apiOptions.System.AthenzDomain
- service = &Service{
+ return &Service{
Name: name,
BaseURL: url,
TLSOptions: t.deploymentOptions.TLSOptions,
- zts: t.zts,
httpClient: t.httpClient,
- }
-
+ auth: t.deploymentAuth,
+ }, nil
default:
return nil, fmt.Errorf("unknown service: %s", name)
-
- }
- if service.TLSOptions.KeyPair != nil {
- util.SetCertificate(service.httpClient, []tls.Certificate{*service.TLSOptions.KeyPair})
- }
- return service, nil
-}
-
-func (t *cloudTarget) SignRequest(req *http.Request, keyID string) error {
- if t.apiOptions.System.IsPublic() {
- if t.apiOptions.APIKey != nil {
- signer := NewRequestSigner(keyID, t.apiOptions.APIKey)
- return signer.SignRequest(req)
- } else {
- return t.addAuth0AccessToken(req)
- }
- } else {
- if t.apiOptions.TLSOptions.KeyPair.Certificate == nil {
- return fmt.Errorf("system %s requires a certificate for authentication", t.apiOptions.System.Name)
- }
- return nil
}
}
@@ -190,7 +158,11 @@ func (t *cloudTarget) CheckVersion(clientVersion version.Version) error {
if err != nil {
return err
}
- response, err := t.httpClient.Do(req, 10*time.Second)
+ deployService, err := t.Service(DeployService, 0, 0, "")
+ if err != nil {
+ return err
+ }
+ response, err := deployService.Do(req, 10*time.Second)
if err != nil {
return err
}
@@ -212,18 +184,6 @@ func (t *cloudTarget) CheckVersion(clientVersion version.Version) error {
return nil
}
-func (t *cloudTarget) addAuth0AccessToken(request *http.Request) error {
- accessToken, err := t.auth0.AccessToken()
- if err != nil {
- return err
- }
- if request.Header == nil {
- request.Header = make(http.Header)
- }
- request.Header.Set("Authorization", "Bearer "+accessToken)
- return nil
-}
-
func (t *cloudTarget) logsURL() string {
return fmt.Sprintf("%s/application/v4/tenant/%s/application/%s/instance/%s/environment/%s/region/%s/logs",
t.apiOptions.System.URL,
@@ -246,11 +206,10 @@ func (t *cloudTarget) PrintLog(options LogOptions) error {
q.Set("to", strconv.FormatInt(toMillis, 10))
}
req.URL.RawQuery = q.Encode()
- t.SignRequest(req, t.deploymentOptions.Deployment.Application.SerializedForm())
return req
}
logFunc := func(status int, response []byte) (bool, error) {
- if ok, err := isCloudOK(status); !ok {
+ if ok, err := isOK(status); !ok {
return ok, err
}
logEntries, err := ReadLogEntries(bytes.NewReader(response))
@@ -275,10 +234,18 @@ func (t *cloudTarget) PrintLog(options LogOptions) error {
if options.Follow {
timeout = math.MaxInt64 // No timeout
}
- _, err = wait(t.httpClient, logFunc, requestFunc, t.apiOptions.TLSOptions.KeyPair, timeout)
+ _, err = t.deployServiceWait(logFunc, requestFunc, timeout)
return err
}
+func (t *cloudTarget) deployServiceWait(fn responseFunc, reqFn requestFunc, timeout time.Duration) (int, error) {
+ deployService, err := t.Service(DeployService, 0, 0, "")
+ if err != nil {
+ return 0, err
+ }
+ return wait(deployService, fn, reqFn, timeout)
+}
+
func (t *cloudTarget) waitForEndpoints(timeout time.Duration, runID int64) error {
if runID > 0 {
if err := t.waitForRun(runID, timeout); err != nil {
@@ -302,13 +269,10 @@ func (t *cloudTarget) waitForRun(runID int64, timeout time.Duration) error {
q := req.URL.Query()
q.Set("after", strconv.FormatInt(lastID, 10))
req.URL.RawQuery = q.Encode()
- if err := t.SignRequest(req, t.deploymentOptions.Deployment.Application.SerializedForm()); err != nil {
- util.JustExitWith(err)
- }
return req
}
jobSuccessFunc := func(status int, response []byte) (bool, error) {
- if ok, err := isCloudOK(status); !ok {
+ if ok, err := isOK(status); !ok {
return ok, err
}
var resp jobResponse
@@ -326,7 +290,7 @@ func (t *cloudTarget) waitForRun(runID int64, timeout time.Duration) error {
}
return true, nil
}
- _, err = wait(t.httpClient, jobSuccessFunc, requestFunc, t.apiOptions.TLSOptions.KeyPair, timeout)
+ _, err = t.deployServiceWait(jobSuccessFunc, requestFunc, timeout)
return err
}
@@ -361,12 +325,9 @@ func (t *cloudTarget) discoverEndpoints(timeout time.Duration) error {
if err != nil {
return err
}
- if err := t.SignRequest(req, t.deploymentOptions.Deployment.Application.SerializedForm()); err != nil {
- return err
- }
urlsByCluster := make(map[string]string)
endpointFunc := func(status int, response []byte) (bool, error) {
- if ok, err := isCloudOK(status); !ok {
+ if ok, err := isOK(status); !ok {
return ok, err
}
var resp deploymentResponse
@@ -384,7 +345,7 @@ func (t *cloudTarget) discoverEndpoints(timeout time.Duration) error {
}
return true, nil
}
- if _, err = wait(t.httpClient, endpointFunc, func() *http.Request { return req }, t.apiOptions.TLSOptions.KeyPair, timeout); err != nil {
+ if _, err := t.deployServiceWait(endpointFunc, func() *http.Request { return req }, timeout); err != nil {
return err
}
if len(urlsByCluster) == 0 {
@@ -393,11 +354,3 @@ func (t *cloudTarget) discoverEndpoints(timeout time.Duration) error {
t.deploymentOptions.ClusterURLs = urlsByCluster
return nil
}
-
-func isCloudOK(status int) (bool, error) {
- if status == 401 {
- // when retrying we should give up immediately if we're not authorized
- return false, fmt.Errorf("status %d: invalid credentials", status)
- }
- return isOK(status), nil
-}
diff --git a/client/go/internal/vespa/target_custom.go b/client/go/internal/vespa/target_custom.go
index 848d19f0a90..0a3a9d48fed 100644
--- a/client/go/internal/vespa/target_custom.go
+++ b/client/go/internal/vespa/target_custom.go
@@ -15,6 +15,7 @@ type customTarget struct {
targetType string
baseURL string
httpClient util.HTTPClient
+ tlsOptions TLSOptions
}
type serviceConvergeResponse struct {
@@ -22,13 +23,13 @@ type serviceConvergeResponse struct {
}
// LocalTarget creates a target for a Vespa platform running locally.
-func LocalTarget(httpClient util.HTTPClient) Target {
- return &customTarget{targetType: TargetLocal, baseURL: "http://127.0.0.1", httpClient: httpClient}
+func LocalTarget(httpClient util.HTTPClient, tlsOptions TLSOptions) Target {
+ return &customTarget{targetType: TargetLocal, baseURL: "http://127.0.0.1", httpClient: httpClient, tlsOptions: tlsOptions}
}
// CustomTarget creates a Target for a Vespa platform running at baseURL.
-func CustomTarget(httpClient util.HTTPClient, baseURL string) Target {
- return &customTarget{targetType: TargetCustom, baseURL: baseURL, httpClient: httpClient}
+func CustomTarget(httpClient util.HTTPClient, baseURL string, tlsOptions TLSOptions) Target {
+ return &customTarget{targetType: TargetCustom, baseURL: baseURL, httpClient: httpClient, tlsOptions: tlsOptions}
}
func (t *customTarget) Type() string { return t.targetType }
@@ -44,7 +45,7 @@ func (t *customTarget) createService(name string) (*Service, error) {
if err != nil {
return nil, err
}
- return &Service{BaseURL: url, Name: name, httpClient: t.httpClient}, nil
+ return &Service{BaseURL: url, Name: name, httpClient: t.httpClient, TLSOptions: t.tlsOptions}, nil
}
return nil, fmt.Errorf("unknown service: %s", name)
}
@@ -60,7 +61,7 @@ func (t *customTarget) Service(name string, timeout time.Duration, sessionOrRunI
if err != nil {
return nil, err
}
- if !isOK(status) {
+ if ok, _ := isOK(status); !ok {
return nil, fmt.Errorf("got status %d from deploy service at %s", status, service.BaseURL)
}
} else {
@@ -76,8 +77,6 @@ func (t *customTarget) PrintLog(options LogOptions) error {
return fmt.Errorf("log access is only supported on cloud: run vespa-logfmt on the admin node instead")
}
-func (t *customTarget) SignRequest(req *http.Request, sigKeyId string) error { return nil }
-
func (t *customTarget) CheckVersion(version version.Version) error { return nil }
func (t *customTarget) urlWithPort(serviceName string) (string, error) {
@@ -101,19 +100,19 @@ func (t *customTarget) urlWithPort(serviceName string) (string, error) {
}
func (t *customTarget) waitForConvergence(timeout time.Duration) error {
- deployURL, err := t.urlWithPort(DeployService)
+ deployService, err := t.createService(DeployService)
if err != nil {
return err
}
- url := fmt.Sprintf("%s/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/serviceconverge", deployURL)
+ url := fmt.Sprintf("%s/application/v2/tenant/default/application/default/environment/prod/region/default/instance/default/serviceconverge", deployService.BaseURL)
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return err
}
converged := false
convergedFunc := func(status int, response []byte) (bool, error) {
- if !isOK(status) {
- return false, nil
+ if ok, err := isOK(status); !ok {
+ return ok, err
}
var resp serviceConvergeResponse
if err := json.Unmarshal(response, &resp); err != nil {
@@ -122,7 +121,7 @@ func (t *customTarget) waitForConvergence(timeout time.Duration) error {
converged = resp.Converged
return converged, nil
}
- if _, err := wait(t.httpClient, convergedFunc, func() *http.Request { return req }, nil, timeout); err != nil {
+ if _, err := wait(deployService, convergedFunc, func() *http.Request { return req }, timeout); err != nil {
return err
}
if !converged {
diff --git a/client/go/internal/vespa/target_test.go b/client/go/internal/vespa/target_test.go
index b9d65f3d8a4..bf266e8f9ec 100644
--- a/client/go/internal/vespa/target_test.go
+++ b/client/go/internal/vespa/target_test.go
@@ -3,7 +3,6 @@ package vespa
import (
"bytes"
- "crypto/tls"
"fmt"
"io"
"net/http"
@@ -19,10 +18,16 @@ import (
type mockVespaApi struct {
deploymentConverged bool
+ authFailure bool
serverURL string
}
func (v *mockVespaApi) mockVespaHandler(w http.ResponseWriter, req *http.Request) {
+ if v.authFailure {
+ response := `{"message":"unauthorized"}`
+ w.WriteHeader(401)
+ w.Write([]byte(response))
+ }
switch req.URL.Path {
case "/cli/v1/":
response := `{"minVersion":"8.0.0"}`
@@ -65,17 +70,17 @@ func (v *mockVespaApi) mockVespaHandler(w http.ResponseWriter, req *http.Request
}
func TestCustomTarget(t *testing.T) {
- lt := LocalTarget(&mock.HTTPClient{})
+ lt := LocalTarget(&mock.HTTPClient{}, TLSOptions{})
assertServiceURL(t, "http://127.0.0.1:19071", lt, "deploy")
assertServiceURL(t, "http://127.0.0.1:8080", lt, "query")
assertServiceURL(t, "http://127.0.0.1:8080", lt, "document")
- ct := CustomTarget(&mock.HTTPClient{}, "http://192.0.2.42")
+ ct := CustomTarget(&mock.HTTPClient{}, "http://192.0.2.42", TLSOptions{})
assertServiceURL(t, "http://192.0.2.42:19071", ct, "deploy")
assertServiceURL(t, "http://192.0.2.42:8080", ct, "query")
assertServiceURL(t, "http://192.0.2.42:8080", ct, "document")
- ct2 := CustomTarget(&mock.HTTPClient{}, "http://192.0.2.42:60000")
+ ct2 := CustomTarget(&mock.HTTPClient{}, "http://192.0.2.42:60000", TLSOptions{})
assertServiceURL(t, "http://192.0.2.42:60000", ct2, "deploy")
assertServiceURL(t, "http://192.0.2.42:60000", ct2, "query")
assertServiceURL(t, "http://192.0.2.42:60000", ct2, "document")
@@ -85,7 +90,7 @@ func TestCustomTargetWait(t *testing.T) {
vc := mockVespaApi{}
srv := httptest.NewServer(http.HandlerFunc(vc.mockVespaHandler))
defer srv.Close()
- target := CustomTarget(util.CreateClient(time.Second*10), srv.URL)
+ target := CustomTarget(util.CreateClient(time.Second*10), srv.URL, TLSOptions{})
_, err := target.Service("query", time.Millisecond, 42, "")
assert.NotNil(t, err)
@@ -107,6 +112,9 @@ func TestCloudTargetWait(t *testing.T) {
var logWriter bytes.Buffer
target := createCloudTarget(t, srv.URL, &logWriter)
+ vc.authFailure = true
+ assertServiceWaitErr(t, 401, true, target, "deploy")
+ vc.authFailure = false
assertServiceWait(t, 200, target, "deploy")
_, err := target.Service("query", time.Millisecond, 42, "")
@@ -157,10 +165,11 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
apiKey, err := CreateAPIKey()
assert.Nil(t, err)
+ auth := &mockAuthenticator{}
target, err := CloudTarget(
util.CreateClient(time.Second*10),
- &mockZTS{},
- &mockAuth0{},
+ auth,
+ auth,
APIOptions{APIKey: apiKey, System: PublicSystem},
CloudDeploymentOptions{
Deployment: Deployment{
@@ -175,7 +184,6 @@ func createCloudTarget(t *testing.T, url string, logWriter io.Writer) Target {
}
if ct, ok := target.(*cloudTarget); ok {
ct.apiOptions.System.URL = url
- ct.zts = &mockZTS{token: "foo bar"}
} else {
t.Fatalf("Wrong target type %T", ct)
}
@@ -189,22 +197,22 @@ func assertServiceURL(t *testing.T, url string, target Target, service string) {
}
func assertServiceWait(t *testing.T, expectedStatus int, target Target, service string) {
+ assertServiceWaitErr(t, expectedStatus, false, target, service)
+}
+
+func assertServiceWaitErr(t *testing.T, expectedStatus int, expectErr bool, target Target, service string) {
s, err := target.Service(service, 0, 42, "")
assert.Nil(t, err)
status, err := s.Wait(0)
- assert.Nil(t, err)
+ if expectErr {
+ assert.NotNil(t, err)
+ } else {
+ assert.Nil(t, err)
+ }
assert.Equal(t, expectedStatus, status)
}
-type mockZTS struct{ token string }
-
-func (c *mockZTS) AccessToken(domain string, certificate tls.Certificate) (string, error) {
- return c.token, nil
-}
-
-type mockAuth0 struct{}
-
-func (a *mockAuth0) AccessToken() (string, error) { return "", nil }
+type mockAuthenticator struct{}
-func (a *mockAuth0) HasCredentials() bool { return true }
+func (a *mockAuthenticator) Authenticate(request *http.Request) error { return nil }
diff --git a/client/pom.xml b/client/pom.xml
index a310e7d6feb..6da6dc74a82 100644
--- a/client/pom.xml
+++ b/client/pom.xml
@@ -35,6 +35,12 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>${vespaClients.jdk.releaseVersion}</release>
+ <compilerArgs> <!-- Remove (to use default) when not compiling for 8 -->
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-rawtypes</arg>
+ <arg>-Xlint:-unchecked</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
</configuration>
</plugin>
<plugin>
diff --git a/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml
index daf3683ad26..1f179013b32 100644
--- a/cloud-tenant-base-dependencies-enforcer/pom.xml
+++ b/cloud-tenant-base-dependencies-enforcer/pom.xml
@@ -30,11 +30,12 @@
<httpcore.version>4.4.16</httpcore.version>
<junit5.version>5.8.1</junit5.version> <!-- TODO: in parent this is named 'junit.version' -->
<onnxruntime.version>1.13.1</onnxruntime.version>
+ <openai-gpt3.version>0.12.0</openai-gpt3.version>
<!-- END parent/pom.xml -->
<!-- ALL BELOW MUST BE KEPT IN SYNC WITH container-dependency-versions pom
- Copied here because vz-tenant-base does not have a parent. -->
+ Copied here because cloud-tenant-base does not have a parent. -->
<aopalliance.version>1.0</aopalliance.version>
<guava.version>27.1-jre</guava.version>
<guice.version>4.2.3</guice.version>
@@ -44,9 +45,9 @@
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
<javax.ws.rs-api.version>2.0.1</javax.ws.rs-api.version>
<jaxb.version>2.3.0</jaxb.version>
- <jetty.version>11.0.14</jetty.version>
+ <jetty.version>11.0.15</jetty.version>
<org.lz4.version>1.8.0</org.lz4.version>
- <org.json.version>20220320</org.json.version> <!-- TODO: Remove on Vespa 9 -->
+ <org.json.version>20230227</org.json.version> <!-- TODO: Remove on Vespa 9 -->
<slf4j.version>1.7.32</slf4j.version> <!-- WARNING: when updated, also update c.y.v.tenant:base pom -->
<xml-apis.version>1.4.01</xml-apis.version>
</properties>
diff --git a/clustercontroller-core/pom.xml b/clustercontroller-core/pom.xml
index b4ac5ca869c..647d8ca4e64 100644
--- a/clustercontroller-core/pom.xml
+++ b/clustercontroller-core/pom.xml
@@ -64,6 +64,11 @@
<scope>test</scope>
</dependency>
<dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <scope>test</scope>
+ </dependency>
+ <dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<scope>provided</scope>
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterInfo.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterInfo.java
index 2cfaf64fe83..0f119d8de50 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterInfo.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterInfo.java
@@ -15,7 +15,7 @@ import java.util.Set;
import java.util.TreeMap;
/**
- * Detail information about the current state of all the distributor and storage nodes of the cluster.
+ * Detailed information about the current state of all the distributor and storage nodes of the cluster.
*
* @author hakonhall
* @author bratseth
@@ -127,11 +127,10 @@ public class ClusterInfo {
/** Returns the node info object for a given node identifier */
private NodeInfo getInfo(Node node) {
- switch (node.getType()) {
- case DISTRIBUTOR : return getDistributorNodeInfo(node.getIndex());
- case STORAGE : return getStorageNodeInfo(node.getIndex());
- default : throw new IllegalArgumentException("No node type " + node.getType().toString());
- }
+ return switch (node.getType()) {
+ case DISTRIBUTOR -> getDistributorNodeInfo(node.getIndex());
+ case STORAGE -> getStorageNodeInfo(node.getIndex());
+ };
}
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterStateVersionSpecificRequest.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterStateVersionSpecificRequest.java
index 70fbbb60e26..6855859c96f 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterStateVersionSpecificRequest.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ClusterStateVersionSpecificRequest.java
@@ -2,7 +2,7 @@
package com.yahoo.vespa.clustercontroller.core;
/**
- * Base class for distributor/content node node RPC requests that are bound
+ * Base class for distributor/content node RPC requests that are bound
* to a particular cluster state version.
*/
public abstract class ClusterStateVersionSpecificRequest {
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ContentCluster.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ContentCluster.java
index 9538167c6de..2535589395d 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ContentCluster.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/ContentCluster.java
@@ -20,32 +20,29 @@ import static com.yahoo.vdslib.state.NodeState.ORCHESTRATOR_RESERVED_DESCRIPTION
public class ContentCluster {
- private final String clusterName;
+ private static final int pollingFrequency = 5000;
+ private final String clusterName;
private final ClusterInfo clusterInfo = new ClusterInfo();
-
private final Map<Node, Long> nodeStartTimestamps = new TreeMap<>();
private int slobrokGenerationCount = 0;
-
- private int pollingFrequency = 5000;
-
private Distribution distribution;
private final int maxNumberOfGroupsAllowedToBeDown;
public ContentCluster(String clusterName, Collection<ConfiguredNode> configuredNodes, Distribution distribution) {
- this(clusterName, configuredNodes, distribution, 1);
+ this(clusterName, configuredNodes, distribution, -1);
}
public ContentCluster(FleetControllerOptions options) {
this(options.clusterName(), options.nodes(), options.storageDistribution(), options.maxNumberOfGroupsAllowedToBeDown());
}
- private ContentCluster(String clusterName,
- Collection<ConfiguredNode> configuredNodes,
- Distribution distribution,
- int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster(String clusterName,
+ Collection<ConfiguredNode> configuredNodes,
+ Distribution distribution,
+ int maxNumberOfGroupsAllowedToBeDown) {
if (configuredNodes == null) throw new IllegalArgumentException("Nodes must be set");
this.clusterName = clusterName;
this.distribution = distribution;
@@ -91,7 +88,6 @@ public class ContentCluster {
}
public int getPollingFrequency() { return pollingFrequency; }
- public void setPollingFrequency(int millisecs) { pollingFrequency = millisecs; }
/** Returns the configured nodes of this as a read-only map indexed on node index (distribution key) */
public Map<Integer, ConfiguredNode> getConfiguredNodes() {
@@ -131,7 +127,7 @@ public class ContentCluster {
* @param clusterState the current cluster state version
* @param condition the upgrade condition
* @param oldState the old/current wanted state
- * @param newState state wanted to be set @return NodeUpgradePrechecker.Response
+ * @param newState state wanted to be set
* @param inMoratorium whether the CC is in moratorium
*/
public NodeStateChangeChecker.Result calculateEffectOfNewState(
@@ -144,22 +140,22 @@ public class ContentCluster {
/** Returns the indices of the nodes that have been safely set to the given state by the Orchestrator (best guess). */
public List<Integer> nodesSafelySetTo(State state) {
- switch (state) {
- case MAINTENANCE: // Orchestrator's ALLOWED_TO_BE_DOWN
- case DOWN: // Orchestrator's PERMANENTLY_DOWN
- return clusterInfo.getStorageNodeInfos().stream()
- .filter(storageNodeInfo -> {
- NodeState userWantedState = storageNodeInfo.getUserWantedState();
- return userWantedState.getState() == state &&
- Objects.equals(userWantedState.getDescription(), ORCHESTRATOR_RESERVED_DESCRIPTION);
- })
- .map(NodeInfo::getNodeIndex)
- .toList();
- default:
- // Note: There is no trace left if the Orchestrator set the state to UP, so that's handled
- // like any other state:
- return List.of();
- }
+ return switch (state) {
+ // Orchestrator's ALLOWED_TO_BE_DOWN or PERMANENTLY_DOWN, respectively
+ case MAINTENANCE, DOWN ->
+ clusterInfo.getStorageNodeInfos().stream()
+ .filter(storageNodeInfo -> {
+ NodeState userWantedState = storageNodeInfo.getUserWantedState();
+ return userWantedState.getState() == state &&
+ Objects.equals(userWantedState.getDescription(), ORCHESTRATOR_RESERVED_DESCRIPTION);
+ })
+ .map(NodeInfo::getNodeIndex)
+ .toList();
+ default ->
+ // Note: There is no trace left if the Orchestrator sets the state to UP, so that's handled
+ // like any other state:
+ List.of();
+ };
}
public boolean hasConfiguredNode(int index) {
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisiting.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisiting.java
index 19ff51f4cc4..09f1824824c 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisiting.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisiting.java
@@ -1,16 +1,38 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-//
package com.yahoo.vespa.clustercontroller.core;
+import com.yahoo.vdslib.distribution.Distribution;
import com.yahoo.vdslib.distribution.GroupVisitor;
-public interface HierarchicalGroupVisiting {
- /** Returns true if the group contains more than one (leaf) group. */
- boolean isHierarchical();
+class HierarchicalGroupVisiting {
+
+ private final Distribution distribution;
+
+ public HierarchicalGroupVisiting(Distribution distribution) {
+ this.distribution = distribution;
+ }
+
+ /**
+ * Returns true if the group contains more than one (leaf) group.
+ */
+ public boolean isHierarchical() {
+ return !distribution.getRootGroup().isLeafGroup();
+ }
/**
* Invoke the visitor for each leaf group of an implied group. If the group is non-hierarchical
* (flat), the visitor will not be invoked.
*/
- void visit(GroupVisitor visitor);
+ public void visit(GroupVisitor visitor) {
+ if (isHierarchical()) {
+ distribution.visitGroups(group -> {
+ if (group.isLeafGroup()) {
+ return visitor.visitGroup(group);
+ }
+
+ return true;
+ });
+ }
+ }
+
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisitingAdapter.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisitingAdapter.java
deleted file mode 100644
index 4bc487bfa7f..00000000000
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/HierarchicalGroupVisitingAdapter.java
+++ /dev/null
@@ -1,37 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-//
-package com.yahoo.vespa.clustercontroller.core;
-
-import com.yahoo.vdslib.distribution.Distribution;
-import com.yahoo.vdslib.distribution.GroupVisitor;
-
-/**
- * Exposes {@link Distribution} as a {@link HierarchicalGroupVisiting}.
- *
- * @author hakon
- */
-public class HierarchicalGroupVisitingAdapter implements HierarchicalGroupVisiting {
- private final Distribution distribution;
-
- public HierarchicalGroupVisitingAdapter(Distribution distribution) {
- this.distribution = distribution;
- }
-
- @Override
- public boolean isHierarchical() {
- return !distribution.getRootGroup().isLeafGroup();
- }
-
- @Override
- public void visit(GroupVisitor visitor) {
- if (isHierarchical()) {
- distribution.visitGroups(group -> {
- if (group.isLeafGroup()) {
- return visitor.visitGroup(group);
- }
-
- return true;
- });
- }
- }
-}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeInfo.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeInfo.java
index 3f1a7ab5d7b..d7aac1c26fa 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeInfo.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeInfo.java
@@ -55,7 +55,10 @@ abstract public class NodeInfo implements Comparable<NodeInfo> {
private long nextAttemptTime;
/** Cached connection to this node. */
private Target connection;
- /** We cache last connection we did request info on, as we want to report appropriate error for node regardless of whether other commands have created new connection. */
+ /**
+ * We cache last connection we did request info on, as we want to report appropriate error for
+ * node regardless of whether other commands have created new connection.
+ */
public Target lastRequestInfoConnection;
/**
* Counts the number of attempts we have tried since last time we had
@@ -163,7 +166,7 @@ abstract public class NodeInfo implements Comparable<NodeInfo> {
}
if (prematureCrashCount != count) {
prematureCrashCount = count;
- log.log(Level.FINE, () -> "Premature crash count on " + toString() + " set to " + count);
+ log.log(Level.FINE, () -> "Premature crash count on " + this + " set to " + count);
}
}
public int getPrematureCrashCount() { return prematureCrashCount; }
@@ -311,13 +314,13 @@ abstract public class NodeInfo implements Comparable<NodeInfo> {
}
if (state.getState().equals(State.DOWN) && !reportedState.getState().oneOf("d")) {
downStableStateTime = time;
- log.log(Level.FINE, () -> "Down stable state on " + toString() + " altered to " + time);
+ log.log(Level.FINE, () -> "Down stable state on " + this + " altered to " + time);
if (reportedState.getState() == State.INITIALIZING) {
recentlyObservedUnstableDuringInit = true;
}
} else if (state.getState().equals(State.UP) && !reportedState.getState().oneOf("u")) {
upStableStateTime = time;
- log.log(Level.FINE, () -> "Up stable state on " + toString() + " altered to " + time);
+ log.log(Level.FINE, () -> "Up stable state on " + this + " altered to " + time);
}
if (!state.getState().validReportedNodeState(node.getType())) {
throw new IllegalStateException("Trying to set illegal reported node state: " + state);
@@ -340,14 +343,14 @@ abstract public class NodeInfo implements Comparable<NodeInfo> {
} else {
nextAttemptTime = time + 5000;
}
- log.log(Level.FINEST, () -> "Failed to get state from node " + toString() + ", scheduling next attempt in " + (nextAttemptTime - time) + " ms.");
+ log.log(Level.FINEST, () -> "Failed to get state from node " + this + ", scheduling next attempt in " + (nextAttemptTime - time) + " ms.");
} else {
connectionAttemptCount = 0;
timeOfFirstFailingConnectionAttempt = 0;
reportedState = state;
if (version == 0 || state.getState().equals(State.STOPPING)) {
nextAttemptTime = time + cluster.getPollingFrequency();
- log.log(Level.FINEST, () -> "Scheduling next attempt to get state from " + toString() + " in " + (nextAttemptTime - time) + " ms (polling freq).");
+ log.log(Level.FINEST, () -> "Scheduling next attempt to get state from " + this + " in " + (nextAttemptTime - time) + " ms (polling freq).");
} else {
nextAttemptTime = time;
}
@@ -368,7 +371,7 @@ abstract public class NodeInfo implements Comparable<NodeInfo> {
} catch (Exception e) {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
- log.warning("Attempted to set wanted state with more than just a main state. Extra data stripped. Original data '" + state.serialize(true) + ":\n" + sw.toString());
+ log.warning("Attempted to set wanted state with more than just a main state. Extra data stripped. Original data '" + state.serialize(true) + ":\n" + sw);
}
}
wantedState = newWanted;
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
index 2025dfef562..c823c94afd1 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeChecker.java
@@ -13,13 +13,20 @@ import com.yahoo.vespa.clustercontroller.core.hostinfo.HostInfo;
import com.yahoo.vespa.clustercontroller.core.hostinfo.Metrics;
import com.yahoo.vespa.clustercontroller.core.hostinfo.StorageNode;
import com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
+import java.util.Set;
+import java.util.logging.Logger;
+import java.util.stream.Collectors;
import static com.yahoo.vdslib.state.NodeType.STORAGE;
import static com.yahoo.vdslib.state.State.DOWN;
+import static com.yahoo.vdslib.state.State.MAINTENANCE;
import static com.yahoo.vdslib.state.State.RETIRED;
import static com.yahoo.vdslib.state.State.UP;
import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Result.allowSettingOfWantedState;
@@ -27,6 +34,7 @@ import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Resu
import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Result.createDisallowed;
import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest.Condition.FORCE;
import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest.Condition.SAFE;
+import static java.util.logging.Level.FINE;
/**
* Checks if a node can be upgraded.
@@ -35,8 +43,9 @@ import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetU
*/
public class NodeStateChangeChecker {
- public static final String BUCKETS_METRIC_NAME = "vds.datastored.bucket_space.buckets_total";
- public static final Map<String, String> BUCKETS_METRIC_DIMENSIONS = Map.of("bucketSpace", "default");
+ private static final Logger log = Logger.getLogger(NodeStateChangeChecker.class.getName());
+ private static final String BUCKETS_METRIC_NAME = "vds.datastored.bucket_space.buckets_total";
+ private static final Map<String, String> BUCKETS_METRIC_DIMENSIONS = Map.of("bucketSpace", "default");
private final int requiredRedundancy;
private final HierarchicalGroupVisiting groupVisiting;
@@ -46,10 +55,12 @@ public class NodeStateChangeChecker {
public NodeStateChangeChecker(ContentCluster cluster, boolean inMoratorium) {
this.requiredRedundancy = cluster.getDistribution().getRedundancy();
- this.groupVisiting = new HierarchicalGroupVisitingAdapter(cluster.getDistribution());
+ this.groupVisiting = new HierarchicalGroupVisiting(cluster.getDistribution());
this.clusterInfo = cluster.clusterInfo();
this.inMoratorium = inMoratorium;
this.maxNumberOfGroupsAllowedToBeDown = cluster.maxNumberOfGroupsAllowedToBeDown();
+ if ( ! groupVisiting.isHierarchical() && maxNumberOfGroupsAllowedToBeDown > 1)
+ throw new IllegalArgumentException("Cannot have both 1 group and maxNumberOfGroupsAllowedToBeDown > 1");
}
public static class Result {
@@ -214,26 +225,34 @@ public class NodeStateChangeChecker {
oldWantedState.getState() + ": " + oldWantedState.getDescription());
}
- Result otherGroupCheck = anotherNodeInAnotherGroupHasWantedState(nodeInfo);
- if (!otherGroupCheck.settingWantedStateIsAllowed()) {
- return otherGroupCheck;
+ if (maxNumberOfGroupsAllowedToBeDown == -1) {
+ var otherGroupCheck = anotherNodeInAnotherGroupHasWantedState(nodeInfo);
+ if (!otherGroupCheck.settingWantedStateIsAllowed()) {
+ return otherGroupCheck;
+ }
+ if (anotherNodeInGroupAlreadyAllowed(nodeInfo, newDescription)) {
+ return allowSettingOfWantedState();
+ }
+ } else {
+ var result = otherNodesHaveWantedState(nodeInfo, newDescription, clusterState);
+ if (result.isPresent())
+ return result.get();
}
if (clusterState.getNodeState(nodeInfo.getNode()).getState() == DOWN) {
- return allowSettingOfWantedState();
- }
-
- if (anotherNodeInGroupAlreadyAllowed(nodeInfo, newDescription)) {
+ log.log(FINE, "node is DOWN, allow");
return allowSettingOfWantedState();
}
Result allNodesAreUpCheck = checkAllNodesAreUp(clusterState);
if (!allNodesAreUpCheck.settingWantedStateIsAllowed()) {
+ log.log(FINE, "allNodesAreUpCheck: " + allNodesAreUpCheck);
return allNodesAreUpCheck;
}
Result checkDistributorsResult = checkDistributors(nodeInfo.getNode(), clusterState.getVersion());
if (!checkDistributorsResult.settingWantedStateIsAllowed()) {
+ log.log(FINE, "checkDistributors: "+ checkDistributorsResult);
return checkDistributorsResult;
}
@@ -268,6 +287,65 @@ public class NodeStateChangeChecker {
}
}
+ /**
+ * Returns an optional Result, where return value is:
+ * For flat setup: Return Optional.of(disallowed) if wanted state is set on some node, else Optional.empty
+ * For hierarchical setup: No wanted state for other nodes, return Optional.empty
+ * Wanted state for nodes/groups are not UP:
+ * if less than maxNumberOfGroupsAllowedToBeDown: return Optional.of(allowed)
+ * else: if node is in group with nodes already down: return Optional.of(allowed), else Optional.of(disallowed)
+ */
+ private Optional<Result> otherNodesHaveWantedState(StorageNodeInfo nodeInfo, String newDescription, ClusterState clusterState) {
+ Node node = nodeInfo.getNode();
+
+ if (groupVisiting.isHierarchical()) {
+ Set<Integer> groupsWithNodesWantedStateNotUp = groupsWithUserWantedStateNotUp();
+ if (groupsWithNodesWantedStateNotUp.size() == 0) {
+ log.log(FINE, "groupsWithNodesWantedStateNotUp=0");
+ return Optional.empty();
+ }
+
+ Set<Integer> groupsWithSameStateAndDescription = groupsWithSameStateAndDescription(MAINTENANCE, newDescription);
+ if (aGroupContainsNode(groupsWithSameStateAndDescription, node)) {
+ log.log(FINE, "Node is in group with same state and description, allow");
+ return Optional.of(allowSettingOfWantedState());
+ }
+ // There are groups with nodes not up, but with another description, probably operator set
+ if (groupsWithSameStateAndDescription.size() == 0) {
+ return Optional.of(createDisallowed("Wanted state already set for another node in groups: " +
+ sortSetIntoList(groupsWithNodesWantedStateNotUp)));
+ }
+
+ Set<Integer> retiredAndNotUpGroups = groupsWithNotRetiredAndNotUp(clusterState);
+ int numberOfGroupsToConsider = retiredAndNotUpGroups.size();
+ // Subtract one group if node is in a group with nodes already retired or not up, since number of such groups will
+ // not increase if we allow node to go down
+ if (aGroupContainsNode(retiredAndNotUpGroups, node)) {
+ numberOfGroupsToConsider = retiredAndNotUpGroups.size() - 1;
+ }
+ if (numberOfGroupsToConsider < maxNumberOfGroupsAllowedToBeDown) {
+ log.log(FINE, "Allow, retiredAndNotUpGroups=" + retiredAndNotUpGroups);
+ return Optional.of(allowSettingOfWantedState());
+ }
+
+ return Optional.of(createDisallowed(String.format("At most %d groups can have wanted state: %s",
+ maxNumberOfGroupsAllowedToBeDown,
+ sortSetIntoList(retiredAndNotUpGroups))));
+ } else {
+ // Return a disallow-result if there is another node with a wanted state
+ var otherNodeHasWantedState = otherNodeHasWantedState(nodeInfo);
+ if ( ! otherNodeHasWantedState.settingWantedStateIsAllowed())
+ return Optional.of(otherNodeHasWantedState);
+ }
+ return Optional.empty();
+ }
+
+ private ArrayList<Integer> sortSetIntoList(Set<Integer> set) {
+ var sortedList = new ArrayList<>(set);
+ Collections.sort(sortedList);
+ return sortedList;
+ }
+
/** Returns a disallow-result, if there is a node in the group with wanted state != UP. */
private Result otherNodeInGroupHasWantedState(Group group) {
for (var configuredNode : group.getNodes()) {
@@ -354,6 +432,22 @@ public class NodeStateChangeChecker {
return false;
}
+ private boolean aGroupContainsNode(Collection<Integer> groupIndexes, Node node) {
+ for (Group group : getGroupsWithIndexes(groupIndexes)) {
+ if (groupContainsNode(group, node))
+ return true;
+ }
+
+ return false;
+ }
+
+ private List<Group> getGroupsWithIndexes(Collection<Integer> groupIndexes) {
+ return clusterInfo.getStorageNodeInfos().stream()
+ .map(NodeInfo::getGroup)
+ .filter(group -> groupIndexes.contains(group.getIndex()))
+ .collect(Collectors.toList());
+ }
+
private Result checkAllNodesAreUp(ClusterState clusterState) {
// This method verifies both storage nodes and distributors are up (or retired).
// The complicated part is making a summary error message.
@@ -441,4 +535,43 @@ public class NodeStateChangeChecker {
return allowSettingOfWantedState();
}
+ private Set<Integer> groupsWithUserWantedStateNotUp() {
+ return clusterInfo.getAllNodeInfos().stream()
+ .filter(sni -> !UP.equals(sni.getUserWantedState().getState()))
+ .map(NodeInfo::getGroup)
+ .filter(Objects::nonNull)
+ .filter(Group::isLeafGroup)
+ .map(Group::getIndex)
+ .collect(Collectors.toSet());
+ }
+
+ // groups with at least one node with the same state & description
+ private Set<Integer> groupsWithSameStateAndDescription(State state, String newDescription) {
+ return clusterInfo.getAllNodeInfos().stream()
+ .filter(nodeInfo -> {
+ var userWantedState = nodeInfo.getUserWantedState();
+ return userWantedState.getState() == state &&
+ Objects.equals(userWantedState.getDescription(), newDescription);
+ })
+ .map(NodeInfo::getGroup)
+ .filter(Objects::nonNull)
+ .filter(Group::isLeafGroup)
+ .map(Group::getIndex)
+ .collect(Collectors.toSet());
+ }
+
+ // groups with at least one node in state (not retired AND not up)
+ private Set<Integer> groupsWithNotRetiredAndNotUp(ClusterState clusterState) {
+ return clusterInfo.getAllNodeInfos().stream()
+ .filter(nodeInfo -> (nodeInfo.getUserWantedState().getState() != RETIRED
+ && nodeInfo.getUserWantedState().getState() != UP)
+ || (clusterState.getNodeState(nodeInfo.getNode()).getState() != RETIRED
+ && clusterState.getNodeState(nodeInfo.getNode()).getState() != UP))
+ .map(NodeInfo::getGroup)
+ .filter(Objects::nonNull)
+ .filter(Group::isLeafGroup)
+ .map(Group::getIndex)
+ .collect(Collectors.toSet());
+ }
+
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateGatherer.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateGatherer.java
index 68e46414c22..6f4d0749f3f 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateGatherer.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/NodeStateGatherer.java
@@ -4,7 +4,6 @@ package com.yahoo.vespa.clustercontroller.core;
import com.yahoo.jrt.ErrorCode;
import com.yahoo.jrt.Target;
import com.yahoo.vdslib.state.NodeState;
-import com.yahoo.vdslib.state.State;
import com.yahoo.vespa.clustercontroller.core.hostinfo.HostInfo;
import com.yahoo.vespa.clustercontroller.core.listeners.NodeListener;
import java.util.LinkedList;
@@ -12,6 +11,9 @@ import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.vdslib.state.State.DOWN;
+import static com.yahoo.vdslib.state.State.STOPPING;
+
/**
* Collects the state of all nodes by making remote requests and handling the replies.
*/
@@ -65,20 +67,20 @@ public class NodeStateGatherer {
if (info.getRpcAddress() == null || info.isNotInSlobrok()) { // Cannot query state of node without RPC address or not in slobrok
log.log(Level.FINE, () -> "Not sending getNodeState request to node " + info.getNode() + ": Not in slobrok");
NodeState reportedState = info.getReportedState().clone();
- if (( ! reportedState.getState().equals(State.DOWN) && currentTime - info.lastSeenInSlobrok() > maxSlobrokDisconnectGracePeriod)
- || reportedState.getState().equals(State.STOPPING)) // Don't wait for grace period if we expect node to be stopping
+ if (( ! reportedState.getState().equals(DOWN) && currentTime - info.lastSeenInSlobrok() > maxSlobrokDisconnectGracePeriod)
+ || reportedState.getState().equals(STOPPING)) // Don't wait for grace period if we expect node to be stopping
{
log.log(Level.FINE, () -> "Setting reported state to DOWN "
- + (reportedState.getState().equals(State.STOPPING)
+ + (reportedState.getState().equals(STOPPING)
? "as node completed stopping."
- : "as node has been out of slobrok longer than " + maxSlobrokDisconnectGracePeriod + "."));
+ : "as node has been out of slobrok longer than " + maxSlobrokDisconnectGracePeriod + " ms."));
if (reportedState.getState().oneOf("iur") || ! reportedState.hasDescription()) {
- StringBuilder sb = new StringBuilder().append("Set node down as it has been out of slobrok for ")
- .append(currentTime - info.lastSeenInSlobrok()).append(" ms which is more than the max limit of ")
- .append(maxSlobrokDisconnectGracePeriod).append(" ms.");
- reportedState.setDescription(sb.toString());
+ reportedState.setDescription("Set node down as it has been out of slobrok for " +
+ (currentTime - info.lastSeenInSlobrok()) +
+ " ms which is more than the max limit of " +
+ maxSlobrokDisconnectGracePeriod + " ms.");
}
- reportedState.setState(State.DOWN);
+ reportedState.setState(DOWN);
listener.handleNewNodeState(info, reportedState.clone());
}
info.setReportedState(reportedState, currentTime); // Must reset it to null to get connection attempts counted
@@ -135,7 +137,7 @@ public class NodeStateGatherer {
info.setReportedState(state, currentTime);
} catch (Exception e) {
log.log(Level.WARNING, "Failed to process get node state response", e);
- info.setReportedState(new NodeState(info.getNode().getType(), State.DOWN), currentTime);
+ info.setReportedState(new NodeState(info.getNode().getType(), DOWN), currentTime);
}
// Important: The old host info should be accessible in info.getHostInfo(), see interface.
@@ -152,7 +154,7 @@ public class NodeStateGatherer {
private NodeState handleError(GetNodeStateRequest req, NodeInfo info, long currentTime) {
String prefix = "Failed get node state request: ";
- NodeState newState = new NodeState(info.getNode().getType(), State.DOWN);
+ NodeState newState = new NodeState(info.getNode().getType(), DOWN);
if (req.getReply().getReturnCode() == ErrorCode.TIMEOUT) {
String msg = "RPC timeout";
if (info.getReportedState().getState().oneOf("ui")) {
@@ -177,7 +179,7 @@ public class NodeStateGatherer {
log.log(Level.FINE, "Failed to talk to node " + info + ": " + req.getReply().getReturnCode()
+ " " + req.getReply().getReturnMessage() + ": " + msg);
}
- newState.setState(State.DOWN);
+ newState.setState(DOWN);
} else if (msg.equals("jrt: Connection closed by peer") || msg.equals("Connection reset by peer")) {
msg = "Connection error: Closed at other end. (Node or switch likely shut down)";
if (info.isNotInSlobrok()) {
@@ -189,7 +191,7 @@ public class NodeStateGatherer {
if (log.isLoggable(Level.FINE))
log.log(Level.FINE, "Failed to talk to node " + info + ": " + req.getReply().getReturnCode() + " " + req.getReply().getReturnMessage() + ": " + msg);
}
- newState.setState(State.DOWN).setDescription(msg);
+ newState.setState(DOWN).setDescription(msg);
} else if (msg.equals("Connection timed out")) {
if (info.getReportedState().getState().oneOf("ui")) {
msg = "Connection error: Timeout";
@@ -228,11 +230,11 @@ public class NodeStateGatherer {
} else if (!info.getReportedState().hasDescription() || !info.getReportedState().getDescription().equals(msg)) {
log.log(Level.FINE, () -> "Failed to talk to node " + info + ": " + req.getReply().getReturnCode() + " " + req.getReply().getReturnMessage() + ": " + msg);
}
- newState.setState(State.DOWN).setDescription(msg + ": get node state");
+ newState.setState(DOWN).setDescription(msg + ": get node state");
} else if (req.getReply().getReturnCode() == 75004) {
String msg = "Node refused to answer RPC request and is likely stopping: " + req.getReply().getReturnMessage();
// The node is shutting down and is not accepting requests from anyone
- if (info.getReportedState().getState().equals(State.STOPPING)) {
+ if (info.getReportedState().getState().equals(STOPPING)) {
log.log(Level.FINE, () -> "Failed to get node state from " + info + " because it is still shutting down.");
} else {
if (info.getReportedState().getState().oneOf("ui")) {
@@ -241,7 +243,7 @@ public class NodeStateGatherer {
log.log(Level.FINE, () -> "Failed to talk to node " + info + ": " + req.getReply().getReturnCode() + " " + req.getReply().getReturnMessage() + ": " + msg);
}
}
- newState.setState(State.STOPPING).setDescription(msg);
+ newState.setState(STOPPING).setDescription(msg);
} else {
String msg = "Got unexpected error, assumed to be node issue " + req.getReply().getReturnCode() + ": " + req.getReply().getReturnMessage();
if (info.getReportedState().getState().oneOf("ui")) {
@@ -249,7 +251,7 @@ public class NodeStateGatherer {
} else if (!info.getReportedState().hasDescription() || !info.getReportedState().getDescription().equals(msg)) {
log.log(Level.FINE, () -> "Failed to talk to node " + info + ": " + req.getReply().getReturnCode() + " " + req.getReply().getReturnMessage() + ": " + msg);
}
- newState.setState(State.DOWN).setDescription(msg);
+ newState.setState(DOWN).setDescription(msg);
}
return newState;
}
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateChangeHandler.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateChangeHandler.java
index 4ab80ec6d7a..28149477e36 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateChangeHandler.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/StateChangeHandler.java
@@ -9,13 +9,19 @@ import com.yahoo.vdslib.state.NodeType;
import com.yahoo.vdslib.state.State;
import com.yahoo.vespa.clustercontroller.core.database.DatabaseHandler;
import com.yahoo.vespa.clustercontroller.core.listeners.NodeListener;
-
+import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.vdslib.state.State.DOWN;
+import static com.yahoo.vdslib.state.State.INITIALIZING;
+import static com.yahoo.vdslib.state.State.STOPPING;
+import static java.util.logging.Level.FINE;
+import static java.util.logging.Level.FINEST;
+
/**
* This class gets node state updates and timer events and uses these to decide
* whether a new cluster state should be generated.
@@ -52,9 +58,9 @@ public class StateChangeHandler {
public void handleAllDistributorsInSync(final ClusterState currentState,
final Set<ConfiguredNode> nodes,
final DatabaseHandler database,
- final DatabaseHandler.DatabaseContext dbContext) throws InterruptedException {
+ final DatabaseHandler.DatabaseContext dbContext) {
int startTimestampsReset = 0;
- context.log(log, Level.FINE, "handleAllDistributorsInSync invoked for state version %d", currentState.getVersion());
+ context.log(log, FINE, "handleAllDistributorsInSync invoked for state version %d", currentState.getVersion());
for (NodeType nodeType : NodeType.getTypes()) {
for (ConfiguredNode configuredNode : nodes) {
final Node node = new Node(nodeType, configuredNode.index());
@@ -62,15 +68,15 @@ public class StateChangeHandler {
final NodeState nodeState = currentState.getNodeState(node);
if (nodeInfo != null && nodeState != null) {
if (nodeState.getStartTimestamp() > nodeInfo.getStartTimestamp()) {
- log.log(Level.FINE, () -> String.format("Storing away new start timestamp for node %s (%d)", node, nodeState.getStartTimestamp()));
+ log.log(FINE, () -> String.format("Storing away new start timestamp for node %s (%d)", node, nodeState.getStartTimestamp()));
nodeInfo.setStartTimestamp(nodeState.getStartTimestamp());
}
if (nodeState.getStartTimestamp() > 0) {
- log.log(Level.FINE, "Resetting timestamp in cluster state for node %s", node);
+ log.log(FINE, "Resetting timestamp in cluster state for node %s", node);
++startTimestampsReset;
}
- } else if (log.isLoggable(Level.FINE)) {
- log.log(Level.FINE, node + ": " +
+ } else if (log.isLoggable(FINE)) {
+ log.log(FINE, node + ": " +
(nodeInfo == null ? "null" : nodeInfo.getStartTimestamp()) + ", " +
(nodeState == null ? "null" : nodeState.getStartTimestamp()));
}
@@ -83,7 +89,7 @@ public class StateChangeHandler {
stateMayHaveChanged = true;
database.saveStartTimestamps(dbContext);
} else {
- log.log(Level.FINE, "Found no start timestamps to reset in cluster state.");
+ log.log(FINE, "Found no start timestamps to reset in cluster state.");
}
}
@@ -110,48 +116,45 @@ public class StateChangeHandler {
// TODO nodeListener is only used via updateNodeInfoFromReportedState -> handlePrematureCrash
// TODO this will recursively invoke proposeNewNodeState, which will presumably (i.e. hopefully) be a no-op...
- public void handleNewReportedNodeState(final ClusterState currentClusterState,
- final NodeInfo node,
- final NodeState reportedState,
- final NodeListener nodeListener)
- {
- final NodeState currentState = currentClusterState.getNodeState(node.getNode());
- final Level level = (currentState.equals(reportedState) && node.getVersion() == 0) ? Level.FINEST : Level.FINE;
- if (log.isLoggable(level)) {
- log.log(level, String.format("Got nodestate reply from %s: %s (Current state is %s)",
- node, node.getReportedState().getTextualDifference(reportedState), currentState.toString(true)));
- }
- final long currentTime = timer.getCurrentTimeInMillis();
-
- if (reportedState.getState().equals(State.DOWN)) {
+ public void handleNewReportedNodeState(ClusterState currentClusterState,
+ NodeInfo node,
+ NodeState reportedState,
+ NodeListener nodeListener) {
+ NodeState currentState = currentClusterState.getNodeState(node.getNode());
+ Level level = (currentState.equals(reportedState) && node.getVersion() == 0) ? FINEST : FINE;
+ log.log(level, () -> String.format("Got nodestate reply from %s: %s (Current state is %s)",
+ node, node.getReportedState().getTextualDifference(reportedState), currentState.toString(true)));
+ long currentTime = timer.getCurrentTimeInMillis();
+
+ if (reportedState.getState().equals(DOWN)) {
node.setTimeOfFirstFailingConnectionAttempt(currentTime);
}
// *** LOGGING ONLY
if ( ! reportedState.similarTo(node.getReportedState())) {
- if (reportedState.getState().equals(State.DOWN)) {
+ if (reportedState.getState().equals(DOWN)) {
eventLog.addNodeOnlyEvent(NodeEvent.forBaseline(node, "Failed to get node state: " + reportedState.toString(true), NodeEvent.Type.REPORTED, currentTime), Level.INFO);
} else {
- eventLog.addNodeOnlyEvent(NodeEvent.forBaseline(node, "Now reporting state " + reportedState.toString(true), NodeEvent.Type.REPORTED, currentTime), Level.FINE);
+ eventLog.addNodeOnlyEvent(NodeEvent.forBaseline(node, "Now reporting state " + reportedState.toString(true), NodeEvent.Type.REPORTED, currentTime), FINE);
}
}
- if (reportedState.equals(node.getReportedState()) && ! reportedState.getState().equals(State.INITIALIZING)) {
+ if (reportedState.equals(node.getReportedState()) && ! reportedState.getState().equals(INITIALIZING)) {
return;
}
updateNodeInfoFromReportedState(node, currentState, reportedState, nodeListener);
if (reportedState.getMinUsedBits() != currentState.getMinUsedBits()) {
- final int oldCount = currentState.getMinUsedBits();
- final int newCount = reportedState.getMinUsedBits();
- log.log(Level.FINE,
+ int oldCount = currentState.getMinUsedBits();
+ int newCount = reportedState.getMinUsedBits();
+ log.log(FINE,
() -> String.format("Altering node state to reflect that min distribution bit count has changed from %d to %d", oldCount, newCount));
eventLog.add(NodeEvent.forBaseline(node, String.format("Altered min distribution bit count from %d to %d", oldCount, newCount),
NodeEvent.Type.CURRENT, currentTime), isMaster);
} else {
- log.log(Level.FINE, () -> String.format("Not altering state of %s in cluster state because new state is too similar: %s",
- node, currentState.getTextualDifference(reportedState)));
+ log.log(FINE, () -> String.format("Not altering state of %s in cluster state because new state is too similar: %s",
+ node, currentState.getTextualDifference(reportedState)));
}
stateMayHaveChanged = true;
@@ -162,10 +165,8 @@ public class StateChangeHandler {
eventLog.add(NodeEvent.forBaseline(node, message, NodeEvent.Type.REPORTED, timer.getCurrentTimeInMillis()), isMaster);
}
- public void handleMissingNode(final ClusterState currentClusterState,
- final NodeInfo node,
- final NodeListener nodeListener) {
- final long timeNow = timer.getCurrentTimeInMillis();
+ public void handleMissingNode(ClusterState currentClusterState, NodeInfo node, NodeListener nodeListener) {
+ long timeNow = timer.getCurrentTimeInMillis();
if (node.getLatestNodeStateRequestTime() != null) {
eventLog.add(NodeEvent.forBaseline(node, "Node is no longer in slobrok, but we still have a pending state request.", NodeEvent.Type.REPORTED, timeNow), isMaster);
@@ -173,13 +174,13 @@ public class StateChangeHandler {
eventLog.add(NodeEvent.forBaseline(node, "Node is no longer in slobrok. No pending state request to node.", NodeEvent.Type.REPORTED, timeNow), isMaster);
}
- if (node.getReportedState().getState().equals(State.STOPPING)) {
- log.log(Level.FINE, () -> "Node " + node.getNode() + " is no longer in slobrok. Was in stopping state, so assuming it has shut down normally. Setting node down");
+ if (node.getReportedState().getState().equals(STOPPING)) {
+ log.log(FINE, () -> "Node " + node.getNode() + " is no longer in slobrok. Was in stopping state, so assuming it has shut down normally. Setting node down");
NodeState ns = node.getReportedState().clone();
- ns.setState(State.DOWN);
+ ns.setState(DOWN);
handleNewReportedNodeState(currentClusterState, node, ns.clone(), nodeListener);
} else {
- log.log(Level.FINE, () -> "Node " + node.getNode() + " no longer in slobrok was in state " + node.getReportedState() + ". Waiting to see if it reappears in slobrok");
+ log.log(FINE, () -> "Node " + node.getNode() + " no longer in slobrok was in state " + node.getReportedState() + ". Waiting to see if it reappears in slobrok");
}
stateMayHaveChanged = true;
@@ -192,19 +193,19 @@ public class StateChangeHandler {
* If the newly proposed state differs from the state the node currently has in the system,
* a cluster state regeneration will be triggered.
*/
- public void proposeNewNodeState(final ClusterState currentClusterState, final NodeInfo node, final NodeState proposedState) {
- final NodeState currentState = currentClusterState.getNodeState(node.getNode());
- final NodeState currentReported = node.getReportedState();
+ public void proposeNewNodeState(ClusterState currentClusterState, NodeInfo node, NodeState proposedState) {
+ NodeState currentState = currentClusterState.getNodeState(node.getNode());
- if (currentState.getState().equals(proposedState.getState())) {
+ if (currentState.getState().equals(proposedState.getState()))
return;
- }
+
stateMayHaveChanged = true;
- log.log(Level.FINE, () -> String.format("Got new wanted nodestate for %s: %s", node, currentState.getTextualDifference(proposedState)));
+ log.log(FINE, () -> String.format("Got new wanted nodestate for %s: %s", node, currentState.getTextualDifference(proposedState)));
// Should be checked earlier before state was set in cluster
assert(proposedState.getState().validWantedNodeState(node.getNode().getType()));
long timeNow = timer.getCurrentTimeInMillis();
+ NodeState currentReported = node.getReportedState();
if (proposedState.above(currentReported)) {
eventLog.add(NodeEvent.forBaseline(node, String.format("Wanted state %s, but we cannot force node into that " +
"state yet as it is currently in %s", proposedState, currentReported),
@@ -239,12 +240,9 @@ public class StateChangeHandler {
// generated cluster state. Still a bit of a mine field...
// TODO remove all node state mutation from this function entirely in favor of ClusterStateGenerator!
// `--> this will require adding more event edges and premature crash handling to it. Which is fine.
- public boolean watchTimers(final ContentCluster cluster,
- final ClusterState currentClusterState,
- final NodeListener nodeListener)
- {
+ public boolean watchTimers(ContentCluster cluster, ClusterState currentClusterState, NodeListener nodeListener) {
boolean triggeredAnyTimers = false;
- final long currentTime = timer.getCurrentTimeInMillis();
+ long currentTime = timer.getCurrentTimeInMillis();
for(NodeInfo node : cluster.getNodeInfos()) {
triggeredAnyTimers |= handleTimeDependentOpsForNode(currentClusterState, nodeListener, currentTime, node);
@@ -256,23 +254,17 @@ public class StateChangeHandler {
return triggeredAnyTimers;
}
- private boolean handleTimeDependentOpsForNode(final ClusterState currentClusterState,
- final NodeListener nodeListener,
- final long currentTime,
- final NodeInfo node)
- {
- final NodeState currentStateInSystem = currentClusterState.getNodeState(node.getNode());
- final NodeState lastReportedState = node.getReportedState();
- boolean triggeredAnyTimers = false;
-
- triggeredAnyTimers = reportDownIfOutdatedSlobrokNode(
- currentClusterState, nodeListener, currentTime, node, lastReportedState);
+ private boolean handleTimeDependentOpsForNode(ClusterState currentClusterState,
+ NodeListener nodeListener,
+ long currentTime,
+ NodeInfo node) {
+ NodeState currentStateInSystem = currentClusterState.getNodeState(node.getNode());
+ NodeState lastReportedState = node.getReportedState();
+ boolean triggeredAnyTimers =
+ reportDownIfOutdatedSlobrokNode(currentClusterState, nodeListener, currentTime, node, lastReportedState);
- if (nodeStillUnavailableAfterTransitionTimeExceeded(
- currentTime, node, currentStateInSystem, lastReportedState))
- {
+ if (nodeStillUnavailableAfterTransitionTimeExceeded(currentTime, node, currentStateInSystem, lastReportedState))
triggeredAnyTimers = true;
- }
if (nodeInitProgressHasTimedOut(currentTime, node, currentStateInSystem, lastReportedState)) {
eventLog.add(NodeEvent.forBaseline(node, String.format(
@@ -287,11 +279,11 @@ public class StateChangeHandler {
if (mayResetCrashCounterOnStableUpNode(currentTime, node, lastReportedState)) {
node.setPrematureCrashCount(0);
- log.log(Level.FINE, () -> "Resetting premature crash count on node " + node + " as it has been up for a long time.");
+ log.log(FINE, () -> "Resetting premature crash count on node " + node + " as it has been up for a long time.");
triggeredAnyTimers = true;
} else if (mayResetCrashCounterOnStableDownNode(currentTime, node, lastReportedState)) {
node.setPrematureCrashCount(0);
- log.log(Level.FINE, () -> "Resetting premature crash count on node " + node + " as it has been down for a long time.");
+ log.log(FINE, () -> "Resetting premature crash count on node " + node + " as it has been down for a long time.");
triggeredAnyTimers = true;
}
@@ -299,17 +291,18 @@ public class StateChangeHandler {
}
private boolean nodeInitProgressHasTimedOut(long currentTime, NodeInfo node, NodeState currentStateInSystem, NodeState lastReportedState) {
- return !currentStateInSystem.getState().equals(State.DOWN)
- && node.getWantedState().above(new NodeState(node.getNode().getType(), State.DOWN))
- && lastReportedState.getState().equals(State.INITIALIZING)
+ return !currentStateInSystem.getState().equals(DOWN)
+ && node.getWantedState().above(new NodeState(node.getNode().getType(), DOWN))
+ && lastReportedState.getState().equals(INITIALIZING)
&& maxInitProgressTime != 0
&& node.getInitProgressTime() + maxInitProgressTime <= currentTime
&& node.getNode().getType().equals(NodeType.STORAGE);
}
+ // TODO: Merge this and the below method
private boolean mayResetCrashCounterOnStableDownNode(long currentTime, NodeInfo node, NodeState lastReportedState) {
return node.getDownStableStateTime() + stableStateTimePeriod <= currentTime
- && lastReportedState.getState().equals(State.DOWN)
+ && lastReportedState.getState().equals(DOWN)
&& node.getPrematureCrashCount() <= maxPrematureCrashes
&& node.getPrematureCrashCount() != 0;
}
@@ -328,8 +321,8 @@ public class StateChangeHandler {
NodeState lastReportedState)
{
return currentStateInSystem.getState().equals(State.MAINTENANCE)
- && node.getWantedState().above(new NodeState(node.getNode().getType(), State.DOWN))
- && (lastReportedState.getState().equals(State.DOWN) || node.isNotInSlobrok())
+ && node.getWantedState().above(new NodeState(node.getNode().getType(), DOWN))
+ && (lastReportedState.getState().equals(DOWN) || node.isNotInSlobrok())
&& node.getTransitionTime() + maxTransitionTime.get(node.getNode().getType()) < currentTime;
}
@@ -340,7 +333,7 @@ public class StateChangeHandler {
NodeState lastReportedState)
{
if (node.isNotInSlobrok()
- && !lastReportedState.getState().equals(State.DOWN)
+ && !lastReportedState.getState().equals(DOWN)
&& node.lastSeenInSlobrok() + maxSlobrokDisconnectGracePeriod <= currentTime)
{
final String desc = String.format(
@@ -350,7 +343,7 @@ public class StateChangeHandler {
maxSlobrokDisconnectGracePeriod);
node.abortCurrentNodeStateRequests();
NodeState state = lastReportedState.clone();
- state.setState(State.DOWN);
+ state.setState(DOWN);
if (!state.hasDescription()) {
state.setDescription(desc);
}
@@ -362,10 +355,12 @@ public class StateChangeHandler {
return false;
}
+ private boolean isNotControlledShutdown(NodeState state) { return ! isControlledShutdown(state); }
+
private boolean isControlledShutdown(NodeState state) {
- return (state.getState() == State.STOPPING
- && (state.getDescription().contains("Received signal 15 (SIGTERM - Termination signal)")
- || state.getDescription().contains("controlled shutdown")));
+ return state.getState() == State.STOPPING
+ && List.of("Received signal 15 (SIGTERM - Termination signal)", "controlled shutdown")
+ .contains(state.getDescription());
}
/**
@@ -381,14 +376,14 @@ public class StateChangeHandler {
final NodeState reportedState,
final NodeListener nodeListener) {
final long timeNow = timer.getCurrentTimeInMillis();
- log.log(Level.FINE, () -> String.format("Finding new cluster state entry for %s switching state %s", node, currentState.getTextualDifference(reportedState)));
+ log.log(FINE, () -> String.format("Finding new cluster state entry for %s switching state %s", node, currentState.getTextualDifference(reportedState)));
if (handleReportedNodeCrashEdge(node, currentState, reportedState, nodeListener, timeNow)) {
return;
}
if (initializationProgressHasIncreased(currentState, reportedState)) {
node.setInitProgressTime(timeNow);
- log.log(Level.FINEST, () -> "Reset initialize timer on " + node + " to " + node.getInitProgressTime());
+ log.log(FINEST, () -> "Reset initialize timer on " + node + " to " + node.getInitProgressTime());
}
if (handleImplicitCrashEdgeFromReverseInitProgress(node, currentState, reportedState, nodeListener, timeNow)) {
return;
@@ -402,9 +397,9 @@ public class StateChangeHandler {
final NodeState reportedState,
final NodeListener nodeListener,
final long timeNow) {
- if (currentState.getState().equals(State.INITIALIZING)
+ if (currentState.getState().equals(INITIALIZING)
&& reportedState.getState().oneOf("ds")
- && !isControlledShutdown(reportedState))
+ && isNotControlledShutdown(reportedState))
{
eventLog.add(NodeEvent.forBaseline(node, String.format("Stop or crash during initialization. " +
"Premature crash count is now %d.", node.getPrematureCrashCount() + 1),
@@ -421,8 +416,8 @@ public class StateChangeHandler {
final NodeState reportedState,
final NodeListener nodeListener,
final long timeNow) {
- if (currentState.getState().equals(State.INITIALIZING) &&
- (reportedState.getState().equals(State.INITIALIZING) && reportedState.getInitProgress() < currentState.getInitProgress()))
+ if (currentState.getState().equals(INITIALIZING) &&
+ (reportedState.getState().equals(INITIALIZING) && reportedState.getInitProgress() < currentState.getInitProgress()))
{
eventLog.add(NodeEvent.forBaseline(node, String.format(
"Stop or crash during initialization detected from reverse initializing progress." +
@@ -442,8 +437,8 @@ public class StateChangeHandler {
long timeNow) {
if (nodeUpToDownEdge(node, currentState, reportedState)) {
node.setTransitionTime(timeNow);
- if (node.getUpStableStateTime() + stableStateTimePeriod > timeNow && !isControlledShutdown(reportedState)) {
- log.log(Level.FINE, () -> "Stable state: " + node.getUpStableStateTime() + " + " + stableStateTimePeriod + " > " + timeNow);
+ if (node.getUpStableStateTime() + stableStateTimePeriod > timeNow && isNotControlledShutdown(reportedState)) {
+ log.log(FINE, () -> "Stable state: " + node.getUpStableStateTime() + " + " + stableStateTimePeriod + " > " + timeNow);
eventLog.add(NodeEvent.forBaseline(node,
String.format("Stopped or possibly crashed after %d ms, which is before " +
"stable state time period. Premature crash count is now %d.",
@@ -457,20 +452,20 @@ public class StateChangeHandler {
}
private boolean initializationProgressHasIncreased(NodeState currentState, NodeState reportedState) {
- return reportedState.getState().equals(State.INITIALIZING) &&
- (!currentState.getState().equals(State.INITIALIZING) ||
+ return reportedState.getState().equals(INITIALIZING) &&
+ (!currentState.getState().equals(INITIALIZING) ||
reportedState.getInitProgress() > currentState.getInitProgress());
}
private boolean nodeUpToDownEdge(NodeInfo node, NodeState currentState, NodeState reportedState) {
return currentState.getState().oneOf("ur") && reportedState.getState().oneOf("dis")
- && (node.getWantedState().getState().equals(State.RETIRED) || !reportedState.getState().equals(State.INITIALIZING));
+ && (node.getWantedState().getState().equals(State.RETIRED) || !reportedState.getState().equals(INITIALIZING));
}
private boolean handlePrematureCrash(NodeInfo node, NodeListener changeListener) {
node.setPrematureCrashCount(node.getPrematureCrashCount() + 1);
if (disableUnstableNodes && node.getPrematureCrashCount() > maxPrematureCrashes) {
- NodeState wantedState = new NodeState(node.getNode().getType(), State.DOWN)
+ NodeState wantedState = new NodeState(node.getNode().getType(), DOWN)
.setDescription("Disabled by fleet controller as it prematurely shut down " + node.getPrematureCrashCount() + " times in a row");
NodeState oldState = node.getWantedState();
node.setWantedState(wantedState);
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
index cfe4c925551..4122abe1521 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/Response.java
@@ -48,6 +48,8 @@ public class Response {
public String getId() { return id; }
@Override
public String getReason() { return reason; }
+ @Override
+ public String toString() { return getId() +": " + getReason(); }
}
public static class Link implements SubUnitList {
private final Map<String, String> links = new LinkedHashMap<>();
diff --git a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/SetNodeStateRequest.java b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/SetNodeStateRequest.java
index 1c72594377a..01a75034ddf 100644
--- a/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/SetNodeStateRequest.java
+++ b/clustercontroller-core/src/main/java/com/yahoo/vespa/clustercontroller/core/restapiv2/requests/SetNodeStateRequest.java
@@ -72,14 +72,13 @@ public class SetNodeStateRequest extends Request<SetResponse> {
static NodeState getRequestedNodeState(Map<String, UnitState> newStates, Node n) throws StateRestApiException {
UnitState newState = newStates.get("user");
if (newState == null) throw new InvalidContentException("No new user state given in request");
- State state;
- switch (newState.getId().toLowerCase()) {
- case "up": state = State.UP; break;
- case "retired": state = State.RETIRED; break;
- case "maintenance": state = State.MAINTENANCE; break;
- case "down": state = State.DOWN; break;
- default: throw new InvalidContentException("Invalid user state '" + newState.getId() + "' given.");
- }
+ State state = switch (newState.getId().toLowerCase()) {
+ case "up" -> State.UP;
+ case "retired" -> State.RETIRED;
+ case "maintenance" -> State.MAINTENANCE;
+ case "down" -> State.DOWN;
+ default -> throw new InvalidContentException("Invalid user state '" + newState.getId() + "' given.");
+ };
return new NodeState(n.getType(), state).setDescription(newState.getReason());
}
@@ -191,25 +190,18 @@ public class SetNodeStateRequest extends Request<SetResponse> {
boolean probe) {
Node distributorNode = new Node(NodeType.DISTRIBUTOR, index);
NodeInfo nodeInfo = cluster.getNodeInfo(distributorNode);
- if (nodeInfo == null) {
- throw new IllegalStateException("Missing distributor at index " +
- distributorNode.getIndex());
- }
+ if (nodeInfo == null)
+ throw new IllegalStateException("Missing distributor at index " + distributorNode.getIndex());
State newState;
switch (newStorageWantedState.getState()) {
- case MAINTENANCE:
- newState = State.DOWN;
- break;
- case RETIRED:
- newState = State.UP;
- break;
- default:
+ case MAINTENANCE -> newState = State.DOWN;
+ case RETIRED -> newState = State.UP;
+ default -> {
newState = newStorageWantedState.getState();
- if (!newState.validWantedNodeState(distributorNode.getType())) {
- throw new IllegalStateException("Distributor cannot be set to wanted state " +
- newState);
- }
+ if (!newState.validWantedNodeState(distributorNode.getType()))
+ throw new IllegalStateException("Distributor cannot be set to wanted state " + newState);
+ }
}
NodeState newWantedState = new NodeState(distributorNode.getType(), newState);
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
index e789a3cc6a6..c4fd7cb69b9 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/NodeStateChangeCheckerTest.java
@@ -10,7 +10,8 @@ import com.yahoo.vdslib.state.State;
import com.yahoo.vespa.clustercontroller.core.hostinfo.HostInfo;
import com.yahoo.vespa.config.content.StorDistributionConfig;
import org.junit.jupiter.api.Test;
-import java.text.ParseException;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -19,6 +20,7 @@ import static com.yahoo.vdslib.state.NodeType.DISTRIBUTOR;
import static com.yahoo.vdslib.state.NodeType.STORAGE;
import static com.yahoo.vdslib.state.State.DOWN;
import static com.yahoo.vdslib.state.State.INITIALIZING;
+import static com.yahoo.vdslib.state.State.MAINTENANCE;
import static com.yahoo.vdslib.state.State.UP;
import static com.yahoo.vespa.clustercontroller.core.NodeStateChangeChecker.Result;
import static com.yahoo.vespa.clustercontroller.utils.staterestapi.requests.SetUnitStateRequest.Condition.FORCE;
@@ -37,37 +39,38 @@ public class NodeStateChangeCheckerTest {
private static final Node nodeStorage = new Node(STORAGE, 1);
private static final NodeState UP_NODE_STATE = new NodeState(STORAGE, UP);
- private static final NodeState MAINTENANCE_NODE_STATE = createNodeState(State.MAINTENANCE, "Orchestrator");
+ private static final NodeState MAINTENANCE_NODE_STATE = createNodeState(MAINTENANCE, "Orchestrator");
private static final NodeState DOWN_NODE_STATE = createNodeState(DOWN, "RetireEarlyExpirer");
private static NodeState createNodeState(State state, String description) {
return new NodeState(STORAGE, state).setDescription(description);
}
- private static ClusterState clusterState(String state) {
- try {
- return new ClusterState(state);
- } catch (ParseException e) {
- throw new RuntimeException(e);
- }
- }
+ private static ClusterState clusterState(String state) { return ClusterState.stateFromString(state); }
private static ClusterState defaultAllUpClusterState() {
- return clusterState(String.format("version:%d distributor:4 storage:4", currentClusterStateVersion));
+ return defaultAllUpClusterState(4);
+ }
+
+ private static ClusterState defaultAllUpClusterState(int nodeCount) {
+ return clusterState(String.format("version:%d distributor:%d storage:%d",
+ currentClusterStateVersion,
+ nodeCount ,
+ nodeCount));
}
private NodeStateChangeChecker createChangeChecker(ContentCluster cluster) {
return new NodeStateChangeChecker(cluster, false);
}
- private ContentCluster createCluster(int nodeCount) {
- return createCluster(nodeCount, 1);
+ private ContentCluster createCluster(int nodeCount, int maxNumberOfGroupsAllowedToBeDown) {
+ return createCluster(nodeCount, 1, maxNumberOfGroupsAllowedToBeDown);
}
- private ContentCluster createCluster(int nodeCount, int groupCount) {
- Collection<ConfiguredNode> nodes = createNodes(nodeCount);
+ private ContentCluster createCluster(int nodeCount, int groupCount, int maxNumberOfGroupsAllowedToBeDown) {
+ List<ConfiguredNode> nodes = createNodes(nodeCount);
Distribution distribution = new Distribution(createDistributionConfig(nodeCount, groupCount));
- return new ContentCluster("Clustername", nodes, distribution);
+ return new ContentCluster("Clustername", nodes, distribution, maxNumberOfGroupsAllowedToBeDown);
}
private String createDistributorHostInfo(int replicationfactor1, int replicationfactor2, int replicationfactor3) {
@@ -105,9 +108,10 @@ public class NodeStateChangeCheckerTest {
}
}
- @Test
- void testCanUpgradeForce() {
- var nodeStateChangeChecker = createChangeChecker(createCluster(1));
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeWithForce(int maxNumberOfGroupsAllowedToBeDown) {
+ var nodeStateChangeChecker = createChangeChecker(createCluster(1, maxNumberOfGroupsAllowedToBeDown));
NodeState newState = new NodeState(STORAGE, INITIALIZING);
Result result = nodeStateChangeChecker.evaluateTransition(
nodeDistributor, defaultAllUpClusterState(), FORCE,
@@ -116,9 +120,10 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testDeniedInMoratorium() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDeniedInMoratorium(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
var nodeStateChangeChecker = new NodeStateChangeChecker(cluster, true);
Result result = nodeStateChangeChecker.evaluateTransition(
new Node(STORAGE, 10), defaultAllUpClusterState(), SAFE,
@@ -128,9 +133,10 @@ public class NodeStateChangeCheckerTest {
assertEquals("Master cluster controller is bootstrapping and in moratorium", result.getReason());
}
- @Test
- void testUnknownStorageNode() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testUnknownStorageNode(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
var nodeStateChangeChecker = createChangeChecker(cluster);
Result result = nodeStateChangeChecker.evaluateTransition(
new Node(STORAGE, 10), defaultAllUpClusterState(), SAFE,
@@ -140,11 +146,12 @@ public class NodeStateChangeCheckerTest {
assertEquals("Unknown node storage.10", result.getReason());
}
- @Test
- void testSafeMaintenanceDisallowedWhenOtherStorageNodeInFlatClusterIsSuspended() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSafeMaintenanceDisallowedWhenOtherStorageNodeInFlatClusterIsSuspended(int maxNumberOfGroupsAllowedToBeDown) {
// Nodes 0-3, storage node 0 being in maintenance with "Orchestrator" description.
- ContentCluster cluster = createCluster(4);
- cluster.clusterInfo().getStorageNodeInfo(0).setWantedState(new NodeState(STORAGE, State.MAINTENANCE).setDescription("Orchestrator"));
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
+ setStorageNodeWantedStateToMaintenance(cluster, 0);
var nodeStateChangeChecker = createChangeChecker(cluster);
ClusterState clusterStateWith0InMaintenance = clusterState(String.format(
"version:%d distributor:4 storage:4 .0.s:m",
@@ -160,11 +167,131 @@ public class NodeStateChangeCheckerTest {
}
@Test
- void testSafeMaintenanceDisallowedWhenOtherDistributorInFlatClusterIsSuspended() {
+ void testMaintenanceAllowedFor2Of4Groups() {
+ // 4 groups with 1 node in each group
+ Collection<ConfiguredNode> nodes = createNodes(4);
+ StorDistributionConfig config = createDistributionConfig(4, 4);
+
+ int maxNumberOfGroupsAllowedToBeDown = 2;
+ var cluster = new ContentCluster("Clustername", nodes, new Distribution(config), maxNumberOfGroupsAllowedToBeDown);
+ setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
+ var nodeStateChangeChecker = createChangeChecker(cluster);
+
+ // All nodes up, set a storage node in group 0 to maintenance
+ {
+ int nodeIndex = 0;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, defaultAllUpClusterState());
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // Node in group 0 in maintenance, set storage node in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:4 .0.s:d storage:4 .0.s:m", currentClusterStateVersion));
+ int nodeIndex = 1;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // Nodes in group 0 and 1 in maintenance, try to set storage node in group 2 to maintenance while storage node 2 is down, should fail
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:4 storage:4 .0.s:m .1.s:m .2.s:d", currentClusterStateVersion));
+ int nodeIndex = 2;
+ cluster.clusterInfo().getStorageNodeInfo(nodeIndex).setReportedState(new NodeState(STORAGE, DOWN), 0);
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertFalse(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("At most 2 groups can have wanted state: [0, 1, 2]", result.getReason());
+ }
+
+ // Nodes in group 0 and 1 in maintenance, try to set storage node in group 2 to maintenance, should fail
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:4 storage:4 .0.s:m .1.s:m", currentClusterStateVersion));
+ int nodeIndex = 2;
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertFalse(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("At most 2 groups can have wanted state: [0, 1]", result.getReason());
+ }
+
+ }
+
+ @Test
+ void testMaintenanceAllowedFor2Of4Groups8Nodes() {
+ // 4 groups with 2 nodes in each group
+ Collection<ConfiguredNode> nodes = createNodes(8);
+ StorDistributionConfig config = createDistributionConfig(8, 4);
+
+ int maxNumberOfGroupsAllowedToBeDown = 2;
+ var cluster = new ContentCluster("Clustername", nodes, new Distribution(config), maxNumberOfGroupsAllowedToBeDown);
+ setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
+ var nodeStateChangeChecker = createChangeChecker(cluster);
+
+ // All nodes up, set a storage node in group 0 to maintenance
+ {
+ ClusterState clusterState = defaultAllUpClusterState(8);
+ int nodeIndex = 0;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 1 Node in group 0 in maintenance, try to set node 1 in group 0 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 .0.s:d storage:8 .0.s:m", currentClusterStateVersion));
+ int nodeIndex = 1;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 2 nodes in group 0 in maintenance, try to set storage node 2 in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m", currentClusterStateVersion));
+ int nodeIndex = 2;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 2 nodes in group 0 and 1 in group 1 in maintenance, try to set storage node 4 in group 2 to maintenance, should fail (different group)
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m .2.s:m", currentClusterStateVersion));
+ int nodeIndex = 4;
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertFalse(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("At most 2 groups can have wanted state: [0, 1]", result.getReason());
+ }
+
+ // 2 nodes in group 0 and 1 in group 1 in maintenance, try to set storage node 3 in group 1 to maintenance
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m .2.s:m", currentClusterStateVersion));
+ int nodeIndex = 3;
+ checkSettingToMaintenanceIsAllowed(nodeIndex, nodeStateChangeChecker, clusterState);
+ setStorageNodeWantedStateToMaintenance(cluster, nodeIndex);
+ }
+
+ // 2 nodes in group 0 in maintenance, storage node 3 in group 1 is in maintenance with another description
+ // (set in maintenance by operator), try to set storage node 3 in group 1 to maintenance, should bew allowed
+ {
+ ClusterState clusterState = clusterState(String.format("version:%d distributor:8 storage:8 .0.s:m .1.s:m .3.s:m", currentClusterStateVersion));
+ setStorageNodeWantedState(cluster, 3, MAINTENANCE, "Maintenance, set by operator"); // Set to another description
+ setStorageNodeWantedState(cluster, 2, UP, ""); // Set back to UP, want to set this to maintenance again
+ int nodeIndex = 2;
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertTrue(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ }
+
+ }
+
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSafeMaintenanceDisallowedWhenOtherDistributorInFlatClusterIsSuspended(int maxNumberOfGroupsAllowedToBeDown) {
// Nodes 0-3, distributor 0 being down with "Orchestrator" description.
- ContentCluster cluster = createCluster(4);
- cluster.clusterInfo().getDistributorNodeInfo(0)
- .setWantedState(new NodeState(DISTRIBUTOR, DOWN).setDescription("Orchestrator"));
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
+ setDistributorNodeWantedState(cluster, 0, DOWN, "Orchestrator");
var nodeStateChangeChecker = createChangeChecker(cluster);
ClusterState clusterStateWith0InMaintenance = clusterState(String.format(
"version:%d distributor:4 .0.s:d storage:4",
@@ -179,13 +306,13 @@ public class NodeStateChangeCheckerTest {
result.getReason());
}
- @Test
- void testSafeMaintenanceDisallowedWhenDistributorInGroupIsDown() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSafeMaintenanceDisallowedWhenDistributorInGroupIsDown(int maxNumberOfGroupsAllowedToBeDown) {
// Nodes 0-3, distributor 0 being in maintenance with "Orchestrator" description.
// 2 groups: nodes 0-1 is group 0, 2-3 is group 1.
- ContentCluster cluster = createCluster(4, 2);
- cluster.clusterInfo().getDistributorNodeInfo(0)
- .setWantedState(new NodeState(STORAGE, DOWN).setDescription("Orchestrator"));
+ ContentCluster cluster = createCluster(4, 2, maxNumberOfGroupsAllowedToBeDown);
+ setDistributorNodeWantedState(cluster, 0, DOWN, "Orchestrator");
var nodeStateChangeChecker = new NodeStateChangeChecker(cluster, false);
ClusterState clusterStateWith0InMaintenance = clusterState(String.format(
"version:%d distributor:4 .0.s:d storage:4",
@@ -198,7 +325,10 @@ public class NodeStateChangeCheckerTest {
SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
- assertEquals("At most one group can have wanted state: Other distributor 0 in group 0 has wanted state Down", result.getReason());
+ if (maxNumberOfGroupsAllowedToBeDown >= 1)
+ assertEquals("Wanted state already set for another node in groups: [0]", result.getReason());
+ else
+ assertEquals("At most one group can have wanted state: Other distributor 0 in group 0 has wanted state Down", result.getReason());
}
{
@@ -207,17 +337,23 @@ public class NodeStateChangeCheckerTest {
Result result = nodeStateChangeChecker.evaluateTransition(
new Node(STORAGE, 1), clusterStateWith0InMaintenance,
SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
- assertFalse(result.settingWantedStateIsAllowed(), result.getReason());
- assertEquals("Another distributor wants state DOWN: 0", result.getReason());
+ if (maxNumberOfGroupsAllowedToBeDown >= 1) {
+ assertFalse(result.settingWantedStateIsAllowed(), result.getReason());
+ assertEquals("Wanted state already set for another node in groups: [0]", result.getReason());
+ } else {
+ assertFalse(result.settingWantedStateIsAllowed(), result.getReason());
+ assertEquals("Another distributor wants state DOWN: 0", result.getReason());
+ }
}
}
- @Test
- void testSafeMaintenanceWhenOtherStorageNodeInGroupIsSuspended() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSafeMaintenanceWhenOtherStorageNodeInGroupIsSuspended(int maxNumberOfGroupsAllowedToBeDown) {
// Nodes 0-3, storage node 0 being in maintenance with "Orchestrator" description.
// 2 groups: nodes 0-1 is group 0, 2-3 is group 1.
- ContentCluster cluster = createCluster(4, 2);
- cluster.clusterInfo().getStorageNodeInfo(0).setWantedState(new NodeState(STORAGE, State.MAINTENANCE).setDescription("Orchestrator"));
+ ContentCluster cluster = createCluster(4, 2, maxNumberOfGroupsAllowedToBeDown);
+ setStorageNodeWantedState(cluster, 0, MAINTENANCE, "Orchestrator");
var nodeStateChangeChecker = new NodeStateChangeChecker(cluster, false);
ClusterState clusterStateWith0InMaintenance = clusterState(String.format(
"version:%d distributor:4 storage:4 .0.s:m",
@@ -230,8 +366,11 @@ public class NodeStateChangeCheckerTest {
SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
- assertEquals("At most one group can have wanted state: Other storage node 0 in group 0 has wanted state Maintenance",
- result.getReason());
+ if (maxNumberOfGroupsAllowedToBeDown >= 1)
+ assertEquals("At most 1 groups can have wanted state: [0]", result.getReason());
+ else
+ assertEquals("At most one group can have wanted state: Other storage node 0 in group 0 has wanted state Maintenance",
+ result.getReason());
}
{
@@ -245,9 +384,10 @@ public class NodeStateChangeCheckerTest {
}
}
- @Test
- void testSafeSetStateDistributors() {
- NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(createCluster(1));
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSafeSetStateDistributors(int maxNumberOfGroupsAllowedToBeDown) {
+ NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(createCluster(1, 1, maxNumberOfGroupsAllowedToBeDown));
Result result = nodeStateChangeChecker.evaluateTransition(
nodeDistributor, defaultAllUpClusterState(), SAFE,
UP_NODE_STATE, MAINTENANCE_NODE_STATE);
@@ -256,10 +396,11 @@ public class NodeStateChangeCheckerTest {
assertTrue(result.getReason().contains("Safe-set of node state is only supported for storage nodes"));
}
- @Test
- void testCanUpgradeSafeMissingStorage() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeSafeMissingStorage(int maxNumberOfGroupsAllowedToBeDown) {
// Create a content cluster with 4 nodes, and storage node with index 3 down.
- ContentCluster cluster = createCluster(4);
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
cluster.clusterInfo().getStorageNodeInfo(3).setReportedState(new NodeState(STORAGE, DOWN), 0);
ClusterState clusterStateWith3Down = clusterState(String.format(
@@ -276,16 +417,18 @@ public class NodeStateChangeCheckerTest {
assertEquals("Another storage node has state DOWN: 3", result.getReason());
}
- @Test
- void testCanUpgradeStorageSafeYes() {
- Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4), defaultAllUpClusterState());
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeStorageSafeYes(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4, 1, maxNumberOfGroupsAllowedToBeDown), defaultAllUpClusterState());
assertTrue(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testSetUpFailsIfReportedIsDown() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSetUpFailsIfReportedIsDown(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
// Not setting nodes up -> all are down
@@ -298,9 +441,10 @@ public class NodeStateChangeCheckerTest {
// A node may be reported as Up but have a generated state of Down if it's part of
// nodes taken down implicitly due to a group having too low node availability.
- @Test
- void testSetUpSucceedsIfReportedIsUpButGeneratedIsDown() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSetUpSucceedsIfReportedIsUpButGeneratedIsDown(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
markAllNodesAsReportingStateUp(cluster);
@@ -316,9 +460,10 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCanSetUpEvenIfOldWantedStateIsDown() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanSetUpEvenIfOldWantedStateIsDown(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 3, 6)));
@@ -329,9 +474,10 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCanUpgradeStorageSafeNo() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeStorageSafeNo(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 3, 6)));
@@ -344,9 +490,10 @@ public class NodeStateChangeCheckerTest {
result.getReason());
}
- @Test
- void testCanUpgradeIfMissingMinReplicationFactor() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeIfMissingMinReplicationFactor(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
setAllNodesUp(cluster, HostInfo.createHostInfo(createDistributorHostInfo(4, 3, 6)));
@@ -357,9 +504,10 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCanUpgradeIfStorageNodeMissingFromNodeInfo() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeIfStorageNodeMissingFromNodeInfo(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
String hostInfo = "{\n" +
" \"cluster-state-version\": 2,\n" +
@@ -381,9 +529,10 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testMissingDistributorState() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testMissingDistributorState(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
cluster.clusterInfo().getStorageNodeInfo(1).setReportedState(new NodeState(STORAGE, UP), 0);
@@ -394,8 +543,8 @@ public class NodeStateChangeCheckerTest {
assertEquals("Distributor node 0 has not reported any cluster state version yet.", result.getReason());
}
- private Result transitionToSameState(State state, String oldDescription, String newDescription) {
- ContentCluster cluster = createCluster(4);
+ private Result transitionToSameState(State state, String oldDescription, String newDescription, int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
NodeState currentNodeState = createNodeState(state, oldDescription);
@@ -405,26 +554,29 @@ public class NodeStateChangeCheckerTest {
currentNodeState, newNodeState);
}
- private Result transitionToSameState(String oldDescription, String newDescription) {
- return transitionToSameState(State.MAINTENANCE, oldDescription, newDescription);
+ private Result transitionToSameState(String oldDescription, String newDescription, int maxNumberOfGroupsAllowedToBeDown) {
+ return transitionToSameState(MAINTENANCE, oldDescription, newDescription, maxNumberOfGroupsAllowedToBeDown);
}
- @Test
- void testSettingUpWhenUpCausesAlreadySet() {
- Result result = transitionToSameState(UP, "foo", "bar");
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSettingUpWhenUpCausesAlreadySet(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToSameState(UP, "foo", "bar", maxNumberOfGroupsAllowedToBeDown);
assertTrue(result.wantedStateAlreadySet());
}
- @Test
- void testSettingAlreadySetState() {
- Result result = transitionToSameState("foo", "foo");
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testSettingAlreadySetState(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToSameState("foo", "foo", maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertTrue(result.wantedStateAlreadySet());
}
- @Test
- void testDifferentDescriptionImpliesDenied() {
- Result result = transitionToSameState("foo", "bar");
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDifferentDescriptionImpliesDenied(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToSameState("foo", "bar", maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
}
@@ -433,10 +585,9 @@ public class NodeStateChangeCheckerTest {
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
for (int x = 0; x < cluster.clusterInfo().getConfiguredNodes().size(); x++) {
- State state = UP;
- cluster.clusterInfo().getDistributorNodeInfo(x).setReportedState(new NodeState(DISTRIBUTOR, state), 0);
+ cluster.clusterInfo().getDistributorNodeInfo(x).setReportedState(new NodeState(DISTRIBUTOR, UP), 0);
cluster.clusterInfo().getDistributorNodeInfo(x).setHostInfo(HostInfo.createHostInfo(createDistributorHostInfo(4, 5, 6)));
- cluster.clusterInfo().getStorageNodeInfo(x).setReportedState(new NodeState(STORAGE, state), 0);
+ cluster.clusterInfo().getStorageNodeInfo(x).setReportedState(new NodeState(STORAGE, UP), 0);
}
return nodeStateChangeChecker.evaluateTransition(
@@ -456,26 +607,29 @@ public class NodeStateChangeCheckerTest {
return transitionToMaintenanceWithOneStorageNodeDown(cluster, clusterState);
}
- @Test
- void testCanUpgradeWhenAllUp() {
- Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4), defaultAllUpClusterState());
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeWhenAllUp(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4, maxNumberOfGroupsAllowedToBeDown), defaultAllUpClusterState());
assertTrue(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCanUpgradeWhenAllUpOrRetired() {
- Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4), defaultAllUpClusterState());
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeWhenAllUpOrRetired(int maxNumberOfGroupsAllowedToBeDown) {
+ Result result = transitionToMaintenanceWithNoStorageNodesDown(createCluster(4, maxNumberOfGroupsAllowedToBeDown), defaultAllUpClusterState());
assertTrue(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCanUpgradeWhenStorageIsDown() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCanUpgradeWhenStorageIsDown(int maxNumberOfGroupsAllowedToBeDown) {
ClusterState clusterState = defaultAllUpClusterState();
var storageNodeIndex = nodeStorage.getIndex();
- ContentCluster cluster = createCluster(4);
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeState downNodeState = new NodeState(STORAGE, DOWN);
cluster.clusterInfo().getStorageNodeInfo(storageNodeIndex).setReportedState(downNodeState, 4 /* time */);
clusterState.setNodeState(new Node(STORAGE, storageNodeIndex), downNodeState);
@@ -485,13 +639,14 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testCannotUpgradeWhenOtherStorageIsDown() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testCannotUpgradeWhenOtherStorageIsDown(int maxNumberOfGroupsAllowedToBeDown) {
int otherIndex = 2;
// If this fails, just set otherIndex to some other valid index.
assertNotEquals(nodeStorage.getIndex(), otherIndex);
- ContentCluster cluster = createCluster(4);
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
ClusterState clusterState = defaultAllUpClusterState();
NodeState downNodeState = new NodeState(STORAGE, DOWN);
cluster.clusterInfo().getStorageNodeInfo(otherIndex).setReportedState(downNodeState, 4 /* time */);
@@ -503,9 +658,10 @@ public class NodeStateChangeCheckerTest {
assertTrue(result.getReason().contains("Another storage node has state DOWN: 2"));
}
- @Test
- void testNodeRatioRequirementConsidersGeneratedNodeStates() {
- ContentCluster cluster = createCluster(4);
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testNodeRatioRequirementConsidersGeneratedNodeStates(int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
markAllNodesAsReportingStateUp(cluster);
@@ -525,62 +681,72 @@ public class NodeStateChangeCheckerTest {
assertFalse(result.wantedStateAlreadySet());
}
- @Test
- void testDownDisallowedByNonRetiredState() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDownDisallowedByNonRetiredState(int maxNumberOfGroupsAllowedToBeDown) {
Result result = evaluateDownTransition(
defaultAllUpClusterState(),
UP,
currentClusterStateVersion,
- 0);
+ 0,
+ maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
assertEquals("Only retired nodes are allowed to be set to DOWN in safe mode - is Up", result.getReason());
}
- @Test
- void testDownDisallowedByBuckets() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDownDisallowedByBuckets(int maxNumberOfGroupsAllowedToBeDown) {
Result result = evaluateDownTransition(
retiredClusterStateSuffix(),
UP,
currentClusterStateVersion,
- 1);
+ 1,
+ maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
assertEquals("The storage node manages 1 buckets", result.getReason());
}
- @Test
- void testDownDisallowedByReportedState() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDownDisallowedByReportedState(int maxNumberOfGroupsAllowedToBeDown) {
Result result = evaluateDownTransition(
retiredClusterStateSuffix(),
INITIALIZING,
currentClusterStateVersion,
- 0);
+ 0,
+ maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
assertEquals("Reported state (Initializing) is not UP, so no bucket data is available", result.getReason());
}
- @Test
- void testDownDisallowedByVersionMismatch() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testDownDisallowedByVersionMismatch(int maxNumberOfGroupsAllowedToBeDown) {
Result result = evaluateDownTransition(
retiredClusterStateSuffix(),
UP,
currentClusterStateVersion - 1,
- 0);
+ 0,
+ maxNumberOfGroupsAllowedToBeDown);
assertFalse(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
assertEquals("Cluster controller at version 2 got info for storage node 1 at a different version 1",
result.getReason());
}
- @Test
- void testAllowedToSetDown() {
+ @ParameterizedTest
+ @ValueSource(ints = {-1, 1})
+ void testAllowedToSetDown(int maxNumberOfGroupsAllowedToBeDown) {
Result result = evaluateDownTransition(
retiredClusterStateSuffix(),
UP,
currentClusterStateVersion,
- 0);
+ 0,
+ maxNumberOfGroupsAllowedToBeDown);
assertTrue(result.settingWantedStateIsAllowed());
assertFalse(result.wantedStateAlreadySet());
}
@@ -588,8 +754,9 @@ public class NodeStateChangeCheckerTest {
private Result evaluateDownTransition(ClusterState clusterState,
State reportedState,
int hostInfoClusterStateVersion,
- int lastAlldisksBuckets) {
- ContentCluster cluster = createCluster(4);
+ int lastAlldisksBuckets,
+ int maxNumberOfGroupsAllowedToBeDown) {
+ ContentCluster cluster = createCluster(4, maxNumberOfGroupsAllowedToBeDown);
NodeStateChangeChecker nodeStateChangeChecker = createChangeChecker(cluster);
StorageNodeInfo nodeInfo = cluster.clusterInfo().getStorageNodeInfo(nodeStorage.getIndex());
@@ -741,13 +908,14 @@ public class NodeStateChangeCheckerTest {
.capacity(nodes)
.partitions("1|*"));
+ int nodeIndex = 0;
for (int i = 0; i < groups; ++i) {
var groupBuilder = new StorDistributionConfig.Group.Builder()
.index(String.valueOf(i))
.name(String.valueOf(i))
.capacity(nodesPerGroup)
.partitions("");
- for (int nodeIndex = 0; nodeIndex < nodesPerGroup; ++nodeIndex) {
+ for (int j = 0; j < nodesPerGroup; ++j, ++nodeIndex) {
groupBuilder.nodes(new StorDistributionConfig.Group.Nodes.Builder()
.index(nodeIndex));
}
@@ -756,4 +924,26 @@ public class NodeStateChangeCheckerTest {
return configBuilder.build();
}
+ private void checkSettingToMaintenanceIsAllowed(int nodeIndex, NodeStateChangeChecker nodeStateChangeChecker, ClusterState clusterState) {
+ Node node = new Node(STORAGE, nodeIndex);
+ Result result = nodeStateChangeChecker.evaluateTransition(node, clusterState, SAFE, UP_NODE_STATE, MAINTENANCE_NODE_STATE);
+ assertTrue(result.settingWantedStateIsAllowed(), result.toString());
+ assertFalse(result.wantedStateAlreadySet());
+ assertEquals("Preconditions fulfilled and new state different", result.getReason());
+ }
+
+ private void setStorageNodeWantedStateToMaintenance(ContentCluster cluster, int nodeIndex) {
+ setStorageNodeWantedState(cluster, nodeIndex, MAINTENANCE, "Orchestrator");
+ }
+
+ private void setStorageNodeWantedState(ContentCluster cluster, int nodeIndex, State state, String description) {
+ NodeState nodeState = new NodeState(STORAGE, state);
+ cluster.clusterInfo().getStorageNodeInfo(nodeIndex).setWantedState(nodeState.setDescription(description));
+ }
+
+ private void setDistributorNodeWantedState(ContentCluster cluster, int nodeIndex, State state, String description) {
+ NodeState nodeState = new NodeState(DISTRIBUTOR, state);
+ cluster.clusterInfo().getDistributorNodeInfo(nodeIndex).setWantedState(nodeState.setDescription(description));
+ }
+
}
diff --git a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
index 131b011df48..50fe6e9a154 100644
--- a/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
+++ b/clustercontroller-core/src/test/java/com/yahoo/vespa/clustercontroller/core/restapiv2/SetNodeStateTest.java
@@ -72,6 +72,9 @@ public class SetNodeStateTest extends StateRestApiTest {
public String getReason() {
return reason;
}
+
+ @Override
+ public String toString() { return getId() +": " + getReason(); }
});
return this;
}
diff --git a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java
index 26ee121af79..39c3ae06b14 100644
--- a/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java
+++ b/clustercontroller-utils/src/main/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/server/JsonReader.java
@@ -37,6 +37,9 @@ public class JsonReader {
return reason;
}
+ @Override
+ public String toString() { return getId() +": " + getReason(); }
+
}
static class SetRequestData {
diff --git a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java
index 247aec369cb..d611b0c0ea8 100644
--- a/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java
+++ b/clustercontroller-utils/src/test/java/com/yahoo/vespa/clustercontroller/utils/staterestapi/DummyStateApi.java
@@ -127,6 +127,8 @@ public class DummyStateApi implements StateRestAPI {
public String getId() { return node.state; }
@Override
public String getReason() { return node.reason; }
+ @Override
+ public String toString() { return getId() +": " + getReason(); }
});
return m;
}
diff --git a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
index fef2354c452..7f2dd4b6acd 100644
--- a/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
+++ b/config-model-api/src/main/java/com/yahoo/config/model/api/ModelContext.java
@@ -113,6 +113,7 @@ public interface ModelContext {
@ModelFeatureFlag(owners = {"tokle, bjorncs"}, removeAfter = "8.108") default boolean enableDataPlaneFilter() { return true; }
@ModelFeatureFlag(owners = {"arnej, bjorncs"}) default boolean enableGlobalPhase() { return true; }
@ModelFeatureFlag(owners = {"baldersheim"}, comment = "Select summary decode type") default String summaryDecodePolicy() { return "eager"; }
+ @ModelFeatureFlag(owners = {"hmusum"}) default boolean allowMoreThanOneContentGroupDown(ClusterSpec.Id id) { return false; }
//Below are all flags that must be kept until 7 is out of the door
@ModelFeatureFlag(owners = {"arnej"}, removeAfter="7.last") default boolean ignoreThreadStackSizes() { return false; }
diff --git a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java
index 4fb61ed8b5e..4306744eb20 100644
--- a/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java
+++ b/config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java
@@ -213,7 +213,7 @@ public class ConfigModelRepo implements ConfigModelRepoAdder, Serializable, Iter
public <T extends ConfigModel> List<T> getModels(Class<T> modelClass) {
List<T> modelsOfModelClass = new ArrayList<>();
- for (ConfigModel model : asMap().values()) {
+ for (ConfigModel model : configModels) {
if (modelClass.isInstance(model))
modelsOfModelClass.add((T)model);
}
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 c72aa23a836..540905bef4c 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
@@ -83,6 +83,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
private boolean useRestrictedDataPlaneBindings = false;
private Optional<CloudAccount> cloudAccount = Optional.empty();
private boolean allowUserFilters = true;
+ private boolean allowMoreThanOneContentGroupDown = false;
@Override public ModelContext.FeatureFlags featureFlags() { return this; }
@Override public boolean multitenant() { return multitenant; }
@@ -140,6 +141,7 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
@Override public Optional<CloudAccount> cloudAccount() { return cloudAccount; }
@Override public boolean allowUserFilters() { return allowUserFilters; }
@Override public boolean enableGlobalPhase() { return true; } // Enable global-phase by default for unit tests only
+ @Override public boolean allowMoreThanOneContentGroupDown(ClusterSpec.Id id) { return allowMoreThanOneContentGroupDown; }
public TestProperties sharedStringRepoNoReclaim(boolean sharedStringRepoNoReclaim) {
this.sharedStringRepoNoReclaim = sharedStringRepoNoReclaim;
@@ -368,6 +370,11 @@ public class TestProperties implements ModelContext.Properties, ModelContext.Fea
return this;
}
+ public TestProperties setAllowMoreThanOneContentGroupDown(boolean allowMoreThanOneContentGroupDown) {
+ this.allowMoreThanOneContentGroupDown = allowMoreThanOneContentGroupDown;
+ return this;
+ }
+
public TestProperties setAllowUserFilters(boolean b) { this.allowUserFilters = b; return this; }
public static class Spec implements ConfigServerSpec {
diff --git a/config-model/src/main/java/com/yahoo/schema/Schema.java b/config-model/src/main/java/com/yahoo/schema/Schema.java
index 180c8e6012f..93bec4975a6 100644
--- a/config-model/src/main/java/com/yahoo/schema/Schema.java
+++ b/config-model/src/main/java/com/yahoo/schema/Schema.java
@@ -709,8 +709,17 @@ public class Schema implements ImmutableSchema {
public FieldSets fieldSets() { return fieldSets; }
+ private Schema inheritedSchema = null;
+
+ public void setInheritedSchema(Schema value) {
+ inheritedSchema = value;
+ }
+
/** Returns the schema inherited by this, or throws if none */
- private Schema requireInherited() { return owner.schemas().get(inherited.get()); }
+ private Schema requireInherited() {
+ if (inheritedSchema != null) return inheritedSchema;
+ return owner.schemas().get(inherited.get());
+ }
/**
* For adding structs defined in document scope
diff --git a/config-model/src/main/java/com/yahoo/schema/derived/VsmFields.java b/config-model/src/main/java/com/yahoo/schema/derived/VsmFields.java
index c8679b6166c..c032a7155b2 100644
--- a/config-model/src/main/java/com/yahoo/schema/derived/VsmFields.java
+++ b/config-model/src/main/java/com/yahoo/schema/derived/VsmFields.java
@@ -13,12 +13,14 @@ import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.TensorFieldValue;
import com.yahoo.schema.FieldSets;
import com.yahoo.schema.Schema;
+import com.yahoo.schema.document.Attribute;
import com.yahoo.schema.document.FieldSet;
import com.yahoo.schema.document.GeoPos;
import com.yahoo.schema.document.Matching;
import com.yahoo.schema.document.MatchType;
import com.yahoo.schema.document.SDDocumentType;
import com.yahoo.schema.document.SDField;
+import com.yahoo.schema.processing.TensorFieldProcessor;
import com.yahoo.vespa.config.search.vsm.VsmfieldsConfig;
import java.util.LinkedHashMap;
@@ -124,63 +126,68 @@ public class VsmFields extends Derived implements VsmfieldsConfig.Producer {
private final Type type;
private final boolean isAttribute;
+ private final Attribute.DistanceMetric distanceMetric;
/** The streaming field type enumeration */
public static class Type {
- public static Type INT8 = new Type("int8","INT8");
- public static Type INT16 = new Type("int16","INT16");
- public static Type INT32 = new Type("int32","INT32");
- public static Type INT64 = new Type("int64","INT64");
- public static Type FLOAT16 = new Type("float16", "FLOAT16");
- public static Type FLOAT = new Type("float","FLOAT");
- public static Type DOUBLE = new Type("double","DOUBLE");
- public static Type STRING = new Type("string","AUTOUTF8");
- public static Type BOOL = new Type("bool","BOOL");
- public static Type UNSEARCHABLESTRING = new Type("string","NONE");
- public static Type GEO_POSITION = new Type("position", "GEOPOS");
-
- private String name;
+ public static Type INT8 = new Type("INT8");
+ public static Type INT16 = new Type("INT16");
+ public static Type INT32 = new Type("INT32");
+ public static Type INT64 = new Type("INT64");
+ public static Type FLOAT16 = new Type("FLOAT16");
+ public static Type FLOAT = new Type("FLOAT");
+ public static Type DOUBLE = new Type("DOUBLE");
+ public static Type STRING = new Type("AUTOUTF8");
+ public static Type BOOL = new Type("BOOL");
+ public static Type UNSEARCHABLESTRING = new Type("NONE");
+ public static Type GEO_POSITION = new Type("GEOPOS");
+ public static Type NEAREST_NEIGHBOR = new Type("NEAREST_NEIGHBOR");
private String searchMethod;
- private Type(String name, String searchMethod) {
- this.name = name;
+ private Type(String searchMethod) {
this.searchMethod = searchMethod;
}
@Override
public int hashCode() {
- return name.hashCode();
+ return searchMethod.hashCode();
}
- /** Returns the name of this type */
- public String getName() { return name; }
-
public String getSearchMethod() { return searchMethod; }
@Override
public boolean equals(Object other) {
if ( ! (other instanceof Type)) return false;
- return this.name.equals(((Type)other).name);
+ return this.searchMethod.equals(((Type)other).searchMethod);
}
@Override
public String toString() {
- return "type: " + name;
+ return "method: " + searchMethod;
}
}
public StreamingField(SDField field) {
- this(field.getName(), field.getDataType(), field.getMatching(), field.doesAttributing());
+ this(field.getName(), field.getDataType(), field.getMatching(), field.doesAttributing(), getDistanceMetric(field));
}
- private StreamingField(String name, DataType sourceType, Matching matching, boolean isAttribute) {
+ private StreamingField(String name, DataType sourceType, Matching matching, boolean isAttribute, Attribute.DistanceMetric distanceMetric) {
this.name = name;
this.type = convertType(sourceType);
this.matching = matching;
this.isAttribute = isAttribute;
+ this.distanceMetric = distanceMetric;
+ }
+
+ private static Attribute.DistanceMetric getDistanceMetric(SDField field) {
+ var attr = field.getAttribute();
+ if (attr != null) {
+ return attr.distanceMetric();
+ }
+ return Attribute.DEFAULT_DISTANCE_METRIC;
}
/** Converts to the right index type from a field datatype */
@@ -211,6 +218,10 @@ public class VsmFields extends Derived implements VsmfieldsConfig.Producer {
} else if (fval instanceof PredicateFieldValue) {
return Type.UNSEARCHABLESTRING;
} else if (fval instanceof TensorFieldValue) {
+ var tensorType = ((TensorFieldValue) fval).getDataType().getTensorType();
+ if (TensorFieldProcessor.isTensorTypeThatSupportsHnswIndex(tensorType)) {
+ return Type.NEAREST_NEIGHBOR;
+ }
return Type.UNSEARCHABLESTRING;
} else if (fieldType instanceof CollectionDataType) {
return convertType(((CollectionDataType) fieldType).getNestedType());
@@ -224,8 +235,7 @@ public class VsmFields extends Derived implements VsmfieldsConfig.Producer {
public String getName() { return name; }
- public VsmfieldsConfig.Fieldspec.Builder getFieldSpecConfig() {
- VsmfieldsConfig.Fieldspec.Builder fB = new VsmfieldsConfig.Fieldspec.Builder();
+ public String getMatchingName() {
String matchingName = matching.getType().getName();
if (matching.getType().equals(MatchType.TEXT))
matchingName = "";
@@ -241,9 +251,21 @@ public class VsmFields extends Derived implements VsmfieldsConfig.Producer {
if (type != Type.STRING) {
matchingName = "";
}
+ return matchingName;
+ }
+
+ public String getArg1() {
+ if (type == Type.NEAREST_NEIGHBOR) {
+ return distanceMetric.name();
+ }
+ return getMatchingName();
+ }
+
+ public VsmfieldsConfig.Fieldspec.Builder getFieldSpecConfig() {
+ var fB = new VsmfieldsConfig.Fieldspec.Builder();
fB.name(getName())
.searchmethod(VsmfieldsConfig.Fieldspec.Searchmethod.Enum.valueOf(type.getSearchMethod()))
- .arg1(matchingName)
+ .arg1(getArg1())
.fieldtype(isAttribute
? VsmfieldsConfig.Fieldspec.Fieldtype.ATTRIBUTE
: VsmfieldsConfig.Fieldspec.Fieldtype.INDEX);
diff --git a/config-model/src/main/java/com/yahoo/schema/document/Attribute.java b/config-model/src/main/java/com/yahoo/schema/document/Attribute.java
index 70fcf64dff3..9e7f14a3d85 100644
--- a/config-model/src/main/java/com/yahoo/schema/document/Attribute.java
+++ b/config-model/src/main/java/com/yahoo/schema/document/Attribute.java
@@ -39,7 +39,7 @@ import java.util.Set;
*/
public final class Attribute implements Cloneable, Serializable {
- public enum DistanceMetric { EUCLIDEAN, ANGULAR, GEODEGREES, INNERPRODUCT, HAMMING }
+ public enum DistanceMetric { EUCLIDEAN, ANGULAR, GEODEGREES, INNERPRODUCT, HAMMING, PRENORMALIZED_ANGULAR }
// Remember to change hashCode and equals when you add new fields
diff --git a/config-model/src/main/java/com/yahoo/schema/expressiontransforms/InputRecorder.java b/config-model/src/main/java/com/yahoo/schema/expressiontransforms/InputRecorder.java
index 7f578f07fe3..5d3624cd3d3 100644
--- a/config-model/src/main/java/com/yahoo/schema/expressiontransforms/InputRecorder.java
+++ b/config-model/src/main/java/com/yahoo/schema/expressiontransforms/InputRecorder.java
@@ -120,11 +120,7 @@ public class InputRecorder extends ExpressionTransformer<InputRecorderContext> {
if (model == null) {
throw new IllegalArgumentException("missing onnx model: " + arg);
}
- model.getInputMap().forEach((onnxName, onnxInput) -> {
- if (model.getInitializers().contains(onnxName)) {
- log.fine(() -> "For input '%s': skipping name '%s' as it's an initializer".formatted(onnxInput, onnxName));
- return;
- }
+ model.getInputMap().forEach((__, onnxInput) -> {
var reader = new StringReader(onnxInput);
try {
var asExpression = new RankingExpression(reader);
diff --git a/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedFields.java b/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedFields.java
index 98463c0584f..8a95edd0467 100644
--- a/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedFields.java
+++ b/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedFields.java
@@ -95,6 +95,7 @@ public class ConvertParsedFields {
var distanceMetric = parsed.getDistanceMetric();
if (distanceMetric.isPresent()) {
String upper = distanceMetric.get().toUpperCase(Locale.ENGLISH);
+ upper = upper.replace('-', '_');
attribute.setDistanceMetric(Attribute.DistanceMetric.valueOf(upper));
}
var sorting = parsed.getSorting();
diff --git a/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java b/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java
index 0abcc9e890a..40ec84ec8bc 100644
--- a/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java
+++ b/config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java
@@ -98,6 +98,13 @@ public class ConvertParsedSchemas {
Schema schema = parsed.getDocumentWithoutSchema()
? new DocumentOnlySchema(applicationPackage, fileRegistry, deployLogger, properties)
: new Schema(parsed.name(), applicationPackage, inherited, fileRegistry, deployLogger, properties);
+ inherited.ifPresent(parentName -> {
+ for (var possibleParent : resultList) {
+ if (possibleParent.getName().equals(parentName)) {
+ schema.setInheritedSchema(possibleParent);
+ }
+ }
+ });
convertSchema(schema, parsed);
resultList.add(schema);
}
@@ -145,7 +152,23 @@ public class ConvertParsedSchemas {
docsum.setOmitSummaryFeatures(true);
}
for (var parsedField : parsed.getSummaryFields()) {
- DataType dataType = typeContext.resolveType(parsedField.getType());
+ var parsedType = parsedField.getType();
+ DataType dataType = (parsedType != null) ? typeContext.resolveType(parsedType) : null;
+ var existingField = schema.getField(parsedField.name());
+ if (existingField != null) {
+ var existingType = existingField.getDataType();
+ if (dataType == null) {
+ dataType = existingType;
+ } else if (!dataType.equals(existingType)) {
+ if (dataType.getValueClass().equals(com.yahoo.document.datatypes.WeightedSet.class)) {
+ // "adjusting type for field " + parsedField.name() + " in document-summary " + parsed.name() + " field already has: " + existingType + " but declared type was: " + dataType
+ dataType = existingType;
+ }
+ }
+ }
+ if (dataType == null) {
+ throw new IllegalArgumentException("Missing data-type for summary field " + parsedField.name() + " in document-summary " + parsed.name());
+ }
var summaryField = new SummaryField(parsedField.name(), dataType);
// XXX does not belong here:
summaryField.setVsmCommand(SummaryField.VsmCommand.FLATTENSPACE);
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java b/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
index d8c1fb3125f..3c7e9b4066f 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/IndexingValidation.java
@@ -62,7 +62,7 @@ public class IndexingValidation extends Processor {
final Set<String> prevNames = new HashSet<>();
@Override
- protected ExpressionConverter branch() {
+ public ExpressionConverter branch() {
MyConverter ret = new MyConverter();
ret.outputs.addAll(outputs);
ret.prevNames.addAll(prevNames);
diff --git a/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java b/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java
index 37da07f8227..227054d9800 100644
--- a/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java
+++ b/config-model/src/main/java/com/yahoo/schema/processing/TensorFieldProcessor.java
@@ -9,6 +9,7 @@ import com.yahoo.schema.Schema;
import com.yahoo.schema.document.HnswIndexParams;
import com.yahoo.schema.document.ImmutableSDField;
import com.yahoo.schema.document.SDField;
+import com.yahoo.tensor.TensorType;
import com.yahoo.vespa.model.container.search.QueryProfiles;
/**
@@ -50,6 +51,14 @@ public class TensorFieldProcessor extends Processor {
private boolean isTensorTypeThatSupportsHnswIndex(ImmutableSDField field) {
var type = ((TensorDataType)field.getDataType()).getTensorType();
+ return isTensorTypeThatSupportsHnswIndex(type);
+ }
+
+ /**
+ * Returns whether the given tensor type supports using HNSW index and
+ * nearest neighbor search.
+ */
+ public static boolean isTensorTypeThatSupportsHnswIndex(TensorType type) {
// Tensors with 1 indexed dimension support hnsw index (used for approximate nearest neighbor search).
if ((type.dimensions().size() == 1) &&
type.dimensions().get(0).isIndexed()) {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogForwarder.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogForwarder.java
index 6284c0bc625..beb96ab8cc8 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/LogForwarder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/LogForwarder.java
@@ -14,24 +14,30 @@ public class LogForwarder extends AbstractService implements LogforwarderConfig.
public final String clientName;
public final String splunkHome;
public final Integer phoneHomeInterval;
+ public final String role;
- private Config(String ds, String cn, String sh, Integer phi) {
+ private Config(String ds, String cn, String sh, Integer phi, String role) {
this.deploymentServer = ds;
this.clientName = cn;
this.splunkHome = sh;
this.phoneHomeInterval = phi;
+ this.role = role;
}
public Config withDeploymentServer(String ds) {
- return new Config(ds, clientName, splunkHome, phoneHomeInterval);
+ return new Config(ds, clientName, splunkHome, phoneHomeInterval, role);
}
public Config withClientName(String cn) {
- return new Config(deploymentServer, cn, splunkHome, phoneHomeInterval);
+ return new Config(deploymentServer, cn, splunkHome, phoneHomeInterval, role);
}
public Config withSplunkHome(String sh) {
- return new Config(deploymentServer, clientName, sh, phoneHomeInterval);
+ return new Config(deploymentServer, clientName, sh, phoneHomeInterval, role);
}
public Config withPhoneHomeInterval(Integer phi) {
- return new Config(deploymentServer, clientName, splunkHome, phi);
+ return new Config(deploymentServer, clientName, splunkHome, phi, role);
+ }
+
+ public Config withRole(String role) {
+ return new Config(deploymentServer, clientName, splunkHome, phoneHomeInterval, role);
}
}
@@ -49,7 +55,7 @@ public class LogForwarder extends AbstractService implements LogforwarderConfig.
}
public static Config cfg() {
- return new Config(null, null, null, null);
+ return new Config(null, null, null, null, null);
}
// LogForwarder does not need any ports.
@@ -79,6 +85,9 @@ public class LogForwarder extends AbstractService implements LogforwarderConfig.
if (config.phoneHomeInterval != null) {
builder.phoneHomeInterval(config.phoneHomeInterval);
}
+ if (config.role != null) {
+ builder.role(config.role);
+ }
}
@Override
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerConfigurer.java b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerConfigurer.java
index d63301a9668..25cca42f703 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerConfigurer.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/admin/clustercontroller/ClusterControllerConfigurer.java
@@ -16,16 +16,16 @@ public class ClusterControllerConfigurer extends SimpleComponent implements Stor
FleetcontrollerConfig.Producer
{
private final ContentCluster cluster;
- private final int index;
+ private final int clusterControllerIndex;
private final int nodeCount;
- public ClusterControllerConfigurer(ContentCluster cluster, int index, int nodeCount) {
+ public ClusterControllerConfigurer(ContentCluster cluster, int clusterControllerIndex, int nodeCount) {
super(new ComponentModel(new BundleInstantiationSpecification(
new ComponentSpecification("clustercontroller" + "-" + cluster.getName() + "-configurer"),
new ComponentSpecification("com.yahoo.vespa.clustercontroller.apps.clustercontroller.ClusterControllerClusterConfigurer"),
new ComponentSpecification("clustercontroller-apps"))));
this.cluster = cluster;
- this.index = index;
+ this.clusterControllerIndex = clusterControllerIndex;
this.nodeCount = nodeCount;
}
@@ -38,7 +38,7 @@ public class ClusterControllerConfigurer extends SimpleComponent implements Stor
public void getConfig(FleetcontrollerConfig.Builder builder) {
cluster.getConfig(builder);
cluster.getClusterControllerConfig().getConfig(builder);
- builder.index(index);
+ builder.index(clusterControllerIndex);
builder.fleet_controller_count(nodeCount);
builder.http_port(0);
builder.rpc_port(0);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidator.java
new file mode 100644
index 00000000000..d9bf82980d7
--- /dev/null
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidator.java
@@ -0,0 +1,19 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.vespa.model.VespaModel;
+
+/**
+ * Validates that a Vespa Cloud application has at least one container cluster.
+ *
+ * @author jonmv
+ */
+public class ContainerInCloudValidator extends Validator {
+
+ @Override
+ public void validate(VespaModel model, DeployState deployState) {
+ if (deployState.isHosted() && model.getContainerClusters().isEmpty())
+ throw new IllegalArgumentException("Vespa Cloud applications must have at least one container cluster");
+ }
+
+}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/StreamingValidator.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/StreamingValidator.java
index 773d696f3e8..ad126cfa22b 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/StreamingValidator.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/StreamingValidator.java
@@ -5,6 +5,7 @@ import com.yahoo.config.application.api.DeployLogger;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.document.DataType;
import com.yahoo.document.NumericDataType;
+import com.yahoo.document.TensorDataType;
import com.yahoo.documentmodel.NewDocumentReferenceDataType;
import com.yahoo.schema.document.Attribute;
import com.yahoo.schema.document.ImmutableSDField;
@@ -63,6 +64,8 @@ public class StreamingValidator extends Validator {
// If the field is numeric, we can't print this, because we may have converted the field to
// attribute indexing ourselves (IntegerIndex2Attribute)
if (sd.getDataType() instanceof NumericDataType) return;
+ // Tensor fields are only searchable via nearest neighbor search, and match semantics are irrelevant.
+ if (sd.getDataType() instanceof TensorDataType) return;
logger.logApplicationPackage(Level.WARNING, "For streaming search cluster '" + sc.getClusterName() +
"', SD field '" + sd.getName() +
"': 'attribute' has same match semantics as 'index'.");
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
index 2576d9cb392..4f2f8e7932c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/application/validation/Validation.java
@@ -76,6 +76,7 @@ public class Validation {
new StreamingValidator().validate(model, deployState);
new RankSetupValidator(validationParameters.ignoreValidationErrors()).validate(model, deployState);
new NoPrefixForIndexes().validate(model, deployState);
+ new ContainerInCloudValidator().validate(model, deployState);
new DeploymentSpecValidator().validate(model, deployState);
new ValidationOverridesValidator().validate(model, deployState);
new ConstantValidator().validate(model, deployState);
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
index 9280f0ceb9a..df998e75268 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminBuilderBase.java
@@ -21,9 +21,11 @@ import com.yahoo.vespa.model.admin.monitoring.builder.Metrics;
import com.yahoo.vespa.model.admin.monitoring.builder.PredefinedMetricSets;
import com.yahoo.vespa.model.admin.monitoring.builder.xml.MetricsBuilder;
import org.w3c.dom.Element;
+
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
+import java.util.regex.Pattern;
/**
* A base class for admin model builders, to support common functionality across versions.
@@ -98,7 +100,7 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu
return Optional.empty();
}
- void addLogForwarders(ModelElement logForwardingElement, Admin admin) {
+ void addLogForwarders(ModelElement logForwardingElement, Admin admin, DeployState deployState) {
if (logForwardingElement == null) return;
boolean alsoForAdminCluster = logForwardingElement.booleanAttribute("include-admin");
for (ModelElement e : logForwardingElement.children("splunk")) {
@@ -106,7 +108,8 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu
.withSplunkHome(e.stringAttribute("splunk-home"))
.withDeploymentServer(e.stringAttribute("deployment-server"))
.withClientName(e.stringAttribute("client-name"))
- .withPhoneHomeInterval(e.integerAttribute("phone-home-interval"));
+ .withPhoneHomeInterval(e.integerAttribute("phone-home-interval"))
+ .withRole(parseLogforwarderRole(e.stringAttribute("role"), deployState));
admin.setLogForwarderConfig(cfg, alsoForAdminCluster);
}
}
@@ -130,4 +133,22 @@ public abstract class DomAdminBuilderBase extends VespaDomBuilder.DomConfigProdu
}
}
+ private String parseLogforwarderRole(String role, DeployState deployState) {
+ if (role == null)
+ return null;
+ if (deployState.zone().system().isPublic())
+ throw new IllegalArgumentException("Logforwarder role not supported in public systems");
+
+ // Currently only support athenz roles on format athenz://<domain>/role/<role>
+ var rolePattern = Pattern.compile("(?<scheme>athenz)://" +
+ "(?<domain>[a-zA-Z0-9_][a-zA-Z0-9_.-]*[a-zA-Z0-9_])" +
+ "/role/" +
+ "(?<role>[a-zA-Z0-9_][a-zA-Z0-9_.-]*[a-zA-Z0-9_])");
+ var matcher = rolePattern.matcher(role);
+ if (!matcher.matches()) {
+ throw new IllegalArgumentException("Invalid role path " + role);
+ }
+ return matcher.group("domain") + ":role." + matcher.group("role");
+ }
+
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
index 7a7092b04dd..152f7e03a4c 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/builder/xml/dom/DomAdminV2Builder.java
@@ -47,7 +47,7 @@ public class DomAdminV2Builder extends DomAdminBuilderBase {
if ( ! admin.multitenant())
admin.setClusterControllers(addConfiguredClusterControllers(deployState, admin, adminE), deployState);
- addLogForwarders(new ModelElement(adminE).child("logforwarding"), admin);
+ addLogForwarders(new ModelElement(adminE).child("logforwarding"), admin, deployState);
addLoggingSpecs(new ModelElement(adminE).child("logging"), admin);
}
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 80000e54b1b..4990ddc9a53 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
@@ -55,7 +55,7 @@ public class DomAdminV4Builder extends DomAdminBuilderBase {
assignSlobroks(deployState, requestedSlobroks.orElse(NodesSpecification.nonDedicated(3, context)), admin);
assignLogserver(deployState, requestedLogservers.orElse(createNodesSpecificationForLogserver()), admin);
- addLogForwarders(adminElement.child("logforwarding"), admin);
+ addLogForwarders(adminElement.child("logforwarding"), admin, deployState);
addLoggingSpecs(adminElement.child("logging"), admin);
}
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/container/IdentityProvider.java b/config-model/src/main/java/com/yahoo/vespa/model/container/IdentityProvider.java
index 5e8bb85c29d..c791fea3a56 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/container/IdentityProvider.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/container/IdentityProvider.java
@@ -17,7 +17,7 @@ import java.net.URI;
* @author mortent
*/
public class IdentityProvider extends SimpleComponent implements IdentityConfig.Producer {
- public static final String CLASS = "com.yahoo.vespa.athenz.identityprovider.client.AthenzIdentityProviderImpl";
+ public static final String CLASS = "com.yahoo.vespa.athenz.identityprovider.client.AthenzIdentityProviderProvider";
public static final String BUNDLE = "vespa-athenz";
private final AthenzDomain domain;
@@ -51,15 +51,18 @@ public class IdentityProvider extends SimpleComponent implements IdentityConfig.
builder.loadBalancerAddress(loadBalancerName.value());
builder.ztsUrl(ztsUrl != null ? ztsUrl.toString() : "");
builder.athenzDnsSuffix(athenzDnsSuffix != null ? athenzDnsSuffix : "");
- builder.nodeIdentityName("vespa.vespa.tenant"); // TODO Move to Oath configmodel amender
+ builder.nodeIdentityName(configServerDomain() + ".tenant"); // TODO Move to Oath configmodel amender
builder.configserverIdentityName(getConfigserverIdentityName());
}
// TODO Move to Oath configmodel amender
private String getConfigserverIdentityName() {
return String.format("%s.provider_%s_%s",
- zone.system() == SystemName.main ? "vespa.vespa" : "vespa.vespa.cd",
+ configServerDomain(),
zone.environment().value(),
zone.region().value());
}
+ private String configServerDomain() {
+ return zone.system() == SystemName.main ? "vespa.vespa" : "vespa.vespa.cd";
+ }
}
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 36d34b99223..f74c218a906 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
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.container.xml;
+import com.yahoo.component.ComponentId;
import com.yahoo.config.provision.ClusterInfo;
import com.yahoo.config.provision.IntRange;
import com.yahoo.component.ComponentSpecification;
@@ -1169,6 +1170,9 @@ public class ContainerModelBuilder extends ConfigModelBuilder<ContainerModel> {
ztsUrl,
zoneDnsSuffix,
zone);
+
+ // Replace AthenzIdentityProviderProvider
+ cluster.removeComponent(ComponentId.fromString("com.yahoo.container.jdisc.AthenzIdentityProviderProvider"));
cluster.addComponent(identityProvider);
cluster.getContainers().forEach(container -> {
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java
index 8ec4ae35658..e2166b263ee 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java
@@ -2,20 +2,20 @@
package com.yahoo.vespa.model.content;
import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.producer.AbstractConfigProducerRoot;
import com.yahoo.config.model.producer.AnyConfigProducer;
import com.yahoo.config.model.producer.TreeConfigProducer;
-import com.yahoo.config.model.producer.AbstractConfigProducerRoot;
import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.builder.xml.dom.ModelElement;
import com.yahoo.vespa.model.builder.xml.dom.VespaDomBuilder;
+import com.yahoo.vespa.model.content.cluster.ContentCluster;
import com.yahoo.vespa.model.utils.Duration;
import org.w3c.dom.Element;
+import java.util.Optional;
/**
* Config generation for common parameters for all fleet controllers.
- *
- * TODO: Author
*/
public class ClusterControllerConfig extends AnyConfigProducer implements FleetcontrollerConfig.Producer {
@@ -23,90 +23,81 @@ public class ClusterControllerConfig extends AnyConfigProducer implements Fleetc
private final String clusterName;
private final ModelElement clusterElement;
private final ResourceLimits resourceLimits;
+ private final boolean allowMoreThanOneContentGroupDown;
- public Builder(String clusterName, ModelElement clusterElement, ResourceLimits resourceLimits) {
+ public Builder(String clusterName,
+ ModelElement clusterElement,
+ ResourceLimits resourceLimits,
+ boolean allowMoreThanOneContentGroupDown) {
this.clusterName = clusterName;
this.clusterElement = clusterElement;
this.resourceLimits = resourceLimits;
+ this.allowMoreThanOneContentGroupDown = allowMoreThanOneContentGroupDown;
}
@Override
protected ClusterControllerConfig doBuild(DeployState deployState, TreeConfigProducer<AnyConfigProducer> ancestor, Element producerSpec) {
- ModelElement tuning = null;
-
- ModelElement clusterTuning = clusterElement.child("tuning");
- Integer bucketSplittingMinimumBits = null;
- Double minNodeRatioPerGroup = deployState.getProperties().featureFlags().minNodeRatioPerGroup();
- if (clusterTuning != null) {
- tuning = clusterTuning.child("cluster-controller");
- minNodeRatioPerGroup = clusterTuning.childAsDouble("min-node-ratio-per-group");
- bucketSplittingMinimumBits = clusterTuning.childAsInteger("bucket-splitting.minimum-bits");
- }
+ ModelElement tuning = clusterElement.child("tuning");
+ ModelElement clusterControllerTuning = null;
+ Optional<Double> minNodeRatioPerGroup = Optional.of(deployState.featureFlags().minNodeRatioPerGroup());
+ Optional<Integer> bucketSplittingMinimumBits = Optional.empty();
if (tuning != null) {
- return new ClusterControllerConfig(ancestor, clusterName,
- tuning.childAsDuration("init-progress-time"),
- tuning.childAsDuration("transition-time"),
- tuning.childAsLong("max-premature-crashes"),
- tuning.childAsDuration("stable-state-period"),
- tuning.childAsDouble("min-distributor-up-ratio"),
- tuning.childAsDouble("min-storage-up-ratio"),
- bucketSplittingMinimumBits,
- minNodeRatioPerGroup,
- resourceLimits);
+ minNodeRatioPerGroup = Optional.ofNullable(tuning.childAsDouble("min-node-ratio-per-group"));
+ bucketSplittingMinimumBits = Optional.ofNullable(tuning.childAsInteger("bucket-splitting.minimum-bits"));
+ clusterControllerTuning = tuning.child("cluster-controller");
+ }
+
+ var tuningConfig = new ClusterControllerTuningBuilder(clusterControllerTuning,
+ minNodeRatioPerGroup,
+ bucketSplittingMinimumBits)
+ .build();
+ if (ancestor instanceof ContentCluster) {
+ int numberOfLeafGroups = ((ContentCluster) ancestor).getRootGroup().getNumberOfLeafGroups();
+ if (tuningConfig.maxGroupsAllowedDown().isPresent()) {
+ Integer maxGroupsAllowedDown = tuningConfig.maxGroupsAllowedDown().get();
+ if (deployState.zone().environment().isProduction() && (maxGroupsAllowedDown > numberOfLeafGroups))
+ throw new IllegalArgumentException("Cannot set max-groups-allowed-down (" + maxGroupsAllowedDown +
+ ") larger than number of groups (" + numberOfLeafGroups + ")");
+ } else {
+ // Reduce to numberOfLeafGroups for tests or in environments where number of groups are reduced by policy (dev, test, staging, perf)
+ tuningConfig = tuningConfig.withMaxGroupsAllowedDown(numberOfLeafGroups);
+ }
} else {
- return new ClusterControllerConfig(ancestor, clusterName,
- null, null, null, null, null, null,
- bucketSplittingMinimumBits,
- minNodeRatioPerGroup,
- resourceLimits);
+ // Reduce to 1 for tests (ancestor is a mock class)
+ tuningConfig = tuningConfig.withMaxGroupsAllowedDown(1);
}
+
+ return new ClusterControllerConfig(ancestor,
+ clusterName,
+ tuningConfig,
+ resourceLimits,
+ allowMoreThanOneContentGroupDown);
}
}
private final String clusterName;
- private final Duration initProgressTime;
- private final Duration transitionTime;
- private final Long maxPrematureCrashes;
- private final Duration stableStateTimePeriod;
- private final Double minDistributorUpRatio;
- private final Double minStorageUpRatio;
- private final Integer minSplitBits;
- private final Double minNodeRatioPerGroup;
+ private final ClusterControllerTuning tuning;
private final ResourceLimits resourceLimits;
+ private final boolean allowMoreThanOneContentGroupDown;
- // TODO refactor; too many args
private ClusterControllerConfig(TreeConfigProducer<?> parent,
String clusterName,
- Duration initProgressTime,
- Duration transitionTime,
- Long maxPrematureCrashes,
- Duration stableStateTimePeriod,
- Double minDistributorUpRatio,
- Double minStorageUpRatio,
- Integer minSplitBits,
- Double minNodeRatioPerGroup,
- ResourceLimits resourceLimits) {
+ ClusterControllerTuning tuning,
+ ResourceLimits resourceLimits,
+ boolean allowMoreThanOneContentGroupDown) {
super(parent, "fleetcontroller");
-
this.clusterName = clusterName;
- this.initProgressTime = initProgressTime;
- this.transitionTime = transitionTime;
- this.maxPrematureCrashes = maxPrematureCrashes;
- this.stableStateTimePeriod = stableStateTimePeriod;
- this.minDistributorUpRatio = minDistributorUpRatio;
- this.minStorageUpRatio = minStorageUpRatio;
- this.minSplitBits = minSplitBits;
- this.minNodeRatioPerGroup = minNodeRatioPerGroup;
+ this.tuning = tuning;
this.resourceLimits = resourceLimits;
+ this.allowMoreThanOneContentGroupDown = allowMoreThanOneContentGroupDown;
}
@Override
public void getConfig(FleetcontrollerConfig.Builder builder) {
AbstractConfigProducerRoot root = getRoot();
if (root instanceof VespaModel) {
- String zooKeeperAddress =
- root.getAdmin().getZooKeepersConfigProvider().getZooKeepersConnectionSpec();
+ String zooKeeperAddress = root.getAdmin().getZooKeepersConfigProvider().getZooKeepersConnectionSpec();
builder.zookeeper_server(zooKeeperAddress);
} else {
builder.zookeeper_server("");
@@ -116,31 +107,94 @@ public class ClusterControllerConfig extends AnyConfigProducer implements Fleetc
builder.cluster_name(clusterName);
builder.fleet_controller_count(getChildren().size());
- if (initProgressTime != null) {
- builder.init_progress_time((int) initProgressTime.getMilliSeconds());
- }
- if (transitionTime != null) {
- builder.storage_transition_time((int) transitionTime.getMilliSeconds());
- }
- if (maxPrematureCrashes != null) {
- builder.max_premature_crashes(maxPrematureCrashes.intValue());
- }
- if (stableStateTimePeriod != null) {
- builder.stable_state_time_period((int) stableStateTimePeriod.getMilliSeconds());
- }
- if (minDistributorUpRatio != null) {
- builder.min_distributor_up_ratio(minDistributorUpRatio);
- }
- if (minStorageUpRatio != null) {
- builder.min_storage_up_ratio(minStorageUpRatio);
- }
- if (minSplitBits != null) {
- builder.ideal_distribution_bits(minSplitBits);
- }
- if (minNodeRatioPerGroup != null) {
- builder.min_node_ratio_per_group(minNodeRatioPerGroup);
- }
+ tuning.initProgressTime.ifPresent(i -> builder.init_progress_time((int) i.getMilliSeconds()));
+ tuning.transitionTime.ifPresent(t -> builder.storage_transition_time((int) t.getMilliSeconds()));
+ tuning.maxPrematureCrashes.ifPresent(var -> builder.max_premature_crashes(var.intValue()));
+ tuning.stableStateTimePeriod.ifPresent(var -> builder.stable_state_time_period((int) var.getMilliSeconds()));
+ tuning.minDistributorUpRatio.ifPresent(builder::min_distributor_up_ratio);
+ tuning.minStorageUpRatio.ifPresent(builder::min_storage_up_ratio);
+ tuning.minSplitBits.ifPresent(builder::ideal_distribution_bits);
+ tuning.minNodeRatioPerGroup.ifPresent(builder::min_node_ratio_per_group);
+ tuning.maxGroupsAllowedDown.ifPresent(max -> builder.max_number_of_groups_allowed_to_be_down(allowMoreThanOneContentGroupDown ? max : -1));
+
resourceLimits.getConfig(builder);
}
+ public ClusterControllerTuning tuning() {return tuning;}
+
+private static class ClusterControllerTuningBuilder {
+
+ private final Optional<Double> minNodeRatioPerGroup;
+ private final Optional<Duration> initProgressTime;
+ private final Optional<Duration> transitionTime;
+ private final Optional<Long> maxPrematureCrashes;
+ private final Optional<Duration> stableStateTimePeriod;
+ private final Optional<Double> minDistributorUpRatio;
+ private final Optional<Double> minStorageUpRatio;
+ private final Optional<Integer> minSplitBits;
+ final Optional<Integer> maxGroupsAllowedDown;
+
+ ClusterControllerTuningBuilder(ModelElement tuning,
+ Optional<Double> minNodeRatioPerGroup,
+ Optional<Integer> bucketSplittingMinimumBits) {
+ this.minSplitBits = bucketSplittingMinimumBits;
+ this.minNodeRatioPerGroup = minNodeRatioPerGroup;
+ if (tuning == null) {
+ this.initProgressTime = Optional.empty();
+ this.transitionTime = Optional.empty();
+ this.maxPrematureCrashes = Optional.empty();
+ this.stableStateTimePeriod = Optional.empty();
+ this.minDistributorUpRatio = Optional.empty();
+ this.minStorageUpRatio = Optional.empty();
+ this.maxGroupsAllowedDown = Optional.empty();
+ } else {
+ this.initProgressTime = Optional.ofNullable(tuning.childAsDuration("init-progress-time"));
+ this.transitionTime = Optional.ofNullable(tuning.childAsDuration("transition-time"));
+ this.maxPrematureCrashes = Optional.ofNullable(tuning.childAsLong("max-premature-crashes"));
+ this.stableStateTimePeriod = Optional.ofNullable(tuning.childAsDuration("stable-state-period"));
+ this.minDistributorUpRatio = Optional.ofNullable(tuning.childAsDouble("min-distributor-up-ratio"));
+ this.minStorageUpRatio = Optional.ofNullable(tuning.childAsDouble("min-storage-up-ratio"));
+ this.maxGroupsAllowedDown = Optional.ofNullable(tuning.childAsInteger("max-groups-allowed-down"));
+ }
+ }
+
+ private ClusterControllerTuning build() {
+ return new ClusterControllerTuning(initProgressTime,
+ transitionTime,
+ maxPrematureCrashes,
+ stableStateTimePeriod,
+ minDistributorUpRatio,
+ minStorageUpRatio,
+ maxGroupsAllowedDown,
+ minNodeRatioPerGroup,
+ minSplitBits);
+ }
+
+}
+
+private record ClusterControllerTuning(Optional<Duration> initProgressTime,
+ Optional<Duration> transitionTime,
+ Optional<Long> maxPrematureCrashes,
+ Optional<Duration> stableStateTimePeriod,
+ Optional<Double> minDistributorUpRatio,
+ Optional<Double> minStorageUpRatio,
+ Optional<Integer> maxGroupsAllowedDown,
+ Optional<Double> minNodeRatioPerGroup,
+ Optional<Integer> minSplitBits) {
+
+ public ClusterControllerTuning withMaxGroupsAllowedDown(int maxGroupsAllowedDown) {
+ return new ClusterControllerConfig.ClusterControllerTuning(
+ initProgressTime,
+ transitionTime,
+ maxPrematureCrashes,
+ stableStateTimePeriod,
+ minDistributorUpRatio,
+ minStorageUpRatio,
+ Optional.of(maxGroupsAllowedDown),
+ minNodeRatioPerGroup,
+ minSplitBits);
+ }
+
+}
+
}
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 7f4fc4cd89d..f1d5c7c9220 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
@@ -125,10 +125,6 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
deployState.featureFlags().resourceLimitDisk(),
deployState.featureFlags().resourceLimitMemory())
.build(contentElement);
- c.clusterControllerConfig = new ClusterControllerConfig.Builder(clusterId,
- contentElement,
- resourceLimits.getClusterControllerLimits())
- .build(deployState, c, contentElement.getXml());
c.search = new ContentSearchCluster.Builder(documentDefinitions,
globallyDistributedDocuments,
fractionOfMemoryReserved(clusterId, containers),
@@ -138,6 +134,7 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
c.storageNodes = new StorageCluster.Builder().build(deployState, c, w3cContentElement);
c.distributorNodes = new DistributorCluster.Builder(c).build(deployState, c, w3cContentElement);
c.rootGroup = new StorageGroup.Builder(contentElement, context).buildRootGroup(deployState, redundancyBuilder, c);
+ c.clusterControllerConfig = createClusterControllerConfig(contentElement, deployState, c, resourceLimits);
validateThatGroupSiblingsAreUnique(c.clusterId, c.rootGroup);
c.search.handleRedundancy(c.redundancy);
setupSearchCluster(c.search, contentElement, deployState.getDeployLogger());
@@ -163,6 +160,18 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
return c;
}
+ private ClusterControllerConfig createClusterControllerConfig(ModelElement contentElement,
+ DeployState deployState,
+ ContentCluster c,
+ ClusterResourceLimits resourceLimits) {
+ return new ClusterControllerConfig.Builder(c.clusterId,
+ contentElement,
+ resourceLimits.getClusterControllerLimits(),
+ deployState.featureFlags()
+ .allowMoreThanOneContentGroupDown(new ClusterSpec.Id(c.clusterId)))
+ .build(deployState, c, contentElement.getXml());
+ }
+
private void setupSearchCluster(ContentSearchCluster csc, ModelElement element, DeployLogger logger) {
ContentSearch search = DomContentSearchBuilder.build(element);
Double visibilityDelay = search.getVisibilityDelay();
@@ -434,6 +443,7 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
public final ContentSearchCluster getSearch() { return search; }
public Redundancy redundancy() { return redundancy; }
+
public ContentCluster setRedundancy(Redundancy redundancy) {
this.redundancy = redundancy;
return this;
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelInfo.java b/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelInfo.java
index 7c89a349d7d..1984ceadac6 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelInfo.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/ml/OnnxModelInfo.java
@@ -23,6 +23,7 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
@@ -36,6 +37,8 @@ import java.util.stream.Collectors;
*/
public class OnnxModelInfo {
+ private static final Logger log = Logger.getLogger(OnnxModelInfo.class.getName());
+
private final ApplicationPackage app;
private final String modelPath;
private final String defaultOutput;
@@ -196,15 +199,27 @@ public class OnnxModelInfo {
}
static private String onnxModelToJson(Onnx.ModelProto model, Path path) throws IOException {
+ var initializerNames = model.getGraph().getInitializerList().stream()
+ .map(Onnx.TensorProto::getName).collect(Collectors.toSet());
ByteArrayOutputStream out = new ByteArrayOutputStream();
JsonGenerator g = new JsonFactory().createGenerator(out, JsonEncoding.UTF8);
g.writeStartObject();
g.writeStringField("path", path.toString());
g.writeArrayFieldStart("inputs");
+ int skippedInput = 0;
for (Onnx.ValueInfoProto valueInfo : model.getGraph().getInputList()) {
+ if (initializerNames.contains(valueInfo.getName())) {
+ log.fine(() -> "For '%s': skipping name '%s' as it's an initializer"
+ .formatted(path.getName(), valueInfo.getName()));
+ ++skippedInput;
+ continue;
+ }
onnxTypeToJson(g, valueInfo);
}
+ if (skippedInput > 0)
+ log.info("For '%s': skipped %d inputs that were also listed in initializers"
+ .formatted(path.getName(), skippedInput));
g.writeEndArray();
g.writeArrayFieldStart("outputs");
diff --git a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeResourcesTuning.java b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeResourcesTuning.java
index ee18eceb719..2de06e2053a 100644
--- a/config-model/src/main/java/com/yahoo/vespa/model/search/NodeResourcesTuning.java
+++ b/config-model/src/main/java/com/yahoo/vespa/model/search/NodeResourcesTuning.java
@@ -18,7 +18,6 @@ public class NodeResourcesTuning implements ProtonConfig.Producer {
private final static double SUMMARY_CACHE_SIZE_AS_FRACTION_OF_MEMORY = 0.04;
private final static double MEMORY_GAIN_AS_FRACTION_OF_MEMORY = 0.08;
private final static double MIN_MEMORY_PER_FLUSH_THREAD_GB = 16.0;
- private final static double MAX_FLUSH_THREAD_RATIO = 1.0/8;
private final static double TLS_SIZE_FRACTION = 0.02;
final static long MB = 1024 * 1024;
public final static long GB = MB * 1024;
@@ -94,13 +93,12 @@ public class NodeResourcesTuning implements ProtonConfig.Producer {
}
private void tuneFlushConcurrentThreads(ProtonConfig.Flush.Builder builder) {
+ int max_concurrent = 2; // TODO bring slowly up towards 4
if (usableMemoryGb() < MIN_MEMORY_PER_FLUSH_THREAD_GB) {
- builder.maxconcurrent(1);
+ max_concurrent = 1;
}
- double min_concurrent_mem = usableMemoryGb() / (2*MIN_MEMORY_PER_FLUSH_THREAD_GB);
- double min_concurrent_cpu = resources.vcpu() * MAX_FLUSH_THREAD_RATIO;
- builder.maxconcurrent(Math.min(builder.build().maxconcurrent(),
- (int)Math.ceil(Math.max(min_concurrent_mem, min_concurrent_cpu))));
+ double min_concurrent_mem = usableMemoryGb() / MIN_MEMORY_PER_FLUSH_THREAD_GB;
+ builder.maxconcurrent(Math.min(max_concurrent, (int)Math.ceil(min_concurrent_mem)));
}
private void tuneFlushStrategyTlsSize(ProtonConfig.Flush.Memory.Builder builder) {
diff --git a/config-model/src/main/javacc/SchemaParser.jj b/config-model/src/main/javacc/SchemaParser.jj
index 9d6e16b3f67..9a38fdc673e 100644
--- a/config-model/src/main/javacc/SchemaParser.jj
+++ b/config-model/src/main/javacc/SchemaParser.jj
@@ -1075,15 +1075,16 @@ void attributeSetting(ParsedAttribute attribute) :
void summaryInDocument(ParsedDocumentSummary docsum) :
{
String name;
- ParsedType type;
+ ParsedType type = null;
ParsedSummaryField psf;
}
{
<SUMMARY> name = identifierWithDash() { }
- <TYPE> type = dataType() {
+ (<TYPE> type = dataType())?
+ lbrace() {
psf = new ParsedSummaryField(name, type);
}
- lbrace() (summaryItem(psf) (<NL>)*)* <RBRACE>
+ (summaryItem(psf) (<NL>)*)* <RBRACE>
{
var old = docsum.addField(psf);
if (old != null) {
diff --git a/config-model/src/main/resources/schema/admin.rnc b/config-model/src/main/resources/schema/admin.rnc
index 392572e1f12..98ab2e61783 100644
--- a/config-model/src/main/resources/schema/admin.rnc
+++ b/config-model/src/main/resources/schema/admin.rnc
@@ -112,7 +112,8 @@ LogForwarding = element logforwarding {
attribute splunk-home { xsd:string }? &
attribute deployment-server { xsd:string } &
attribute client-name { xsd:string } &
- attribute phone-home-interval { xsd:positiveInteger }?
+ attribute phone-home-interval { xsd:positiveInteger }? &
+ attribute role { xsd:string }?
}
}
diff --git a/config-model/src/main/resources/schema/content.rnc b/config-model/src/main/resources/schema/content.rnc
index 84338d49314..6486fdacc18 100644
--- a/config-model/src/main/resources/schema/content.rnc
+++ b/config-model/src/main/resources/schema/content.rnc
@@ -81,7 +81,8 @@ ClusterControllerTuning = element cluster-controller {
element max-premature-crashes { xsd:nonNegativeInteger }? &
element stable-state-period { xsd:string { pattern = "([0-9\.]+)\s*([a-z]+)?" } }? &
element min-distributor-up-ratio { xsd:double }? &
- element min-storage-up-ratio { xsd:double }?
+ element min-storage-up-ratio { xsd:double }? &
+ element max-groups-allowed-down { xsd:nonNegativeInteger }?
}
DispatchTuning = element dispatch {
diff --git a/config-model/src/test/derived/hnsw_index/attributes.cfg b/config-model/src/test/derived/hnsw_index/attributes.cfg
index 831327f8a0f..6a03afa7b91 100644
--- a/config-model/src/test/derived/hnsw_index/attributes.cfg
+++ b/config-model/src/test/derived/hnsw_index/attributes.cfg
@@ -22,7 +22,7 @@ attribute[].densepostinglistthreshold 0.4
attribute[].tensortype "tensor(x[128])"
attribute[].imported false
attribute[].maxuncommittedmemory 77777
-attribute[].distancemetric ANGULAR
+attribute[].distancemetric PRENORMALIZED_ANGULAR
attribute[].index.hnsw.enabled true
attribute[].index.hnsw.maxlinkspernode 32
attribute[].index.hnsw.neighborstoexploreatinsert 300
diff --git a/config-model/src/test/derived/hnsw_index/test.sd b/config-model/src/test/derived/hnsw_index/test.sd
index 066c66df298..30f804bae99 100644
--- a/config-model/src/test/derived/hnsw_index/test.sd
+++ b/config-model/src/test/derived/hnsw_index/test.sd
@@ -4,7 +4,7 @@ schema test {
field t1 type tensor(x[128]) {
indexing: attribute | index
attribute {
- distance-metric: angular
+ distance-metric: prenormalized-angular
}
index {
hnsw {
diff --git a/config-model/src/test/derived/indexswitches/ilscripts.cfg b/config-model/src/test/derived/indexswitches/ilscripts.cfg
index 77ac18e3261..5cda0a9fdc7 100644
--- a/config-model/src/test/derived/indexswitches/ilscripts.cfg
+++ b/config-model/src/test/derived/indexswitches/ilscripts.cfg
@@ -4,7 +4,7 @@ ilscript[].doctype "indexswitches"
ilscript[].docfield[] "title"
ilscript[].docfield[] "descr"
ilscript[].docfield[] "source_src"
-ilscript[].content[] "clear_state | guard { input source_src | switch { case \"theweb\": input source_src | tokenize normalize | summary source | index source; case \"amg\": input source_src | tokenize normalize | summary source; default: input source_src . \" partner\" | tokenize normalize | summary source | index source; }; }"
+ilscript[].content[] "clear_state | guard { input source_src | switch { case \"amg\": input source_src | tokenize normalize | summary source; case \"theweb\": input source_src | tokenize normalize | summary source | index source; default: input source_src . \" partner\" | tokenize normalize | summary source | index source; }; }"
ilscript[].content[] "clear_state | guard { input title | tokenize normalize stem:\"BEST\" | summary title | index title; }"
ilscript[].content[] "clear_state | guard { input descr | tokenize normalize stem:\"BEST\" | summary descr | index descr; }"
ilscript[].content[] "input source_src | passthrough source_src"
diff --git a/config-model/src/test/derived/multiplesummaries/index-info.cfg b/config-model/src/test/derived/multiplesummaries/index-info.cfg
index 085a9eb232f..50f2419fc58 100644
--- a/config-model/src/test/derived/multiplesummaries/index-info.cfg
+++ b/config-model/src/test/derived/multiplesummaries/index-info.cfg
@@ -82,8 +82,6 @@ indexinfo[].command[].command "index"
indexinfo[].command[].indexname "h"
indexinfo[].command[].command "multivalue"
indexinfo[].command[].indexname "h"
-indexinfo[].command[].command "string"
-indexinfo[].command[].indexname "h"
indexinfo[].command[].command "type WeightedSet<string>"
indexinfo[].command[].indexname "loc"
indexinfo[].command[].command "index"
diff --git a/config-model/src/test/derived/multiplesummaries/multiplesummaries.sd b/config-model/src/test/derived/multiplesummaries/multiplesummaries.sd
index 51259802a3a..5f93a6e512b 100644
--- a/config-model/src/test/derived/multiplesummaries/multiplesummaries.sd
+++ b/config-model/src/test/derived/multiplesummaries/multiplesummaries.sd
@@ -62,6 +62,7 @@ schema multiplesummaries {
field h type weightedset<string> {
indexing: summary
+ weightedset: create-if-nonexistent
}
field loc type string {
@@ -91,7 +92,7 @@ schema multiplesummaries {
summary e type string {
}
- summary f type array<string> {
+ summary f {
}
summary g type array<int> {
@@ -209,7 +210,7 @@ schema multiplesummaries {
bolding: on
}
- summary c type string {
+ summary c {
}
}
diff --git a/config-model/src/test/derived/nearestneighbor_streaming/test.sd b/config-model/src/test/derived/nearestneighbor_streaming/test.sd
new file mode 100644
index 00000000000..4427fa08ab6
--- /dev/null
+++ b/config-model/src/test/derived/nearestneighbor_streaming/test.sd
@@ -0,0 +1,24 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+schema test {
+ document test {
+ field vec_a type tensor<float>(x[16]) {
+ indexing: attribute
+ }
+ field vec_b type tensor<float>(x[16]) {
+ indexing: attribute
+ attribute {
+ distance-metric: angular
+ }
+ }
+ field vec_c type tensor<float>(m{},x[16]) {
+ indexing: attribute
+ attribute {
+ distance-metric: innerproduct
+ }
+ }
+ # This tensor field can not be used with nearest neighbor search.
+ field vec_d type tensor<float>(x{}) {
+ indexing: attribute
+ }
+ }
+}
diff --git a/config-model/src/test/derived/nearestneighbor_streaming/vsmfields.cfg b/config-model/src/test/derived/nearestneighbor_streaming/vsmfields.cfg
new file mode 100644
index 00000000000..f8b1cf62048
--- /dev/null
+++ b/config-model/src/test/derived/nearestneighbor_streaming/vsmfields.cfg
@@ -0,0 +1,31 @@
+documentverificationlevel 0
+searchall 1
+fieldspec[].name "vec_a"
+fieldspec[].searchmethod NEAREST_NEIGHBOR
+fieldspec[].arg1 "EUCLIDEAN"
+fieldspec[].maxlength 1048576
+fieldspec[].fieldtype ATTRIBUTE
+fieldspec[].name "vec_b"
+fieldspec[].searchmethod NEAREST_NEIGHBOR
+fieldspec[].arg1 "ANGULAR"
+fieldspec[].maxlength 1048576
+fieldspec[].fieldtype ATTRIBUTE
+fieldspec[].name "vec_c"
+fieldspec[].searchmethod NEAREST_NEIGHBOR
+fieldspec[].arg1 "INNERPRODUCT"
+fieldspec[].maxlength 1048576
+fieldspec[].fieldtype ATTRIBUTE
+fieldspec[].name "vec_d"
+fieldspec[].searchmethod NONE
+fieldspec[].arg1 ""
+fieldspec[].maxlength 1048576
+fieldspec[].fieldtype ATTRIBUTE
+documenttype[].name "test"
+documenttype[].index[].name "vec_a"
+documenttype[].index[].field[].name "vec_a"
+documenttype[].index[].name "vec_b"
+documenttype[].index[].field[].name "vec_b"
+documenttype[].index[].name "vec_c"
+documenttype[].index[].field[].name "vec_c"
+documenttype[].index[].name "vec_d"
+documenttype[].index[].field[].name "vec_d"
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 90b4625a282..f1dffe53ad7 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
@@ -15,6 +15,7 @@ import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.core.ApplicationMetadataConfig;
import com.yahoo.search.config.QrStartConfig;
+import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.config.content.core.StorCommunicationmanagerConfig;
import com.yahoo.vespa.config.content.core.StorStatusConfig;
import com.yahoo.vespa.config.search.core.ProtonConfig;
@@ -2352,6 +2353,38 @@ public class ModelProvisioningTest {
}
@Test
+ public void testAllow2ContentGroupsDown() {
+ String servicesXml =
+ "<?xml version='1.0' encoding='utf-8' ?>" +
+ "<services>" +
+ " <container version='1.0' id='qrs'>" +
+ " <nodes count='1'/>" +
+ " </container>" +
+ " <content version='1.0' id='content'>" +
+ " <redundancy>1</redundancy>" +
+ " <documents>" +
+ " <document type='type1' mode='index'/>" +
+ " </documents>" +
+ " <nodes count='4' groups='4'/>" +
+ " <tuning>" +
+ " <cluster-controller>" +
+ " <max-groups-allowed-down>2</max-groups-allowed-down>" +
+ " </cluster-controller>" +
+ " </tuning>" +
+ " </content>" +
+ "</services>";
+ VespaModelTester tester = new VespaModelTester();
+ tester.setModelProperties(new TestProperties().setAllowMoreThanOneContentGroupDown(true));
+ tester.addHosts(9);
+ VespaModel model = tester.createModel(servicesXml, true, new DeployState.Builder()
+ .properties(new TestProperties().setAllowMoreThanOneContentGroupDown(true)));
+
+ var fleetControllerConfigBuilder = new FleetcontrollerConfig.Builder();
+ model.getConfig(fleetControllerConfigBuilder, "admin/standalone/cluster-controllers/0/components/clustercontroller-content-configurer");
+ assertEquals(2, fleetControllerConfigBuilder.build().max_number_of_groups_allowed_to_be_down());
+ }
+
+ @Test
public void containerWithZooKeeperSuboptimalNodeCountDuringRetirement() {
String servicesXml =
"<?xml version='1.0' encoding='utf-8' ?>" +
diff --git a/config-model/src/test/java/com/yahoo/schema/derived/NearestNeighborTestCase.java b/config-model/src/test/java/com/yahoo/schema/derived/NearestNeighborTestCase.java
index b3a0b8d4558..713da6f5cbe 100644
--- a/config-model/src/test/java/com/yahoo/schema/derived/NearestNeighborTestCase.java
+++ b/config-model/src/test/java/com/yahoo/schema/derived/NearestNeighborTestCase.java
@@ -35,4 +35,9 @@ public class NearestNeighborTestCase extends AbstractExportingTestCase {
}
}
+ @Test
+ void test_nearest_neighbor_streaming() throws IOException, ParseException {
+ assertCorrectDeriving("nearestneighbor_streaming");
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
index a8ffc625ee6..b809f25ced2 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/admin/DedicatedAdminV4Test.java
@@ -146,7 +146,7 @@ public class DedicatedAdminV4Test {
" <slobroks><nodes count='2' dedicated='true'/></slobroks>" +
" <logservers><nodes count='1' dedicated='true'/></logservers>" +
" <logforwarding include-admin='true'>" +
- " <splunk deployment-server='foo:123' client-name='foocli' phone-home-interval='900'/>" +
+ " <splunk deployment-server='foo:123' client-name='foocli' phone-home-interval='900' role='athenz://some-domain/role/role-name'/>" +
" </logforwarding>" +
" </admin>" +
"</services>";
@@ -176,6 +176,7 @@ public class DedicatedAdminV4Test {
assertEquals("foocli", config.clientName());
assertEquals("/opt/splunkforwarder", config.splunkHome());
assertEquals(900, config.phoneHomeInterval());
+ assertEquals("some-domain:role.role-name", config.role());
}
// Other host's forwarder
@@ -188,6 +189,7 @@ public class DedicatedAdminV4Test {
assertEquals("foocli", config.clientName());
assertEquals("/opt/splunkforwarder", config.splunkHome());
assertEquals(900, config.phoneHomeInterval());
+ assertEquals("some-domain:role.role-name", config.role());
}
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidatorTest.java
new file mode 100644
index 00000000000..1c22423147c
--- /dev/null
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidatorTest.java
@@ -0,0 +1,60 @@
+package com.yahoo.vespa.model.application.validation;
+
+import com.yahoo.config.application.api.ApplicationPackage;
+import com.yahoo.config.model.NullConfigModelRegistry;
+import com.yahoo.config.model.deploy.DeployState;
+import com.yahoo.config.model.deploy.TestProperties;
+import com.yahoo.config.model.test.MockApplicationPackage;
+import com.yahoo.vespa.model.VespaModel;
+import org.junit.jupiter.api.Test;
+import org.xml.sax.SAXException;
+
+import java.io.IOException;
+
+import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertThrows;
+
+public class ContainerInCloudValidatorTest {
+
+ @Test
+ void failsWhenNoContainerInCloud() throws IOException, SAXException {
+ String noContainer = "";
+ String container = """
+ <container id='routing' version='1.0'>
+ <nodes count='2' />
+ </container>
+ """;
+ runValidatorOnApp(false, container);
+ runValidatorOnApp(false, noContainer);
+ runValidatorOnApp(true, container);
+ assertEquals("Vespa Cloud applications must have at least one container cluster",
+ assertThrows(IllegalArgumentException.class,
+ () -> runValidatorOnApp(true, noContainer))
+ .getMessage());
+ }
+
+ private static void runValidatorOnApp(boolean isHosted, String container) throws IOException, SAXException {
+ String servicesXml = """
+ <services version='1.0'>
+ %s
+ <content id='foo' version='1.0'>
+ <redundancy>2</redundancy>
+ <documents>
+ </documents>
+ <nodes count='2' />
+ </content>
+ </services>
+ """.formatted(container);
+ ApplicationPackage app = new MockApplicationPackage.Builder()
+ .withServices(servicesXml)
+ .build();
+ DeployState deployState = new DeployState.Builder()
+ .applicationPackage(app)
+ .properties(new TestProperties().setHostedVespa(isHosted).setAllowUserFilters(false))
+ .build();
+ VespaModel model = new VespaModel(new NullConfigModelRegistry(), deployState);
+ new ContainerInCloudValidator().validate(model, deployState);
+ }
+
+}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java
index cb535380b18..78d3838d39d 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/ValidationTester.java
@@ -35,7 +35,7 @@ public class ValidationTester {
/** Creates a validation tester with 1 node available (in addition to cluster controllers) */
public ValidationTester() {
- this(4);
+ this(5);
}
/** Creates a validation tester with number of nodes available and the given test properties */
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java
index 5d0a1704a1d..784174a35a0 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/application/validation/change/IndexingModeChangeValidatorTest.java
@@ -79,6 +79,9 @@ public class IndexingModeChangeValidatorTest {
private static String getServices(String indexingMode) {
return "<services version='1.0'>" +
+ " <container id='default-container' version='1.0'>" +
+ " <nodes count='1'/>" +
+ " </container>" +
" <content id='default' version='1.0'>" +
" <redundancy>1</redundancy>" +
" <documents>" +
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
index 488ad9f8484..12a6ac00f48 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java
@@ -70,7 +70,7 @@ public class ContentClusterTest extends ContentBaseTest {
" </proton>" +
" </engine>" +
" <redundancy>15</redundancy>\n" +
- " <group name='root' distribution-key='0'>" +
+ " <group name='root'>" +
" <distribution partitions='1|1|*'/>" +
" <group name='g-1' distribution-key='0'>" +
" <node hostalias='mockhost' distribution-key='0'/>" +
@@ -167,7 +167,7 @@ public class ContentClusterTest extends ContentBaseTest {
<content version='1.0' id='storage'>
<documents/>
<min-redundancy>2</min-redundancy>
- <group name='root' distribution-key='0'>"
+ <group name='root'>"
<distribution partitions='1|*'/>
<group name='g0' distribution-key='0'>
<node hostalias='mockhost' distribution-key='0'/>
@@ -214,7 +214,7 @@ public class ContentClusterTest extends ContentBaseTest {
<content version='1.0' id='storage'>
<documents/>
<min-redundancy>4</min-redundancy>
- <group name='root' distribution-key='0'>"
+ <group name='root'>"
<distribution partitions='1|*'/>
<group name='g0' distribution-key='0'>
<node hostalias='mockhost' distribution-key='0'/>
@@ -1294,4 +1294,48 @@ public class ContentClusterTest extends ContentBaseTest {
clusterControllers);
}
+ @Test
+ void testAllow2GroupsDown() {
+ String services = "<?xml version='1.0' encoding='UTF-8' ?>" +
+ "<services version='1.0'>" +
+ " <container id='default' version='1.0' />" +
+ " <content id='storage' version='1.0'>" +
+ " <redundancy>4</redundancy>" +
+ " <documents>" +
+ " <document mode='index' type='type1' />" +
+ " </documents>" +
+ " <group name='root'>" +
+ " <distribution partitions='1|1|1|*'/>" +
+ " <group name='g-1' distribution-key='0'>" +
+ " <node hostalias='mockhost' distribution-key='0'/>" +
+ " </group>" +
+ " <group name='g-2' distribution-key='1'>" +
+ " <node hostalias='mockhost' distribution-key='1'/>" +
+ " </group>" +
+ " <group name='g-3' distribution-key='2'>" +
+ " <node hostalias='mockhost' distribution-key='2'/>" +
+ " </group>" +
+ " <group name='g-4' distribution-key='3'>" +
+ " <node hostalias='mockhost' distribution-key='3'/>" +
+ " </group>" +
+ " </group>" +
+ " <tuning>" +
+ " <cluster-controller>" +
+ " <max-groups-allowed-down>2</max-groups-allowed-down>" +
+ " </cluster-controller>" +
+ " </tuning>" +
+ " <engine>" +
+ " <proton>" +
+ " <searchable-copies>4</searchable-copies>" +
+ " </proton>" +
+ " </engine>" +
+ " </content>" +
+ " </services>";
+ VespaModel model = createEnd2EndOneNode(new TestProperties().setAllowMoreThanOneContentGroupDown(true), services);
+
+ var fleetControllerConfigBuilder = new FleetcontrollerConfig.Builder();
+ model.getConfig(fleetControllerConfigBuilder, "admin/cluster-controllers/0/components/clustercontroller-storage-configurer");
+ assertEquals(2, fleetControllerConfigBuilder.build().max_number_of_groups_allowed_to_be_down());
+ }
+
}
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java b/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java
index 1e6847a47be..138852e1c5c 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java
@@ -1,10 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.model.content;
-import com.yahoo.config.model.api.ModelContext;
import com.yahoo.config.model.deploy.DeployState;
import com.yahoo.config.model.deploy.TestProperties;
import com.yahoo.config.model.test.MockRoot;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.text.XML;
import com.yahoo.vespa.config.content.FleetcontrollerConfig;
import com.yahoo.vespa.model.builder.xml.dom.ModelElement;
@@ -21,13 +21,13 @@ public class FleetControllerClusterTest {
var deployState = new DeployState.Builder().properties(props).build();
MockRoot root = new MockRoot("", deployState);
var clusterElement = new ModelElement(doc.getDocumentElement());
- ModelContext.FeatureFlags featureFlags = new TestProperties();
return new ClusterControllerConfig.Builder("storage",
clusterElement,
new ClusterResourceLimits.Builder(false,
- featureFlags.resourceLimitDisk(),
- featureFlags.resourceLimitMemory())
- .build(clusterElement).getClusterControllerLimits())
+ props.resourceLimitDisk(),
+ props.resourceLimitMemory())
+ .build(clusterElement).getClusterControllerLimits(),
+ props.allowMoreThanOneContentGroupDown(new ClusterSpec.Id("default")))
.build(root.getDeployState(), root, clusterElement.getXml());
}
@@ -38,20 +38,21 @@ public class FleetControllerClusterTest {
@Test
void testParameters() {
FleetcontrollerConfig.Builder builder = new FleetcontrollerConfig.Builder();
- parse("<cluster id=\"storage\">\n" +
- " <documents/>" +
- " <tuning>\n" +
- " <bucket-splitting minimum-bits=\"7\" />" +
- " <cluster-controller>\n" +
- " <init-progress-time>13</init-progress-time>\n" +
- " <transition-time>27</transition-time>\n" +
- " <max-premature-crashes>4</max-premature-crashes>\n" +
- " <stable-state-period>72</stable-state-period>\n" +
- " <min-distributor-up-ratio>0.7</min-distributor-up-ratio>\n" +
- " <min-storage-up-ratio>0.3</min-storage-up-ratio>\n" +
- " </cluster-controller>\n" +
- " </tuning>\n" +
- "</cluster>").
+ parse("""
+ <cluster id="storage">
+ <documents/> <tuning>
+ <bucket-splitting minimum-bits="7" />
+ <cluster-controller>
+ <init-progress-time>13</init-progress-time>
+ <transition-time>27</transition-time>
+ <max-premature-crashes>4</max-premature-crashes>
+ <stable-state-period>72</stable-state-period>
+ <min-distributor-up-ratio>0.7</min-distributor-up-ratio>
+ <min-storage-up-ratio>0.3</min-storage-up-ratio>
+ </cluster-controller>
+ </tuning>
+ </cluster>""",
+ new TestProperties().setAllowMoreThanOneContentGroupDown(true)).
getConfig(builder);
FleetcontrollerConfig config = new FleetcontrollerConfig(builder);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
index 291ae97696b..a7c019f162b 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/content/utils/ContentClusterUtils.java
@@ -60,7 +60,7 @@ public class ContentClusterUtils {
Admin admin = new Admin(root,
new DefaultMonitoring(),
new Metrics(),
- false,
+ root.getDeployState().getProperties().multitenant(),
root.getDeployState().isHosted(),
applicationType);
Document doc = XML.getDocument(clusterXml);
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/search/NodeResourcesTuningTest.java b/config-model/src/test/java/com/yahoo/vespa/model/search/NodeResourcesTuningTest.java
index 9fe38512fc0..d344be3da9a 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/search/NodeResourcesTuningTest.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/search/NodeResourcesTuningTest.java
@@ -182,14 +182,12 @@ public class NodeResourcesTuningTest {
}
@Test
public void require_that_concurrent_flush_threads_is_1_with_low_memory() {
- assertEquals(2, fromMemAndCpu(17, 9).flush().maxconcurrent());
- assertEquals(2, fromMemAndCpu(17, 64).flush().maxconcurrent()); // still capped by max
- assertEquals(2, fromMemAndCpu(65, 8).flush().maxconcurrent()); // still capped by max
- assertEquals(2, fromMemAndCpu(33, 8).flush().maxconcurrent());
- assertEquals(1, fromMemAndCpu(31, 8).flush().maxconcurrent());
- assertEquals(1, fromMemAndCpu(15, 8).flush().maxconcurrent());
- assertEquals(1, fromMemAndCpu(17, 8).flush().maxconcurrent());
+ assertEquals(1, fromMemAndCpu(1, 8).flush().maxconcurrent());
assertEquals(1, fromMemAndCpu(15, 8).flush().maxconcurrent());
+ assertEquals(1, fromMemAndCpu(16, 8).flush().maxconcurrent());
+ assertEquals(2, fromMemAndCpu(17, 8).flush().maxconcurrent());
+ assertEquals(2, fromMemAndCpu(65, 8).flush().maxconcurrent()); // still capped by max
+ assertEquals(2, fromMemAndCpu(65, 65).flush().maxconcurrent()); // still capped by max
}
private static void assertDocumentStoreMaxFileSize(long expFileSizeBytes, int wantedMemoryGb) {
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 48ddf6b8a82..500fb0838e1 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
@@ -21,7 +21,6 @@ import com.yahoo.config.provision.ProvisionLogger;
import com.yahoo.config.provision.Zone;
import com.yahoo.vespa.model.VespaModel;
import com.yahoo.vespa.model.test.utils.VespaModelCreatorWithMockPkg;
-
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
@@ -50,6 +49,7 @@ public class VespaModelTester {
private final ConfigModelRegistry configModelRegistry;
private boolean hosted = true;
+ private TestProperties modelProperties = new TestProperties();
private final Map<NodeResources, Collection<Host>> hostsByResources = new HashMap<>();
private ApplicationId applicationId = ApplicationId.defaultId();
private boolean useDedicatedNodeForLogserver = false;
@@ -101,6 +101,9 @@ public class VespaModelTester {
/** Sets whether this sets up a model for a hosted system. Default: true */
public void setHosted(boolean hosted) { this.hosted = hosted; }
+ /** Sets whether this sets up a model for a hosted system. Default: true */
+ public void setModelProperties(TestProperties testProperties) { this.modelProperties = testProperties; }
+
/** Sets architecture to use for admin clusters. Default: x86_64 */
public void setAdminClusterArchitecture(Architecture architecture) {
this.adminClusterArchitecture = architecture;
@@ -206,7 +209,7 @@ public class VespaModelTester {
provisioner = new SingleNodeProvisioner();
}
- TestProperties properties = new TestProperties()
+ TestProperties properties = modelProperties
.setMultitenant(hosted) // Note: system tests are multitenant but not hosted
.setHostedVespa(hosted)
.setApplicationId(applicationId)
diff --git a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/VespaModelCreatorWithMockPkg.java b/config-model/src/test/java/com/yahoo/vespa/model/test/utils/VespaModelCreatorWithMockPkg.java
index 2c70c7b2da5..0ca3cb4af2e 100644
--- a/config-model/src/test/java/com/yahoo/vespa/model/test/utils/VespaModelCreatorWithMockPkg.java
+++ b/config-model/src/test/java/com/yahoo/vespa/model/test/utils/VespaModelCreatorWithMockPkg.java
@@ -66,7 +66,7 @@ public class VespaModelCreatorWithMockPkg {
try {
this.deployState = deployState;
VespaModel model = new VespaModel(configModelRegistry, deployState);
- Version vespaVersion = new Version(6);
+ Version vespaVersion = new Version(8);
if (validate) {
SchemaValidators validators = new SchemaValidators(vespaVersion);
try {
diff --git a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/FileReferencesAndDownloadsMaintainer.java b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/FileReferencesAndDownloadsMaintainer.java
index 5d5775275c3..17025b10568 100644
--- a/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/FileReferencesAndDownloadsMaintainer.java
+++ b/config-proxy/src/main/java/com/yahoo/vespa/config/proxy/filedistribution/FileReferencesAndDownloadsMaintainer.java
@@ -23,7 +23,9 @@ import java.util.stream.Collectors;
import static java.nio.file.Files.readAttributes;
/**
- * Deletes file references and url downloads that have not been used for some time
+ * Deletes file references and url downloads that have not been used for some time.
+ * See {@link com.yahoo.vespa.config.proxy.filedistribution.RequestTracker} for how we track
+ * when a file reference or download was last used.
*
* @author hmusum
*/
@@ -32,7 +34,7 @@ class FileReferencesAndDownloadsMaintainer implements Runnable {
private static final Logger log = Logger.getLogger(FileReferencesAndDownloadsMaintainer.class.getName());
private static final File defaultUrlDownloadDir = UrlDownloadRpcServer.downloadDir;
private static final File defaultFileReferencesDownloadDir = FileDownloader.defaultDownloadDirectory;
- private static final Duration defaultDurationToKeepFiles = Duration.ofDays(14);
+ private static final Duration defaultDurationToKeepFiles = Duration.ofDays(21);
private static final Duration interval = Duration.ofMinutes(1);
private final ScheduledExecutorService executor =
diff --git a/configdefinitions/src/vespa/attributes.def b/configdefinitions/src/vespa/attributes.def
index 2810284b3a3..4f30e4610ec 100644
--- a/configdefinitions/src/vespa/attributes.def
+++ b/configdefinitions/src/vespa/attributes.def
@@ -37,7 +37,7 @@ attribute[].maxuncommittedmemory long default=130000
# The distance metric to use for nearest neighbor search.
# Is only used when the attribute is a 1-dimensional indexed tensor.
-attribute[].distancemetric enum { EUCLIDEAN, ANGULAR, GEODEGREES, INNERPRODUCT, HAMMING } default=EUCLIDEAN
+attribute[].distancemetric enum { EUCLIDEAN, ANGULAR, GEODEGREES, INNERPRODUCT, HAMMING, PRENORMALIZED_ANGULAR } default=EUCLIDEAN
# Configuration parameters for a hnsw index used together with a 1-dimensional indexed tensor for approximate nearest neighbor search.
attribute[].index.hnsw.enabled bool default=false
diff --git a/configdefinitions/src/vespa/fleetcontroller.def b/configdefinitions/src/vespa/fleetcontroller.def
index 10eb408ed69..93a20e4ee0d 100644
--- a/configdefinitions/src/vespa/fleetcontroller.def
+++ b/configdefinitions/src/vespa/fleetcontroller.def
@@ -64,11 +64,6 @@ init_progress_time int default=0
## we dont change the state too often.
min_time_between_new_systemstates int default=10000
-## Sets how many milliseconds to wait between each state poll for old nodes
-## requiring state polling. (4.1 or older)
-## TODO: Not used, remove in Vespa 9
-state_polling_frequency int default=5000
-
## The maximum amount of premature crashes a node is allowed to have in a row
## before the fleetcontroller disables that node.
max_premature_crashes int default=100000
@@ -181,9 +176,6 @@ min_merge_completion_ratio double default=1.0
## transition logic aims to minimize the window of time where active states diverge.
enable_two_phase_cluster_state_transitions bool default=false
-## Deprecated - not used
-determine_buckets_from_bucket_space_metric bool default=true
-
# If enabled, the cluster controller observes reported (categorized) resource usage from content nodes (via host info),
# and decides whether external feed should be blocked (or unblocked) in the entire cluster.
#
@@ -207,6 +199,7 @@ cluster_feed_block_limit{} double
# This is in absolute numbers, so 0.01 implies that a block limit of 0.8 effectively
# becomes 0.79 for an already blocked node.
cluster_feed_block_noise_level double default=0.0
-# For apps that have several groups this controls how many are allowed to be down
-# simultaneously.
-max_number_of_groups_allowed_to_be_down int default=1
+
+# For apps that have several groups this controls how many groups are allowed to
+# be down simultaneously in this cluster.
+max_number_of_groups_allowed_to_be_down int default=-1
diff --git a/configdefinitions/src/vespa/logforwarder.def b/configdefinitions/src/vespa/logforwarder.def
index 60a607098e0..4f6b3fc61a7 100644
--- a/configdefinitions/src/vespa/logforwarder.def
+++ b/configdefinitions/src/vespa/logforwarder.def
@@ -7,3 +7,4 @@ deploymentServer string default=""
clientName string default=""
splunkHome string default="/opt/splunkforwarder"
phoneHomeInterval int default=60
+role string default=""
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 f6988a6b566..955b1bc8f4f 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
@@ -534,7 +534,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
NestedTransaction transaction = new NestedTransaction();
Optional<ApplicationTransaction> applicationTransaction = hostProvisioner.map(provisioner -> provisioner.lock(applicationId))
.map(lock -> new ApplicationTransaction(lock, transaction));
- try (var applicationLock = tenantApplications.lock(applicationId)) {
+ try (@SuppressWarnings("unused") var applicationLock = tenantApplications.lock(applicationId)) {
Optional<Long> activeSession = tenantApplications.activeSessionOf(applicationId);
CompletionWaiter waiter;
if (activeSession.isPresent()) {
@@ -796,7 +796,7 @@ public class ApplicationRepository implements com.yahoo.config.provision.Deploye
NestedTransaction transaction = new NestedTransaction();
Optional<ApplicationTransaction> applicationTransaction = hostProvisioner.map(provisioner -> provisioner.lock(applicationId))
.map(lock -> new ApplicationTransaction(lock, transaction));
- try (var sessionLock = tenant.getApplicationRepo().lock(applicationId)) {
+ try (@SuppressWarnings("unused") var sessionLock = tenant.getApplicationRepo().lock(applicationId)) {
Optional<Session> activeSession = getActiveSession(applicationId);
var sessionZooKeeperClient = tenant.getSessionRepository().createSessionZooKeeperClient(session.getSessionId());
CompletionWaiter waiter = sessionZooKeeperClient.createActiveWaiter();
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
index 88e3134ccad..cddcb0f316d 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/application/TenantApplications.java
@@ -74,7 +74,7 @@ public class TenantApplications implements RequestHandler, HostValidator {
private final MetricUpdater tenantMetricUpdater;
private final Clock clock;
private final TenantFileSystemDirs tenantFileSystemDirs;
- private final ConfigserverConfig configserverConfig;
+ private final String serverId;
private final ListFlag<String> incompatibleVersions;
public TenantApplications(TenantName tenant, Curator curator, StripedExecutor<TenantName> zkWatcherExecutor,
@@ -95,7 +95,7 @@ public class TenantApplications implements RequestHandler, HostValidator {
this.hostRegistry = hostRegistry;
this.tenantFileSystemDirs = tenantFileSystemDirs;
this.clock = clock;
- this.configserverConfig = configserverConfig;
+ this.serverId = configserverConfig.serverId();
this.incompatibleVersions = PermanentFlags.INCOMPATIBLE_VERSIONS.bindTo(flagSource);
}
@@ -230,7 +230,7 @@ public class TenantApplications implements RequestHandler, HostValidator {
*/
public void activateApplication(ApplicationSet applicationSet, long activeSessionId) {
ApplicationId id = applicationSet.getId();
- try (Lock lock = lock(id)) {
+ try (@SuppressWarnings("unused") Lock lock = lock(id)) {
if ( ! exists(id))
return; // Application was deleted before activation.
if (applicationSet.getApplicationGeneration() != activeSessionId)
@@ -269,7 +269,7 @@ public class TenantApplications implements RequestHandler, HostValidator {
public void removeApplicationsExcept(Set<ApplicationId> applications) {
for (ApplicationId activeApplication : applicationMapper.listApplicationIds()) {
if ( ! applications.contains(activeApplication)) {
- try (var applicationLock = lock(activeApplication)){
+ try (@SuppressWarnings("unused") var applicationLock = lock(activeApplication)){
removeApplication(activeApplication);
}
}
@@ -404,11 +404,11 @@ public class TenantApplications implements RequestHandler, HostValidator {
public TenantFileSystemDirs getTenantFileSystemDirs() { return tenantFileSystemDirs; }
public CompletionWaiter createRemoveApplicationWaiter(ApplicationId applicationId) {
- return RemoveApplicationWaiter.createAndInitialize(curator, applicationId, configserverConfig.serverId());
+ return RemoveApplicationWaiter.createAndInitialize(curator, applicationId, serverId);
}
public CompletionWaiter getRemoveApplicationWaiter(ApplicationId applicationId) {
- return RemoveApplicationWaiter.create(curator, applicationId, configserverConfig.serverId());
+ return RemoveApplicationWaiter.create(curator, applicationId, serverId);
}
/**
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
index 7a2377594a1..62431ce4c06 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/deploy/ModelContextImpl.java
@@ -22,10 +22,10 @@ import com.yahoo.config.provision.AthenzDomain;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.DockerImage;
+import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.Zone;
import com.yahoo.container.jdisc.secretstore.SecretStore;
-import com.yahoo.config.provision.HostName;
import com.yahoo.vespa.config.server.tenant.SecretStoreExternalIdRetriever;
import com.yahoo.vespa.flags.FetchVector;
import com.yahoo.vespa.flags.FlagSource;
@@ -33,7 +33,6 @@ import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.flags.StringFlag;
import com.yahoo.vespa.flags.UnboundFlag;
-
import java.io.File;
import java.net.URI;
import java.security.cert.X509Certificate;
@@ -41,7 +40,7 @@ import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
-import java.util.function.ToIntFunction;
+import java.util.function.Predicate;
import static com.yahoo.config.provision.NodeResources.Architecture;
import static com.yahoo.vespa.config.server.ConfigServerSpec.fromConfig;
@@ -174,7 +173,7 @@ public class ModelContextImpl implements ModelContext {
private final double feedNiceness;
private final List<String> allowedAthenzProxyIdentities;
private final int maxActivationInhibitedOutOfSyncGroups;
- private final ToIntFunction<ClusterSpec.Type> jvmOmitStackTraceInFastThrow;
+ private final Predicate<ClusterSpec.Type> jvmOmitStackTraceInFastThrow;
private final double resourceLimitDisk;
private final double resourceLimitMemory;
private final double minNodeRatioPerGroup;
@@ -204,6 +203,7 @@ public class ModelContextImpl implements ModelContext {
private final int heapPercentage;
private final boolean enableGlobalPhase;
private final String summaryDecodePolicy;
+ private final Predicate<ClusterSpec.Id> allowMoreThanOneContentGroupDown;
public FeatureFlags(FlagSource source, ApplicationId appId, Version version) {
this.defaultTermwiseLimit = flagValue(source, appId, version, Flags.DEFAULT_TERM_WISE_LIMIT);
@@ -219,7 +219,7 @@ public class ModelContextImpl implements ModelContext {
this.mbus_network_threads = flagValue(source, appId, version, Flags.MBUS_NUM_NETWORK_THREADS);
this.allowedAthenzProxyIdentities = flagValue(source, appId, version, Flags.ALLOWED_ATHENZ_PROXY_IDENTITIES);
this.maxActivationInhibitedOutOfSyncGroups = flagValue(source, appId, version, Flags.MAX_ACTIVATION_INHIBITED_OUT_OF_SYNC_GROUPS);
- this.jvmOmitStackTraceInFastThrow = type -> flagValueAsInt(source, appId, version, type, PermanentFlags.JVM_OMIT_STACK_TRACE_IN_FAST_THROW);
+ this.jvmOmitStackTraceInFastThrow = type -> flagValue(source, appId, version, type, PermanentFlags.JVM_OMIT_STACK_TRACE_IN_FAST_THROW);
this.resourceLimitDisk = flagValue(source, appId, version, PermanentFlags.RESOURCE_LIMIT_DISK);
this.resourceLimitMemory = flagValue(source, appId, version, PermanentFlags.RESOURCE_LIMIT_MEMORY);
this.minNodeRatioPerGroup = flagValue(source, appId, version, Flags.MIN_NODE_RATIO_PER_GROUP);
@@ -250,6 +250,7 @@ public class ModelContextImpl implements ModelContext {
this.heapPercentage = flagValue(source, appId, version, PermanentFlags.HEAP_SIZE_PERCENTAGE);
this.enableGlobalPhase = flagValue(source, appId, version, Flags.ENABLE_GLOBAL_PHASE);
this.summaryDecodePolicy = flagValue(source, appId, version, Flags.SUMMARY_DECODE_POLICY);
+ this.allowMoreThanOneContentGroupDown = clusterId -> flagValue(source, appId, version, clusterId, Flags.ALLOW_MORE_THAN_ONE_CONTENT_GROUP_DOWN);
}
@Override public int heapSizePercentage() { return heapPercentage; }
@@ -270,7 +271,7 @@ public class ModelContextImpl implements ModelContext {
@Override public List<String> allowedAthenzProxyIdentities() { return allowedAthenzProxyIdentities; }
@Override public int maxActivationInhibitedOutOfSyncGroups() { return maxActivationInhibitedOutOfSyncGroups; }
@Override public String jvmOmitStackTraceInFastThrowOption(ClusterSpec.Type type) {
- return translateJvmOmitStackTraceInFastThrowIntToString(jvmOmitStackTraceInFastThrow, type);
+ return translateJvmOmitStackTraceInFastThrowToString(jvmOmitStackTraceInFastThrow, type);
}
@Override public double resourceLimitDisk() { return resourceLimitDisk; }
@Override public double resourceLimitMemory() { return resourceLimitMemory; }
@@ -304,6 +305,7 @@ public class ModelContextImpl implements ModelContext {
}
@Override public boolean useRestrictedDataPlaneBindings() { return useRestrictedDataPlaneBindings; }
@Override public boolean enableGlobalPhase() { return enableGlobalPhase; }
+ @Override public boolean allowMoreThanOneContentGroupDown(ClusterSpec.Id id) { return allowMoreThanOneContentGroupDown.test(id); }
private static <V> V flagValue(FlagSource source, ApplicationId appId, Version vespaVersion, UnboundFlag<? extends V, ?, ?> flag) {
return flag.bindTo(source)
@@ -331,17 +333,21 @@ public class ModelContextImpl implements ModelContext {
.boxedValue();
}
- static int flagValueAsInt(FlagSource source,
- ApplicationId appId,
- Version version,
- ClusterSpec.Type clusterType,
- UnboundFlag<? extends Boolean, ?, ?> flag) {
- return flagValue(source, appId, version, clusterType, flag) ? 1 : 0;
+ private static <V> V flagValue(FlagSource source,
+ ApplicationId appId,
+ Version vespaVersion,
+ ClusterSpec.Id clusterId,
+ UnboundFlag<? extends V, ?, ?> flag) {
+ return flag.bindTo(source)
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.serializedForm())
+ .with(FetchVector.Dimension.CLUSTER_ID, clusterId.value())
+ .with(FetchVector.Dimension.VESPA_VERSION, vespaVersion.toFullString())
+ .boxedValue();
}
- private String translateJvmOmitStackTraceInFastThrowIntToString(ToIntFunction<ClusterSpec.Type> function,
- ClusterSpec.Type clusterType) {
- return function.applyAsInt(clusterType) == 1 ? "" : "-XX:-OmitStackTraceInFastThrow";
+ private String translateJvmOmitStackTraceInFastThrowToString(Predicate<ClusterSpec.Type> function,
+ ClusterSpec.Type clusterType) {
+ return function.test(clusterType) ? "" : "-XX:-OmitStackTraceInFastThrow";
}
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
index b9118602058..da18c4e4fcc 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java
@@ -175,25 +175,20 @@ public class FileDirectory extends AbstractComponent {
ensureRootExist();
Path tempDestinationDir = uncheck(() -> Files.createTempDirectory(root.toPath(), "writing"));
try {
- // Prepare and verify
logfileInfo(source);
- File destinationDir = destinationDir(reference);
- File tempDestination = new File(tempDestinationDir.toFile(), source.getName());
- if ( ! destinationDir.mkdir())
- log.log(Level.WARNING, () -> "destination dir " + destinationDir + " already exists");
- // Copy files
+ // Copy files to temp dir
+ File tempDestination = new File(tempDestinationDir.toFile(), source.getName());
log.log(Level.FINE, () -> "Copying " + source.getAbsolutePath() + " to " + tempDestination.getAbsolutePath());
if (source.isDirectory())
IOUtils.copyDirectory(source, tempDestination, -1);
else
copyFile(source, tempDestination);
- // Move to final destination
- log.log(Level.FINE, () -> "Moving " + tempDestinationDir + " to " + destinationDir.getAbsolutePath());
- if ( ! tempDestinationDir.toFile().renameTo(destinationDir))
- log.log(Level.WARNING, "Failed moving '" + tempDestinationDir.toFile().getAbsolutePath() +
- "' to '" + tempDestination.getAbsolutePath() + "'.");
+ // Move to destination dir
+ Path destinationDir = destinationDir(reference).toPath();
+ log.log(Level.FINE, () -> "Moving " + tempDestinationDir + " to " + destinationDir);
+ Files.move(tempDestinationDir, destinationDir);
return reference;
} catch (IOException e) {
throw new UncheckedIOException(e);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
index 86c0c90ca12..62a1704b350 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/http/v2/ApplicationHandler.java
@@ -97,8 +97,6 @@ public class ApplicationHandler extends HttpHandler {
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/filedistributionstatus")) return filedistributionStatus(applicationId(path), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/logs")) return logs(applicationId(path), request);
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/deployment")) return deploymentMetrics(applicationId(path));
- // TODO: Remove when all usage has migrated to .../metrics/searchnode
- if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/proton")) return searchNodeMetrics(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/metrics/searchnode")) return searchNodeMetrics(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/reindexing")) return getReindexingStatus(applicationId(path));
if (path.matches("/application/v2/tenant/{tenant}/application/{application}/environment/{ignore}/region/{ignore}/instance/{instance}/service/{service}/{hostname}/status/{*}")) return serviceStatusPage(applicationId(path), path.get("service"), path.get("hostname"), path.getRest(), request);
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
index 2948b82dd96..22ef6cc2547 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ApplicationPackageMaintainer.java
@@ -88,7 +88,7 @@ public class ApplicationPackageMaintainer extends ConfigServerMaintainer {
createLocalSessionIfMissing(applicationId, sessionId);
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
private static FileDownloader createFileDownloader(List<String> otherConfigServersInCluster,
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
index dad687aae67..ef48c3bdb98 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ConfigServerMaintainer.java
@@ -46,9 +46,9 @@ public abstract class ConfigServerMaintainer extends Maintainer {
}
@Override
- public void completed(String job, double successFactor, long durationMs) {
+ public void completed(String job, double successFactorDeviation, long durationMs) {
var context = metric.createContext(Map.of("job", job));
- metric.set("maintenance.successFactor", successFactor, context);
+ metric.set("maintenance.successFactorDeviation", successFactorDeviation, context);
metric.set("maintenance.duration", durationMs, context);
}
diff --git a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ReindexingMaintainer.java b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ReindexingMaintainer.java
index a9672455d09..e81c9b6bcbd 100644
--- a/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ReindexingMaintainer.java
+++ b/configserver/src/main/java/com/yahoo/vespa/config/server/maintenance/ReindexingMaintainer.java
@@ -77,7 +77,7 @@ public class ReindexingMaintainer extends ConfigServerMaintainer {
}
});
}
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
private Supplier<Long> lazyGeneration(Application application) {
diff --git a/configserver/src/main/sh/start-logd b/configserver/src/main/sh/start-logd
index 30aa357de12..3339338cb15 100644
--- a/configserver/src/main/sh/start-logd
+++ b/configserver/src/main/sh/start-logd
@@ -79,7 +79,7 @@ export ROOT
export VESPA_CONFIG_ID="file:${VESPA_HOME}/conf/logd/logd.cfg"
-if [ "$cloudconfig_server__multitenant" = "true" ] || [ "$VESPA_CONFIGSERVER_MULTITENANT" = "true" ]; then
+if [ "$VESPA_CONFIGSERVER_MULTITENANT" = "true" ]; then
PIDFILE_LOGD=var/run/logd.pid
VESPA_SERVICE_NAME=logd
export VESPA_SERVICE_NAME
diff --git a/configserver/src/main/sh/stop-configserver b/configserver/src/main/sh/stop-configserver
index 2ee6ac4fccf..00e3e009e1a 100755
--- a/configserver/src/main/sh/stop-configserver
+++ b/configserver/src/main/sh/stop-configserver
@@ -89,7 +89,7 @@ PIDFILE_LOGD=var/run/logd.pid
VESPA_LOG_TARGET="file:${VESPA_HOME}/logs/vespa/vespa.log"
export VESPA_LOG_TARGET
-if [ "$cloudconfig_server__multitenant" = "true" ] || [ "$VESPA_CONFIGSERVER_MULTITENANT" = "true" ]; then
+if [ "$VESPA_CONFIGSERVER_MULTITENANT" = "true" ]; then
vespa-run-as-vespa-user vespa-runserver -s logd -p $PIDFILE_LOGD -S
fi
diff --git a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java
index 728f3e8510f..2ad04fdd572 100644
--- a/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java
+++ b/configserver/src/test/java/com/yahoo/vespa/config/server/application/TenantApplicationsTest.java
@@ -12,8 +12,8 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.TenantName;
import com.yahoo.text.Utf8;
import com.yahoo.vespa.config.ConfigKey;
-import com.yahoo.vespa.config.server.ConfigServerDB;
import com.yahoo.vespa.config.server.ConfigActivationListener;
+import com.yahoo.vespa.config.server.ConfigServerDB;
import com.yahoo.vespa.config.server.ServerCache;
import com.yahoo.vespa.config.server.deploy.TenantFileSystemDirs;
import com.yahoo.vespa.config.server.host.HostRegistry;
@@ -26,7 +26,6 @@ import com.yahoo.vespa.config.server.tenant.TestTenantRepository;
import com.yahoo.vespa.curator.CompletionTimeoutException;
import com.yahoo.vespa.curator.Curator;
import com.yahoo.vespa.curator.mock.MockCurator;
-import com.yahoo.vespa.curator.mock.MockCuratorFramework;
import com.yahoo.vespa.flags.InMemoryFlagSource;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.model.VespaModel;
@@ -37,22 +36,17 @@ import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.xml.sax.SAXException;
-
import java.io.File;
import java.io.IOException;
import java.time.Clock;
import java.time.Duration;
import java.util.Arrays;
-import java.util.Collection;
-import java.util.LinkedHashMap;
import java.util.List;
-import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.IntStream;
-import static com.yahoo.vespa.config.server.application.TenantApplications.RemoveApplicationWaiter;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
@@ -195,7 +189,6 @@ public class TenantApplicationsTest {
public static class MockConfigActivationListener implements ConfigActivationListener {
public final AtomicInteger activated = new AtomicInteger(0);
final AtomicInteger removed = new AtomicInteger(0);
- final Map<String, Collection<String>> tenantHosts = new LinkedHashMap<>();
@Override
public void configActivated(ApplicationSet application) {
@@ -247,22 +240,22 @@ public class TenantApplicationsTest {
@Test
public void testRemoveApplication2of3Respond() throws InterruptedException {
- Curator curator = new MockCurator3ConfigServers();
- Thread t1 = setupWaiter(curator);
- notifyCompletion(curator, 2);
+ TenantApplications applications = createZKAppRepo(new InMemoryFlagSource());
+ Thread t1 = setupWaiter(applications);
+ notifyCompletion(applications, 2);
t1.join();
}
@Test
public void testRemoveApplicationAllRespond() throws InterruptedException {
- Curator curator = new MockCurator3ConfigServers();
- Thread t1 = setupWaiter(curator);
- notifyCompletion(curator, 3);
+ TenantApplications applications = createZKAppRepo(new InMemoryFlagSource());
+ Thread t1 = setupWaiter(applications);
+ notifyCompletion(applications, 3);
t1.join();
}
- private Thread setupWaiter(Curator curator) {
- Curator.CompletionWaiter waiter = RemoveApplicationWaiter.createAndInitialize(curator, createApplicationId(), "cfg1", Duration.ofSeconds(1));
+ private Thread setupWaiter(TenantApplications applications) {
+ Curator.CompletionWaiter waiter = applications.getRemoveApplicationWaiter(createApplicationId());
Thread t1 = new Thread(() -> {
try {
waiter.awaitCompletion(Duration.ofSeconds(120));
@@ -274,10 +267,10 @@ public class TenantApplicationsTest {
return t1;
}
- private void notifyCompletion(Curator curator, int respondentCount) {
+ private void notifyCompletion(TenantApplications applications, int respondentCount) {
IntStream.range(0, respondentCount)
- .forEach(i -> RemoveApplicationWaiter.create(curator, createApplicationId(), "cfg" + i, Duration.ofSeconds(1))
- .notifyCompletion());
+ .forEach(i -> applications.createRemoveApplicationWaiter(createApplicationId())
+ .notifyCompletion());
}
private TenantApplications createZKAppRepo() {
@@ -332,12 +325,4 @@ public class TenantApplicationsTest {
flagSource);
}
- private static class MockCurator3ConfigServers extends Curator {
-
- public MockCurator3ConfigServers() {
- super("host1:2181,host2:2181,host3:2181", "host1:2181,host2:2181,host3:2181", (retryPolicy) -> new MockCuratorFramework(true, false));
- }
-
- }
-
}
diff --git a/container-disc/abi-spec.json b/container-disc/abi-spec.json
index dd681e4124f..75246a77e03 100644
--- a/container-disc/abi-spec.json
+++ b/container-disc/abi-spec.json
@@ -19,7 +19,8 @@
"public abstract java.util.List getIdentityCertificate()",
"public abstract java.security.cert.X509Certificate getRoleCertificate(java.lang.String, java.lang.String)",
"public abstract java.security.PrivateKey getPrivateKey()",
- "public abstract java.nio.file.Path trustStorePath()"
+ "public abstract java.nio.file.Path trustStorePath()",
+ "public abstract void deconstruct()"
],
"fields" : [ ]
},
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/AthenzIdentityProviderProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/AthenzIdentityProviderProvider.java
index f04e2291ee8..9d2e06ed9da 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/AthenzIdentityProviderProvider.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/AthenzIdentityProviderProvider.java
@@ -89,6 +89,9 @@ public class AthenzIdentityProviderProvider implements Provider<AthenzIdentityPr
public Path trustStorePath() {
throw new UnsupportedOperationException(message);
}
+
+ @Override
+ public void deconstruct() {}
}
}
diff --git a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java
index af5133eceac..46803988b20 100644
--- a/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java
+++ b/container-disc/src/main/java/com/yahoo/container/jdisc/athenz/AthenzIdentityProvider.java
@@ -24,5 +24,5 @@ public interface AthenzIdentityProvider {
X509Certificate getRoleCertificate(String domain, String role);
PrivateKey getPrivateKey();
Path trustStorePath();
-
+ void deconstruct();
}
diff --git a/container-search/abi-spec.json b/container-search/abi-spec.json
index 36531fbf5e1..84411b31274 100644
--- a/container-search/abi-spec.json
+++ b/container-search/abi-spec.json
@@ -209,8 +209,11 @@
],
"methods" : [
"public void <init>(byte[])",
+ "public byte[] value()",
"public int compareTo(com.yahoo.prelude.hitfield.RawBase64)",
"public java.lang.String toString()",
+ "public boolean equals(java.lang.Object)",
+ "public int hashCode()",
"public bridge synthetic int compareTo(java.lang.Object)"
],
"fields" : [ ]
diff --git a/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java b/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
index 88a37ea5a02..92ce6abb319 100644
--- a/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
+++ b/container-search/src/main/java/com/yahoo/prelude/IndexFacts.java
@@ -6,11 +6,11 @@ import com.yahoo.search.Query;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
+import java.util.TreeSet;
import static com.yahoo.text.Lowercase.toLowerCase;
@@ -32,6 +32,16 @@ public class IndexFacts {
private Map<String, List<String>> clusterByDocument;
+ private static class DocumentTypeListOffset {
+ public final int offset;
+ public final SearchDefinition searchDefinition;
+
+ public DocumentTypeListOffset(int offset, SearchDefinition searchDefinition) {
+ this.offset = offset;
+ this.searchDefinition = searchDefinition;
+ }
+ }
+
/** A Map of all known search definitions indexed by name */
private Map<String, SearchDefinition> searchDefinitions = new LinkedHashMap<>();
@@ -100,32 +110,34 @@ public class IndexFacts {
private boolean isIndexFromDocumentTypes(String indexName, List<String> documentTypes) {
if ( ! isInitialized()) return true;
- if (documentTypes.isEmpty()) return unionSearchDefinition.getIndex(indexName) != null;
+ if (documentTypes.isEmpty()) {
+ return unionSearchDefinition.getIndex(indexName) != null;
+ }
- for (String docName : documentTypes) {
- SearchDefinition sd = searchDefinitions.get(docName);
- if (sd != null) {
- Index index = sd.getIndex(indexName);
- if (index != null) return true;
+ DocumentTypeListOffset sd = chooseSearchDefinition(documentTypes, 0);
+ while (sd != null) {
+ Index index = sd.searchDefinition.getIndex(indexName);
+ if (index != null) {
+ return true;
}
+ sd = chooseSearchDefinition(documentTypes, sd.offset);
}
+
return false;
}
private String getCanonicNameFromDocumentTypes(String indexName, List<String> documentTypes) {
if (!isInitialized()) return indexName;
- String lowerCased = toLowerCase(indexName);
if (documentTypes.isEmpty()) {
- Index index = unionSearchDefinition.getIndexByLowerCase(lowerCased);
+ Index index = unionSearchDefinition.getIndexByLowerCase(toLowerCase(indexName));
return index == null ? indexName : index.getName();
}
- for (String docName : documentTypes) {
- SearchDefinition sd = searchDefinitions.get(docName);
- if (sd != null) {
- Index index = sd.getIndexByLowerCase(lowerCased);
- if (index != null) return index.getName();
- }
+ DocumentTypeListOffset sd = chooseSearchDefinition(documentTypes, 0);
+ while (sd != null) {
+ Index index = sd.searchDefinition.getIndexByLowerCase(toLowerCase(indexName));
+ if (index != null) return index.getName();
+ sd = chooseSearchDefinition(documentTypes, sd.offset);
}
return indexName;
}
@@ -146,12 +158,13 @@ public class IndexFacts {
return index;
}
- for (String docName : documentTypes) {
- SearchDefinition sd = searchDefinitions.get(docName);
- if (sd != null) {
- Index index = sd.getIndex(canonicName);
- if (index != null) return index;
- }
+ DocumentTypeListOffset sd = chooseSearchDefinition(documentTypes, 0);
+
+ while (sd != null) {
+ Index index = sd.searchDefinition.getIndex(canonicName);
+
+ if (index != null) return index;
+ sd = chooseSearchDefinition(documentTypes, sd.offset);
}
return Index.nullIndex;
}
@@ -174,7 +187,7 @@ public class IndexFacts {
* Given a search list which is a mixture of document types and cluster
* names, and a restrict list which is a list of document types, return a
* set of all valid document types for this combination. Most use-cases for
- * fetching index settings will involve calling this method with the
+ * fetching index settings will involve calling this method with the the
* incoming query's {@link com.yahoo.search.query.Model#getSources()} and
* {@link com.yahoo.search.query.Model#getRestrict()} as input parameters
* before calling any other method of this class.
@@ -183,20 +196,20 @@ public class IndexFacts {
* @param restrict the restrict list for a query
* @return a (possibly empty) set of valid document types
*/
- private Set<String> resolveDocumentTypes(Collection<String> sources, Set<String> restrict,
+ private Set<String> resolveDocumentTypes(Collection<String> sources, Collection<String> restrict,
Set<String> candidateDocumentTypes) {
sources = emptyCollectionIfNull(sources);
- restrict = emptySetIfNull(restrict);
+ restrict = emptyCollectionIfNull(restrict);
if (sources.isEmpty()) {
if ( ! restrict.isEmpty()) {
- return Set.copyOf(restrict);
+ return new TreeSet<>(restrict);
} else {
return candidateDocumentTypes;
}
}
- Set<String> toSearch = new HashSet<>();
+ Set<String> toSearch = new TreeSet<>();
for (String source : sources) { // source: a document type or a cluster containing them
List<String> clusterDocTypes = clusters.get(source);
if (clusterDocTypes == null) { // source was a document type
@@ -222,8 +235,21 @@ public class IndexFacts {
private Collection<String> emptyCollectionIfNull(Collection<String> collection) {
return collection == null ? List.of() : collection;
}
- private Set<String> emptySetIfNull(Set<String> collection) {
- return collection == null ? Set.of() : collection;
+
+ /**
+ * Chooses the correct search definition, default if in doubt.
+ *
+ * @return the search definition to use
+ */
+ private DocumentTypeListOffset chooseSearchDefinition(List<String> documentTypes, int index) {
+ while (index < documentTypes.size()) {
+ String docName = documentTypes.get(index++);
+ SearchDefinition sd = searchDefinitions.get(docName);
+ if (sd != null) {
+ return new DocumentTypeListOffset(index, sd);
+ }
+ }
+ return null;
}
/**
@@ -253,6 +279,10 @@ public class IndexFacts {
return frozen;
}
+ private void ensureNotFrozen() {
+ if (frozen) throw new IllegalStateException("Tried to modify frozen IndexFacts instance.");
+ }
+
public String getDefaultPosition(String sdName) {
SearchDefinition sd;
if (sdName == null) {
@@ -270,16 +300,12 @@ public class IndexFacts {
return new Session(query);
}
- public Session newSession() {
- return new Session(Set.of(), Set.of());
- }
-
- public Session newSession(Collection<String> sources, Set<String> restrict) {
+ public Session newSession(Collection<String> sources, Collection<String> restrict) {
return new Session(sources, restrict);
}
public Session newSession(Collection<String> sources,
- Set<String> restrict,
+ Collection<String> restrict,
Set<String> candidateDocumentTypes) {
return new Session(sources, restrict, candidateDocumentTypes);
}
@@ -297,12 +323,12 @@ public class IndexFacts {
documentTypes = List.copyOf(resolveDocumentTypes(query));
}
- private Session(Collection<String> sources, Set<String> restrict) {
+ private Session(Collection<String> sources, Collection<String> restrict) {
// Assumption: Search definition name equals document name.
documentTypes = List.copyOf(resolveDocumentTypes(sources, restrict, searchDefinitions.keySet()));
}
- private Session(Collection<String> sources, Set<String> restrict, Set<String> candidateDocumentTypes) {
+ private Session(Collection<String> sources, Collection<String> restrict, Set<String> candidateDocumentTypes) {
documentTypes = List.copyOf(resolveDocumentTypes(sources, restrict, candidateDocumentTypes));
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
index f0e3e3f3e44..46332d632fe 100644
--- a/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
+++ b/container-search/src/main/java/com/yahoo/prelude/cluster/ClusterSearcher.java
@@ -1,8 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.prelude.cluster;
-import com.yahoo.component.ComponentId;
import com.yahoo.component.annotation.Inject;
+import com.yahoo.component.ComponentId;
import com.yahoo.component.chain.dependencies.After;
import com.yahoo.component.provider.ComponentRegistry;
import com.yahoo.container.QrSearchersConfig;
@@ -28,6 +28,10 @@ import com.yahoo.vespa.streamingvisitors.VdsStreamingSearcher;
import com.yahoo.yolean.Exceptions;
import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
@@ -53,7 +57,8 @@ public class ClusterSearcher extends Searcher {
private final String searchClusterName;
- private final SchemaResolver schemaResolver;
+ // The set of document types contained in this search cluster
+ private final Set<String> schemas;
private final long maxQueryTimeout; // in milliseconds
private final long maxQueryCacheTimeout; // in milliseconds
@@ -79,7 +84,7 @@ public class ClusterSearcher extends Searcher {
searchClusterName = clusterConfig.clusterName();
QrSearchersConfig.Searchcluster searchClusterConfig = getSearchClusterConfigFromClusterName(qrsConfig, searchClusterName);
this.globalPhaseRanker = searchClusterConfig.globalphase() ? globalPhaseRanker : null;
- this.schemaResolver = new SchemaResolver(documentDbConfig);
+ schemas = new LinkedHashSet<>();
maxQueryTimeout = ParameterParser.asMilliSeconds(clusterConfig.maxQueryTimeout(), DEFAULT_MAX_QUERY_TIMEOUT);
maxQueryCacheTimeout = ParameterParser.asMilliSeconds(clusterConfig.maxQueryCacheTimeout(), DEFAULT_MAX_QUERY_CACHE_TIMEOUT);
@@ -88,6 +93,9 @@ public class ClusterSearcher extends Searcher {
.com().yahoo().prelude().fastsearch().FastSearcher().docsum()
.defaultclass());
+ for (DocumentdbInfoConfig.Documentdb docDb : documentDbConfig.documentdb())
+ schemas.add(docDb.name());
+
String uniqueServerId = UUID.randomUUID().toString();
if (searchClusterConfig.indexingmode() == STREAMING) {
server = vdsCluster(uniqueServerId, searchClusterIndex,
@@ -149,7 +157,7 @@ public class ClusterSearcher extends Searcher {
/** Do not use, for internal testing purposes only. **/
ClusterSearcher(Set<String> schemas, VespaBackEndSearcher searcher, Executor executor) {
- this.schemaResolver = new SchemaResolver(schemas);
+ this.schemas = schemas;
searchClusterName = "testScenario";
maxQueryTimeout = DEFAULT_MAX_QUERY_TIMEOUT;
maxQueryCacheTimeout = DEFAULT_MAX_QUERY_CACHE_TIMEOUT;
@@ -221,9 +229,8 @@ public class ClusterSearcher extends Searcher {
}
private Result doSearch(Searcher searcher, Query query, Execution execution) {
- var schemas = schemaResolver.resolve(query, execution);
if (schemas.size() > 1) {
- return searchMultipleDocumentTypes(searcher, query, execution, schemas);
+ return searchMultipleDocumentTypes(searcher, query, execution);
} else {
String docType = schemas.iterator().next();
query.getModel().setRestrict(docType);
@@ -259,7 +266,8 @@ public class ClusterSearcher extends Searcher {
}
}
- private Result searchMultipleDocumentTypes(Searcher searcher, Query query, Execution execution, Set<String> schemas) {
+ private Result searchMultipleDocumentTypes(Searcher searcher, Query query, Execution execution) {
+ Set<String> schemas = resolveSchemas(query, execution.context().getIndexFacts());
List<Query> queries = createQueries(query, schemas);
if (queries.size() == 1) {
return perSchemaSearch(searcher, queries.get(0), execution);
@@ -293,7 +301,25 @@ public class ClusterSearcher extends Searcher {
}
Set<String> resolveSchemas(Query query, IndexFacts indexFacts) {
- return schemaResolver.resolve(query, indexFacts);
+ Set<String> restrict = query.getModel().getRestrict();
+ if (restrict == null || restrict.isEmpty()) {
+ Set<String> sources = query.getModel().getSources();
+ return (sources == null || sources.isEmpty())
+ ? schemas
+ : new HashSet<>(indexFacts.newSession(sources, Collections.emptyList(), schemas).documentTypes());
+ } else {
+ return filterValidDocumentTypes(restrict);
+ }
+ }
+
+ private Set<String> filterValidDocumentTypes(Collection<String> restrict) {
+ Set<String> retval = new LinkedHashSet<>();
+ for (String docType : restrict) {
+ if (docType != null && schemas.contains(docType)) {
+ retval.add(docType);
+ }
+ }
+ return retval;
}
private List<Query> createQueries(Query query, Set<String> docTypes) {
diff --git a/container-search/src/main/java/com/yahoo/prelude/cluster/SchemaResolver.java b/container-search/src/main/java/com/yahoo/prelude/cluster/SchemaResolver.java
deleted file mode 100644
index 3a2125d1d38..00000000000
--- a/container-search/src/main/java/com/yahoo/prelude/cluster/SchemaResolver.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-package com.yahoo.prelude.cluster;
-
-import com.yahoo.prelude.IndexFacts;
-import com.yahoo.prelude.fastsearch.DocumentdbInfoConfig;
-import com.yahoo.search.Query;
-import com.yahoo.search.searchchain.Execution;
-
-import java.util.Collection;
-import java.util.LinkedHashSet;
-import java.util.Set;
-
-/**
- * Resolves schemas from query and execution context
- *
- * @author bjorncs
- */
-class SchemaResolver {
-
- private final Set<String> schemas;
-
- SchemaResolver(DocumentdbInfoConfig cfg) {
- this(cfg.documentdb().stream().map(DocumentdbInfoConfig.Documentdb::name).toList());
- }
-
- SchemaResolver(Collection<String> schemas) {
- this.schemas = new LinkedHashSet<>(schemas);
- }
-
- Set<String> resolve(Query query, Execution execution) {
- return resolve(query, execution.context().getIndexFacts());
- }
-
- Set<String> resolve(Query query, IndexFacts indexFacts) {
- if (schemas.size() == 1) return Set.of(schemas.iterator().next());
- var restrict = query.getModel().getRestrict();
- if (restrict == null || restrict.isEmpty()) {
- Set<String> sources = query.getModel().getSources();
- return (sources == null || sources.isEmpty())
- ? schemas
- : new LinkedHashSet<>(indexFacts.newSession(sources, Set.of(), schemas).documentTypes());
- } else {
- return filterValidDocumentTypes(restrict);
- }
- }
-
- private Set<String> filterValidDocumentTypes(Collection<String> restrict) {
- Set<String> retval = new LinkedHashSet<>();
- for (String docType : restrict) {
- if (docType != null && schemas.contains(docType)) {
- retval.add(docType);
- }
- }
- return retval;
- }
-
-}
diff --git a/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java b/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java
index 2071e43f54c..485e2c9a8c3 100644
--- a/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java
+++ b/container-search/src/main/java/com/yahoo/prelude/hitfield/RawBase64.java
@@ -3,16 +3,22 @@ package com.yahoo.prelude.hitfield;
import java.util.Arrays;
import java.util.Base64;
+import java.util.Objects;
/**
+ * Wraps a byte [] and renders it as base64 encoded string
* @author baldersheim
*/
public class RawBase64 implements Comparable<RawBase64> {
+ private final static Base64.Encoder encoder = Base64.getEncoder().withoutPadding();
private final byte[] content;
public RawBase64(byte[] content) {
+ Objects.requireNonNull(content);
this.content = content;
}
+ public byte [] value() { return content; }
+
@Override
public int compareTo(RawBase64 rhs) {
return Arrays.compareUnsigned(content, rhs.content);
@@ -20,6 +26,19 @@ public class RawBase64 implements Comparable<RawBase64> {
@Override
public String toString() {
- return Base64.getEncoder().encodeToString(content);
+ return encoder.encodeToString(content);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+ RawBase64 rawBase64 = (RawBase64) o;
+ return Arrays.equals(content, rawBase64.content);
+ }
+
+ @Override
+ public int hashCode() {
+ return Arrays.hashCode(content);
}
}
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/MultiRangeItem.java b/container-search/src/main/java/com/yahoo/prelude/query/MultiRangeItem.java
index 7ba7a13936f..3dac4cb92c0 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/MultiRangeItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/MultiRangeItem.java
@@ -271,7 +271,7 @@ public class MultiRangeItem<Type extends Number> extends MultiTermItem {
if (endInclusive) metadata |= 0b00000100;
encoder = type.encoderFor(sortedRanges());
- metadata |= encoder.id << 3;
+ metadata |= (byte)(encoder.id << 3);
buffer.put(metadata);
putString(startIndex, buffer);
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/MultiTermItem.java b/container-search/src/main/java/com/yahoo/prelude/query/MultiTermItem.java
index a7ca62d153c..03a661499e0 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/MultiTermItem.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/MultiTermItem.java
@@ -67,8 +67,8 @@ abstract class MultiTermItem extends SimpleTaggableItem {
super.encodeThis(buffer);
byte metadata = 0;
- metadata |= (operatorType().code << 5) & 0b11100000;
- metadata |= ( termType().code ) & 0b00011111;
+ metadata |= (byte)((byte)(operatorType().code << 5) & (byte)0b11100000);
+ metadata |= (byte)(termType().code & (byte)0b00011111);
buffer.put(metadata);
buffer.putInt(terms());
encodeBlueprint(buffer);
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
index 2bd408220cd..e3b2278475b 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/CustomParser.java
@@ -6,6 +6,7 @@ import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.query.Item;
import com.yahoo.search.query.parser.Parser;
+import java.util.Collections;
import java.util.Set;
/**
@@ -22,7 +23,7 @@ public interface CustomParser extends Parser {
Set<String> toSearch, IndexFacts indexFacts, String defaultIndexName) {
if (indexFacts == null)
indexFacts = new IndexFacts();
- return parse(queryToParse, filterToParse, parsingLanguage, indexFacts.newSession(toSearch, Set.of()), defaultIndexName);
+ return parse(queryToParse, filterToParse, parsingLanguage, indexFacts.newSession(toSearch, Collections.emptySet()), defaultIndexName);
}
Item parse(String queryToParse, String filterToParse, Language parsingLanguage,
diff --git a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
index 9952ec64d13..c1d415b8e27 100644
--- a/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
+++ b/container-search/src/main/java/com/yahoo/prelude/query/parser/Tokenizer.java
@@ -8,6 +8,7 @@ import com.yahoo.prelude.Index;
import com.yahoo.prelude.IndexFacts;
import com.yahoo.prelude.query.Substring;
+import java.util.Collections;
import java.util.List;
import static com.yahoo.prelude.query.parser.Token.Kind.*;
@@ -62,7 +63,7 @@ public final class Tokenizer {
* @return a read-only list of tokens. This list can only be used by this thread
*/
public List<Token> tokenize(String string) {
- return tokenize(string, new IndexFacts().newSession());
+ return tokenize(string, new IndexFacts().newSession(Collections.emptySet(), Collections.emptySet()));
}
/**
@@ -170,10 +171,13 @@ public final class Tokenizer {
// this is a heuristic to check whether we probably have reached the end of an URL element
for (int i = tokens.size() - 1; i >= 0; --i) {
switch (tokens.get(i).kind) {
- case COLON -> { if (i == indexLastExplicitlyChangedAt) return false; }
- case SPACE -> { return true; }
- default -> { }
- // do nothing
+ case COLON:
+ if (i == indexLastExplicitlyChangedAt) return false;
+ break;
+ case SPACE:
+ return true;
+ default:
+ // do nothing
}
}
// really not sure whether we should choose false instead, on cause of the guard at
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/result/BucketGroupId.java b/container-search/src/main/java/com/yahoo/search/grouping/result/BucketGroupId.java
index 05efc134465..0bea390ad63 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/result/BucketGroupId.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/result/BucketGroupId.java
@@ -1,8 +1,6 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.result;
-import static com.yahoo.text.Lowercase.toLowerCase;
-
/**
* This abstract class is used in {@link Group} instances where the identifying expression evaluated to a {@link
* com.yahoo.search.grouping.request.BucketValue}. The range is inclusive-from and exclusive-to.
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/result/HitRenderer.java b/container-search/src/main/java/com/yahoo/search/grouping/result/HitRenderer.java
index 343fea82b6e..91c46960ab0 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/result/HitRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/result/HitRenderer.java
@@ -7,7 +7,6 @@ import com.yahoo.text.Utf8String;
import com.yahoo.text.XMLWriter;
import java.io.IOException;
-import java.util.Arrays;
import java.util.Map;
/**
@@ -63,28 +62,15 @@ public abstract class HitRenderer {
private static void renderGroupId(GroupId id, XMLWriter writer) {
writer.openTag(TAG_GROUP_ID).attribute(ATR_TYPE, id.getTypeName());
- if (id instanceof ValueGroupId) {
- writer.content(getIdValue((ValueGroupId)id), false);
- } else if (id instanceof BucketGroupId) {
- BucketGroupId bucketId = (BucketGroupId)id;
- writer.openTag(TAG_BUCKET_FROM).content(getBucketFrom(bucketId), false).closeTag();
- writer.openTag(TAG_BUCKET_TO).content(getBucketTo(bucketId), false).closeTag();
+ if (id instanceof ValueGroupId<?> valueGroupId) {
+ writer.content(valueGroupId.getValue(), false);
+ } else if (id instanceof BucketGroupId bucketId) {
+ writer.openTag(TAG_BUCKET_FROM).content(bucketId.getFrom(), false).closeTag();
+ writer.openTag(TAG_BUCKET_TO).content(bucketId.getTo(), false).closeTag();
}
writer.closeTag();
}
- private static Object getIdValue(ValueGroupId id) {
- return id instanceof RawId ? Arrays.toString(((RawId)id).getValue()) : id.getValue();
- }
-
- private static Object getBucketFrom(BucketGroupId id) {
- return id instanceof RawBucketId ? Arrays.toString(((RawBucketId)id).getFrom()) : id.getFrom();
- }
-
- private static Object getBucketTo(BucketGroupId id) {
- return id instanceof RawBucketId ? Arrays.toString(((RawBucketId)id).getTo()) : id.getTo();
- }
-
private static void renderContinuations(Map<String, Continuation> continuations, XMLWriter writer) {
for (Map.Entry<String, Continuation> entry : continuations.entrySet()) {
renderContinuation(entry.getKey(), entry.getValue(), writer);
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/result/RawBucketId.java b/container-search/src/main/java/com/yahoo/search/grouping/result/RawBucketId.java
index 9576f548f4a..9b5ad6660b0 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/result/RawBucketId.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/result/RawBucketId.java
@@ -1,7 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.result;
-import java.util.Arrays;
+import com.yahoo.prelude.hitfield.RawBase64;
/**
* This class is used in {@link Group} instances where the identifying
@@ -9,7 +9,7 @@ import java.util.Arrays;
*
* @author Ulf Lilleengen
*/
-public class RawBucketId extends BucketGroupId<byte[]> {
+public class RawBucketId extends BucketGroupId<RawBase64> {
/**
* Constructs a new instance of this class.
@@ -18,6 +18,6 @@ public class RawBucketId extends BucketGroupId<byte[]> {
* @param to The identifying exclusive-to raw buffer.
*/
public RawBucketId(byte[] from, byte[] to) {
- super("raw_bucket", from, Arrays.toString(from), to, Arrays.toString(to));
+ super("raw_bucket", new RawBase64(from), new RawBase64(to));
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/result/RawId.java b/container-search/src/main/java/com/yahoo/search/grouping/result/RawId.java
index de711d0c218..fd0d38c37fd 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/result/RawId.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/result/RawId.java
@@ -1,14 +1,14 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.result;
-import java.util.Arrays;
+import com.yahoo.prelude.hitfield.RawBase64;
/**
* This class is used in {@link Group} instances where the identifying expression evaluated to a {@link Byte} array.
*
* @author Simon Thoresen Hult
*/
-public class RawId extends ValueGroupId<byte[]> {
+public class RawId extends ValueGroupId<RawBase64> {
/**
* Constructs a new instance of this class.
@@ -16,6 +16,6 @@ public class RawId extends ValueGroupId<byte[]> {
* @param value The identifying byte array.
*/
public RawId(byte[] value) {
- super("raw", value, Arrays.toString(value));
+ super("raw", new RawBase64(value));
}
}
diff --git a/container-search/src/main/java/com/yahoo/search/grouping/vespa/ResultBuilder.java b/container-search/src/main/java/com/yahoo/search/grouping/vespa/ResultBuilder.java
index 7f006b098cd..e746706f9c5 100644
--- a/container-search/src/main/java/com/yahoo/search/grouping/vespa/ResultBuilder.java
+++ b/container-search/src/main/java/com/yahoo/search/grouping/vespa/ResultBuilder.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.vespa;
+import com.yahoo.prelude.hitfield.RawBase64;
import com.yahoo.search.grouping.Continuation;
import com.yahoo.search.grouping.GroupingRequest;
import com.yahoo.search.grouping.result.BoolId;
@@ -28,6 +29,7 @@ import com.yahoo.searchlib.aggregation.Hit;
import com.yahoo.searchlib.aggregation.HitsAggregationResult;
import com.yahoo.searchlib.aggregation.MaxAggregationResult;
import com.yahoo.searchlib.aggregation.MinAggregationResult;
+import com.yahoo.searchlib.aggregation.RawData;
import com.yahoo.searchlib.aggregation.StandardDeviationAggregationResult;
import com.yahoo.searchlib.aggregation.SumAggregationResult;
import com.yahoo.searchlib.aggregation.XorAggregationResult;
@@ -169,7 +171,7 @@ class ResultBuilder {
} else {
String label = transform.getLabel(result.getTag());
if (label != null) {
- group.setField(label, newResult(result, tag));
+ group.setField(label, convertResult(newResult(result, tag)));
}
}
}
@@ -228,24 +230,27 @@ class ResultBuilder {
return new RawId(res.getRaw());
} else if (res instanceof StringResultNode) {
return new StringId(res.getString());
- } else if (res instanceof FloatBucketResultNode) {
- FloatBucketResultNode bucketId = (FloatBucketResultNode)res;
+ } else if (res instanceof FloatBucketResultNode bucketId) {
return new DoubleBucketId(bucketId.getFrom(), bucketId.getTo());
- } else if (res instanceof IntegerBucketResultNode) {
- IntegerBucketResultNode bucketId = (IntegerBucketResultNode)res;
+ } else if (res instanceof IntegerBucketResultNode bucketId) {
return new LongBucketId(bucketId.getFrom(), bucketId.getTo());
- } else if (res instanceof StringBucketResultNode) {
- StringBucketResultNode bucketId = (StringBucketResultNode)res;
+ } else if (res instanceof StringBucketResultNode bucketId) {
return new StringBucketId(bucketId.getFrom(), bucketId.getTo());
- } else if (res instanceof RawBucketResultNode) {
- RawBucketResultNode bucketId = (RawBucketResultNode)res;
+ } else if (res instanceof RawBucketResultNode bucketId) {
return new RawBucketId(bucketId.getFrom(), bucketId.getTo());
} else {
throw new UnsupportedOperationException(res.getClass().getName());
}
}
- Object newResult(ExpressionNode execResult, int tag) {
+ private Object convertResult(Object value) {
+ if (value instanceof RawData raw) {
+ return new RawBase64(raw.getData());
+ }
+ return value;
+ }
+
+ private Object newResult(ExpressionNode execResult, int tag) {
if (execResult instanceof AverageAggregationResult) {
return ((AverageAggregationResult)execResult).getAverage().getNumber();
} else if (execResult instanceof CountAggregationResult) {
diff --git a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
index 99c3477274d..01bbef13129 100644
--- a/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
+++ b/container-search/src/main/java/com/yahoo/search/query/profile/compiled/Binding.java
@@ -48,7 +48,7 @@ public class Binding implements Comparable<Binding> {
for (int i = 0; i <= maxDimensions; i++) {
String value = i < dimensionBinding.getDimensions().size() ? dimensionBinding.getValues().get(i) : null;
if (value == null)
- generality += Math.pow(2, maxDimensions - i-1);
+ generality += (int)Math.pow(2, maxDimensions - i - 1);
else
context.put(dimensionBinding.getDimensions().get(i), value);
}
diff --git a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
index b36c8788877..90f4e6ae65c 100644
--- a/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
+++ b/container-search/src/main/java/com/yahoo/search/rendering/JsonRenderer.java
@@ -32,8 +32,6 @@ import com.yahoo.search.grouping.result.AbstractList;
import com.yahoo.search.grouping.result.BucketGroupId;
import com.yahoo.search.grouping.result.Group;
import com.yahoo.search.grouping.result.GroupId;
-import com.yahoo.search.grouping.result.RawBucketId;
-import com.yahoo.search.grouping.result.RawId;
import com.yahoo.search.grouping.result.RootGroup;
import com.yahoo.search.grouping.result.ValueGroupId;
import com.yahoo.search.result.Coverage;
@@ -57,7 +55,6 @@ import java.math.BigDecimal;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
-import java.util.Arrays;
import java.util.Deque;
import java.util.Map;
import java.util.Optional;
@@ -420,31 +417,19 @@ public class JsonRenderer extends AsynchronousSectionedRenderer<Result> {
}
protected void renderGroupMetadata(GroupId id) throws IOException {
- if (!(id instanceof ValueGroupId || id instanceof BucketGroupId)) return;
+ if (!(id instanceof ValueGroupId<?> || id instanceof BucketGroupId)) return;
- if (id instanceof ValueGroupId valueId) {
- generator.writeStringField(GROUPING_VALUE, getIdValue(valueId));
+ if (id instanceof ValueGroupId<?> valueId) {
+ generator.writeStringField(GROUPING_VALUE, valueId.getValue().toString());
} else {
BucketGroupId<?> bucketId = (BucketGroupId<?>) id;
generator.writeObjectFieldStart(BUCKET_LIMITS);
- generator.writeStringField(BUCKET_FROM, getBucketFrom(bucketId));
- generator.writeStringField(BUCKET_TO, getBucketTo(bucketId));
+ generator.writeStringField(BUCKET_FROM, bucketId.getFrom().toString());
+ generator.writeStringField(BUCKET_TO, bucketId.getTo().toString());
generator.writeEndObject();
}
}
- private static String getIdValue(ValueGroupId<?> id) {
- return (id instanceof RawId ? Arrays.toString(((RawId) id).getValue()) : id.getValue()).toString();
- }
-
- private static String getBucketFrom(BucketGroupId<?> id) {
- return (id instanceof RawBucketId ? Arrays.toString(((RawBucketId) id).getFrom()) : id.getFrom()).toString();
- }
-
- private static String getBucketTo(BucketGroupId<?> id) {
- return (id instanceof RawBucketId ? Arrays.toString(((RawBucketId) id).getTo()) : id.getTo()).toString();
- }
-
protected void renderTotalHitCount(Hit hit) throws IOException {
if ( ! (getRecursionLevel() == 1 && hit instanceof HitGroup)) return;
diff --git a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
index 70dff6730ff..409b807c833 100644
--- a/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
+++ b/container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java
@@ -9,9 +9,7 @@ import com.yahoo.documentapi.VisitorDataHandler;
import com.yahoo.documentapi.VisitorParameters;
import com.yahoo.documentapi.VisitorSession;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
-import com.yahoo.documentapi.messagebus.protocol.DocumentSummaryMessage;
import com.yahoo.documentapi.messagebus.protocol.QueryResultMessage;
-import com.yahoo.documentapi.messagebus.protocol.SearchResultMessage;
import com.yahoo.io.GrowableByteBuffer;
import com.yahoo.messagebus.Message;
@@ -300,12 +298,8 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
public void onMessage(Message m, AckToken token) {
if (m instanceof QueryResultMessage qm) {
onQueryResult(qm.getResult(), qm.getSummary());
- } else if (m instanceof SearchResultMessage) {
- onSearchResult(((SearchResultMessage) m).getResult());
- } else if (m instanceof DocumentSummaryMessage dsm) {
- onDocumentSummary(dsm.getResult());
} else {
- throw new UnsupportedOperationException("Received unsupported message " + m + ". VdsVisitor can only accept query result, search result, and documentsummary messages.");
+ throw new UnsupportedOperationException("Received unsupported message " + m + ". VdsVisitor can only accept query result messages.");
}
ack(token);
}
@@ -320,13 +314,6 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
handleSummary(summary);
}
- public void onSearchResult(SearchResult sr) {
- if (log.isLoggable(Level.FINEST)) {
- log.log(Level.FINEST, "Got SearchResult for query with selection " + params.getDocumentSelection());
- }
- handleSearchResult(sr);
- }
-
private void handleSearchResult(SearchResult sr) {
final int hitCountTotal = sr.getTotalHitCount();
final int hitCount = sr.getHitCount();
@@ -377,13 +364,6 @@ class VdsVisitor extends VisitorDataHandler implements Visitor {
}
}
- public void onDocumentSummary(DocumentSummary ds) {
- if (log.isLoggable(Level.FINEST)) {
- log.log(Level.FINEST, "Got DocumentSummary for query with selection " + params.getDocumentSelection());
- }
- handleSummary(ds);
- }
-
private void handleSummary(DocumentSummary ds) {
int summaryCount = ds.getSummaryCount();
if (log.isLoggable(Level.FINE)) {
diff --git a/container-search/src/test/java/com/yahoo/prelude/query/parser/test/TokenizerTestCase.java b/container-search/src/test/java/com/yahoo/prelude/query/parser/test/TokenizerTestCase.java
index 3a6be1521e2..1ff5574ec03 100644
--- a/container-search/src/test/java/com/yahoo/prelude/query/parser/test/TokenizerTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/query/parser/test/TokenizerTestCase.java
@@ -13,6 +13,7 @@ import com.yahoo.prelude.query.parser.Tokenizer;
import org.junit.jupiter.api.Test;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
import static com.yahoo.prelude.query.parser.Token.Kind.COLON;
@@ -28,9 +29,7 @@ import static com.yahoo.prelude.query.parser.Token.Kind.SPACE;
import static com.yahoo.prelude.query.parser.Token.Kind.STAR;
import static com.yahoo.prelude.query.parser.Token.Kind.UNDERSCORE;
import static com.yahoo.prelude.query.parser.Token.Kind.WORD;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
/**
* Tests the tokenizer
@@ -284,7 +283,7 @@ public class TokenizerTestCase {
sd.addIndex(index2);
IndexFacts facts = new IndexFacts(new IndexModel(sd));
- IndexFacts.Session session = facts.newSession();
+ IndexFacts.Session session = facts.newSession(Collections.emptySet(), Collections.emptySet());
Tokenizer tokenizer = new Tokenizer(new SimpleLinguistics());
List<?> tokens = tokenizer.tokenize("normal a:b (normal testexact1:/,%#%&+-+ ) testexact2:ho_/&%&/()/aa*::*& b:c", "default", session);
// tokenizer.print();
@@ -329,7 +328,7 @@ public class TokenizerTestCase {
IndexFacts facts = new IndexFacts(new IndexModel(sd));
Tokenizer tokenizer = new Tokenizer(new SimpleLinguistics());
- IndexFacts.Session session = facts.newSession();
+ IndexFacts.Session session = facts.newSession(Collections.emptySet(), Collections.emptySet());
List<?> tokens = tokenizer.tokenize("normal a:b (normal testexact1:/,%#%&+-+ ) testexact2:ho_/&%&/()/aa*::*&", session);
assertEquals(new Token(WORD, "normal"), tokens.get(0));
assertEquals(new Token(SPACE, " "), tokens.get(1));
@@ -366,7 +365,7 @@ public class TokenizerTestCase {
IndexFacts facts = new IndexFacts(new IndexModel(sd));
Tokenizer tokenizer = new Tokenizer(new SimpleLinguistics());
- IndexFacts.Session session = facts.newSession();
+ IndexFacts.Session session = facts.newSession(Collections.emptySet(), Collections.emptySet());
List<?> tokens = tokenizer.tokenize("normal a:b (normal testexact1:/,%#%&+-+ ) testexact2:ho_/&%&/()/aa*::*", session);
assertEquals(new Token(WORD, "normal"), tokens.get(0));
assertEquals(new Token(SPACE, " "), tokens.get(1));
@@ -403,7 +402,7 @@ public class TokenizerTestCase {
IndexFacts facts = new IndexFacts(new IndexModel(sd));
Tokenizer tokenizer = new Tokenizer(new SimpleLinguistics());
- IndexFacts.Session session = facts.newSession();
+ IndexFacts.Session session = facts.newSession(Collections.emptySet(), Collections.emptySet());
List<?> tokens = tokenizer.tokenize("normal a:b (normal testexact1:!/%#%&+-+ ) testexact2:ho_/&%&/()/aa*::*&b:", session);
assertEquals(new Token(WORD, "normal"), tokens.get(0));
assertEquals(new Token(SPACE, " "), tokens.get(1));
@@ -440,7 +439,7 @@ public class TokenizerTestCase {
sd.addIndex(index2);
IndexFacts indexFacts = new IndexFacts(new IndexModel(sd));
- IndexFacts.Session facts = indexFacts.newSession();
+ IndexFacts.Session facts = indexFacts.newSession(Collections.emptySet(), Collections.emptySet());
Tokenizer tokenizer = new Tokenizer(new SimpleLinguistics());
List<?> tokens = tokenizer.tokenize("normal a:b (normal testexact1:foo) testexact2:bar", facts);
diff --git a/container-search/src/test/java/com/yahoo/prelude/test/IndexFactsTestCase.java b/container-search/src/test/java/com/yahoo/prelude/test/IndexFactsTestCase.java
index dbcb393c922..e6c5a18c9da 100644
--- a/container-search/src/test/java/com/yahoo/prelude/test/IndexFactsTestCase.java
+++ b/container-search/src/test/java/com/yahoo/prelude/test/IndexFactsTestCase.java
@@ -15,12 +15,8 @@ import org.junit.jupiter.api.Test;
import java.util.Collection;
import java.util.List;
import java.util.Map;
-import java.util.Set;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertNull;
-import static org.junit.jupiter.api.Assertions.assertTrue;
+import static org.junit.jupiter.api.Assertions.*;
/**
* Tests using synthetic index names for IndexFacts class.
@@ -184,7 +180,7 @@ public class IndexFactsTestCase {
query.getModel().getSources().add("one");
query.getModel().getRestrict().add("two");
- IndexFacts.Session indexFacts = createIndexFacts().newSession(List.of("clusterOne"), Set.of());
+ IndexFacts.Session indexFacts = createIndexFacts().newSession(List.of("clusterOne"), List.of());
assertTrue(indexFacts.isIndex("a"));
assertFalse(indexFacts.isIndex("b"));
assertTrue(indexFacts.isIndex("d"));
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/result/GroupIdTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/result/GroupIdTestCase.java
index 7b2f0d52742..77ed858b14b 100644
--- a/container-search/src/test/java/com/yahoo/search/grouping/result/GroupIdTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/grouping/result/GroupIdTestCase.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.search.grouping.result;
+import com.yahoo.prelude.hitfield.RawBase64;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
@@ -25,10 +26,10 @@ public class GroupIdTestCase {
assertEquals(9L, rangeId.getTo());
valueId = new RawId(new byte[]{6, 9});
- assertArrayEquals(new byte[]{6, 9}, (byte[]) valueId.getValue());
+ assertEquals(new RawBase64(new byte[]{6, 9}), valueId.getValue());
rangeId = new RawBucketId(new byte[]{6, 9}, new byte[]{9, 6});
- assertArrayEquals(new byte[]{6, 9}, (byte[]) rangeId.getFrom());
- assertArrayEquals(new byte[]{9, 6}, (byte[]) rangeId.getTo());
+ assertEquals(new RawBase64(new byte[]{6, 9}), rangeId.getFrom());
+ assertEquals(new RawBase64(new byte[]{9, 6}), rangeId.getTo());
valueId = new StringId("69");
assertEquals("69", valueId.getValue());
@@ -47,8 +48,8 @@ public class GroupIdTestCase {
assertEquals("group:long:69", new LongId(69L).toString());
assertEquals("group:long_bucket:6:9", new LongBucketId(6L, 9L).toString());
assertEquals("group:null", new NullId().toString());
- assertEquals("group:raw:[6, 9]", new RawId(new byte[]{6, 9}).toString());
- assertEquals("group:raw_bucket:[6, 9]:[9, 6]", new RawBucketId(new byte[]{6, 9}, new byte[]{9, 6}).toString());
+ assertEquals("group:raw:Bgk", new RawId(new byte[]{6, 9}).toString());
+ assertEquals("group:raw_bucket:Bgk:CQY", new RawBucketId(new byte[]{6, 9}, new byte[]{9, 6}).toString());
assertTrue(new RootId(0).toString().startsWith("group:root:"));
assertEquals("group:string:69", new StringId("69").toString());
assertEquals("group:string_bucket:6:9", new StringBucketId("6", "9").toString());
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/result/HitRendererTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/result/HitRendererTestCase.java
index 8e98f49df48..69bd848ebcd 100644
--- a/container-search/src/test/java/com/yahoo/search/grouping/result/HitRendererTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/grouping/result/HitRendererTestCase.java
@@ -57,7 +57,7 @@ public class HitRendererTestCase {
"</group>\n");
assertRender(newGroup(new RawId(Utf8.toBytes("foo"))),
"<group relevance=\"1.0\">\n" +
- "<id type=\"raw\">[102, 111, 111]</id>\n" +
+ "<id type=\"raw\">Zm9v</id>\n" +
"</group>\n");
assertRender(newGroup(new StringId("foo")),
"<group relevance=\"1.0\">\n" +
@@ -85,7 +85,7 @@ public class HitRendererTestCase {
"</group>\n");
assertRender(newGroup(new RawBucketId(Utf8.toBytes("bar"), Utf8.toBytes("baz"))),
"<group relevance=\"1.0\">\n" +
- "<id type=\"raw_bucket\">\n<from>[98, 97, 114]</from>\n<to>[98, 97, 122]</to>\n</id>\n" +
+ "<id type=\"raw_bucket\">\n<from>YmFy</from>\n<to>YmF6</to>\n</id>\n" +
"</group>\n");
}
diff --git a/container-search/src/test/java/com/yahoo/search/grouping/vespa/ResultBuilderTestCase.java b/container-search/src/test/java/com/yahoo/search/grouping/vespa/ResultBuilderTestCase.java
index 019a022b7e6..b0b48bb8731 100644
--- a/container-search/src/test/java/com/yahoo/search/grouping/vespa/ResultBuilderTestCase.java
+++ b/container-search/src/test/java/com/yahoo/search/grouping/vespa/ResultBuilderTestCase.java
@@ -10,12 +10,39 @@ import com.yahoo.search.grouping.result.GroupList;
import com.yahoo.search.grouping.result.HitList;
import com.yahoo.search.result.HitGroup;
import com.yahoo.search.result.Relevance;
-import com.yahoo.searchlib.aggregation.*;
+import com.yahoo.searchlib.aggregation.AggregationResult;
+import com.yahoo.searchlib.aggregation.AverageAggregationResult;
+import com.yahoo.searchlib.aggregation.CountAggregationResult;
+import com.yahoo.searchlib.aggregation.ExpressionCountAggregationResult;
+import com.yahoo.searchlib.aggregation.FS4Hit;
+import com.yahoo.searchlib.aggregation.Group;
+import com.yahoo.searchlib.aggregation.Grouping;
+import com.yahoo.searchlib.aggregation.HitsAggregationResult;
+import com.yahoo.searchlib.aggregation.MaxAggregationResult;
+import com.yahoo.searchlib.aggregation.MinAggregationResult;
+import com.yahoo.searchlib.aggregation.SumAggregationResult;
+import com.yahoo.searchlib.aggregation.XorAggregationResult;
import com.yahoo.searchlib.aggregation.hll.SparseSketch;
-import com.yahoo.searchlib.expression.*;
+import com.yahoo.searchlib.expression.FloatBucketResultNode;
+import com.yahoo.searchlib.expression.FloatResultNode;
+import com.yahoo.searchlib.expression.IntegerBucketResultNode;
+import com.yahoo.searchlib.expression.IntegerResultNode;
+import com.yahoo.searchlib.expression.NullResultNode;
+import com.yahoo.searchlib.expression.RawBucketResultNode;
+import com.yahoo.searchlib.expression.RawResultNode;
+import com.yahoo.searchlib.expression.ResultNode;
+import com.yahoo.searchlib.expression.StringBucketResultNode;
+import com.yahoo.searchlib.expression.StringResultNode;
import org.junit.jupiter.api.Test;
-import java.util.*;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
@@ -33,19 +60,19 @@ public class ResultBuilderTestCase {
assertGroupId("group:6.9", new FloatResultNode(6.9));
assertGroupId("group:69", new IntegerResultNode(69));
assertGroupId("group:null", new NullResultNode());
- assertGroupId("group:[6, 9]", new RawResultNode(new byte[]{6, 9}));
+ assertGroupId("group:Bgk", new RawResultNode(new byte[]{6, 9}));
assertGroupId("group:a", new StringResultNode("a"));
assertGroupId("group:6.9:9.6", new FloatBucketResultNode(6.9, 9.6));
assertGroupId("group:6:9", new IntegerBucketResultNode(6, 9));
assertGroupId("group:a:b", new StringBucketResultNode("a", "b"));
- assertGroupId("group:[6, 9]:[9, 6]", new RawBucketResultNode(new RawResultNode(new byte[]{6, 9}),
+ assertGroupId("group:Bgk:CQY", new RawBucketResultNode(new RawResultNode(new byte[]{6, 9}),
new RawResultNode(new byte[]{9, 6})));
}
@Test
void requireThatUnknownGroupIdThrows() {
assertBuildFail("all(group(a) each(output(count())))",
- Arrays.asList(newGrouping(new Group().setTag(2).setId(new MyResultNode()))),
+ List.of(newGrouping(new Group().setTag(2).setId(new MyResultNode()))),
"com.yahoo.search.grouping.vespa.ResultBuilderTestCase$MyResultNode");
}
@@ -61,9 +88,17 @@ public class ResultBuilderTestCase {
}
@Test
+ void requireThatAllBasicResultsCanBeConverted() {
+ assertResult("69", new MinAggregationResult(new IntegerResultNode(69)));
+ assertResult("69.3", new MinAggregationResult(new FloatResultNode(69.3)));
+ assertResult("69.6", new MinAggregationResult(new StringResultNode("69.6")));
+ assertResult("Bgk", new MinAggregationResult(new RawResultNode(new byte[]{6,9})));
+ }
+
+ @Test
void requireThatUnknownExpressionNodeThrows() {
assertBuildFail("all(group(a) each(output(count())))",
- Arrays.asList(newGrouping(newGroup(2, 2, new MyAggregationResult().setTag(3)))),
+ List.of(newGrouping(newGroup(2, 2, new MyAggregationResult().setTag(3)))),
"com.yahoo.search.grouping.vespa.ResultBuilderTestCase$MyAggregationResult");
}
@@ -127,10 +162,10 @@ public class ResultBuilderTestCase {
@Test
void requireThatParallelResultsAreTransformed() {
assertBuild("all(group(foo) each(output(count())) as(bar) each(output(count())) as(baz))",
- Arrays.asList(new Grouping().setRoot(newGroup(1, 0)),
+ List.of(new Grouping().setRoot(newGroup(1, 0)),
new Grouping().setRoot(newGroup(1, 0))));
assertBuildFail("all(group(foo) each(output(count())) as(bar) each(output(count())) as(baz))",
- Arrays.asList(new Grouping().setRoot(newGroup(2)),
+ List.of(new Grouping().setRoot(newGroup(2)),
new Grouping().setRoot(newGroup(3))),
"Expected 1 group, got 2.");
}
@@ -138,15 +173,15 @@ public class ResultBuilderTestCase {
@Test
void requireThatTagsAreHandledCorrectly() {
assertBuild("all(group(a) each(output(count())))",
- Arrays.asList(newGrouping(
+ List.of(newGrouping(
newGroup(7, new CountAggregationResult(0)))));
}
@Test
void requireThatEmptyBranchesArePruned() {
- assertBuildFail("all()", Collections.<Grouping>emptyList(), "Expected 1 group, got 0.");
- assertBuildFail("all(group(a))", Collections.<Grouping>emptyList(), "Expected 1 group, got 0.");
- assertBuildFail("all(group(a) each())", Collections.<Grouping>emptyList(), "Expected 1 group, got 0.");
+ assertBuildFail("all()", List.of(), "Expected 1 group, got 0.");
+ assertBuildFail("all(group(a))", List.of(), "Expected 1 group, got 0.");
+ assertBuildFail("all(group(a) each())", List.of(), "Expected 1 group, got 0.");
Grouping grouping = newGrouping(newGroup(2, new CountAggregationResult(69).setTag(3)));
String expectedOutput = "RootGroup{id=group:root}[GroupList{label=a}[Group{id=group:2, count()=69}[]]]";
@@ -189,14 +224,14 @@ public class ResultBuilderTestCase {
"HitList{label=bar}[Hit{id=hit:1}, Hit{id=hit:2}]]]]");
assertLayout("all(group(foo) each(each(output(summary())) as(bar)" +
" each(output(summary())) as(baz)))",
- Arrays.asList(newGrouping(newGroup(2, newHitList(3, 2))),
+ List.of(newGrouping(newGroup(2, newHitList(3, 2))),
newGrouping(newGroup(2, newHitList(4, 2)))),
"RootGroup{id=group:root}[GroupList{label=foo}[Group{id=group:2}[" +
"HitList{label=bar}[Hit{id=hit:1}, Hit{id=hit:2}], " +
"HitList{label=baz}[Hit{id=hit:1}, Hit{id=hit:2}]]]]");
assertLayout("all(group(foo) each(each(output(summary())))" +
" each(each(output(summary()))) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, newHitList(3, 2))),
+ List.of(newGrouping(newGroup(2, newHitList(3, 2))),
newGrouping(newGroup(4, newHitList(5, 2)))),
"RootGroup{id=group:root}[" +
"GroupList{label=foo}[Group{id=group:2}[HitList{label=hits}[Hit{id=hit:1}, Hit{id=hit:2}]]], " +
@@ -273,18 +308,18 @@ public class ResultBuilderTestCase {
assertResultCont("all(group(a) max(2) each(output(count())) as(foo)" +
" each(output(count())) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
+ List.of(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
newGrouping(newGroup(4, 2, new CountAggregationResult(4)))),
"[]");
assertResultCont("all(group(a) max(2) each(output(count())) as(foo)" +
" each(output(count())) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
+ List.of(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
newGrouping(newGroup(4, 2, new CountAggregationResult(4)))),
newOffset(newResultId(0), 2, 1),
"[0=1]");
assertResultCont("all(group(a) max(2) each(output(count())) as(foo)" +
" each(output(count())) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
+ List.of(newGrouping(newGroup(2, 1, new CountAggregationResult(1))),
newGrouping(newGroup(4, 2, new CountAggregationResult(4)))),
newComposite(newOffset(newResultId(0), 2, 2),
newOffset(newResultId(1), 4, 1)),
@@ -299,18 +334,18 @@ public class ResultBuilderTestCase {
assertResultCont("all(group(a) each(max(2) each(output(summary()))) as(foo)" +
" each(max(2) each(output(summary()))) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, newHitList(3, 4))),
+ List.of(newGrouping(newGroup(2, newHitList(3, 4))),
newGrouping(newGroup(4, newHitList(5, 4)))),
"[]");
assertResultCont("all(group(a) each(max(2) each(output(summary()))) as(foo)" +
" each(max(2) each(output(summary()))) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, newHitList(3, 4))),
+ List.of(newGrouping(newGroup(2, newHitList(3, 4))),
newGrouping(newGroup(4, newHitList(5, 4)))),
newOffset(newResultId(0, 0, 0), 3, 1),
"[0.0.0=1]");
assertResultCont("all(group(a) each(max(2) each(output(summary()))) as(foo)" +
" each(max(2) each(output(summary()))) as(bar))",
- Arrays.asList(newGrouping(newGroup(2, newHitList(3, 4))),
+ List.of(newGrouping(newGroup(2, newHitList(3, 4))),
newGrouping(newGroup(4, newHitList(5, 4)))),
newComposite(newOffset(newResultId(0, 0, 0), 3, 2),
newOffset(newResultId(1, 0, 0), 5, 1)),
@@ -404,7 +439,7 @@ public class ResultBuilderTestCase {
void requireThatGroupListContinuationsCanBeSetInSiblingGroupLists() {
String request = "all(group(a) max(2) each(output(count())) as(foo)" +
" each(output(count())) as(bar))";
- List<Grouping> result = Arrays.asList(newGrouping(newGroup(2, 1, new CountAggregationResult(1)),
+ List<Grouping> result = List.of(newGrouping(newGroup(2, 1, new CountAggregationResult(1)),
newGroup(2, 2, new CountAggregationResult(2)),
newGroup(2, 3, new CountAggregationResult(3)),
newGroup(2, 4, new CountAggregationResult(4))),
@@ -646,7 +681,7 @@ public class ResultBuilderTestCase {
void requireThatHitListContinuationsCanBeSetInSiblingHitLists() {
String request = "all(group(a) each(max(2) each(output(summary()))) as(foo)" +
" each(max(2) each(output(summary()))) as(bar))";
- List<Grouping> result = Arrays.asList(newGrouping(newGroup(2, newHitList(3, 4))),
+ List<Grouping> result = List.of(newGrouping(newGroup(2, newHitList(3, 4))),
newGrouping(newGroup(4, newHitList(5, 4))));
assertContinuation(request, result, newComposite(newOffset(newResultId(0, 0, 0), 3, 0),
newOffset(newResultId(1, 0, 0), 5, 5)),
@@ -839,7 +874,7 @@ public class ResultBuilderTestCase {
}
private static void assertResultCont(String request, Grouping result, Continuation cont, String expected) {
- assertOutput(request, Arrays.asList(result), cont, new ResultContWriter(), expected);
+ assertOutput(request, List.of(result), cont, new ResultContWriter(), expected);
}
private static void assertResultCont(String request, List<Grouping> result, String expected) {
@@ -851,11 +886,11 @@ public class ResultBuilderTestCase {
}
private static void assertContinuation(String request, Grouping result, String expected) {
- assertOutput(request, Arrays.asList(result), null, new ContinuationWriter(), expected);
+ assertOutput(request, List.of(result), null, new ContinuationWriter(), expected);
}
private static void assertContinuation(String request, Grouping result, Continuation cont, String expected) {
- assertOutput(request, Arrays.asList(result), cont, new ContinuationWriter(), expected);
+ assertOutput(request, List.of(result), cont, new ContinuationWriter(), expected);
}
private static void assertContinuation(String request, List<Grouping> result, Continuation cont, String expected) {
@@ -863,7 +898,7 @@ public class ResultBuilderTestCase {
}
private static void assertLayout(String request, Grouping result, String expected) {
- assertOutput(request, Arrays.asList(result), null, new LayoutWriter(), expected);
+ assertOutput(request, List.of(result), null, new LayoutWriter(), expected);
}
private static void assertLayout(String request, List<Grouping> result, String expected) {
@@ -953,8 +988,7 @@ public class ResultBuilderTestCase {
}
String toString(Continuation cnt) {
- if (cnt instanceof OffsetContinuation) {
- OffsetContinuation off = (OffsetContinuation)cnt;
+ if (cnt instanceof OffsetContinuation off) {
String id = off.getResultId().toString().replace(", ", ".");
return id.substring(5, id.length() - 1) + "=" + off.getOffset();
} else if (cnt instanceof CompositeContinuation) {
diff --git a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
index 0e3ecf1c8cc..28a34ff2f6d 100644
--- a/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
+++ b/container-search/src/test/java/com/yahoo/vespa/streamingvisitors/VdsVisitorTestCase.java
@@ -5,9 +5,7 @@ import com.yahoo.document.fieldset.AllFields;
import com.yahoo.document.select.parser.ParseException;
import com.yahoo.documentapi.*;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
-import com.yahoo.documentapi.messagebus.protocol.DocumentSummaryMessage;
import com.yahoo.documentapi.messagebus.protocol.QueryResultMessage;
-import com.yahoo.documentapi.messagebus.protocol.SearchResultMessage;
import com.yahoo.messagebus.Message;
import com.yahoo.messagebus.Trace;
import com.yahoo.messagebus.routing.Route;
@@ -63,18 +61,6 @@ public class VdsVisitorTestCase {
return qrm;
}
- private SearchResultMessage createSRM(String docId, double rank) {
- SearchResultMessage srm = new SearchResultMessage();
- srm.setSearchResult(createSR(docId, rank));
- return srm;
- }
-
- private DocumentSummaryMessage createDSM(String docId) {
- DocumentSummaryMessage dsm = new DocumentSummaryMessage();
- dsm.setDocumentSummary(createDS(docId));
- return dsm;
- }
-
private Message createM() {
return new Message() {
@Override
@@ -357,15 +343,13 @@ public class VdsVisitorTestCase {
private void supplyResults(VdsVisitor visitor) {
AckToken ackToken = null;
visitor.onMessage(createQRM("id:ns:type::0", 0.3), ackToken);
- visitor.onMessage(createSRM("id:ns:type::1", 1.0), ackToken);
- visitor.onMessage(createSRM("id:ns:type::2", 0.5), ackToken);
- visitor.onMessage(createDSM("id:ns:type::1"), ackToken);
- visitor.onMessage(createDSM("id:ns:type::2"), ackToken);
+ visitor.onMessage(createQRM("id:ns:type::1", 1.0), ackToken);
+ visitor.onMessage(createQRM("id:ns:type::2", 0.5), ackToken);
try {
visitor.onMessage(createM(), ackToken);
assertTrue(false, "Unsupported message did not cause exception");
} catch (UnsupportedOperationException uoe) {
- assertTrue(uoe.getMessage().contains("VdsVisitor can only accept query result, search result, and documentsummary messages"));
+ assertTrue(uoe.getMessage().contains("VdsVisitor can only accept query result messages"));
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
index 854413a4adb..9c29f5c30f4 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/Bill.java
@@ -116,6 +116,10 @@ public class Bill {
return sumResourceValues(LineItem::getDiskCost);
}
+ public BigDecimal sumGpuCost() {
+ return sumResourceValues(LineItem::getGpuCost);
+ }
+
public BigDecimal sumAdditionalCost() {
// anything that is not covered by the cost for resources is "additional" costs
var resourceCosts = sumCpuCost().add(sumMemoryCost()).add(sumDiskCost());
@@ -185,9 +189,11 @@ public class Bill {
private BigDecimal cpuHours;
private BigDecimal memoryHours;
private BigDecimal diskHours;
+ private BigDecimal gpuHours;
private BigDecimal cpuCost;
private BigDecimal memoryCost;
private BigDecimal diskCost;
+ private BigDecimal gpuCost;
private NodeResources.Architecture architecture;
private int majorVersion;
@@ -201,7 +207,7 @@ public class Bill {
}
public LineItem(String id, String description, BigDecimal amount, String plan, String agent, ZonedDateTime addedAt, ZonedDateTime startedAt, ZonedDateTime endedAt, ApplicationId applicationId, ZoneId zoneId,
- BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture, int majorVersion) {
+ BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal gpuHours, BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, BigDecimal gpuCost, NodeResources.Architecture architecture, int majorVersion) {
this(id, description, amount, plan, agent, addedAt);
this.startedAt = startedAt;
this.endedAt = endedAt;
@@ -214,11 +220,13 @@ public class Bill {
this.cpuHours = cpuHours;
this.memoryHours = memoryHours;
this.diskHours = diskHours;
+ this.gpuHours = gpuHours;
this.cpuCost = cpuCost;
this.memoryCost = memoryCost;
this.diskCost = diskCost;
this.architecture = architecture;
this.majorVersion = majorVersion;
+ this.gpuCost = gpuCost;
}
/** The opaque ID of this */
@@ -283,6 +291,10 @@ public class Bill {
return Optional.ofNullable(diskHours);
}
+ public Optional<BigDecimal> getGpuHours() {
+ return Optional.ofNullable(gpuHours);
+ }
+
public Optional<BigDecimal> getCpuCost() {
return Optional.ofNullable(cpuCost);
}
@@ -295,6 +307,10 @@ public class Bill {
return Optional.ofNullable(diskCost);
}
+ public Optional<BigDecimal> getGpuCost() {
+ return Optional.ofNullable(gpuCost);
+ }
+
public Optional<NodeResources.Architecture> getArchitecture() {
return Optional.ofNullable(architecture);
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
index ecce9b7ccfa..89226d3309e 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/billing/PlanRegistryMock.java
@@ -12,9 +12,9 @@ import java.util.stream.Stream;
public class PlanRegistryMock implements PlanRegistry {
- public static final Plan freeTrial = new MockPlan("trial", false, false, 0, 0, 0, 200, "Free Trial - for testing purposes");
- public static final Plan paidPlan = new MockPlan("paid", true, true, "0.09", "0.009", "0.0003", 500, "Paid Plan - for testing purposes");
- public static final Plan nonePlan = new MockPlan("none", false, false, 0, 0, 0, 0, "None Plan - for testing purposes");
+ public static final Plan freeTrial = new MockPlan("trial", false, false, 0, 0, 0, 0, 200, "Free Trial - for testing purposes");
+ public static final Plan paidPlan = new MockPlan("paid", true, true, "0.09", "0.009", "0.0003", "0.075", 500, "Paid Plan - for testing purposes");
+ public static final Plan nonePlan = new MockPlan("none", false, false, 0, 0, 0, 0, 0, "None Plan - for testing purposes");
@Override
public Plan defaultPlan() {
@@ -41,14 +41,18 @@ public class PlanRegistryMock implements PlanRegistry {
private final boolean billed;
private final boolean supported;
- public MockPlan(String planId, boolean billed, boolean supported, double cpuPrice, double memPrice, double dgbPrice, int quota, String description) {
- this(PlanId.from(planId), billed, supported, new MockCostCalculator(cpuPrice, memPrice, dgbPrice), () -> Quota.unlimited().withBudget(quota), description);
+ public MockPlan(String planId, boolean billed, boolean supported, double cpuPrice, double memPrice, double dgbPrice, double gpuPrice, int quota, String description) {
+ this(PlanId.from(planId), billed, supported, new MockCostCalculator(cpuPrice, memPrice, dgbPrice, gpuPrice), () -> createQuota(quota), description);
}
- public MockPlan(String planId, boolean billed, boolean supported, String cpuPrice, String memPrice, String dgbPrice, int quota, String description) {
- this(PlanId.from(planId), billed, supported, new MockCostCalculator(cpuPrice, memPrice, dgbPrice), () -> Quota.unlimited().withBudget(quota), description);
+ public MockPlan(String planId, boolean billed, boolean supported, String cpuPrice, String memPrice, String dgbPrice, String gpuPrice, int quota, String description) {
+ this(PlanId.from(planId), billed, supported, new MockCostCalculator(cpuPrice, memPrice, dgbPrice, gpuPrice), () -> createQuota(quota), description);
}
+ private static Quota createQuota(int quota) {
+ return quota == 0 ? Quota.zero() : Quota.unlimited().withBudget(quota);
+ }
+
public MockPlan(PlanId planId, boolean billed, boolean supported, MockCostCalculator calculator, QuotaCalculator quota, String description) {
this.planId = planId;
this.billed = billed;
@@ -94,19 +98,21 @@ public class PlanRegistryMock implements PlanRegistry {
private final BigDecimal cpuHourCost;
private final BigDecimal memHourCost;
private final BigDecimal dgbHourCost;
+ private final BigDecimal gpuHourCost;
- public MockCostCalculator(String cpuPrice, String memPrice, String dgbPrice) {
- this(new BigDecimal(cpuPrice), new BigDecimal(memPrice), new BigDecimal(dgbPrice));
+ public MockCostCalculator(String cpuPrice, String memPrice, String dgbPrice, String gpuPrice) {
+ this(new BigDecimal(cpuPrice), new BigDecimal(memPrice), new BigDecimal(dgbPrice), new BigDecimal(gpuPrice));
}
- public MockCostCalculator(double cpuPrice, double memPrice, double dgbPrice) {
- this(BigDecimal.valueOf(cpuPrice), BigDecimal.valueOf(memPrice), BigDecimal.valueOf(dgbPrice));
+ public MockCostCalculator(double cpuPrice, double memPrice, double dgbPrice, double gpuPrice) {
+ this(BigDecimal.valueOf(cpuPrice), BigDecimal.valueOf(memPrice), BigDecimal.valueOf(dgbPrice), BigDecimal.valueOf(gpuPrice));
}
- public MockCostCalculator(BigDecimal cpuPrice, BigDecimal memPrice, BigDecimal dgbPrice) {
+ public MockCostCalculator(BigDecimal cpuPrice, BigDecimal memPrice, BigDecimal dgbPrice, BigDecimal gpuPrice) {
this.cpuHourCost = cpuPrice;
this.memHourCost = memPrice;
this.dgbHourCost = dgbPrice;
+ this.gpuHourCost = gpuPrice;
}
@Override
@@ -114,6 +120,7 @@ public class PlanRegistryMock implements PlanRegistry {
var cpuCost = usage.getCpuMillis().multiply(cpuHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
var memCost = usage.getMemoryMillis().multiply(memHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
var dgbCost = usage.getDiskMillis().multiply(dgbHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
+ var gpuCost = usage.getGpuMillis().multiply(gpuHourCost).divide(millisPerHour, RoundingMode.HALF_UP).setScale(2, RoundingMode.HALF_UP);
return new CostInfo(
usage.getApplicationId(),
@@ -121,9 +128,11 @@ public class PlanRegistryMock implements PlanRegistry {
usage.getCpuMillis().divide(millisPerHour, RoundingMode.HALF_UP),
usage.getMemoryMillis().divide(millisPerHour, RoundingMode.HALF_UP),
usage.getDiskMillis().divide(millisPerHour, RoundingMode.HALF_UP),
+ usage.getGpuMillis().divide(millisPerHour, RoundingMode.HALF_UP),
cpuCost,
memCost,
dgbCost,
+ gpuCost,
usage.getArchitecture(),
usage.getMajorVersion()
);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java
index ecea1ce6913..cff61f1a50a 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/certificates/EndpointCertificateValidatorImpl.java
@@ -69,7 +69,9 @@ public class EndpointCertificateValidatorImpl implements EndpointCertificateVali
// Normally because the cert is in the process of being provisioned - this will cause a retry in InternalStepRunner
throw new EndpointCertificateException(EndpointCertificateException.Type.CERT_NOT_AVAILABLE, "Certificate not found in secret store");
} catch (EndpointCertificateException e) {
- log.log(Level.WARNING, "Certificate validation failure for " + serializedInstanceId, e);
+ if (!e.type().equals(EndpointCertificateException.Type.CERT_NOT_AVAILABLE)) { // such failures are normal and will be retried, it takes some time to show up in the secret store
+ log.log(Level.WARNING, "Certificate validation failure for " + serializedInstanceId, e);
+ }
throw e;
} catch (Exception e) {
log.log(Level.WARNING, "Certificate validation failure for " + serializedInstanceId, e);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeFilter.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeFilter.java
index 796ce5da449..65c8e8390c8 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeFilter.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeFilter.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.configserver;
import com.google.common.collect.ImmutableSet;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import java.util.Objects;
@@ -22,15 +23,20 @@ public class NodeFilter {
private final Set<HostName> hostnames;
private final Set<HostName> parentHostnames;
private final Set<ApplicationId> applications;
+ private final Set<ClusterSpec.Id> clusterIds;
+ private final Set<Node.ClusterType> clusterTypes;
private NodeFilter(boolean includeDeprovisioned, Set<Node.State> states, Set<HostName> hostnames,
- Set<HostName> parentHostnames, Set<ApplicationId> applications) {
+ Set<HostName> parentHostnames, Set<ApplicationId> applications,
+ Set<ClusterSpec.Id> clusterIds, Set<Node.ClusterType> clusterTypes) {
this.includeDeprovisioned = includeDeprovisioned;
// Uses Guava Set to preserve insertion order
this.states = ImmutableSet.copyOf(Objects.requireNonNull(states));
this.hostnames = ImmutableSet.copyOf(Objects.requireNonNull(hostnames));
this.parentHostnames = ImmutableSet.copyOf(Objects.requireNonNull(parentHostnames));
this.applications = ImmutableSet.copyOf(Objects.requireNonNull(applications));
+ this.clusterIds = ImmutableSet.copyOf(Objects.requireNonNull(clusterIds));
+ this.clusterTypes = ImmutableSet.copyOf(Objects.requireNonNull(clusterTypes));
if (!includeDeprovisioned && states.contains(Node.State.deprovisioned)) {
throw new IllegalArgumentException("Must include deprovisioned nodes when matching deprovisioned state");
}
@@ -56,8 +62,16 @@ public class NodeFilter {
return applications;
}
+ public Set<ClusterSpec.Id> clusterIds() {
+ return clusterIds;
+ }
+
+ public Set<Node.ClusterType> clusterTypes() {
+ return clusterTypes;
+ }
+
public NodeFilter includeDeprovisioned(boolean includeDeprovisioned) {
- return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications);
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
}
public NodeFilter states(Node.State... states) {
@@ -65,7 +79,7 @@ public class NodeFilter {
}
public NodeFilter states(Set<Node.State> states) {
- return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications);
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
}
public NodeFilter hostnames(HostName... hostnames) {
@@ -73,7 +87,7 @@ public class NodeFilter {
}
public NodeFilter hostnames(Set<HostName> hostnames) {
- return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications);
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
}
public NodeFilter parentHostnames(HostName... parentHostnames) {
@@ -81,7 +95,7 @@ public class NodeFilter {
}
public NodeFilter parentHostnames(Set<HostName> parentHostnames) {
- return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications);
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
}
public NodeFilter applications(ApplicationId... applications) {
@@ -89,12 +103,28 @@ public class NodeFilter {
}
public NodeFilter applications(Set<ApplicationId> applications) {
- return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications);
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
+ }
+
+ public NodeFilter clusterIds(ClusterSpec.Id... clusterIds) {
+ return clusterIds(ImmutableSet.copyOf(clusterIds));
+ }
+
+ public NodeFilter clusterIds(Set<ClusterSpec.Id> clusterIds) {
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
+ }
+
+ public NodeFilter clusterTypes(Node.ClusterType... clusterTypes) {
+ return clusterTypes(ImmutableSet.copyOf(clusterTypes));
+ }
+
+ public NodeFilter clusterTypes(Set<Node.ClusterType> clusterTypes) {
+ return new NodeFilter(includeDeprovisioned, states, hostnames, parentHostnames, applications, clusterIds, clusterTypes);
}
/** A filter which matches all nodes, except deprovisioned ones */
public static NodeFilter all() {
- return new NodeFilter(false, Set.of(), Set.of(), Set.of(), Set.of());
+ return new NodeFilter(false, Set.of(), Set.of(), Set.of(), Set.of(), Set.of(), Set.of());
}
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java
index 4c5a67626ea..485bf627c87 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/configserver/NodeRepository.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.hosted.controller.api.integration.configserver;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.HostName;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.zone.ZoneId;
@@ -11,6 +12,7 @@ import com.yahoo.vespa.hosted.controller.api.integration.noderepository.Applicat
import java.util.List;
import java.util.Map;
+import java.util.Optional;
/**
* Node repository interface intended for use by the controller.
@@ -67,6 +69,9 @@ public interface NodeRepository {
/** Retire given node */
void retire(ZoneId zone, String hostname, boolean wantToRetire, boolean wantToDeprovision);
+ /** Drop all documents on content nodes in the given zone, application and cluster */
+ void dropDocuments(ZoneId zoneId, ApplicationId applicationId, Optional<ClusterSpec.Id> clusterId);
+
/** Update reports for given node. A key with null value clears that report */
void updateReports(ZoneId zone, String hostname, Map<String, String> reports);
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
index 04604ae7007..eb2005bf268 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/deployment/ApplicationVersion.java
@@ -1,11 +1,9 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.api.integration.deployment;
-import ai.vespa.validation.Validation;
import com.yahoo.component.Version;
import java.time.Instant;
-import java.util.Objects;
import java.util.Optional;
import java.util.OptionalLong;
@@ -33,6 +31,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
private final Optional<String> sourceUrl;
private final Optional<String> commit;
private final Optional<String> bundleHash;
+ private final Optional<Instant> obsoleteAt;
private final boolean hasPackage;
private final boolean shouldSkip;
private final Optional<String> description;
@@ -41,7 +40,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
public ApplicationVersion(RevisionId id, Optional<SourceRevision> source, Optional<String> authorEmail,
Optional<Version> compileVersion, Optional<Integer> allowedMajor, Optional<Instant> buildTime,
Optional<String> sourceUrl, Optional<String> commit, Optional<String> bundleHash,
- boolean hasPackage, boolean shouldSkip, Optional<String> description, int risk) {
+ Optional<Instant> obsoleteAt, boolean hasPackage, boolean shouldSkip, Optional<String> description, int risk) {
if (commit.isPresent() && commit.get().length() > 128)
throw new IllegalArgumentException("Commit may not be longer than 128 characters");
@@ -61,6 +60,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
this.sourceUrl = requireNonNull(sourceUrl, "sourceUrl cannot be null");
this.commit = requireNonNull(commit, "commit cannot be null");
this.bundleHash = bundleHash;
+ this.obsoleteAt = obsoleteAt;
this.hasPackage = hasPackage;
this.shouldSkip = shouldSkip;
this.description = description;
@@ -71,19 +71,9 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
return id;
}
- /** Create an application package version from a completed build, without an author email */
- public static ApplicationVersion from(RevisionId id, SourceRevision source) {
- return new ApplicationVersion(id, Optional.of(source), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
- }
-
- /** Creates a version from a completed build, an author email, and build metadata. */
- public static ApplicationVersion from(RevisionId id, SourceRevision source, String authorEmail, Version compileVersion, Instant buildTime) {
- return new ApplicationVersion(id, Optional.of(source), Optional.of(authorEmail), Optional.of(compileVersion), Optional.empty(), Optional.of(buildTime), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
- }
-
/** Creates a minimal version for a development build. */
public static ApplicationVersion forDevelopment(RevisionId id, Optional<Version> compileVersion, Optional<Integer> allowedMajor) {
- return new ApplicationVersion(id, Optional.empty(), Optional.empty(), compileVersion, allowedMajor, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
+ return new ApplicationVersion(id, Optional.empty(), Optional.empty(), compileVersion, allowedMajor, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
}
/** Creates a version from a completed build, an author email, and build metadata. */
@@ -91,7 +81,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
Optional<Version> compileVersion, Optional<Integer> allowedMajor, Optional<Instant> buildTime, Optional<String> sourceUrl,
Optional<String> commit, Optional<String> bundleHash, Optional<String> description, int risk) {
return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime,
- sourceUrl, commit, bundleHash, true, false, description, risk);
+ sourceUrl, commit, bundleHash, Optional.empty(), true, false, description, risk);
}
/** Returns a unique identifier for this version or "unknown" if version is not known */
@@ -150,7 +140,17 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
/** Returns a copy of this without a package stored. */
public ApplicationVersion withoutPackage() {
- return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, false, shouldSkip, description, risk);
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, obsoleteAt, false, shouldSkip, description, risk);
+ }
+
+ /** Returns a copy of this which is obsolete now. */
+ public ApplicationVersion obsoleteAt(Instant now) {
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, Optional.of(now), hasPackage, shouldSkip, description, risk);
+ }
+
+ /** Returns the instant at which this became obsolete, i.e., no longer relevant for automated deployments. */
+ public Optional<Instant> obsoleteAt() {
+ return obsoleteAt;
}
/** Whether we still have the package for this revision. */
@@ -160,7 +160,7 @@ public class ApplicationVersion implements Comparable<ApplicationVersion> {
/** Returns a copy of this which will not be rolled out to production. */
public ApplicationVersion skipped() {
- return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, hasPackage, true, description, risk);
+ return new ApplicationVersion(id, source, authorEmail, compileVersion, allowedMajor, buildTime, sourceUrl, commit, bundleHash, obsoleteAt, hasPackage, true, description, risk);
}
/** Whether we still have the package for this revision. */
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
index 571a7b5a58e..a05d8c9d52b 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/CostInfo.java
@@ -17,24 +17,28 @@ public class CostInfo {
private final BigDecimal cpuHours;
private final BigDecimal memoryHours;
private final BigDecimal diskHours;
+ private final BigDecimal gpuHours;
private final BigDecimal cpuCost;
private final BigDecimal memoryCost;
private final BigDecimal diskCost;
+ private final BigDecimal gpuCost;
private final NodeResources.Architecture architecture;
private final int majorVersion;
public CostInfo(ApplicationId applicationId, ZoneId zoneId,
- BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours,
- BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, NodeResources.Architecture architecture, int majorVersion) {
+ BigDecimal cpuHours, BigDecimal memoryHours, BigDecimal diskHours, BigDecimal gpuHours,
+ BigDecimal cpuCost, BigDecimal memoryCost, BigDecimal diskCost, BigDecimal gpuCost, NodeResources.Architecture architecture, int majorVersion) {
this.applicationId = applicationId;
this.zoneId = zoneId;
this.cpuHours = cpuHours;
this.memoryHours = memoryHours;
this.diskHours = diskHours;
+ this.gpuHours = gpuHours;
this.cpuCost = cpuCost;
this.memoryCost = memoryCost;
this.diskCost = diskCost;
+ this.gpuCost = gpuCost;
this.architecture = architecture;
this.majorVersion = majorVersion;
}
@@ -59,6 +63,10 @@ public class CostInfo {
return diskHours;
}
+ public BigDecimal getGpuHours() {
+ return gpuHours;
+ }
+
public BigDecimal getCpuCost() {
return cpuCost;
}
@@ -71,8 +79,12 @@ public class CostInfo {
return diskCost;
}
+ public BigDecimal getGpuCost() {
+ return gpuCost;
+ }
+
public BigDecimal getTotalCost() {
- return cpuCost.add(memoryCost).add(diskCost);
+ return cpuCost.add(memoryCost).add(diskCost).add(gpuCost);
}
public NodeResources.Architecture getArchitecture() {
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
index 5c9a5817dff..570ca87e538 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceDatabaseClientMock.java
@@ -82,8 +82,8 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient {
a.getMajorVersion(),
BigDecimal.valueOf(a.resources().vcpu()).multiply(d),
BigDecimal.valueOf(a.resources().memoryGb()).multiply(d),
- BigDecimal.valueOf(a.resources().diskGb()).multiply(d)
- );
+ BigDecimal.valueOf(a.resources().diskGb()).multiply(d),
+ BigDecimal.valueOf(a.resources().gpuResources().count() * a.resources().gpuResources().memoryGb()).multiply(d));
})
.toList();
}
@@ -101,8 +101,8 @@ public class ResourceDatabaseClientMock implements ResourceDatabaseClient {
a.getMajorVersion(),
a.getCpuMillis().add(b.getCpuMillis()),
a.getMemoryMillis().add(b.getMemoryMillis()),
- a.getDiskMillis().add(b.getDiskMillis())
- );
+ a.getDiskMillis().add(b.getDiskMillis()),
+ a.getGpuMillis().add(b.getGpuMillis()));
}
@Override
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
index bd1252d0879..0834cf52ec1 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/resource/ResourceUsage.java
@@ -17,16 +17,18 @@ public class ResourceUsage {
private final BigDecimal cpuMillis;
private final BigDecimal memoryMillis;
private final BigDecimal diskMillis;
+ private final BigDecimal gpuMillis;
private final NodeResources.Architecture architecture;
private final int majorVersion;
public ResourceUsage(ApplicationId applicationId, ZoneId zoneId, Plan plan, NodeResources.Architecture architecture,
- int majorVersion, BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis) {
+ int majorVersion, BigDecimal cpuMillis, BigDecimal memoryMillis, BigDecimal diskMillis, BigDecimal gpuMillis) {
this.applicationId = applicationId;
this.zoneId = zoneId;
this.cpuMillis = cpuMillis;
this.memoryMillis = memoryMillis;
this.diskMillis = diskMillis;
+ this.gpuMillis = gpuMillis;
this.plan = plan;
this.architecture = architecture;
this.majorVersion = majorVersion;
@@ -52,6 +54,8 @@ public class ResourceUsage {
return diskMillis;
}
+ public BigDecimal getGpuMillis() { return gpuMillis; }
+
public Plan getPlan() {
return plan;
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/HostAction.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/HostAction.java
index f3bee721343..85c7f78eabc 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/HostAction.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/HostAction.java
@@ -67,6 +67,7 @@ public class HostAction {
OUT_OF_SYNC,
NONE,
RETIRING,
+ READY,
RETIRED,
COMPLETE
}
diff --git a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VcmrReport.java b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VcmrReport.java
index 969e6fb1e01..660f3c50556 100644
--- a/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VcmrReport.java
+++ b/controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/integration/vcmr/VcmrReport.java
@@ -11,7 +11,6 @@ import java.time.ZonedDateTime;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
-import java.util.Objects;
import java.util.Set;
import static com.yahoo.yolean.Exceptions.uncheck;
@@ -47,18 +46,18 @@ public class VcmrReport {
/**
* @return true if list of VCMRs is changed
*/
- public boolean addVcmr(String id, ZonedDateTime plannedStartTime, ZonedDateTime plannedEndtime) {
- var vcmr = new Vcmr(id, plannedStartTime, plannedEndtime);
+ public boolean addVcmr(ChangeRequestSource source) {
+ var vcmr = new Vcmr(source.getId(), source.getStatus().name(), source.getPlannedStartTime(), source.getPlannedEndTime());
if (vcmrs.contains(vcmr))
return false;
// Remove to catch any changes in start/end time
- removeVcmr(id);
+ removeVcmr(source.getId());
return vcmrs.add(vcmr);
}
public boolean removeVcmr(String id) {
- return vcmrs.removeIf(vcmr -> id.equals(vcmr.getId()));
+ return vcmrs.removeIf(vcmr -> id.equals(vcmr.id()));
}
public static String getReportId() {
@@ -93,55 +92,9 @@ public class VcmrReport {
return "VCMRReport{" + vcmrs + "}";
}
- public static class Vcmr {
-
- private String id;
- private ZonedDateTime plannedStartTime;
- private ZonedDateTime plannedEndTime;
-
- Vcmr(@JsonProperty("id") String id,
- @JsonProperty("plannedStartTime") ZonedDateTime plannedStartTime,
- @JsonProperty("plannedEndTime") ZonedDateTime plannedEndTime) {
- this.id = id;
- this.plannedStartTime = plannedStartTime;
- this.plannedEndTime = plannedEndTime;
- }
-
- public String getId() {
- return id;
- }
-
- public ZonedDateTime getPlannedStartTime() {
- return plannedStartTime;
- }
-
- public ZonedDateTime getPlannedEndTime() {
- return plannedEndTime;
- }
-
- @Override
- public boolean equals(Object o) {
- if (this == o) return true;
- if (o == null || getClass() != o.getClass()) return false;
- Vcmr vcmr = (Vcmr) o;
- return Objects.equals(id, vcmr.id) &&
- Objects.equals(plannedStartTime, vcmr.plannedStartTime) &&
- Objects.equals(plannedEndTime, vcmr.plannedEndTime);
- }
-
- @Override
- public int hashCode() {
- return Objects.hash(id, plannedStartTime, plannedEndTime);
- }
-
- @Override
- public String toString() {
- return "VCMR{" +
- "id='" + id + '\'' +
- ", plannedStartTime=" + plannedStartTime +
- ", plannedEndTime=" + plannedEndTime +
- '}';
- }
- }
+ public record Vcmr (@JsonProperty("id") String id,
+ @JsonProperty("status") String status,
+ @JsonProperty("plannedStartTime") ZonedDateTime plannedStartTime,
+ @JsonProperty("plannedEndTime") ZonedDateTime plannedEndTime) {}
}
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 fef29a99a47..ac895022130 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
@@ -173,6 +173,10 @@ enum PathGroup {
Matcher.application,
"/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/node/{node}/service-dump"),
+ dropDocuments(Matcher.tenant,
+ Matcher.application,
+ "/application/v4/tenant/{tenant}/application/{application}/instance/{ignored}/environment/{environment}/region/{region}/drop-documents"),
+
/** Paths used for development deployments. */
developmentDeployment(Matcher.tenant,
Matcher.application,
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 2d4f98dfa8d..9a28226c921 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
@@ -33,7 +33,10 @@ enum Policy {
/** Full access to everything. */
supporter(Privilege.grant(Action.read)
- .on(PathGroup.all())
+ .on(PathGroup.allExcept(PathGroup.classifiedOperator))
+ .in(SystemName.all()),
+ Privilege.grant(Action.all())
+ .on(PathGroup.classifiedOperator)
.in(SystemName.all())),
/** Full access to user management for a tenant in select systems. */
@@ -84,7 +87,7 @@ enum Policy {
/** Read access to application information and settings. */
applicationRead(Privilege.grant(Action.read)
- .on(PathGroup.application, PathGroup.applicationInfo, PathGroup.reindexing, PathGroup.serviceDump)
+ .on(PathGroup.application, PathGroup.applicationInfo, PathGroup.reindexing, PathGroup.serviceDump, PathGroup.dropDocuments)
.in(SystemName.all())),
/** Update access to application information and settings. */
@@ -99,7 +102,7 @@ enum Policy {
/** Full access to application information and settings. */
applicationOperations(Privilege.grant(Action.write())
- .on(PathGroup.applicationInfo, PathGroup.productionRestart, PathGroup.reindexing, PathGroup.serviceDump)
+ .on(PathGroup.applicationInfo, PathGroup.productionRestart, PathGroup.reindexing, PathGroup.serviceDump, PathGroup.dropDocuments)
.in(SystemName.all())),
/** Access to create and delete developer and deploy keys under a tenant. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
index 44ea693d8cd..08a8440fbe2 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/ApplicationController.java
@@ -30,6 +30,7 @@ import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeploymentData
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.identifiers.InstanceId;
import com.yahoo.vespa.hosted.controller.api.integration.billing.BillingController;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.Plan;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ApplicationReindexing;
@@ -78,7 +79,6 @@ import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;
import com.yahoo.yolean.Exceptions;
-
import java.io.ByteArrayInputStream;
import java.security.Principal;
import java.security.cert.X509Certificate;
@@ -1060,4 +1060,14 @@ public class ApplicationController {
collectingAndThen(counting(), Long::intValue)));
}
+ public void verifyPlan(TenantName tenantName) {
+ var planId = controller.serviceRegistry().billingController().getPlan(tenantName);
+ Optional<Plan> plan = controller.serviceRegistry().planRegistry().plan(planId);
+ if (plan.isEmpty())
+ throw new IllegalArgumentException("Tenant '" + tenantName.value() + "' has no plan, not allowed to deploy. See https://cloud.vespa.ai/support");
+ if (plan.get().quota().calculate().equals(Quota.zero()))
+ throw new IllegalArgumentException("Tenant '" + tenantName.value() + "' has a plan '" +
+ plan.get().displayName() + "' with zero quota, not allowed to deploy. See https://cloud.vespa.ai/support");
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
index 64cad599168..5ebb3d53529 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Change.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.hosted.controller.application;
import com.yahoo.component.Version;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import java.util.Objects;
@@ -23,7 +22,7 @@ import static java.util.Objects.requireNonNull;
*/
public final class Change {
- private static final Change empty = new Change(Optional.empty(), Optional.empty(), false);
+ private static final Change empty = new Change(Optional.empty(), Optional.empty(), false, false);
/** The platform version we are upgrading to, or empty if none */
private final Optional<Version> platform;
@@ -32,23 +31,27 @@ public final class Change {
private final Optional<RevisionId> revision;
/** Whether this change is a pin to its contained Vespa version, or to the application's current. */
- private final boolean pinned;
+ private final boolean platformPinned;
- private Change(Optional<Version> platform, Optional<RevisionId> revision, boolean pinned) {
+ /** Whether this change is a pin to its contained application revision, or to the application's current. */
+ private final boolean revisionPinned;
+
+ private Change(Optional<Version> platform, Optional<RevisionId> revision, boolean platformPinned, boolean revisionPinned) {
this.platform = requireNonNull(platform, "platform cannot be null");
this.revision = requireNonNull(revision, "revision cannot be null");
if (revision.isPresent() && ( ! revision.get().isProduction())) {
throw new IllegalArgumentException("Application version to deploy must be a known version");
}
- this.pinned = pinned;
+ this.platformPinned = platformPinned;
+ this.revisionPinned = revisionPinned;
}
public Change withoutPlatform() {
- return new Change(Optional.empty(), revision, pinned);
+ return new Change(Optional.empty(), revision, platformPinned, revisionPinned);
}
public Change withoutApplication() {
- return new Change(platform, Optional.empty(), pinned);
+ return new Change(platform, Optional.empty(), platformPinned, revisionPinned);
}
/** Returns whether a change should currently be deployed */
@@ -58,7 +61,7 @@ public final class Change {
/** Returns whether this is the empty change. */
public boolean isEmpty() {
- return ! hasTargets() && ! pinned;
+ return ! hasTargets() && ! platformPinned && ! revisionPinned;
}
/** Returns the platform version carried by this. */
@@ -67,42 +70,55 @@ public final class Change {
/** Returns the application version carried by this. */
public Optional<RevisionId> revision() { return revision; }
- public boolean isPinned() { return pinned; }
+ public boolean isPlatformPinned() { return platformPinned; }
+
+ public boolean isRevisionPinned() { return revisionPinned; }
/** Returns an instance representing no change */
public static Change empty() { return empty; }
/** Returns a version of this change which replaces or adds this platform change */
public Change with(Version platformVersion) {
- if (pinned)
+ if (platformPinned)
throw new IllegalArgumentException("Not allowed to set a platform version when pinned.");
- return new Change(Optional.of(platformVersion), revision, pinned);
+ return new Change(Optional.of(platformVersion), revision, platformPinned, revisionPinned);
}
/** Returns a version of this change which replaces or adds this revision change */
public Change with(RevisionId revision) {
- return new Change(platform, Optional.of(revision), pinned);
+ if (revisionPinned)
+ throw new IllegalArgumentException("Not allowed to set a revision when pinned.");
+
+ return new Change(platform, Optional.of(revision), platformPinned, revisionPinned);
+ }
+
+ /** Returns a change with the versions of this, and with the platform version pinned. */
+ public Change withPlatformPin() {
+ return new Change(platform, revision, true, revisionPinned);
+ }
+
+ /** Returns a change with the versions of this, and with the platform version unpinned. */
+ public Change withoutPlatformPin() {
+ return new Change(platform, revision, false, revisionPinned);
}
/** Returns a change with the versions of this, and with the platform version pinned. */
- public Change withPin() {
- return new Change(platform, revision, true);
+ public Change withRevisionPin() {
+ return new Change(platform, revision, platformPinned, true);
}
/** Returns a change with the versions of this, and with the platform version unpinned. */
- public Change withoutPin() {
- return new Change(platform, revision, false);
+ public Change withoutRevisionPin() {
+ return new Change(platform, revision, platformPinned, false);
}
/** Returns the change obtained when overwriting elements of the given change with any present in this */
public Change onTopOf(Change other) {
- if (platform.isPresent())
- other = other.with(platform.get());
- if (revision.isPresent())
- other = other.with(revision.get());
- if (pinned)
- other = other.withPin();
+ if (platform.isPresent()) other = other.with(platform.get());
+ if (revision.isPresent()) other = other.with(revision.get());
+ if (platformPinned) other = other.withPlatformPin();
+ if (revisionPinned) other = other.withRevisionPin();
return other;
}
@@ -111,34 +127,38 @@ public final class Change {
if (this == o) return true;
if (!(o instanceof Change)) return false;
Change change = (Change) o;
- return pinned == change.pinned &&
+ return platformPinned == change.platformPinned &&
+ revisionPinned == change.revisionPinned &&
Objects.equals(platform, change.platform) &&
Objects.equals(revision, change.revision);
}
@Override
public int hashCode() {
- return Objects.hash(platform, revision, pinned);
+ return Objects.hash(platform, revision, platformPinned, revisionPinned);
}
@Override
public String toString() {
StringJoiner changes = new StringJoiner(" and ");
- if (pinned)
+ if (platformPinned)
changes.add("pin to " + platform.map(Version::toString).orElse("current platform"));
else
platform.ifPresent(version -> changes.add("upgrade to " + version));
- revision.ifPresent(revision -> changes.add("revision change to " + revision));
+ if (revisionPinned)
+ changes.add("pin to " + revision.map(RevisionId::toString).orElse("current revision"));
+ else
+ revision.ifPresent(revision -> changes.add("revision change to " + revision));
changes.setEmptyValue("no change");
return changes.toString();
}
public static Change of(RevisionId revision) {
- return new Change(Optional.empty(), Optional.of(revision), false);
+ return new Change(Optional.empty(), Optional.of(revision), false, false);
}
public static Change of(Version platformChange) {
- return new Change(Optional.of(platformChange), Optional.empty(), false);
+ return new Change(Optional.of(platformChange), Optional.empty(), false, false);
}
/** Returns whether this change carries a revision downgrade relative to the given revision. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
index 1eb68c14353..80b52e0c7a4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/Endpoint.java
@@ -31,7 +31,8 @@ import static java.util.Comparator.comparing;
*/
public class Endpoint {
- private static final String OATH_DNS_SUFFIX = ".vespa.oath.cloud";
+ private static final String MAIN_OATH_DNS_SUFFIX = ".vespa.oath.cloud";
+ private static final String CD_OATH_DNS_SUFFIX = ".cd.vespa.oath.cloud";
private static final String PUBLIC_DNS_SUFFIX = ".vespa-app.cloud";
private static final String PUBLIC_CD_DNS_SUFFIX = ".cd.vespa-app.cloud";
@@ -243,18 +244,13 @@ public class Endpoint {
/** Returns the DNS suffix used for endpoints in given system */
private static String dnsSuffix(SystemName system) {
- switch (system) {
- case cd, main -> {
- return OATH_DNS_SUFFIX;
- }
- case Public -> {
- return PUBLIC_DNS_SUFFIX;
- }
- case PublicCd -> {
- return PUBLIC_CD_DNS_SUFFIX;
- }
+ return switch (system) {
+ case cd -> CD_OATH_DNS_SUFFIX;
+ case main -> MAIN_OATH_DNS_SUFFIX;
+ case Public -> PUBLIC_DNS_SUFFIX;
+ case PublicCd -> PUBLIC_CD_DNS_SUFFIX;
default -> throw new IllegalArgumentException("No DNS suffix declared for system " + system);
- }
+ };
}
/** Returns the DNS suffix used for internal names (i.e. names not exposed to tenants) in given system */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
index 2441da19b90..b94779994e4 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/InstanceList.java
@@ -21,7 +21,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.function.Function;
import static java.util.Comparator.comparing;
@@ -81,8 +80,7 @@ public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceL
/** Returns the subset of instances that are allowed to upgrade to the given version at the given time */
public InstanceList canUpgradeAt(Version version, Instant instant) {
return matching(id -> instances.get(id).instanceSteps().get(id.instance())
- .readyAt(Change.of(version))
- .map(readyAt -> ! readyAt.isAfter(instant)).orElse(false));
+ .readiness(Change.of(version)).okAt(instant));
}
/** Returns the subset of instances which have at least one production deployment */
@@ -127,7 +125,7 @@ public class InstanceList extends AbstractFilteringList<ApplicationId, InstanceL
/** Returns the subset of instances which are not pinned to a certain Vespa version. */
public InstanceList unpinned() {
- return matching(id -> ! instance(id).change().isPinned());
+ return matching(id -> ! instance(id).change().isPlatformPinned());
}
/** Returns the subset of instances which are currently failing a job. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
index 4a8bc3cd09a..3ebaebf680a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackage.java
@@ -1,15 +1,10 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.controller.application.pkg;
-import com.google.common.hash.Funnel;
-import com.google.common.hash.HashFunction;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.hash.HashingOutputStream;
import com.yahoo.component.Version;
-import com.yahoo.vespa.archive.ArchiveStreamReader;
-import com.yahoo.vespa.archive.ArchiveStreamReader.ArchiveFile;
-import com.yahoo.vespa.archive.ArchiveStreamReader.Options;
import com.yahoo.config.application.FileSystemWrapper;
import com.yahoo.config.application.FileSystemWrapper.FileWrapper;
import com.yahoo.config.application.XmlPreProcessor;
@@ -23,10 +18,12 @@ import com.yahoo.config.provision.Tags;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
+import com.yahoo.vespa.archive.ArchiveStreamReader;
+import com.yahoo.vespa.archive.ArchiveStreamReader.ArchiveFile;
+import com.yahoo.vespa.archive.ArchiveStreamReader.Options;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.deployment.ZipBuilder;
import com.yahoo.yolean.Exceptions;
-
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
@@ -44,10 +41,7 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.Random;
import java.util.Set;
-import java.util.SortedMap;
-import java.util.TreeMap;
import java.util.concurrent.ConcurrentSkipListMap;
import java.util.function.Function;
import java.util.function.Predicate;
@@ -66,7 +60,8 @@ import static java.util.stream.Collectors.toMap;
*/
public class ApplicationPackage {
- static final String trustedCertificatesFile = "security/clients.pem";
+ static final String trustedCertificatesDir = "security/";
+ static final String trustedCertificatesFile = trustedCertificatesDir + "clients.pem";
static final String buildMetaFile = "build-meta.json";
static final String deploymentFile = "deployment.xml";
static final String validationOverridesFile = "validation-overrides.xml";
@@ -90,7 +85,7 @@ public class ApplicationPackage {
* it must not be further changed by the caller.
*/
public ApplicationPackage(byte[] zippedContent) {
- this(zippedContent, false);
+ this(zippedContent, false, false);
}
/**
@@ -99,9 +94,9 @@ public class ApplicationPackage {
* it must not be further changed by the caller.
* If 'requireFiles' is true, files needed by deployment orchestration must be present.
*/
- public ApplicationPackage(byte[] zippedContent, boolean requireFiles) {
+ public ApplicationPackage(byte[] zippedContent, boolean requireFiles, boolean checkCertificateFile) {
this.zippedContent = Objects.requireNonNull(zippedContent, "The application package content cannot be null");
- this.files = new ZipArchiveCache(zippedContent, prePopulated);
+ this.files = new ZipArchiveCache(zippedContent, prePopulated, checkCertificateFile);
Optional<DeploymentSpec> deploymentSpec = files.get(deploymentFile).map(bytes -> new String(bytes, UTF_8)).map(DeploymentSpec::fromXml);
if (requireFiles && deploymentSpec.isEmpty())
@@ -253,10 +248,12 @@ public class ApplicationPackage {
private final byte[] zip;
private final Map<Path, Optional<byte[]>> cache;
- public ZipArchiveCache(byte[] zip, Collection<String> prePopulated) {
+ public ZipArchiveCache(byte[] zip, Collection<String> prePopulated, boolean checkCertificateFile) {
this.zip = zip;
this.cache = new ConcurrentSkipListMap<>();
this.cache.putAll(read(prePopulated));
+ if (checkCertificateFile)
+ verifyThatTrustedCertificateExists();
}
public Optional<byte[]> get(String path) {
@@ -274,17 +271,26 @@ public class ApplicationPackage {
}
private Map<Path, Optional<byte[]>> read(Collection<String> names) {
- var entries = ZipEntries.from(zip,
- names::contains,
- maxSize,
- true)
- .asList().stream()
- .collect(toMap(entry -> Paths.get(entry.name()).normalize(),
- ZipEntries.ZipEntryWithContent::content));
+ var entries = findZipFileEntries(names::contains);
names.stream().map(Paths::get).forEach(path -> entries.putIfAbsent(path.normalize(), Optional.empty()));
return entries;
}
+
+ private void verifyThatTrustedCertificateExists() {
+ // Any name is valid for certificate files
+ var entries = findZipFileEntries((entry) -> entry.contains(trustedCertificatesDir) && entry.endsWith(".pem"));
+ if (entries.size() == 0)
+ throw new IllegalArgumentException("No client certificate found in " + trustedCertificatesDir + " in application package" +
+ ", see https://cloud.vespa.ai/en/security/guide");
+ }
+
+ private Map<Path, Optional<byte[]>> findZipFileEntries(Predicate<String> names) {
+ return ZipEntries.from(zip, names, maxSize, true)
+ .asList().stream()
+ .collect(toMap(entry -> Paths.get(entry.name()).normalize(),
+ ZipEntries.ZipEntryWithContent::content));
+ }
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
index aa6e3b0c44d..cbd0f685d80 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLog.java
@@ -49,7 +49,7 @@ public record AuditLog(List<Entry> entries) {
public record Entry(Instant at, String principal, Method method, String resource, Optional<String> data,
Client client) implements Comparable<Entry> {
- private final static int maxDataLength = 1024;
+ final static int maxDataLength = 1024;
private final static Comparator<Entry> comparator = Comparator.comparing(Entry::at).reversed();
public Entry(Instant at, Client client, String principal, Method method, String resource, byte[] data) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
index 033cd0a52c9..13b3d9d170f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/auditlog/AuditLogger.java
@@ -4,11 +4,12 @@ package com.yahoo.vespa.hosted.controller.auditlog;
import com.yahoo.container.jdisc.HttpRequest;
import com.yahoo.jdisc.http.HttpHeaders;
import com.yahoo.transaction.Mutex;
+import com.yahoo.vespa.hosted.controller.auditlog.AuditLog.Entry;
import com.yahoo.vespa.hosted.controller.persistence.CuratorDb;
import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.UncheckedIOException;
+import java.io.InputStream;
+import java.io.SequenceInputStream;
import java.net.URI;
import java.security.Principal;
import java.time.Clock;
@@ -17,6 +18,9 @@ import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
+import static com.yahoo.yolean.Exceptions.uncheck;
+import static java.util.Objects.requireNonNullElse;
+
/**
* This provides read and write operations for the audit log.
*
@@ -58,14 +62,8 @@ public class AuditLogger {
"misconfiguration and should not happen");
}
- byte[] data = new byte[0];
- try {
- if (request.getData() != null) {
- data = request.getData().readAllBytes();
- }
- } catch (IOException e) {
- throw new UncheckedIOException(e);
- }
+ InputStream requestData = requireNonNullElse(request.getData(), InputStream.nullInputStream());
+ byte[] data = uncheck(() -> requestData.readNBytes(Entry.maxDataLength));
AuditLog.Entry.Client client = parseClient(request);
Instant now = clock.instant();
@@ -80,7 +78,9 @@ public class AuditLogger {
}
// Create a new input stream to allow callers to consume request body
- return new HttpRequest(request.getJDiscRequest(), new ByteArrayInputStream(data), request.propertyMap());
+ return new HttpRequest(request.getJDiscRequest(),
+ new SequenceInputStream(new ByteArrayInputStream(data), requestData),
+ request.propertyMap());
}
private static AuditLog.Entry.Client parseClient(HttpRequest request) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
index 050b77a391e..0f1bbfeb25e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentStatus.java
@@ -39,11 +39,11 @@ import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.HashSet;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
+import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
@@ -229,7 +229,7 @@ public class DeploymentStatus {
.anyMatch(deployment -> ! compatibleWithCompileVersion.test(deployment.version()))) {
for (Version platform : targetsForPolicy(versionStatus, systemVersion, application.deploymentSpec().requireInstance(instance).upgradePolicy()))
if (compatibleWithCompileVersion.test(platform))
- return change.withoutPin().with(platform);
+ return change.withoutPlatformPin().with(platform);
}
return change;
}
@@ -265,7 +265,7 @@ public class DeploymentStatus {
for (InstanceName instance : application.deploymentSpec().instanceNames()) {
Change outstanding = outstandingChange(instance);
if (outstanding.hasTargets())
- outstandingChanges.put(instance, outstanding.onTopOf(application.require(instance).change()));
+ outstandingChanges.put(instance, outstanding.onTopOf(application.require(instance).change().withoutRevisionPin()));
}
var testJobs = jobsToRun(outstandingChanges, true).entrySet().stream()
.filter(entry -> ! entry.getKey().type().isProduction());
@@ -303,7 +303,11 @@ public class DeploymentStatus {
fallbackPlatform(change, job));
if (step.completedAt(change, firstProductionJobWithDeploymentInCloud).isEmpty()) {
JobType typeWithZone = job.type().isSystemTest() ? JobType.systemTest(zones, cloud) : JobType.stagingTest(zones, cloud);
- jobs.merge(job, List.of(new Job(typeWithZone, versions, step.readyAt(change), change)), DeploymentStatus::union);
+ Readiness readiness = step.readiness(change, firstProductionJobWithDeploymentInCloud);
+ jobs.merge(job, List.of(new Job(typeWithZone,
+ versions,
+ readiness.okAt(now) && jobs().get(job).get().isRunning() ? readiness.running() : readiness,
+ change)), DeploymentStatus::union);
}
});
});
@@ -498,21 +502,23 @@ public class DeploymentStatus {
}
/** Earliest instant when job was triggered with given versions, or both system and staging tests were successful. */
- public Optional<Instant> verifiedAt(JobId job, Versions versions) {
- Optional<Instant> triggeredAt = allJobs.get(job)
- .flatMap(status -> status.runs().values().stream()
- .filter(run -> run.versions().equals(versions))
- .findFirst())
- .map(Run::start);
- Optional<Instant> systemTestedAt = testedAt(job, systemTest(job.type()), versions);
- Optional<Instant> stagingTestedAt = testedAt(job, stagingTest(job.type()), versions);
- if (systemTestedAt.isEmpty() || stagingTestedAt.isEmpty()) return triggeredAt;
- Optional<Instant> testedAt = systemTestedAt.get().isAfter(stagingTestedAt.get()) ? systemTestedAt : stagingTestedAt;
- return triggeredAt.isPresent() && triggeredAt.get().isBefore(testedAt.get()) ? triggeredAt : testedAt;
+ public Readiness verifiedAt(JobId job, Versions versions) {
+ Readiness triggered = allJobs.get(job)
+ .flatMap(status -> status.runs().values().stream()
+ .filter(run -> run.versions().equals(versions))
+ .findFirst())
+ .map(Run::start)
+ .map(Readiness::new)
+ .orElse(Readiness.unverified);
+ Readiness systemTested = testedAt(job, systemTest(job.type()), versions);
+ Readiness stagingTested = testedAt(job, stagingTest(job.type()), versions);
+ if (! systemTested.ok() || ! stagingTested.ok()) return triggered;
+ Readiness tested = min(systemTested, stagingTested);
+ return triggered.ok() && triggered.at().isBefore(tested.at) ? triggered : tested;
}
/** Earliest instant when versions were tested for the given instance. */
- private Optional<Instant> testedAt(JobId job, JobType type, Versions versions) {
+ private Readiness testedAt(JobId job, JobType type, Versions versions) {
return prerequisiteTests(job, type).stream()
.map(test -> allJobs.get(test).stream()
.flatMap(status -> RunList.from(status)
@@ -522,19 +528,21 @@ public class DeploymentStatus {
.asList().stream()
.map(run -> run.end().get()))
.min(naturalOrder()))
- .reduce((o, n) -> o.isEmpty() || n.isEmpty() ? Optional.empty() : o.get().isBefore(n.get()) ? n : o)
- .orElse(Optional.empty());
+ .map(testedAt -> testedAt.map(Readiness::new).orElse(Readiness.unverified))
+ .reduce(Readiness.empty, DeploymentStatus::max);
}
private Map<JobId, List<Job>> productionJobs(InstanceName instance, Change change, boolean assumeUpgradesSucceed) {
Map<JobId, List<Job>> jobs = new LinkedHashMap<>();
- jobSteps.forEach((job, step) -> {
+ for (Entry<JobId, StepStatus> entry : reversed(List.copyOf(jobSteps.entrySet()))) {
+ JobId job = entry.getKey();
+ StepStatus step = entry.getValue();
if ( ! job.application().instance().equals(instance) || ! job.type().isProduction())
- return;
+ continue;
// Signal strict completion criterion by depending on job itself.
if (step.completedAt(change, Optional.of(job)).isPresent())
- return;
+ continue;
// When computing eager test jobs for outstanding changes, assume current change completes successfully.
Optional<Deployment> deployment = deploymentFor(job);
@@ -544,7 +552,7 @@ public class DeploymentStatus {
|| areIncompatible(change.platform(), existingRevision, job);
if (assumeUpgradesSucceed) {
if (deployingCompatibilityChange) // No eager tests for this.
- return;
+ continue;
Change currentChange = application.require(instance).change();
Versions target = Versions.from(currentChange, application, deployment, fallbackPlatform(currentChange, job));
@@ -554,21 +562,28 @@ public class DeploymentStatus {
List<Job> toRun = new ArrayList<>();
List<Change> changes = deployingCompatibilityChange
|| allJobs.get(job).flatMap(status -> status.lastCompleted()).isEmpty()
- ? List.of(change)
- : changes(job, step, change);
+ ? List.of(change)
+ : changes(job, step, change);
for (Change partial : changes) {
- Job jobToRun = new Job(job.type(),
- Versions.from(partial, application, existingPlatform, existingRevision, fallbackPlatform(partial, job)),
- step.readyAt(partial, Optional.of(job)),
- partial);
- toRun.add(jobToRun);
+ Versions versions = Versions.from(partial, application, existingPlatform, existingRevision, fallbackPlatform(partial, job));
+ Readiness readiness = step.readiness(partial, Optional.of(job));
+ // This job is blocked if it is already running ...
+ readiness = jobs().get(job).get().isRunning() && readiness.okAt(now) ? readiness.running() : readiness;
+ // ... or if it is a deployment, and a test job for the current state is not yet complete,
+ // which is the case when the next versions to run that test with is not the same as we want to deploy here.
+ List<Job> tests = job.type().isTest() ? null : jobs.get(new JobId(job.application(), JobType.productionTestOf(job.type().zone())));
+ readiness = tests != null && ! versions.targetsMatch(tests.get(0).versions) && readiness.okAt(now) ? readiness.blocked() : readiness;
+ toRun.add(new Job(job.type(), versions, readiness, partial));
// Assume first partial change is applied before the second.
- existingPlatform = Optional.of(jobToRun.versions.targetPlatform());
- existingRevision = Optional.of(jobToRun.versions.targetRevision());
+ existingPlatform = Optional.of(versions.targetPlatform());
+ existingRevision = Optional.of(versions.targetRevision());
}
jobs.put(job, toRun);
- });
- return jobs;
+ }
+ Map<JobId, List<Job>> jobsInOrder = new LinkedHashMap<>();
+ for (Entry<JobId, List<Job>> entry : reversed(List.copyOf(jobs.entrySet())))
+ jobsInOrder.put(entry.getKey(), entry.getValue());
+ return jobsInOrder;
}
private boolean areIncompatible(Optional<Version> platform, Optional<RevisionId> revision, JobId job) {
@@ -581,7 +596,8 @@ public class DeploymentStatus {
/** Changes to deploy with the given job, possibly split in two steps. */
private List<Change> changes(JobId job, StepStatus step, Change change) {
- if (change.platform().isEmpty() || change.revision().isEmpty() || change.isPinned())
+ if ( change.platform().isEmpty() || change.revision().isEmpty()
+ || change.isPlatformPinned() || change.isRevisionPinned())
return List.of(change);
if ( step.completedAt(change.withoutApplication(), Optional.of(job)).isPresent()
@@ -606,8 +622,7 @@ public class DeploymentStatus {
// the revision is now blocked by waiting for the production test to verify the upgrade.
// In this case we must abandon the production test on the pure upgrade, so the revision can be deployed.
if (platformDeployedAt.isPresent() && revisionDeployedAt.isEmpty()) {
- if (jobSteps.get(deployment).readyAt(change, Optional.of(deployment))
- .map(ready -> ! now.isBefore(ready)).orElse(false)) {
+ if (jobSteps.get(deployment).readiness(change, Optional.of(deployment)).okAt(now)) {
return switch (rollout) {
// If separate rollout, this test should keep blocking the revision, unless there are failures.
case separate -> hasFailures(jobSteps.get(deployment), jobSteps.get(job)) ? List.of(change) : List.of(change.withoutApplication(), change);
@@ -676,12 +691,15 @@ public class DeploymentStatus {
for (Job productionJob : versionsList)
if (allJobs.successOn(testType, productionJob.versions())
.instance(testJob.application().instance())
- .asList().isEmpty())
+ .asList().isEmpty()) {
+ Readiness readiness = jobSteps().get(testJob).readiness(productionJob.change, Optional.of(job));
testJobs.merge(testJob, List.of(new Job(testJob.type(),
productionJob.versions(),
- jobSteps().get(testJob).readyAt(productionJob.change),
+ readiness.okAt(now) && jobs().get(testJob).get().isRunning() ? readiness.running() : readiness,
productionJob.change)),
DeploymentStatus::union);
+
+ }
});
}
}
@@ -894,16 +912,17 @@ public class DeploymentStatus {
abstract Optional<Instant> completedAt(Change change, Optional<JobId> dependent);
/** The time at which this step is ready to run the specified change and / or versions. */
- public Optional<Instant> readyAt(Change change) { return readyAt(change, Optional.empty()); }
+ public Readiness readiness(Change change) { return readiness(change, Optional.empty()); }
/** The time at which this step is ready to run the specified change and / or versions. */
- Optional<Instant> readyAt(Change change, Optional<JobId> dependent) {
+ Readiness readiness(Change change, Optional<JobId> dependent) {
return dependenciesCompletedAt(change, dependent)
+ .map(Readiness::new)
.map(ready -> Stream.of(blockedUntil(change),
pausedUntil(),
coolingDownUntil(change, dependent))
- .flatMap(Optional::stream)
- .reduce(ready, maxBy(naturalOrder())));
+ .reduce(ready, maxBy(naturalOrder())))
+ .orElse(Readiness.notReady);
}
/** The time at which all dependencies completed on the given change and / or versions. */
@@ -918,13 +937,13 @@ public class DeploymentStatus {
}
/** The time until which this step is blocked by a change blocker. */
- public Optional<Instant> blockedUntil(Change change) { return Optional.empty(); }
+ public Readiness blockedUntil(Change change) { return Readiness.empty; }
/** The time until which this step is paused by user intervention. */
- public Optional<Instant> pausedUntil() { return Optional.empty(); }
+ public Readiness pausedUntil() { return Readiness.empty; }
/** The time until which this step is cooling down, due to consecutive failures. */
- public Optional<Instant> coolingDownUntil(Change change, Optional<JobId> dependent) { return Optional.empty(); }
+ public Readiness coolingDownUntil(Change change, Optional<JobId> dependent) { return Readiness.empty; }
/** Whether this step is declared in the deployment spec, or is an implicit step. */
public boolean isDeclared() { return true; }
@@ -940,7 +959,8 @@ public class DeploymentStatus {
@Override
Optional<Instant> completedAt(Change change, Optional<JobId> dependent) {
- return readyAt(change, dependent).map(completion -> completion.plus(step().delay()));
+ return Optional.ofNullable(readiness(change, dependent).at())
+ .map(completion -> completion.plus(step().delay()));
}
}
@@ -964,12 +984,12 @@ public class DeploymentStatus {
/** The time at which this step is ready to run the specified change and / or versions. */
@Override
- public Optional<Instant> readyAt(Change change) {
+ public Readiness readiness(Change change) {
return status.jobSteps.keySet().stream()
.filter(job -> job.type().isProduction() && job.application().instance().equals(instance.name()))
- .map(job -> super.readyAt(change, Optional.of(job)))
- .reduce((o, n) -> o.isEmpty() || n.isEmpty() ? Optional.empty() : n.get().isBefore(o.get()) ? n : o)
- .orElseGet(() -> super.readyAt(change, Optional.empty()));
+ .map(job -> super.readiness(change, Optional.of(job)))
+ .reduce((a, b) -> ! a.ok() ? a : ! b.ok() ? b : min(a, b))
+ .orElseGet(() -> super.readiness(change, Optional.empty()));
}
/**
@@ -986,7 +1006,7 @@ public class DeploymentStatus {
}
@Override
- public Optional<Instant> blockedUntil(Change change) {
+ public Readiness blockedUntil(Change change) {
for (Instant current = now; now.plus(Duration.ofDays(7)).isAfter(current); ) {
boolean blocked = false;
for (DeploymentSpec.ChangeBlocker blocker : spec.changeBlocker()) {
@@ -999,9 +1019,9 @@ public class DeploymentStatus {
}
}
if ( ! blocked)
- return current == now ? Optional.empty() : Optional.of(current);
+ return current == now ? Readiness.empty : new Readiness(current, DelayCause.changeBlocked);
}
- return Optional.of(now.plusSeconds(1 << 30)); // Some time in the future that doesn't look like anything you'd expect.
+ return new Readiness(now.plusSeconds(1 << 30), DelayCause.changeBlocked); // Some time in the future that doesn't look like anything you'd expect.
}
}
@@ -1023,31 +1043,34 @@ public class DeploymentStatus {
public Optional<JobId> job() { return Optional.of(job.id()); }
@Override
- public Optional<Instant> pausedUntil() {
- return status.application().require(job.id().application().instance()).jobPause(job.id().type());
+ public Readiness pausedUntil() {
+ return status.application().require(job.id().application().instance()).jobPause(job.id().type())
+ .map(pause -> new Readiness(pause, DelayCause.paused))
+ .orElse(Readiness.empty);
}
@Override
- public Optional<Instant> coolingDownUntil(Change change, Optional<JobId> dependent) {
- if (job.lastTriggered().isEmpty()) return Optional.empty();
- if (job.lastCompleted().isEmpty()) return Optional.empty();
- if (job.firstFailing().isEmpty() || ! job.firstFailing().get().hasEnded()) return Optional.empty();
+ public Readiness coolingDownUntil(Change change, Optional<JobId> dependent) {
+ if (job.lastTriggered().isEmpty()) return Readiness.empty;
+ if (job.lastCompleted().isEmpty()) return Readiness.empty;
+ if (job.firstFailing().isEmpty() || ! job.firstFailing().get().hasEnded()) return Readiness.empty;
Versions lastVersions = job.lastCompleted().get().versions();
Versions toRun = Versions.from(change, status.application, dependent.flatMap(status::deploymentFor), status.fallbackPlatform(change, job.id()));
- if ( ! toRun.targetsMatch(lastVersions)) return Optional.empty();
+ if ( ! toRun.targetsMatch(lastVersions)) return Readiness.empty;
if ( job.id().type().environment().isTest()
&& ! dependent.map(JobId::type).map(status::findCloud).map(List.of(CloudName.AWS, CloudName.GCP)::contains).orElse(true)
- && job.isNodeAllocationFailure()) return Optional.empty();
+ && job.isNodeAllocationFailure()) return Readiness.empty;
- if (job.lastStatus().get() == invalidApplication) return Optional.of(status.now.plus(Duration.ofDays(36524))); // 100 years
+ if (job.lastStatus().get() == invalidApplication) return new Readiness(status.now.plus(Duration.ofSeconds(1 << 30)), DelayCause.invalidPackage);
Instant firstFailing = job.firstFailing().get().end().get();
Instant lastCompleted = job.lastCompleted().get().end().get();
- return firstFailing.equals(lastCompleted) ? Optional.of(lastCompleted)
- : Optional.of(lastCompleted.plus(Duration.ofMinutes(10))
- .plus(Duration.between(firstFailing, lastCompleted)
- .dividedBy(2)))
- .filter(status.now::isBefore);
+ Duration penalty = firstFailing.equals(lastCompleted) ? Duration.ZERO
+ : Duration.ofMinutes(10)
+ .plus(Duration.between(firstFailing, lastCompleted)
+ .dividedBy(2));
+ return lastCompleted.plus(penalty).isAfter(status.now) ? new Readiness(lastCompleted.plus(penalty), DelayCause.coolingDown)
+ : Readiness.empty;
}
private static JobStepStatus ofProductionDeployment(DeclaredZone step, List<StepStatus> dependencies,
@@ -1059,24 +1082,23 @@ public class DeploymentStatus {
return new JobStepStatus(StepType.deployment, step, dependencies, job, status) {
@Override
- public Optional<Instant> readyAt(Change change, Optional<JobId> dependent) {
- Optional<Instant> readyAt = super.readyAt(change, dependent);
- Optional<Instant> testedAt = status.verifiedAt(job.id(), Versions.from(change, status.application, existingDeployment, status.fallbackPlatform(change, job.id())));
- if (readyAt.isEmpty() || testedAt.isEmpty()) return Optional.empty();
- return readyAt.get().isAfter(testedAt.get()) ? readyAt : testedAt;
+ public Readiness readiness(Change change, Optional<JobId> dependent) {
+ Readiness readyAt = super.readiness(change, dependent);
+ Readiness testedAt = status.verifiedAt(job.id(), Versions.from(change, status.application, existingDeployment, status.fallbackPlatform(change, job.id())));
+ return max(readyAt, testedAt);
}
/** Complete if deployment is on pinned version, and last successful deployment, or if given versions is strictly a downgrade, and this isn't forced by a pin. */
@Override
Optional<Instant> completedAt(Change change, Optional<JobId> dependent) {
- if ( change.isPinned()
+ if ( change.isPlatformPinned()
&& change.platform().isPresent()
&& ! existingDeployment.map(Deployment::version).equals(change.platform()))
return Optional.empty();
if ( change.revision().isPresent()
- && ! existingDeployment.map(Deployment::revision).equals(change.revision())
- && dependent.equals(job())) // Job should (re-)run in this case, but other dependents need not wait.
+ && change.isRevisionPinned()
+ && ! existingDeployment.map(Deployment::revision).equals(change.revision()))
return Optional.empty();
Change fullChange = status.application().require(job.id().application().instance()).change();
@@ -1103,11 +1125,11 @@ public class DeploymentStatus {
JobId prodId = new JobId(job.id().application(), JobType.deploymentTo(job.id().type().zone()));
return new JobStepStatus(StepType.test, step, dependencies, job, status) {
@Override
- Optional<Instant> readyAt(Change change, Optional<JobId> dependent) {
- Optional<Instant> readyAt = super.readyAt(change, dependent);
- Optional<Instant> deployedAt = status.jobSteps().get(prodId).completedAt(change, Optional.of(prodId));
- if (readyAt.isEmpty() || deployedAt.isEmpty()) return Optional.empty();
- return readyAt.get().isAfter(deployedAt.get()) ? readyAt : deployedAt;
+ Readiness readiness(Change change, Optional<JobId> dependent) {
+ Readiness readyAt = super.readiness(change, dependent);
+ Readiness deployedAt = status.jobSteps().get(prodId).completedAt(change, Optional.of(prodId))
+ .map(Readiness::new).orElse(Readiness.notReady);
+ return max(readyAt, deployedAt);
}
@Override
@@ -1163,13 +1185,13 @@ public class DeploymentStatus {
private final JobType type;
private final Versions versions;
- private final Optional<Instant> readyAt;
+ private final Readiness readiness;
private final Change change;
- public Job(JobType type, Versions versions, Optional<Instant> readyAt, Change change) {
+ public Job(JobType type, Versions versions, Readiness readiness, Change change) {
this.type = type;
this.versions = type.isSystemTest() ? versions.withoutSources() : versions;
- this.readyAt = readyAt;
+ this.readiness = readiness;
this.change = change;
}
@@ -1181,8 +1203,8 @@ public class DeploymentStatus {
return versions;
}
- public Optional<Instant> readyAt() {
- return readyAt;
+ public Readiness readiness() {
+ return readiness;
}
@Override
@@ -1190,19 +1212,60 @@ public class DeploymentStatus {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Job job = (Job) o;
- return type.zone().equals(job.type.zone()) && versions.equals(job.versions) && readyAt.equals(job.readyAt) && change.equals(job.change);
+ return type.zone().equals(job.type.zone()) && versions.equals(job.versions) && readiness.equals(job.readiness) && change.equals(job.change);
}
@Override
public int hashCode() {
- return Objects.hash(type.zone(), versions, readyAt, change);
+ return Objects.hash(type.zone(), versions, readiness, change);
}
@Override
public String toString() {
- return change + " with versions " + versions + ", ready at " + readyAt;
+ return change + " with versions " + versions + ", " + readiness;
}
}
+ public enum DelayCause { none, unverified, notReady, blocked, running, coolingDown, invalidPackage, changeBlocked, paused }
+ public record Readiness(Instant at, DelayCause cause) implements Comparable<Readiness> {
+ public static final Readiness unverified = new Readiness(null, DelayCause.unverified);
+ public static final Readiness notReady = new Readiness(null, DelayCause.notReady);
+ public static final Readiness empty = new Readiness(Instant.EPOCH, DelayCause.none);
+ public Readiness(Instant at) { this(at, DelayCause.none); }
+ public Readiness blocked() { return new Readiness(at, DelayCause.blocked); }
+ public Readiness running() { return new Readiness(at, DelayCause.running); }
+ public boolean ok() { return at != null; }
+ public boolean okAt(Instant at) { return ok() && cause != DelayCause.running && cause != DelayCause.blocked && ! at.isBefore(this.at); }
+ @Override public int compareTo(Readiness o) {
+ return at == null ? o.at == null ? 0 : 1
+ : o.at == null ? -1 : at.compareTo(o.at);
+ }
+ @Override public String toString() {
+ return ok() ? "ready at " + at + switch (cause) {
+ case none -> "";
+ case coolingDown -> ": cooling down after repeated failures";
+ case blocked -> ": waiting for verification test to complete";
+ case running -> ": waiting for current run to complete";
+ case invalidPackage -> ": invalid application package, must resubmit";
+ case changeBlocked -> ": deployment configuration blocks changes";
+ case paused -> ": manually paused";
+ default -> throw new IllegalStateException(cause + " should not have an instant at which it is ready");
+ }
+ : "not ready" + switch (cause) {
+ case unverified -> ": waiting for verification test to complete";
+ case notReady -> ": waiting for dependencies to complete";
+ default -> throw new IllegalStateException(cause + " should have an instant at which it is ready");
+ };
+ }
+ }
+
+ static <T extends Comparable<T>> T min(T a, T b) {
+ return a.compareTo(b) > 0 ? b : a;
+ }
+
+ static <T extends Comparable<T>> T max(T a, T b) {
+ return a.compareTo(b) < 0 ? b : a;
+ }
+
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
index a5cb839e9c9..4e699f2c28f 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTrigger.java
@@ -20,6 +20,8 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.DelayCause;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.Readiness;
import java.math.BigDecimal;
import java.time.Clock;
@@ -80,8 +82,7 @@ public class DeploymentTrigger {
Change outstanding = status.outstandingChange(instanceName);
boolean deployOutstanding = outstanding.hasTargets()
&& status.instanceSteps().get(instanceName)
- .readyAt(outstanding)
- .map(readyAt -> ! readyAt.isAfter(clock.instant())).orElse(false)
+ .readiness(outstanding).okAt(clock.instant())
&& acceptNewRevision(status, instanceName, outstanding.revision().get());
application = application.with(instanceName,
instance -> withRemainingChange(instance,
@@ -235,7 +236,7 @@ public class DeploymentTrigger {
if ( ! upgradeRevision && change.revision().isPresent()) change = change.withoutApplication();
if ( ! upgradePlatform && change.platform().isPresent()) change = change.withoutPlatform();
Versions versions = Versions.from(change, application, status.deploymentFor(job), status.fallbackPlatform(change, job));
- DeploymentStatus.Job toTrigger = new DeploymentStatus.Job(job.type(), versions, Optional.of(controller.clock().instant()), instance.change());
+ DeploymentStatus.Job toTrigger = new DeploymentStatus.Job(job.type(), versions, new Readiness(controller.clock().instant()), instance.change());
Map<JobId, List<DeploymentStatus.Job>> testJobs = status.testJobs(Map.of(job, List.of(toTrigger)));
Map<JobId, List<DeploymentStatus.Job>> jobs = testJobs.isEmpty() || ! requireTests
@@ -329,15 +330,14 @@ public class DeploymentTrigger {
/** Cancels the indicated part of the given application's change. */
public void cancelChange(ApplicationId instanceId, ChangesToCancel cancellation) {
applications().lockApplicationOrThrow(TenantAndApplicationId.from(instanceId), application -> {
- Change change;
- switch (cancellation) {
- case ALL: change = Change.empty(); break;
- case VERSIONS: change = Change.empty().withPin(); break;
- case PLATFORM: change = application.get().require(instanceId.instance()).change().withoutPlatform(); break;
- case APPLICATION: change = application.get().require(instanceId.instance()).change().withoutApplication(); break;
- case PIN: change = application.get().require(instanceId.instance()).change().withoutPin(); break;
- default: throw new IllegalArgumentException("Unknown cancellation choice '" + cancellation + "'!");
- }
+ Change change = switch (cancellation) {
+ case ALL -> Change.empty();
+ case PLATFORM -> application.get().require(instanceId.instance()).change().withoutPlatform();
+ case APPLICATION -> application.get().require(instanceId.instance()).change().withoutApplication();
+ case PIN -> application.get().require(instanceId.instance()).change().withoutPlatformPin();
+ case PLATFORM_PIN -> application.get().require(instanceId.instance()).change().withoutPlatformPin();
+ case APPLICATION_PIN -> application.get().require(instanceId.instance()).change().withoutRevisionPin();
+ };
applications().store(application.with(instanceId.instance(),
instance -> withRemainingChange(instance,
change,
@@ -346,7 +346,7 @@ public class DeploymentTrigger {
});
}
- public enum ChangesToCancel { ALL, PLATFORM, APPLICATION, VERSIONS, PIN }
+ public enum ChangesToCancel { ALL, PLATFORM, APPLICATION, PIN, PLATFORM_PIN, APPLICATION_PIN }
// ---------- Conveniences ----------
@@ -374,17 +374,17 @@ public class DeploymentTrigger {
List<Job> jobs = new ArrayList<>();
Map<JobId, List<DeploymentStatus.Job>> jobsToRun = status.jobsToRun();
jobsToRun.forEach((jobId, jobsList) -> {
+ abortIfOutdated(status, jobsToRun, jobId);
DeploymentStatus.Job job = jobsList.get(0);
- if ( job.readyAt().isPresent()
- && ! clock.instant().isBefore(job.readyAt().get())
+ if ( job.readiness().okAt(clock.instant())
&& ! controller.jobController().isDisabled(new JobId(jobId.application(), job.type()))
- && ! (jobId.type().isProduction() && isUnhealthyInAnotherZone(status.application(), jobId))
- && abortIfRunning(status, jobsToRun, jobId)) // Abort and trigger this later if running with outdated parameters.
+ && ! (jobId.type().isProduction() && isUnhealthyInAnotherZone(status.application(), jobId))) {
jobs.add(deploymentJob(status.application().require(jobId.application().instance()),
job.versions(),
job.type(),
status.instanceJobs(jobId.application().instance()).get(jobId.type()).isNodeAllocationFailure(),
- job.readyAt().get()));
+ job.readiness().at()));
+ }
});
return Collections.unmodifiableList(jobs);
}
@@ -403,41 +403,29 @@ public class DeploymentTrigger {
return false;
}
- private void abortIfOutdated(DeploymentStatus status, Map<JobId, List<DeploymentStatus.Job>> jobs, JobId job) {
- status.jobs().get(job)
- .flatMap(JobStatus::lastTriggered)
- .filter(last -> ! last.hasEnded() && last.reason().isEmpty())
- .ifPresent(last -> {
- if (jobs.get(job).stream().noneMatch(versions -> versions.versions().targetsMatch(last.versions())
- && versions.versions().sourcesMatchIfPresent(last.versions()))) {
- String blocked = jobs.get(job).stream()
- .map(scheduled -> scheduled.versions().toString())
- .collect(Collectors.joining(", "));
- log.log(Level.INFO, "Aborting outdated run " + last + ", which is blocking runs: " + blocked);
- controller.jobController().abort(last.id(), "run no longer scheduled, and is blocking scheduled runs: " + blocked);
- }
- });
+ private void abortIfOutdated(JobStatus job, List<DeploymentStatus.Job> jobs) {
+ job.lastTriggered()
+ .filter(last -> ! last.hasEnded() && last.reason().isEmpty())
+ .ifPresent(last -> {
+ if (jobs.stream().noneMatch(versions -> versions.versions().targetsMatch(last.versions())
+ && versions.versions().sourcesMatchIfPresent(last.versions()))) {
+ String blocked = jobs.stream()
+ .map(scheduled -> scheduled.versions().toString())
+ .collect(Collectors.joining(", "));
+ log.log(Level.INFO, "Aborting outdated run " + last + ", which is blocking runs: " + blocked);
+ controller.jobController().abort(last.id(), "run no longer scheduled, and is blocking scheduled runs: " + blocked);
+ }
+ });
}
/** Returns whether the job is free to start, and also aborts it if it's running with outdated versions. */
- private boolean abortIfRunning(DeploymentStatus status, Map<JobId, List<DeploymentStatus.Job>> jobs, JobId job) {
- abortIfOutdated(status, jobs, job);
- boolean blocked = status.jobs().get(job).get().isRunning();
-
- if ( ! job.type().isTest()) {
- Optional<JobStatus> productionTest = status.jobs().get(new JobId(job.application(), JobType.productionTestOf(job.type().zone())));
- if (productionTest.isPresent()) {
- abortIfOutdated(status, jobs, productionTest.get().id());
- // Production deployments are also blocked by their declared tests, if the next versions to run
- // for those are not the same as the versions we're considering running in the deployment job now.
- if (productionTest.map(JobStatus::id).map(jobs::get)
- .map(versions -> ! versions.get(0).versions().targetsMatch(jobs.get(job).get(0).versions()))
- .orElse(false))
- blocked = true;
- }
- }
-
- return ! blocked;
+ private void abortIfOutdated(DeploymentStatus status, Map<JobId, List<DeploymentStatus.Job>> jobs, JobId job) {
+ Readiness readiness = jobs.get(job).get(0).readiness();
+ if (readiness.cause() == DelayCause.running)
+ abortIfOutdated(status.jobs().get(job).get(), jobs.get(job));
+ if (readiness.cause() == DelayCause.blocked && ! job.type().isTest())
+ status.jobs().get(new JobId(job.application(), JobType.productionTestOf(job.type().zone())))
+ .ifPresent(jobStatus -> abortIfOutdated(jobStatus, jobs.get(jobStatus.id())));
}
// ---------- Change management o_O ----------
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 451f5555eb2..52ddcfd5171 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
@@ -279,7 +279,7 @@ public class InternalStepRunner implements StepRunner {
switch (e.type()) {
case CERT_NOT_AVAILABLE:
// Same as CERTIFICATE_NOT_READY above, only from the controller
- logger.log("Creating a CA signed certificate for the application. " +
+ logger.log("Retrieving CA signed certificate for the application. " +
"This may take up to " + timeouts.endpointCertificate() + " on first deployment.");
if (startTime.plus(timeouts.endpointCertificate()).isBefore(controller.clock().instant())) {
logger.log(WARNING, "CA signed certificate for app not available within " +
@@ -437,7 +437,7 @@ public class InternalStepRunner implements StepRunner {
Version targetPlatform = controller.jobController().run(id).versions().targetPlatform();
Version systemVersion = controller.readSystemVersion();
boolean incompatible = controller.applications().versionCompatibility(id.application()).refuse(targetPlatform, systemVersion);
- return incompatible || application(id.application()).change().isPinned() ? targetPlatform : systemVersion;
+ return incompatible || application(id.application()).change().isPlatformPinned() ? targetPlatform : systemVersion;
}
private Optional<RunStatus> installTester(RunId id, DualLogger logger) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
index 10e4052f067..318a6ffe820 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/JobController.java
@@ -111,6 +111,7 @@ import static java.util.logging.Level.WARNING;
public class JobController {
public static final Duration maxHistoryAge = Duration.ofDays(60);
+ public static final Duration obsoletePackageExpiry = Duration.ofDays(7);
private static final Logger log = Logger.getLogger(JobController.class.getName());
@@ -165,8 +166,8 @@ public class JobController {
return Optional.empty();
return active(id).isPresent()
- ? Optional.of(logs.readActive(id.application(), id.type(), after))
- : logs.readFinished(id, after);
+ ? Optional.of(logs.readActive(id.application(), id.type(), after))
+ : logs.readFinished(id, after);
}
}
@@ -284,10 +285,10 @@ public class JobController {
private Optional<InputStream> getVespaLogsFromLogserver(Run run, long fromMillis, boolean tester) {
return deploymentCompletedAt(run, tester).map(at ->
- controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
- run.id().type().zone()),
- Map.of("from", Long.toString(Math.max(fromMillis, at.toEpochMilli())),
- "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli()))));
+ controller.serviceRegistry().configServer().getLogs(new DeploymentId(tester ? run.id().tester().id() : run.id().application(),
+ run.id().type().zone()),
+ Map.of("from", Long.toString(Math.max(fromMillis, at.toEpochMilli())),
+ "to", Long.toString(run.end().orElse(controller.clock().instant()).toEpochMilli()))));
}
/** Fetches any new test log entries, and records the id of the last of these, for continuation. */
@@ -509,14 +510,14 @@ public class JobController {
long successes = runs.values().stream().filter(Run::hasSucceeded).count();
var oldEntries = runs.entrySet().iterator();
for (var old = oldEntries.next();
- old.getKey().number() <= last - historyLength
+ old.getKey().number() <= last - historyLength
|| old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge));
old = oldEntries.next()) {
// Make sure we keep the last success and the first failing
if ( successes == 1
- && old.getValue().hasSucceeded()
- && ! old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge))) {
+ && old.getValue().hasSucceeded()
+ && ! old.getValue().start().isBefore(controller.clock().instant().minus(maxHistoryAge))) {
oldEntries.next();
continue;
}
@@ -624,7 +625,7 @@ public class JobController {
});
}
- private LockedApplication withPrunedPackages(LockedApplication application, RevisionId latest){
+ private LockedApplication withPrunedPackages(LockedApplication application, RevisionId latest) {
TenantAndApplicationId id = application.get().id();
Application wrapped = application.get();
RevisionId oldestDeployed = application.get().oldestDeployedRevision()
@@ -632,11 +633,28 @@ public class JobController {
.flatMap(instance -> instance.change().revision().stream())
.min(naturalOrder()))
.orElse(latest);
- controller.applications().applicationStore().prune(id.tenant(), id.application(), oldestDeployed);
+ RevisionId oldestToKeep = null;
+ Instant now = controller.clock().instant();
+ for (ApplicationVersion version : application.get().revisions().withPackage()) {
+ if (version.id().compareTo(oldestDeployed) < 0) {
+ if (version.obsoleteAt().isEmpty()) {
+ application = application.withRevisions(revisions -> revisions.with(version.obsoleteAt(now)));
+ if (oldestToKeep == null)
+ oldestToKeep = version.id();
+ }
+ else {
+ if (oldestToKeep == null && !version.obsoleteAt().get().isBefore(now.minus(obsoletePackageExpiry)))
+ oldestToKeep = version.id();
+ }
+ }
+ }
- for (ApplicationVersion version : application.get().revisions().withPackage())
- if (version.id().compareTo(oldestDeployed) < 0)
- application = application.withRevisions(revisions -> revisions.with(version.withoutPackage()));
+ if (oldestToKeep != null) {
+ controller.applications().applicationStore().prune(id.tenant(), id.application(), oldestToKeep);
+ for (ApplicationVersion version : application.get().revisions().withPackage())
+ if (version.id().compareTo(oldestToKeep) < 0)
+ application = application.withRevisions(revisions -> revisions.with(version.withoutPackage()));
+ }
return application;
}
@@ -703,8 +721,8 @@ public class JobController {
VersionStatus versionStatus = controller.readVersionStatus();
if ( ! controller.system().isCd()
- && platform.isPresent()
- && versionStatus.deployableVersions().stream().map(VespaVersion::versionNumber).noneMatch(platform.get()::equals))
+ && platform.isPresent()
+ && versionStatus.deployableVersions().stream().map(VespaVersion::versionNumber).noneMatch(platform.get()::equals))
throw new IllegalArgumentException("platform version " + platform.get() + " is not present in this system");
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
@@ -731,8 +749,8 @@ public class JobController {
controller.applications().lockApplicationOrThrow(TenantAndApplicationId.from(id), application -> {
Version targetPlatform = platform.orElseGet(() -> findTargetPlatform(applicationPackage, deploymentId, application.get().get(id.instance()), versionStatus));
if ( ! allowOutdatedPlatform
- && ! controller.readVersionStatus().isOnCurrentMajor(targetPlatform)
- && runs(id, type).values().stream().noneMatch(run -> run.versions().targetPlatform().getMajor() == targetPlatform.getMajor()))
+ && ! controller.readVersionStatus().isOnCurrentMajor(targetPlatform)
+ && runs(id, type).values().stream().noneMatch(run -> run.versions().targetPlatform().getMajor() == targetPlatform.getMajor()))
throw new IllegalArgumentException("platform version " + targetPlatform + " is not on a current major version in this system");
controller.applications().applicationStore().putDev(deploymentId, version.id(), applicationPackage.zippedContent(), diff);
@@ -872,7 +890,7 @@ public class JobController {
/** Locks all runs and modifies the list of historic runs for the given application and job type. */
private void locked(ApplicationId id, JobType type, Consumer<SortedMap<RunId, Run>> modifications) {
- try (Mutex __ = curator.lock(id, type)) {
+ try (Mutex __ = curator.lock(id, type)) {
SortedMap<RunId, Run> runs = new TreeMap<>(curator.readHistoricRuns(id, type));
modifications.accept(runs);
curator.writeHistoricRuns(id, type, runs.values());
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
index bbab9487ea2..272417ba0ac 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/RevisionHistory.java
@@ -93,7 +93,7 @@ public class RevisionHistory {
// Fallback for when an application version isn't known for the given key.
private static ApplicationVersion revisionOf(RevisionId id) {
- return new ApplicationVersion(id, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), false, false, Optional.empty(), 0);
+ return new ApplicationVersion(id, Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), false, false, Optional.empty(), 0);
}
/** Returns the production {@link ApplicationVersion} with this revision ID. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
index e7371561636..f752e396c09 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/deployment/Versions.java
@@ -126,7 +126,7 @@ public class Versions {
private static Version targetPlatform(Application application, Change change, Optional<Version> existing,
Supplier<Version> defaultVersion) {
- if (change.isPinned() && change.platform().isPresent())
+ if (change.isPlatformPinned() && change.platform().isPresent())
return change.platform().get();
return max(change.platform(), existing)
@@ -135,6 +135,9 @@ public class Versions {
private static RevisionId targetRevision(Application application, Change change,
Optional<RevisionId> existing) {
+ if (change.isRevisionPinned() && change.revision().isPresent())
+ return change.revision().get();
+
return change.revision()
.or(() -> existing)
.orElseGet(() -> defaultRevision(application));
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
index 02e1818932e..cbdfcf70123 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ApplicationOwnershipConfirmer.java
@@ -79,7 +79,7 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
log.log(Level.INFO, "Exception caught when attempting to file an issue for '" + application.id() + "': " + Exceptions.toMessageString(e));
}
});
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
private boolean isInCurrentShard(TenantAndApplicationId id) {
@@ -122,7 +122,7 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
}
});
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
private double updateConfirmedApplicationOwners() {
@@ -149,7 +149,7 @@ public class ApplicationOwnershipConfirmer extends ControllerMaintainer {
log.log(Level.INFO, "Exception caught when attempting to find confirmed owner of issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
}
});
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
private ApplicationList applications() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
index 518027f8099..c4f3c611cc5 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArchiveUriUpdater.java
@@ -98,7 +98,7 @@ public class ArchiveUriUpdater extends ControllerMaintainer {
}
}
- return asSuccessFactor(tenantsByZone.size(), failures);
+ return asSuccessFactorDeviation(tenantsByZone.size(), failures);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
index 25c4121c271..32d06286820 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ArtifactExpirer.java
@@ -3,7 +3,6 @@ package com.yahoo.vespa.hosted.controller.maintenance;
import com.yahoo.component.Version;
import com.yahoo.config.provision.CloudName;
-import com.yahoo.config.provision.SystemName;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.api.integration.artifact.Artifact;
import com.yahoo.vespa.hosted.controller.api.integration.artifact.ArtifactRegistry;
@@ -13,12 +12,9 @@ import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import java.time.Duration;
import java.time.Instant;
import java.util.Comparator;
-import java.util.EnumSet;
import java.util.List;
-import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
/**
* Periodically expire unused artifacts, e.g. container images and RPMs.
@@ -32,7 +28,7 @@ public class ArtifactExpirer extends ControllerMaintainer {
private static final Duration MIN_AGE = Duration.ofDays(14);
public ArtifactExpirer(Controller controller, Duration interval) {
- super(controller, interval, null, expiringSystems());
+ super(controller, interval);
}
@Override
@@ -56,10 +52,10 @@ public class ArtifactExpirer extends ControllerMaintainer {
log.log(Level.INFO, "Expiring " + artifactsToExpire.size() + " artifacts in " + cloudName + ": " + artifactsToExpire);
artifactRegistry.deleteAll(artifactsToExpire);
}
- return 1;
+ return 0;
} catch (RuntimeException e) {
log.log(Level.WARNING, "Failed to expire artifacts in " + cloudName + ". Will retry in " + interval(), e);
- return 0;
+ return 1;
}
}
@@ -77,11 +73,4 @@ public class ArtifactExpirer extends ControllerMaintainer {
return true;
}
- /** Returns systems where artifacts can be expired */
- private static Set<SystemName> expiringSystems() {
- // Run only in public and main. Public systems have distinct container registries, while main and CD have
- // shared registries.
- return EnumSet.of(SystemName.Public, SystemName.PublicCd, SystemName.main);
- }
-
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
index 32e6ad0d557..d61b84e73e9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdater.java
@@ -42,11 +42,17 @@ public class BcpGroupUpdater extends ControllerMaintainer {
private final ApplicationController applications;
private final NodeRepository nodeRepository;
+ private final Double successFactorBaseline;
- public BcpGroupUpdater(Controller controller, Duration duration) {
- super(controller, duration);
+ public BcpGroupUpdater(Controller controller, Duration duration, Double successFactorBaseline) {
+ super(controller, duration, successFactorBaseline);
this.applications = controller.applications();
this.nodeRepository = controller.serviceRegistry().configServer().nodeRepository();
+ this.successFactorBaseline = successFactorBaseline;
+ }
+
+ public BcpGroupUpdater(Controller controller, Duration duration) {
+ this(controller, duration, 1.0);
}
@Override
@@ -58,7 +64,7 @@ public class BcpGroupUpdater extends ControllerMaintainer {
for (var application : applications.asList()) {
for (var instance : application.instances().values()) {
for (var deployment : instance.productionDeployments().values()) {
- if (shuttingDown()) return 1.0;
+ if (shuttingDown()) return 0.0;
try {
attempts++;
var bcpGroups = BcpGroup.groupsFrom(instance, application.deploymentSpec());
@@ -75,12 +81,12 @@ public class BcpGroupUpdater extends ControllerMaintainer {
}
}
}
- double successFactor = asSuccessFactor(attempts, failures);
- if ( successFactor == 0 )
+ double successFactorDeviation = asSuccessFactorDeviation(attempts, failures);
+ if ( successFactorDeviation == -successFactorBaseline )
log.log(Level.WARNING, "Could not update traffic share on any applications", lastException);
- else if ( successFactor < 0.9 )
+ else if ( successFactorDeviation < -0.1 )
log.log(Level.FINE, "Could not update traffic share on all applications", lastException);
- return successFactor;
+ return successFactorDeviation;
}
/** Adds deployment traffic share to the given patch. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
index a7ebaec7c09..b40078eef51 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/BillingDatabaseMaintainer.java
@@ -19,6 +19,6 @@ public class BillingDatabaseMaintainer extends ControllerMaintainer {
@Override
protected double maintain() {
controller().serviceRegistry().billingDatabase().maintain();
- return 1;
+ return 0.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
index 914707aa318..68fd5c8bafe 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CloudDatabaseMaintainer.java
@@ -19,8 +19,8 @@ public class CloudDatabaseMaintainer extends ControllerMaintainer {
controller().serviceRegistry().billingController().updateCache(tenants);
} catch (Exception e) {
log.warning("Could not update cloud database cache: " + Exceptions.toMessageString(e));
- return 0.0;
+ return 1.0;
}
- return 1.0;
+ return 0.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
index 6ecad482cd2..f9c93a87c44 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ContactInformationMaintainer.java
@@ -64,7 +64,7 @@ public class ContactInformationMaintainer extends ControllerMaintainer {
interval());
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
index c861d522818..f21803283eb 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainer.java
@@ -12,7 +12,6 @@ import java.util.EnumSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
-import java.util.logging.Logger;
/**
* A maintainer is some job which runs at a fixed interval to perform some maintenance task in the controller.
@@ -26,13 +25,22 @@ public abstract class ControllerMaintainer extends Maintainer {
/** The systems in which this maintainer should run */
private final Set<SystemName> activeSystems;
+
public ControllerMaintainer(Controller controller, Duration interval) {
- this(controller, interval, null, EnumSet.allOf(SystemName.class));
+ this(controller, interval, null, EnumSet.allOf(SystemName.class), 1.0);
+ }
+
+ public ControllerMaintainer(Controller controller, Duration interval, Double successFactorBaseline) {
+ this(controller, interval, null, EnumSet.allOf(SystemName.class), successFactorBaseline);
}
public ControllerMaintainer(Controller controller, Duration interval, String name, Set<SystemName> activeSystems) {
+ this(controller, interval, name, activeSystems, 1.0);
+ }
+
+ public ControllerMaintainer(Controller controller, Duration interval, String name, Set<SystemName> activeSystems, Double successFactorBaseline) {
super(name, interval, controller.clock(), controller.jobControl(),
- new ControllerJobMetrics(controller.metric()), controller.curator().cluster(), true);
+ new ControllerJobMetrics(controller.metric()), controller.curator().cluster(), true, successFactorBaseline);
this.controller = controller;
this.activeSystems = Set.copyOf(Objects.requireNonNull(activeSystems));
}
@@ -54,8 +62,8 @@ public abstract class ControllerMaintainer extends Maintainer {
}
@Override
- public void completed(String job, double successFactor, long durationMs) {
- metric.set("maintenance.successFactor", successFactor, metric.createContext(Map.of("job", job)));
+ public void completed(String job, double successFactorDeviation, long durationMs) {
+ metric.set("maintenance.successFactorDeviation", successFactorDeviation, metric.createContext(Map.of("job", job)));
metric.set("maintenance.duration", durationMs, metric.createContext(Map.of("job", job)));
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
index 05159b38ec6..c83ddbd8045 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintenance.java
@@ -40,6 +40,7 @@ public class ControllerMaintenance extends AbstractComponent {
@SuppressWarnings("unused") // instantiated by Dependency Injection
public ControllerMaintenance(Controller controller, Metric metric, UserManagement userManagement, AthenzClientFactory athenzClientFactory) {
Intervals intervals = new Intervals(controller.system());
+ SuccessFactorBaseline successFactorBaseline = new SuccessFactorBaseline(controller.system());
upgrader = new Upgrader(controller, intervals.defaultInterval);
osUpgradeScheduler = new OsUpgradeScheduler(controller, intervals.osUpgradeScheduler);
maintainers.add(upgrader);
@@ -53,7 +54,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new OutstandingChangeDeployer(controller, intervals.outstandingChangeDeployer));
maintainers.add(new VersionStatusUpdater(controller, intervals.versionStatusUpdater));
maintainers.add(new ReadyJobsTrigger(controller, intervals.readyJobsTrigger));
- maintainers.add(new DeploymentMetricsMaintainer(controller, intervals.deploymentMetricsMaintainer));
+ maintainers.add(new DeploymentMetricsMaintainer(controller, intervals.deploymentMetricsMaintainer, successFactorBaseline.deploymentMetricsMaintainerBaseline));
maintainers.add(new ApplicationOwnershipConfirmer(controller, intervals.applicationOwnershipConfirmer, controller.serviceRegistry().ownershipIssues()));
maintainers.add(new SystemUpgrader(controller, intervals.systemUpgrader));
maintainers.add(new JobRunner(controller, intervals.jobRunner));
@@ -68,7 +69,7 @@ public class ControllerMaintenance extends AbstractComponent {
maintainers.add(new HostInfoUpdater(controller, intervals.hostInfoUpdater));
maintainers.add(new ReindexingTriggerer(controller, intervals.reindexingTriggerer));
maintainers.add(new EndpointCertificateMaintainer(controller, intervals.endpointCertificateMaintainer));
- maintainers.add(new BcpGroupUpdater(controller, intervals.trafficFractionUpdater));
+ maintainers.add(new BcpGroupUpdater(controller, intervals.trafficFractionUpdater, successFactorBaseline.trafficFractionUpdater));
maintainers.add(new ArchiveUriUpdater(controller, intervals.archiveUriUpdater));
maintainers.add(new ArchiveAccessMaintainer(controller, metric, intervals.archiveAccessMaintainer));
maintainers.add(new TenantRoleMaintainer(controller, intervals.tenantRoleMaintainer));
@@ -189,4 +190,17 @@ public class ControllerMaintenance extends AbstractComponent {
}
+ private static class SuccessFactorBaseline {
+
+ private final Double defaultSuccessFactorBaseline;
+ private final Double deploymentMetricsMaintainerBaseline;
+ private final Double trafficFractionUpdater;
+
+ public SuccessFactorBaseline(SystemName system) {
+ Objects.requireNonNull(system);
+ this.defaultSuccessFactorBaseline = 1.0;
+ this.deploymentMetricsMaintainerBaseline = 0.90;
+ this.trafficFractionUpdater = system.isCd() ? 0.5 : 0.65;
+ }
+ }
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
index 403b5aed1ce..668893d5a7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/CostReportMaintainer.java
@@ -34,7 +34,7 @@ public class CostReportMaintainer extends ControllerMaintainer {
protected double maintain() {
var csv = CostCalculator.resourceShareByPropertyToCsv(nodeRepository, controller(), clock, consumer.fixedAllocations());
consumer.consume(csv);
- return 1.0;
+ return 0.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
index 97f3f955a20..c22cb1efdb3 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentExpirer.java
@@ -5,8 +5,6 @@ import com.yahoo.config.provision.ApplicationId;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.application.Deployment;
import com.yahoo.yolean.Exceptions;
@@ -47,7 +45,7 @@ public class DeploymentExpirer extends ControllerMaintainer {
}
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
/** Returns whether given deployment has expired according to its TTL */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
index f6029eade37..d8d89177a9e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentInfoMaintainer.java
@@ -38,7 +38,7 @@ public class DeploymentInfoMaintainer extends ControllerMaintainer {
}
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
private Collection<DeploymentId> instanceDeployments(Instance instance) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
index 6b058537c2d..c352fb053dc 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentIssueReporter.java
@@ -77,7 +77,7 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
fileDeploymentIssueFor(application);
else
store(application.id(), null);
- return 1.0;
+ return 0.0;
}
/**
@@ -87,24 +87,24 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
*/
private double maintainPlatformIssue(List<Application> applications) {
if (controller().system() == SystemName.cd)
- return 1.0;
+ return 0.0;
VersionStatus versionStatus = controller().readVersionStatus();
Version systemVersion = controller().systemVersion(versionStatus);
if (versionStatus.version(systemVersion).confidence() != broken)
- return 1.0;
+ return 0.0;
DeploymentStatusList statuses = controller().jobController().deploymentStatuses(ApplicationList.from(applications));
if (statuses.failingUpgradeToVersionSince(systemVersion, controller().clock().instant().minus(upgradeGracePeriod)).isEmpty())
- return 1.0;
+ return 0.0;
List<ApplicationId> failingApplications = statuses.failingUpgradeToVersionSince(systemVersion, controller().clock().instant())
.mapToList(status -> status.application().id().defaultInstance());
// TODO jonmv: Send only tenant and application, here and elsewhere in this.
deploymentIssues.fileUnlessOpen(failingApplications, systemVersion);
- return 1.0;
+ return 0.0;
}
private Tenant ownerOf(TenantAndApplicationId applicationId) {
@@ -145,7 +145,7 @@ public class DeploymentIssueReporter extends ControllerMaintainer {
log.log(Level.INFO, "Exception caught when attempting to escalate issue with id '" + issueId + "': " + Exceptions.toMessageString(e));
}
}));
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
private void store(TenantAndApplicationId id, IssueId issueId) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
index fa917d2eb4e..29266a25c5e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentMetricsMaintainer.java
@@ -39,11 +39,15 @@ public class DeploymentMetricsMaintainer extends ControllerMaintainer {
private final ApplicationController applications;
- public DeploymentMetricsMaintainer(Controller controller, Duration duration) {
- super(controller, duration);
+ public DeploymentMetricsMaintainer(Controller controller, Duration duration, Double successFactorBaseline) {
+ super(controller, duration, successFactorBaseline);
this.applications = controller.applications();
}
+ public DeploymentMetricsMaintainer(Controller controller, Duration duration) {
+ this(controller, duration, 1.0);
+ }
+
@Override
protected double maintain() {
AtomicInteger failures = new AtomicInteger(0);
@@ -96,7 +100,7 @@ public class DeploymentMetricsMaintainer extends ControllerMaintainer {
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
static DeploymentMetrics updateDeploymentMetrics(DeploymentMetrics current, List<ClusterMetrics> metrics) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
index 934a1b4fa2f..8a3a2a11e09 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/DeploymentUpgrader.java
@@ -78,7 +78,7 @@ public class DeploymentUpgrader extends ControllerMaintainer {
": " + Exceptions.toMessageString(e) + ". Retrying in " +
interval());
}
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
/** Returns whether query and feed metrics are ~zero, or currently platform has been deployed for a week. */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
index 7af96d10f2f..5218da91c46 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EnclaveAccessMaintainer.java
@@ -26,7 +26,7 @@ public class EnclaveAccessMaintainer extends ControllerMaintainer {
return controller().serviceRegistry().enclaveAccessService().allowAccessFor(externalAccounts());
} catch (RuntimeException e) {
logger.log(WARNING, "Failed sharing resources with enclave", e);
- return 0;
+ return 1.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
index 0b96d8adc1a..713782eb7b9 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainer.java
@@ -75,10 +75,10 @@ public class EndpointCertificateMaintainer extends ControllerMaintainer {
deleteOrReportUnmanagedCertificates();
} catch (Exception e) {
log.log(Level.SEVERE, "Exception caught while maintaining endpoint certificates", e);
- return 0.0;
+ return 1.0;
}
- return 1.0;
+ return 0.0;
}
private void updateRefreshedCertificates() {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
index 1f21c688540..31236f4fcda 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/HostInfoUpdater.java
@@ -73,7 +73,7 @@ public class HostInfoUpdater extends ControllerMaintainer {
LOG.info("Updated information for " + hostsUpdated + " hosts(s)");
}
}
- return 1.0;
+ return 0.0;
}
private static Optional<String> modelNameOf(NodeEntity nodeEntity) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
index b051590ac5a..68c79fdca7e 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/InfrastructureUpgrader.java
@@ -50,7 +50,7 @@ public abstract class InfrastructureUpgrader<TARGET extends VersionTarget> exten
@Override
protected double maintain() {
return target().map(target -> upgradeAll(target, managedApplications))
- .orElse(1.0);
+ .orElse(0.0);
}
/** Deploy a list of system applications until they converge on the given version */
@@ -81,7 +81,7 @@ public abstract class InfrastructureUpgrader<TARGET extends VersionTarget> exten
break;
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
/** Returns whether all applications have converged to the target version in zone */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
index 294d5bad42d..67188eb5e3a 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/MetricsReporter.java
@@ -90,7 +90,7 @@ public class MetricsReporter extends ControllerMaintainer {
reportBrokenSystemVersion(versionStatus);
reportTenantMetrics();
reportZmsQuotaMetrics();
- return 1.0;
+ return 0.0;
}
private void reportBrokenSystemVersion(VersionStatus versionStatus) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
index f930e64fc5a..f0d218ae6cf 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsUpgradeScheduler.java
@@ -40,7 +40,7 @@ public class OsUpgradeScheduler extends ControllerMaintainer {
if (!change.get().scheduleAt(now)) continue;
controller().upgradeOsIn(cloud, change.get().version(), false);
}
- return 1.0;
+ return 0.0;
}
/** Returns the wanted change for cloud at given instant, if any */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
index 119540eaa68..c643df6af68 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/OsVersionStatusUpdater.java
@@ -22,12 +22,12 @@ public class OsVersionStatusUpdater extends ControllerMaintainer {
try {
OsVersionStatus newStatus = OsVersionStatus.compute(controller());
controller().updateOsVersionStatus(newStatus);
- return 1.0;
+ return 0.0;
} catch (Exception e) {
log.log(Level.WARNING, "Failed to compute OS version status: " + Exceptions.toMessageString(e) +
". Retrying in " + interval());
}
- return 0.0;
+ return 1.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
index 400673bfd0c..945b6d32a30 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ReindexingTriggerer.java
@@ -53,11 +53,11 @@ public class ReindexingTriggerer extends ControllerMaintainer {
controller().applications().reindex(id, deployment.zone(), List.of(), List.of(), true, speed,
"bakground reindexing, to account for changes in built-in linguistics components");
});
- return 1.0;
+ return 0.0;
}
catch (RuntimeException e) {
log.log(Level.WARNING, "Failed to trigger reindexing: " + Exceptions.toMessageString(e));
- return 0.0;
+ return 1.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
index 3f20c2eac8f..52206d41c00 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceMeterMaintainer.java
@@ -98,13 +98,13 @@ public class ResourceMeterMaintainer extends ControllerMaintainer {
} catch (Exception e) {
log.log(Level.WARNING, "Failed to collect resource snapshots. Retrying in " + interval() + ". Error: " +
Exceptions.toMessageString(e));
- return 0.0;
+ return 1.0;
}
if (systemName.isPublic()) reportResourceSnapshots(resourceSnapshots);
if (systemName.isPublic()) reportAllScalingEvents();
updateDeploymentCost(resourceSnapshots);
- return 1.0;
+ return 0.0;
}
void updateDeploymentCost(Collection<ResourceSnapshot> resourceSnapshots) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
index 18ed154fcf1..59871f716e0 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/ResourceTagMaintainer.java
@@ -43,7 +43,7 @@ public class ResourceTagMaintainer extends ControllerMaintainer {
if (taggedResources > 0)
log.log(Level.INFO, "Tagged " + taggedResources + " resources in " + zone.getId());
});
- return 1.0;
+ return 0.0;
}
private Map<HostName, ApplicationId> getTenantOfParentHosts(ZoneId zoneId) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
index 74bb89e4105..aaf730cc158 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/RetriggerMaintainer.java
@@ -47,9 +47,9 @@ public class RetriggerMaintainer extends ControllerMaintainer {
controller().curator().writeRetriggerEntries(remaining);
} catch (Exception e) {
logger.log(Level.WARNING, "Exception while triggering jobs", e);
- return 0.0;
+ return 1.0;
}
- return 1.0;
+ return 0.0;
}
/** Returns true if a job is ready to run, i.e. is currently not running */
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
index f29df1bc0d5..e3a3415e170 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleCleanupMaintainer.java
@@ -26,6 +26,6 @@ public class TenantRoleCleanupMaintainer extends ControllerMaintainer {
controller().serviceRegistry().tenantSecretService().cleanupSecretStores(deletedTenants);
}
- return 1.0;
+ return 0.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
index bd121871c7c..c7b236880fd 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/TenantRoleMaintainer.java
@@ -37,7 +37,7 @@ public class TenantRoleMaintainer extends ControllerMaintainer {
controller().tenants().updateLastTenantRolesMaintained(t.name(), updated);
});
- return 1.0;
+ return 0.0;
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
index 82b3141e503..edcfcc317a7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/Upgrader.java
@@ -64,7 +64,7 @@ public class Upgrader extends ControllerMaintainer {
for (UpgradePolicy policy : UpgradePolicy.values())
updateTargets(versionStatus, deploymentStatuses, policy);
- return 1.0;
+ return 0.0;
}
private DeploymentStatusList deploymentStatuses(VersionStatus versionStatus) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
index 03987efab8b..7c4645a6e48 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/UserManagementMaintainer.java
@@ -11,6 +11,7 @@ import java.util.Optional;
import java.util.logging.Logger;
import java.util.stream.Collectors;
+
/**
* Maintains user management resources.
* For now, ensures there's no discrepnacy between expected tenant/application roles and auth0/athenz roles
@@ -46,7 +47,7 @@ public class UserManagementMaintainer extends ControllerMaintainer {
});
}
- return 1.0;
+ return 0.0;
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
index da0fa890960..d4c4b4efda7 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainer.java
@@ -90,7 +90,7 @@ public class VcmrMaintainer extends ControllerMaintainer {
}
});
updateMetrics();
- return 1.0;
+ return 0.0;
}
/**
@@ -139,7 +139,10 @@ public class VcmrMaintainer extends ControllerMaintainer {
return Stream.empty();
}
var spareCapacity = hasSpareCapacity(zone, nodes);
- return nodes.stream().map(node -> nextAction(zone, node, changeRequest, spareCapacity));
+ var impactedProxyCount = nodes.stream()
+ .filter(node -> node.type() == NodeType.proxy)
+ .count();
+ return nodes.stream().map(node -> nextAction(zone, node, changeRequest, spareCapacity, impactedProxyCount));
}).toList();
}
@@ -162,7 +165,7 @@ public class VcmrMaintainer extends ControllerMaintainer {
.findFirst();
}
- private HostAction nextAction(ZoneId zoneId, Node node, VespaChangeRequest changeRequest, boolean spareCapacity) {
+ private HostAction nextAction(ZoneId zoneId, Node node, VespaChangeRequest changeRequest, boolean spareCapacity, long impactedProxyCount) {
var hostAction = getPreviousAction(node, changeRequest)
.orElse(new HostAction(node.hostname().value(), State.NONE, Instant.now()));
@@ -176,7 +179,8 @@ public class VcmrMaintainer extends ControllerMaintainer {
if (isLowImpact(changeRequest))
return hostAction;
- addReport(zoneId, changeRequest, node);
+ if (shouldAddReport(node, changeRequest.getChangeRequestSource().getId(), hostAction))
+ addReport(zoneId, changeRequest, node);
if (isOutOfSync(node, hostAction))
return hostAction.withState(State.OUT_OF_SYNC);
@@ -187,7 +191,13 @@ public class VcmrMaintainer extends ControllerMaintainer {
return hostAction.withState(State.PENDING_RETIREMENT);
}
- if (node.type() != NodeType.host || !spareCapacity) {
+ if (!spareCapacity) {
+ return hostAction.withState(State.REQUIRES_OPERATOR_ACTION);
+ }
+
+ if (node.type() != NodeType.host) {
+ if (node.type() == NodeType.proxy && impactedProxyCount == 1)
+ return hostAction.withState(State.READY);
return hostAction.withState(State.REQUIRES_OPERATOR_ACTION);
}
@@ -267,6 +277,16 @@ public class VcmrMaintainer extends ControllerMaintainer {
&& node.state() == Node.State.active;
}
+ private boolean shouldAddReport(Node node, String vcmrId, HostAction previousAction) {
+ var vcmrReport = VcmrReport.fromReports(node.reports());
+ var hasReport = vcmrReport.getVcmrs().stream().map(VcmrReport.Vcmr::id).anyMatch(id -> id.equals(vcmrId));
+ // Don't add report if none exists and this is not initial assessment
+ // Presumably removed manually by operator.
+ if (!hasReport && previousAction.getState() != State.NONE)
+ return false;
+ return true;
+ }
+
// Determines if node state is unexpected based on previous action taken
private boolean isOutOfSync(Node node, HostAction action) {
return action.getState() == State.RETIRED && node.state() != Node.State.parked ||
@@ -343,8 +363,7 @@ public class VcmrMaintainer extends ControllerMaintainer {
private void addReport(ZoneId zoneId, VespaChangeRequest changeRequest, Node node) {
var report = VcmrReport.fromReports(node.reports());
- var source = changeRequest.getChangeRequestSource();
- if (report.addVcmr(source.getId(), source.getPlannedStartTime(), source.getPlannedEndTime())) {
+ if (report.addVcmr(changeRequest.getChangeRequestSource())) {
updateReport(zoneId, node, report);
}
}
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
index 154455c5198..1c4d13aa16d 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/maintenance/VersionStatusUpdater.java
@@ -39,12 +39,12 @@ public class VersionStatusUpdater extends ControllerMaintainer {
controller().serviceRegistry().systemMonitor().reportSystemVersion(version.versionNumber(),
convert(version.confidence()));
});
- return 1.0;
+ return 0.0;
} catch (Exception e) {
log.log(Level.WARNING, "Failed to compute version status: " + Exceptions.toMessageString(e) +
". Retrying in " + interval());
}
- return 0.0;
+ return 1.0;
}
static SystemMonitor.Confidence convert(VespaVersion.Confidence confidence) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
index ee12c9957b1..e5006ab9785 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializer.java
@@ -82,7 +82,8 @@ public class ApplicationSerializer {
private static final String versionsField = "versions";
private static final String prodVersionsField = "prodVersions";
private static final String devVersionsField = "devVersions";
- private static final String pinnedField = "pinned";
+ private static final String platformPinnedField = "pinned";
+ private static final String revisionPinnedField = "revisionPinned";
private static final String deploymentIssueField = "deploymentIssueId";
private static final String ownershipIssueIdField = "ownershipIssueId";
private static final String ownerField = "confirmedOwner";
@@ -118,6 +119,7 @@ public class ApplicationSerializer {
private static final String riskField = "risk";
private static final String authorEmailField = "authorEmailField";
private static final String deployedDirectlyField = "deployedDirectly";
+ private static final String obsoleteAtField = "obsoleteAt";
private static final String hasPackageField = "hasPackage";
private static final String shouldSkipField = "shouldSkip";
private static final String compileVersionField = "compileVersion";
@@ -265,6 +267,7 @@ public class ApplicationSerializer {
applicationVersion.sourceUrl().ifPresent(url -> object.setString(sourceUrlField, url));
applicationVersion.commit().ifPresent(commit -> object.setString(commitField, commit));
object.setBool(deployedDirectlyField, applicationVersion.isDeployedDirectly());
+ applicationVersion.obsoleteAt().ifPresent(at -> object.setLong(obsoleteAtField, at.toEpochMilli()));
object.setBool(hasPackageField, applicationVersion.hasPackage());
object.setBool(shouldSkipField, applicationVersion.shouldSkip());
applicationVersion.description().ifPresent(description -> object.setString(descriptionField, description));
@@ -295,8 +298,10 @@ public class ApplicationSerializer {
object.setString(versionField, deploying.platform().get().toString());
if (deploying.revision().isPresent())
toSlime(deploying.revision().get(), object);
- if (deploying.isPinned())
- object.setBool(pinnedField, true);
+ if (deploying.isPlatformPinned())
+ object.setBool(platformPinnedField, true);
+ if (deploying.isRevisionPinned())
+ object.setBool(revisionPinnedField, true);
}
private void toSlime(RotationStatus status, Cursor array) {
@@ -487,6 +492,7 @@ public class ApplicationSerializer {
Optional<Instant> buildTime = SlimeUtils.optionalInstant(object.field(buildTimeField));
Optional<String> sourceUrl = SlimeUtils.optionalString(object.field(sourceUrlField));
Optional<String> commit = SlimeUtils.optionalString(object.field(commitField));
+ Optional<Instant> obsoleteAt = SlimeUtils.optionalInstant(object.field(obsoleteAtField));
boolean hasPackage = object.field(hasPackageField).asBool();
boolean shouldSkip = object.field(shouldSkipField).asBool();
Optional<String> description = SlimeUtils.optionalString(object.field(descriptionField));
@@ -494,7 +500,7 @@ public class ApplicationSerializer {
Optional<String> bundleHash = SlimeUtils.optionalString(object.field(bundleHashField));
return new ApplicationVersion(id, sourceRevision, authorEmail, compileVersion, allowedMajor, buildTime,
- sourceUrl, commit, bundleHash, hasPackage, shouldSkip, description, risk);
+ sourceUrl, commit, bundleHash, obsoleteAt, hasPackage, shouldSkip, description, risk);
}
private Optional<SourceRevision> sourceRevisionFromSlime(Inspector object) {
@@ -520,8 +526,10 @@ public class ApplicationSerializer {
change = Change.of(Version.fromString(versionFieldValue.asString()));
if (object.field(applicationBuildNumberField).valid())
change = change.with(revisionFromSlime(object, null));
- if (object.field(pinnedField).asBool())
- change = change.withPin();
+ if (object.field(platformPinnedField).asBool())
+ change = change.withPlatformPin();
+ if (object.field(revisionPinnedField).asBool())
+ change = change.withRevisionPin();
return change;
}
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 8a0e2d01d8c..9224c53136d 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
@@ -52,6 +52,8 @@ import com.yahoo.slime.SlimeUtils;
import com.yahoo.text.Text;
import com.yahoo.vespa.athenz.api.OAuthCredentials;
import com.yahoo.vespa.athenz.client.zms.ZmsClientException;
+import com.yahoo.vespa.flags.BooleanFlag;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.Instance;
@@ -131,7 +133,6 @@ import com.yahoo.vespa.hosted.controller.tenant.TenantInfo;
import com.yahoo.vespa.hosted.controller.versions.VersionStatus;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import com.yahoo.yolean.Exceptions;
-
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
@@ -166,6 +167,7 @@ import java.util.stream.Stream;
import static com.yahoo.jdisc.Response.Status.BAD_REQUEST;
import static com.yahoo.jdisc.Response.Status.CONFLICT;
+import static com.yahoo.vespa.flags.FetchVector.Dimension.APPLICATION_ID;
import static com.yahoo.yolean.Exceptions.uncheck;
import static java.util.Comparator.comparingInt;
import static java.util.Map.Entry.comparingByKey;
@@ -187,6 +189,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
private final Controller controller;
private final AccessControlRequests accessControlRequests;
private final TestConfigSerializer testConfigSerializer;
+ private final BooleanFlag failDeploymentOnMissingCertificateFile;
@Inject
public ApplicationApiHandler(ThreadedHttpRequestHandler.Context parentCtx,
@@ -196,6 +199,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
this.controller = controller;
this.accessControlRequests = accessControlRequests;
this.testConfigSerializer = new TestConfigSerializer(controller.system());
+ this.failDeploymentOnMissingCertificateFile = Flags.FAIL_DEPLOYMENT_ON_MISSING_CERTIFICATE_FILE.bindTo(controller.flagSource());
}
@Override
@@ -261,11 +265,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/package")) return applicationPackage(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/diff/{number}")) return applicationPackageDiff(path.get("tenant"), path.get("application"), path.get("number"));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying")) return deploying(path.get("tenant"), path.get("application"), "default", request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), "default", request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance")) return applications(path.get("tenant"), Optional.of(path.get("application")), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return instance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deploying(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job")) return JobControllerApiHandlerHelper.jobTypeResponse(controller, appIdFromPath(path), request.getUri());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return JobControllerApiHandlerHelper.runResponse(controller.applications().requireApplication(TenantAndApplicationId.from(path.get("tenant"), path.get("application"))), controller.jobController().runs(appIdFromPath(path), jobTypeFromPath(path)).descendingMap(), Optional.ofNullable(request.getProperty("limit")), request.getUri()); // (((\(✘෴✘)/)))
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/package")) return devApplicationPackage(appIdFromPath(path), jobTypeFromPath(path));
@@ -284,6 +286,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/content/{*}")) return content(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.getRest(), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/logs")) return logs(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/private-services")) return getPrivateServiceInfo(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"));
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/drop-documents")) return dropDocumentsStatus(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), Optional.ofNullable(request.getProperty("clusterId")).map(ClusterSpec.Id::from));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/access/support")) return supportAccess(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request.propertyMap());
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/node/{node}/service-dump")) return getServiceDump(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("node"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/scaling")) return scaling(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
@@ -328,14 +331,18 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}")) return createApplication(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), "default", false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), "default", true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), "default", request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/platform-pin")) return deployPlatform(path.get("tenant"), path.get("application"), "default", true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application-pin")) return deployApplication(path.get("tenant"), path.get("application"), "default", true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), "default", false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/key")) return addDeployKey(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/submit")) return submit(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}")) return createInstance(path.get("tenant"), path.get("application"), path.get("instance"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploy/{jobtype}")) return jobDeploy(appIdFromPath(path), jobTypeFromPath(path), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/platform")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/pin")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
- if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/platform-pin")) return deployPlatform(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application-pin")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), true, request);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/deploying/application")) return deployApplication(path.get("tenant"), path.get("application"), path.get("instance"), false, request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/submit")) return submit(path.get("tenant"), path.get("application"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}")) return trigger(appIdFromPath(path), jobTypeFromPath(path), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/job/{jobtype}/pause")) return pause(appIdFromPath(path), jobTypeFromPath(path));
@@ -345,6 +352,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/reindexing")) return enableReindexing(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/restart")) return restart(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/suspend")) return suspend(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), true);
+ if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/drop-documents")) return dropDocuments(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), Optional.ofNullable(request.getProperty("clusterId")).map(ClusterSpec.Id::from));
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/access/support")) return allowSupportAccess(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/instance/{instance}/environment/{environment}/region/{region}/node/{node}/service-dump")) return requestServiceDump(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), path.get("node"), request);
if (path.matches("/application/v4/tenant/{tenant}/application/{application}/environment/{environment}/region/{region}/instance/{instance}")) return deploySystemApplication(path.get("tenant"), path.get("application"), path.get("instance"), path.get("environment"), path.get("region"), request);
@@ -1897,7 +1905,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
JobControllerApiHandlerHelper.toSlime(response.setObject("applicationVersion"), application.revisions().get(deployment.revision()));
if ( ! status.jobsToRun().containsKey(stepStatus.job().get()))
response.setString("status", "complete");
- else if (stepStatus.readyAt(instance.change()).map(controller.clock().instant()::isBefore).orElse(true))
+ else if ( ! stepStatus.readiness(instance.change()).okAt(controller.clock().instant()))
response.setString("status", "pending");
else
response.setString("status", "running");
@@ -2016,6 +2024,65 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return new SlimeJsonResponse(slime);
}
+ private HttpResponse dropDocumentsStatus(String tenant, String application, String instance, String environment, String region, Optional<ClusterSpec.Id> clusterId) {
+ ZoneId zone = ZoneId.from(environment, region);
+ if (!zone.environment().isManuallyDeployed())
+ throw new IllegalArgumentException("Drop documents status is only available for manually deployed environments");
+
+ ApplicationId applicationId = ApplicationId.from(tenant, application, instance);
+ NodeFilter filters = NodeFilter.all()
+ .states(Node.State.active)
+ .applications(applicationId)
+ .clusterTypes(Node.ClusterType.content, Node.ClusterType.combined);
+ List<Node> nodes = controller.serviceRegistry().configServer().nodeRepository().list(zone, clusterId.map(filters::clusterIds).orElse(filters));
+ if (nodes.isEmpty()) {
+ throw new NotExistsException("No content nodes found for %s%s in %s".formatted(
+ applicationId.toFullString(), clusterId.map(id -> " cluster " + id).orElse(""), zone));
+ }
+
+ Instant readiedAt = null;
+ int numNoReport = 0, numInitial = 0, numDropped = 0, numReadied = 0, numStarted = 0;
+ for (Node node : nodes) {
+ Inspector report = Optional.ofNullable(node.reports().get("dropDocuments"))
+ .map(json -> SlimeUtils.jsonToSlime(json).get()).orElse(null);
+ if (report == null) numNoReport++;
+ else if (report.field("startedAt").valid()) {
+ numStarted++;
+ readiedAt = SlimeUtils.instant(report.field("readiedAt"));
+ } else if (report.field("readiedAt").valid()) numReadied++;
+ else if (report.field("droppedAt").valid()) numDropped++;
+ else numInitial++;
+ }
+
+ if (numInitial + numDropped > 0 && numNoReport + numReadied + numStarted > 0)
+ return ErrorResponse.conflict("Last dropping of documents may have failed to clear all documents due " +
+ "to concurrent topology changes, consider retrying");
+
+ Slime slime = new Slime();
+ Cursor root = slime.setObject();
+ if (numStarted + numNoReport == nodes.size()) {
+ if (readiedAt != null) root.setLong("lastDropped", readiedAt.toEpochMilli());
+ } else {
+ Cursor progress = root.setObject("progress");
+ progress.setLong("total", nodes.size());
+ progress.setLong("dropped", numDropped + numReadied + numStarted);
+ progress.setLong("started", numStarted + numNoReport);
+ }
+
+ return new SlimeJsonResponse(slime);
+ }
+
+ private HttpResponse dropDocuments(String tenant, String application, String instance, String environment, String region, Optional<ClusterSpec.Id> clusterId) {
+ ZoneId zone = ZoneId.from(environment, region);
+ if (!zone.environment().isManuallyDeployed())
+ throw new IllegalArgumentException("Drop documents status is only available for manually deployed environments");
+
+ ApplicationId applicationId = ApplicationId.from(tenant, application, instance);
+ controller.serviceRegistry().configServer().nodeRepository().dropDocuments(zone, applicationId, clusterId);
+ return new MessageResponse("Triggered drop documents for " + applicationId.toFullString() +
+ clusterId.map(id -> " and cluster " + id).orElse("") + " in " + zone);
+ }
+
private HttpResponse getGlobalRotationOverride(String tenantName, String applicationName, String instanceName, String environment, String region) {
DeploymentId deploymentId = new DeploymentId(ApplicationId.from(tenantName, applicationName, instanceName),
requireZone(environment, region));
@@ -2060,7 +2127,9 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
if ( ! instance.change().isEmpty()) {
instance.change().platform().ifPresent(version -> root.setString("platform", version.toString()));
instance.change().revision().ifPresent(revision -> root.setString("application", revision.toString()));
- root.setBool("pinned", instance.change().isPinned());
+ root.setBool("pinned", instance.change().isPlatformPinned());
+ root.setBool("platform-pinned", instance.change().isPlatformPinned());
+ root.setBool("application-pinned", instance.change().isRevisionPinned());
}
return new SlimeJsonResponse(slime);
}
@@ -2173,7 +2242,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
.collect(joining(", ")));
Change change = Change.of(version);
if (pin)
- change = change.withPin();
+ change = change.withPlatformPin();
controller.applications().deploymentTrigger().forceChange(id, change, isOperator(request));
response.append("Triggered ").append(change).append(" for ").append(id);
@@ -2182,7 +2251,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
}
/** Trigger deployment to the last known application package for the given application. */
- private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, HttpRequest request) {
+ private HttpResponse deployApplication(String tenantName, String applicationName, String instanceName, boolean pin, HttpRequest request) {
ApplicationId id = ApplicationId.from(tenantName, applicationName, instanceName);
Inspector buildField = toSlime(request.getData()).get().field("build");
long build = buildField.valid() ? buildField.asLong() : -1;
@@ -2192,6 +2261,8 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
RevisionId revision = build == -1 ? application.get().revisions().last().get().id()
: getRevision(application.get(), build);
Change change = Change.of(revision);
+ if (pin)
+ change = change.withRevisionPin();
controller.applications().deploymentTrigger().forceChange(id, change, isOperator(request));
response.append("Triggered ").append(change).append(" for ").append(id);
});
@@ -2232,7 +2303,7 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
return;
}
- ChangesToCancel cancel = ChangesToCancel.valueOf(choice.toUpperCase());
+ ChangesToCancel cancel = ChangesToCancel.valueOf(choice.replaceAll("-", "_").toUpperCase());
controller.applications().deploymentTrigger().cancelChange(id, cancel);
response.append("Changed deployment from '").append(change).append("' to '").append(controller.applications().requireInstance(id).change()).append("' for ").append(id);
});
@@ -2999,12 +3070,19 @@ public class ApplicationApiHandler extends AuditLoggingRequestHandler {
throw new IllegalArgumentException("Source URL must include scheme and host");
});
- ApplicationPackage applicationPackage = new ApplicationPackage(dataParts.get(EnvironmentResource.APPLICATION_ZIP), true);
+ ApplicationPackage applicationPackage =
+ new ApplicationPackage(dataParts.get(EnvironmentResource.APPLICATION_ZIP),
+ true,
+ failDeploymentOnMissingCertificateFile
+ .with(APPLICATION_ID, ApplicationId.from(tenant, application, "default").serializedForm())
+ .value());
byte[] testPackage = dataParts.getOrDefault(EnvironmentResource.APPLICATION_TEST_ZIP, new byte[0]);
Submission submission = new Submission(applicationPackage, testPackage, sourceUrl, sourceRevision, authorEmail, description, risk);
- controller.applications().verifyApplicationIdentityConfiguration(TenantName.from(tenant),
+ TenantName tenantName = TenantName.from(tenant);
+ controller.applications().verifyPlan(tenantName);
+ controller.applications().verifyApplicationIdentityConfiguration(tenantName,
Optional.empty(),
Optional.empty(),
applicationPackage,
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
index ce60e0054c4..9ff8c7df18b 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/JobControllerApiHandlerHelper.java
@@ -15,7 +15,6 @@ import com.yahoo.text.Text;
import com.yahoo.vespa.hosted.controller.Application;
import com.yahoo.vespa.hosted.controller.Controller;
import com.yahoo.vespa.hosted.controller.NotExistsException;
-import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
import com.yahoo.vespa.hosted.controller.api.integration.LogEntry;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobId;
@@ -26,6 +25,8 @@ import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.ConvergenceSummary;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.DelayCause;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.Readiness;
import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.JobStatus;
import com.yahoo.vespa.hosted.controller.deployment.Run;
@@ -269,17 +270,39 @@ class JobControllerApiHandlerHelper {
stepObject.setString("instance", stepStatus.instance().value());
// TODO: recursively search dependents for what is the relevant partial change when this is a delay step ...
- Optional<Instant> readyAt = stepStatus.job().map(jobsToRun::get).map(jobs -> jobs.get(0).readyAt())
- .orElse(stepStatus.readyAt(change));
- readyAt.ifPresent(ready -> stepObject.setLong("readyAt", ready.toEpochMilli()));
- readyAt.filter(controller.clock().instant()::isBefore)
- .ifPresent(until -> stepObject.setLong("delayedUntil", until.toEpochMilli()));
- stepStatus.pausedUntil().ifPresent(until -> stepObject.setLong("pausedUntil", until.toEpochMilli()));
- stepStatus.coolingDownUntil(change, Optional.empty()).ifPresent(until -> stepObject.setLong("coolingDownUntil", until.toEpochMilli()));
- stepStatus.blockedUntil(Change.of(controller.systemVersion(versionStatus))) // Dummy version — just anything with a platform.
- .ifPresent(until -> stepObject.setLong("platformBlockedUntil", until.toEpochMilli()));
- stepStatus.blockedUntil(Change.of(RevisionId.forProduction(1))) // Dummy version — just anything with an application.
- .ifPresent(until -> stepObject.setLong("applicationBlockedUntil", until.toEpochMilli()));
+ Readiness readiness = stepStatus.job().map(jobsToRun::get).map(job -> job.get(0).readiness())
+ .orElse(stepStatus.readiness(change));
+ if (readiness.ok()) {
+ stepObject.setLong("readyAt", readiness.at().toEpochMilli());
+ if ( ! readiness.okAt(controller.clock().instant())) {
+ Instant until = readiness.at();
+ stepObject.setLong("delayedUntil", readiness.at().toEpochMilli());
+ switch (readiness.cause()) {
+ case paused -> stepObject.setLong("pausedUntil", until.toEpochMilli());
+ case coolingDown -> stepObject.setLong("coolingDownUntil", until.toEpochMilli());
+ case changeBlocked -> {
+ Readiness platformReadiness = stepStatus.readiness(Change.of(controller.systemVersion(versionStatus))); // Dummy version — just anything with a platform.
+ if (platformReadiness.cause() == DelayCause.changeBlocked)
+ stepObject.setLong("platformBlockedUntil", platformReadiness.at().toEpochMilli());
+ Readiness applicationReadiness = stepStatus.readiness(Change.of(RevisionId.forProduction(1))); // Dummy version — just anything with an application.
+ if (applicationReadiness.cause() == DelayCause.changeBlocked)
+ stepObject.setLong("applicationBlockedUntil", applicationReadiness.at().toEpochMilli());
+ }
+ }
+ }
+ }
+ stepObject.setString("delayCause",
+ switch (readiness.cause()) {
+ case none -> null;
+ case invalidPackage -> "invalidPackage";
+ case paused -> "paused";
+ case coolingDown -> "coolingDown";
+ case changeBlocked -> "changeBlocked";
+ case blocked -> "blocked";
+ case running -> "running";
+ case notReady -> "notReady";
+ case unverified -> "unverified";
+ });
if (stepStatus.type() == DeploymentStatus.StepType.delay)
stepStatus.completedAt(change).ifPresent(completed -> stepObject.setLong("completedAt", completed.toEpochMilli()));
@@ -289,7 +312,9 @@ class JobControllerApiHandlerHelper {
if ( ! change.isEmpty()) {
change.platform().ifPresent(version -> deployingObject.setString("platform", version.toFullString()));
change.revision().ifPresent(revision -> toSlime(deployingObject.setObject("application"), application.revisions().get(revision)));
- if (change.isPinned()) deployingObject.setBool("pinned", true);
+ if (change.isPlatformPinned()) deployingObject.setBool("pinned", true);
+ if (change.isPlatformPinned()) deployingObject.setBool("platformPinned", true);
+ if (change.isRevisionPinned()) deployingObject.setBool("revisionPinned", true);
}
Cursor latestVersionsObject = stepObject.setObject("latestVersions");
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
index bc7dd4199c7..d29603c529c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/billing/BillingApiHandler.java
@@ -433,6 +433,9 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler {
lineItem.getDiskHours().ifPresent(diskHours ->
cursor.setString("diskHours", diskHours.toString())
);
+ lineItem.getGpuHours().ifPresent(gpuHours ->
+ cursor.setString("gpuHours", gpuHours.toString())
+ );
lineItem.getCpuCost().ifPresent(cpuCost ->
cursor.setString("cpuCost", cpuCost.toString())
);
@@ -442,7 +445,9 @@ public class BillingApiHandler extends ThreadedHttpRequestHandler {
lineItem.getDiskCost().ifPresent(diskCost ->
cursor.setString("diskCost", diskCost.toString())
);
-
+ lineItem.getGpuCost().ifPresent(gpuCost ->
+ cursor.setString("gpuCost", gpuCost.toString())
+ );
}
private HttpResponse deleteInstrument(String tenant, String userId, String instrument) {
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
index 759b2366229..6e5635e8c8c 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiHandler.java
@@ -22,6 +22,8 @@ import com.yahoo.vespa.hosted.controller.application.ApplicationList;
import com.yahoo.vespa.hosted.controller.application.Change;
import com.yahoo.vespa.hosted.controller.application.TenantAndApplicationId;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.DelayCause;
+import com.yahoo.vespa.hosted.controller.deployment.DeploymentStatus.Readiness;
import com.yahoo.vespa.hosted.controller.deployment.Run;
import com.yahoo.vespa.hosted.controller.deployment.RunStatus;
import com.yahoo.vespa.hosted.controller.deployment.Versions;
@@ -169,11 +171,14 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
instanceObject.setString("application", instance.application().value());
instanceObject.setString("instance", instance.instance().value());
instanceObject.setBool("upgrading", status.application().require(instance.instance()).change().platform().equals(Optional.of(statistics.version())));
- instanceObject.setBool("pinned", status.application().require(instance.instance()).change().isPinned());
+ instanceObject.setBool("pinned", status.application().require(instance.instance()).change().isPlatformPinned());
+ instanceObject.setBool("platformPinned", status.application().require(instance.instance()).change().isPlatformPinned());
+ instanceObject.setBool("revisionPinned", status.application().require(instance.instance()).change().isRevisionPinned());
DeploymentStatus.StepStatus stepStatus = status.instanceSteps().get(instance.instance());
if (stepStatus != null) { // Instance may not have any steps, i.e. an empty deployment spec has been submitted
- stepStatus.blockedUntil(Change.of(statistics.version()))
- .ifPresent(until -> instanceObject.setLong("blockedUntil", until.toEpochMilli()));
+ Readiness platformReadiness = stepStatus.blockedUntil(Change.of(statistics.version()));
+ if (platformReadiness.cause() == DelayCause.changeBlocked)
+ instanceObject.setLong("blockedUntil", platformReadiness.at().toEpochMilli());
}
instanceObject.setString("upgradePolicy", toString(status.application().deploymentSpec().instance(instance.instance())
.map(DeploymentInstanceSpec::upgradePolicy)
@@ -185,10 +190,12 @@ public class DeploymentApiHandler extends ThreadedHttpRequestHandler {
if ( ! job.application().equals(instance)) return;
Cursor jobObject = jobsArray.addObject();
jobObject.setString("name", job.type().jobName());
- jobStatus.pausedUntil().ifPresent(until -> jobObject.setLong("pausedUntil", until.toEpochMilli()));
- jobStatus.coolingDownUntil(status.application().require(instance.instance()).change(), Optional.empty())
- .ifPresent(until -> jobObject.setLong("coolingDownUntil", until.toEpochMilli()));
if (jobsToRun.containsKey(job)) {
+ Readiness readiness = jobsToRun.get(job).get(0).readiness();
+ switch (readiness.cause()) {
+ case paused -> jobObject.setLong("pausedUntil", readiness.at().toEpochMilli());
+ case coolingDown -> jobObject.setLong("coolingDownUntil", readiness.at().toEpochMilli());
+ }
List<Versions> versionsOnThisPlatform = jobsToRun.get(job).stream()
.map(DeploymentStatus.Job::versions)
.filter(versions -> versions.targetPlatform().equals(statistics.version()))
diff --git a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
index e9947f3d565..45c00848407 100644
--- a/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
+++ b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/versions/VespaVersion.java
@@ -41,8 +41,8 @@ public record VespaVersion(Version version,
.not().upgradingTo(statistics.version());
InstanceList failingOnThis = all.matching(instance -> statistics.failingUpgrades().stream().anyMatch(run -> run.id().application().equals(instance)));
- // 'broken' if any canary fails
- if ( ! failingOnThis.with(UpgradePolicy.canary).isEmpty())
+ // 'broken' if any canary fails, and no non-canary is upgraded
+ if ( ! failingOnThis.with(UpgradePolicy.canary).isEmpty() && productionOnThis.not().with(UpgradePolicy.canary).isEmpty())
return Confidence.broken;
// 'broken' if 6 non-canary was broken by this, and that is at least 5% of all
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 d9ee82f5e90..04c8c46e1ef 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
@@ -22,6 +22,7 @@ import com.yahoo.path.Path;
import com.yahoo.vespa.flags.PermanentFlags;
import com.yahoo.vespa.hosted.controller.api.application.v4.model.DeploymentData;
import com.yahoo.vespa.hosted.controller.api.identifiers.DeploymentId;
+import com.yahoo.vespa.hosted.controller.api.integration.billing.PlanRegistryMock;
import com.yahoo.vespa.hosted.controller.api.integration.billing.Quota;
import com.yahoo.vespa.hosted.controller.api.integration.certificates.EndpointCertificateMetadata;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ContainerEndpoint;
@@ -40,6 +41,7 @@ import com.yahoo.vespa.hosted.controller.application.pkg.ApplicationPackage;
import com.yahoo.vespa.hosted.controller.deployment.ApplicationPackageBuilder;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
+import com.yahoo.vespa.hosted.controller.deployment.JobController;
import com.yahoo.vespa.hosted.controller.deployment.Submission;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.notification.Notification;
@@ -54,7 +56,6 @@ import com.yahoo.vespa.hosted.controller.routing.rotation.RotationLock;
import com.yahoo.vespa.hosted.controller.versions.VespaVersion.Confidence;
import com.yahoo.vespa.hosted.rotation.config.RotationsConfig;
import org.junit.jupiter.api.Test;
-
import java.io.InputStream;
import java.time.Duration;
import java.time.Instant;
@@ -106,9 +107,11 @@ public class ControllerTest {
Version version1 = tester.configServer().initialVersion();
var context = tester.newDeploymentContext();
context.submit(applicationPackage);
- assertEquals(ApplicationVersion.from(RevisionId.forProduction(1), DeploymentContext.defaultSourceRevision, "a@b", new Version("6.1"), Instant.ofEpochSecond(1)),
- context.application().revisions().get(context.instance().change().revision().get()),
- "Application version is known from completion of initial job");
+ RevisionId id = RevisionId.forProduction(1);
+ Version compileVersion = new Version("6.1");
+ assertEquals(new ApplicationVersion(id, Optional.of(DeploymentContext.defaultSourceRevision), Optional.of("a@b"), Optional.of(compileVersion), Optional.empty(), Optional.of(Instant.ofEpochSecond(1)), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0),
+ context.application().revisions().get(context.instance().change().revision().get()),
+ "Application version is known from completion of initial job");
context.runJob(systemTest);
context.runJob(stagingTest);
@@ -220,6 +223,59 @@ public class ControllerTest {
}
@Test
+ void testPackagePruning() {
+ DeploymentContext app = tester.newDeploymentContext().submit().deploy();
+ RevisionId revision1 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+
+ app.submit().deploy();
+ RevisionId revision2 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+
+ // Revision 1 is marked as obsolete now
+ app.submit().deploy();
+ RevisionId revision3 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+
+ // Time advances, and revision 2 is marked as obsolete now
+ tester.clock().advance(JobController.obsoletePackageExpiry);
+ app.submit().deploy();
+ RevisionId revision4 = app.lastSubmission().get();
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision4.number()));
+
+ // Time advances, and revision is now old enough to be pruned
+ tester.clock().advance(Duration.ofMillis(1));
+ app.submit().deploy();
+ RevisionId revision5 = app.lastSubmission().get();
+ assertFalse(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision1.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision2.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision3.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision4.number()));
+ assertTrue(tester.controllerTester().serviceRegistry().applicationStore()
+ .hasBuild(app.instanceId().tenant(), app.instanceId().application(), revision5.number()));
+ }
+
+ @Test
void testGlobalRotationStatus() {
var context = tester.newDeploymentContext();
var zone1 = ZoneId.from("prod", "us-west-1");
@@ -1513,4 +1569,19 @@ public class ControllerTest {
assertFalse(tester.configServer().application(deployment.applicationId(), deployment.zoneId()).isPresent());
}
+ @Test
+ void testVerifyPlan() {
+ DeploymentId deployment = tester.newDeploymentContext().deploymentIdIn(ZoneId.from("prod", "us-west-1"));
+ TenantName tenant = deployment.applicationId().tenant();
+
+ tester.controller().serviceRegistry().billingController().setPlan(tenant, PlanRegistryMock.nonePlan.id(), false, false);
+ try {
+ tester.controller().applications().verifyPlan(tenant);
+ fail("should have thrown an exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("Tenant 'tenant' has a plan 'None Plan - for testing purposes' with zero quota, not allowed to deploy. " +
+ "See https://cloud.vespa.ai/support", e.getMessage());
+ }
+ }
+
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
index a76d2eca521..ca31ceebc17 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/EndpointTest.java
@@ -126,7 +126,7 @@ public class EndpointTest {
Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls()).in(SystemName.main),
// Prod endpoint in CD
- "https://cd.a1.t1.us-north-1.vespa.oath.cloud/",
+ "https://cd.a1.t1.us-north-1.cd.vespa.oath.cloud/",
Endpoint.of(instance1).target(cluster, prodZone).on(Port.tls()).in(SystemName.cd),
// Test endpoint in main
@@ -300,7 +300,7 @@ public class EndpointTest {
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.main),
- "cd.a2.t2.us-east-3-r.vespa.oath.cloud",
+ "cd.a2.t2.us-east-3-r.cd.vespa.oath.cloud",
Endpoint.of(app2)
.targetApplication(EndpointId.defaultId(), ClusterSpec.Id.from("qrs"),
Map.of(new DeploymentId(app2.instance("i1"), ZoneId.from("prod", "us-east-3")), 1))
@@ -335,7 +335,7 @@ public class EndpointTest {
.routingMethod(RoutingMethod.exclusive)
.on(Port.tls())
.in(SystemName.main),
- "https://cd.a2.t2.a.vespa.oath.cloud/",
+ "https://cd.a2.t2.a.cd.vespa.oath.cloud/",
Endpoint.of(app2)
.targetApplication(EndpointId.defaultId(), ClusterSpec.Id.from("qrs"),
Map.of(new DeploymentId(app2.instance("i1"), ZoneId.from("prod", "us-east-3")), 1))
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
index 7f578d3017e..e915a204e4b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/application/pkg/ApplicationPackageTest.java
@@ -5,7 +5,6 @@ import com.yahoo.config.application.api.DeploymentSpec;
import com.yahoo.config.application.api.ValidationId;
import com.yahoo.io.LazyInputStream;
import org.junit.jupiter.api.Test;
-
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
@@ -97,7 +96,7 @@ public class ApplicationPackageTest {
"jdisc.xml", jdiscXml,
"content/content.xml", contentXml,
"content/nodes.xml", nodesXml),
- unzip(new ApplicationPackage(zip, false).metaDataZip()));
+ unzip(new ApplicationPackage(zip).metaDataZip()));
}
@Test
@@ -105,7 +104,7 @@ public class ApplicationPackageTest {
byte[] zip = filesZip(Map.of("services.xml", servicesXml.getBytes(UTF_8)));
try {
- new ApplicationPackage(zip, false).metaDataZip();
+ new ApplicationPackage(zip).metaDataZip();
fail("Should fail on missing include file");
}
catch (RuntimeException e) {
@@ -152,6 +151,21 @@ public class ApplicationPackageTest {
assertEquals(originalPackage.bundleHash(), similarDeploymentXml.bundleHash());
}
+ @Test
+ void testCertificateFileExists() throws Exception {
+ getApplicationZip("with-certificate.zip", true);
+ }
+
+ @Test
+ void testCertificateFileMissing() throws Exception {
+ try {
+ getApplicationZip("original.zip", true);
+ fail("Should fail on missing certificate file file");
+ } catch (RuntimeException e) {
+ assertEquals("No client certificate found in security/ in application package, see https://cloud.vespa.ai/en/security/guide", e.getMessage());
+ }
+ }
+
static Map<String, String> unzip(byte[] zip) {
return ZipEntries.from(zip, __ -> true, 1 << 24, true)
.asList().stream()
@@ -160,7 +174,11 @@ public class ApplicationPackageTest {
}
private ApplicationPackage getApplicationZip(String path) throws IOException {
- return new ApplicationPackage(Files.readAllBytes(Path.of("src/test/resources/application-packages/" + path)), true);
+ return getApplicationZip(path, false);
+ }
+
+ private ApplicationPackage getApplicationZip(String path, boolean checkCertificateFile) throws IOException {
+ return new ApplicationPackage(Files.readAllBytes(Path.of("src/test/resources/application-packages/" + path)), true, checkCertificateFile);
}
static byte[] zip(Map<String, String> content) {
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
index 226fb785bf6..6e5c2458c92 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/deployment/DeploymentTriggerTest.java
@@ -653,15 +653,21 @@ public class DeploymentTriggerTest {
assertEquals(appVersion1, latestDeployed(app.instance()));
// Downgrading application version.
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(appVersion0));
- assertEquals(Change.of(appVersion0), app.instance().change());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(appVersion0).withRevisionPin());
+ assertEquals(Change.of(appVersion0).withRevisionPin(), app.instance().change());
app.runJob(stagingTest)
- .runJob(productionUsCentral1)
- .runJob(productionUsEast3)
- .runJob(productionUsWest1);
- assertEquals(Change.empty(), app.instance().change());
+ .runJob(productionUsCentral1)
+ .runJob(productionUsEast3)
+ .runJob(productionUsWest1);
+ assertEquals(Change.empty().withRevisionPin(), app.instance().change());
assertEquals(appVersion0, app.instance().deployments().get(productionUsEast3.zone()).revision());
assertEquals(appVersion0, latestDeployed(app.instance()));
+
+ tester.outstandingChangeDeployer().run();
+ assertEquals(Change.empty().withRevisionPin(), app.instance().change());
+ tester.deploymentTrigger().cancelChange(app.instanceId(), ALL);
+ tester.outstandingChangeDeployer().run();
+ assertEquals(Change.of(appVersion1), app.instance().change());
}
@Test
@@ -756,8 +762,8 @@ public class DeploymentTriggerTest {
// Last job has a different deployment target, so tests need to run again.
app1.runJob(productionEuWest1) // Upgrade completes, and revision is the only change.
- .runJob(productionUsCentral1) // With only revision change, central should run to cover a previous failure.
- .runJob(productionEuWest1); // Finally, west changes revision.
+ .runJob(productionUsCentral1) // With only revision change, central should run to cover a previous failure.
+ .runJob(productionEuWest1); // Finally, west changes revision.
assertEquals(Change.empty(), app1.instance().change());
assertEquals(Optional.of(RunStatus.success), app1.instanceJobs().get(productionUsCentral1).lastStatus());
}
@@ -1239,13 +1245,13 @@ public class DeploymentTriggerTest {
assertEquals(Change.empty(), app.instance().change());
// Application is pinned to previous version, and downgrades to that. Tests are re-run.
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version0).withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(version0).withPlatformPin());
app.runJob(stagingTest).runJob(productionUsEast3);
tester.clock().advance(Duration.ofMinutes(1));
app.failDeployment(testUsEast3);
tester.clock().advance(Duration.ofMinutes(11)); // Job is cooling down after consecutive failures.
app.runJob(testUsEast3);
- assertEquals(Change.empty().withPin(), app.instance().change());
+ assertEquals(Change.empty().withPlatformPin(), app.instance().change());
// A new upgrade is attempted, and production tests wait for redeployment.
tester.controllerTester().upgradeSystem(version2);
@@ -2234,7 +2240,7 @@ public class DeploymentTriggerTest {
.majorVersion(7)
.compileVersion(version1)
.build());
- tester.deploymentTrigger().forceChange(app.instanceId(), app.instance().change().withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), app.instance().change().withPlatformPin());
app.deploy();
assertEquals(version1, tester.jobs().last(app.instanceId(), productionUsEast3).get().versions().targetPlatform());
assertEquals(version1, app.application().revisions().get(tester.jobs().last(app.instanceId(), productionUsEast3).get().versions().targetRevision()).compileVersion().get());
@@ -2251,7 +2257,7 @@ public class DeploymentTriggerTest {
// The new app enters a platform block window, and is pinned to the old platform;
// the new submission overrides both those settings, as the new revision should roll out regardless.
tester.atMondayMorning();
- tester.deploymentTrigger().forceChange(newApp.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(newApp.instanceId(), Change.empty().withPlatformPin());
newApp.submit(new ApplicationPackageBuilder().compileVersion(version2)
.systemTest()
.blockChange(false, true, "mon", "0-23", "UTC")
@@ -2280,11 +2286,11 @@ public class DeploymentTriggerTest {
tester.upgrader().run();
assertEquals(Change.of(newRevision).with(version1), newApp.instance().change());
- tester.deploymentTrigger().forceChange(newApp.instanceId(), newApp.instance().change().withPin());
+ tester.deploymentTrigger().forceChange(newApp.instanceId(), newApp.instance().change().withPlatformPin());
tester.outstandingChangeDeployer().run();
- assertEquals(Change.of(newRevision).with(version1).withPin(), newApp.instance().change());
+ assertEquals(Change.of(newRevision).with(version1).withPlatformPin(), newApp.instance().change());
tester.upgrader().run();
- assertEquals(Change.of(newRevision).with(version1).withPin(), newApp.instance().change());
+ assertEquals(Change.of(newRevision).with(version1).withPlatformPin(), newApp.instance().change());
newApp.deploy();
assertEquals(version1, tester.jobs().last(newApp.instanceId(), productionUsEast3).get().versions().targetPlatform());
@@ -2381,7 +2387,7 @@ public class DeploymentTriggerTest {
.build()))
.getMessage());
- tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(oldVersion).with(app.application().revisions().last().get().id()).withPin());
+ tester.deploymentTrigger().forceChange(app.instanceId(), Change.of(oldVersion).with(app.application().revisions().last().get().id()).withPlatformPin());
app.deploy();
assertEquals(oldVersion, app.deployment(ZoneId.from("prod", "us-east-3")).version());
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 297997365b0..7004028c072 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
@@ -88,6 +88,8 @@ public class NodeRepositoryMock implements NodeRepository {
(node.owner().isPresent() && filter.applications().contains(node.owner().get())))
.filter(node -> filter.hostnames().isEmpty() || filter.hostnames().contains(node.hostname()))
.filter(node -> filter.states().isEmpty() || filter.states().contains(node.state()))
+ .filter(node -> filter.clusterIds().isEmpty() || filter.clusterIds().contains(ClusterSpec.Id.from(node.clusterId())))
+ .filter(node -> filter.clusterTypes().isEmpty() || filter.clusterTypes().contains(node.clusterType()))
.toList();
}
@@ -201,6 +203,10 @@ public class NodeRepositoryMock implements NodeRepository {
}
@Override
+ public void dropDocuments(ZoneId zoneId, ApplicationId applicationId, Optional<ClusterSpec.Id> clusterId) {
+ }
+
+ @Override
public void updateReports(ZoneId zone, String hostname, Map<String, String> reports) {
Map<String, String> trimmedReports = reports.entrySet().stream()
// Null value clears a report
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
index 5deba19c5ea..8537fdaa9f5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/BcpGroupUpdaterTest.java
@@ -57,7 +57,7 @@ public class BcpGroupUpdaterTest {
setQpsMetric(50.0, context.application().id().defaultInstance(), prod1, tester);
setBcpMetrics(1.5, 0.1, 0.45, context.instanceId(), prod1, "cluster1", tester);
deploymentMetricsMaintainer.maintain();
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertTrafficFraction(1.0, 1.0, context.instanceId(), prod1, tester);
assertNoBcpGroupInfo(context.instanceId(), prod1, "cluster1", tester, "No other regions in group");
@@ -67,7 +67,7 @@ public class BcpGroupUpdaterTest {
setQpsMetric(20.0, context.application().id().defaultInstance(), prod2, tester);
setBcpMetrics(100.0, 0.1, 0.45, context.instanceId(), prod1, "cluster1", tester);
deploymentMetricsMaintainer.maintain();
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertTrafficFraction(0.75, 1.0, context.instanceId(), prod1, tester);
assertTrafficFraction(0.25, 1.0, context.instanceId(), prod2, tester);
assertNoBcpGroupInfo(context.instanceId(), prod1, "cluster1", tester,
@@ -75,7 +75,7 @@ public class BcpGroupUpdaterTest {
assertBcpGroupInfo(100.0, 0.1, 0.45,
context.instanceId(), prod2, "cluster1", tester);
setBcpMetrics(50.0, 0.2, 0.5, context.instanceId(), prod2, "cluster1", tester);
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertBcpGroupInfo(50.0, 0.2, 0.5,
context.instanceId(), prod1, "cluster1", tester);
@@ -85,7 +85,7 @@ public class BcpGroupUpdaterTest {
setQpsMetric(45.0, context.application().id().defaultInstance(), prod2, tester);
setQpsMetric(02.0, context.application().id().defaultInstance(), prod3, tester);
deploymentMetricsMaintainer.maintain();
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertTrafficFraction(0.53, 0.53 + (double)45/2 / 100, context.instanceId(), prod1, tester);
assertTrafficFraction(0.45, 0.45 + (double)53/2 / 100, context.instanceId(), prod2, tester);
assertTrafficFraction(0.02, 0.02 + (double)53/2 / 100, context.instanceId(), prod3, tester);
@@ -150,7 +150,7 @@ public class BcpGroupUpdaterTest {
setQpsMetric(40.0, context.application().id().defaultInstance(), eu1, tester);
deploymentMetricsMaintainer.maintain();
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertTrafficFraction(0.5, 0.5, context.instanceId(), ap1, tester);
assertTrafficFraction(0.0, 0.5, context.instanceId(), ap2, tester);
assertTrafficFraction(0.1, 0.1, context.instanceId(), us1, tester);
@@ -221,7 +221,7 @@ public class BcpGroupUpdaterTest {
setQpsMetric(60.0, context.application().id().defaultInstance(), eu1, tester);
deploymentMetricsMaintainer.maintain();
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertTrafficFraction(0.10, 0.10 + 50 / 200.0 / 1.5, context.instanceId(), ap1, tester);
assertTrafficFraction(0.25, 0.25 + 30 / 200.0 / 1.5, context.instanceId(), ap2, tester);
assertTrafficFraction(0.00, 0.00 + 40 / 200.0 / 2.5, context.instanceId(), us1, tester);
@@ -242,7 +242,7 @@ public class BcpGroupUpdaterTest {
setBcpMetrics(300, 0.3, 0.3, context.instanceId(), us3, "cluster2", tester);
setBcpMetrics(100, 0.1, 0.1, context.instanceId(), eu1, "cluster2", tester);
- assertEquals(1.0, updater.maintain(), 0.0000001);
+ assertEquals(0.0, updater.maintain(), 0.0000001);
assertNoBcpGroupInfo(context.instanceId(), ap1, "cluster1", tester, "No info in ap");
assertNoBcpGroupInfo(context.instanceId(), ap2, "cluster1", tester, "No info in ap");
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
index 63e2c99cb6e..6452edc9e61 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/ControllerMaintainerTest.java
@@ -38,14 +38,14 @@ public class ControllerMaintainerTest {
void records_metric() {
TestControllerMaintainer maintainer = new TestControllerMaintainer(tester.controller(), SystemName.main, new AtomicInteger());
maintainer.run();
- assertEquals(1.0, successFactorMetric(), 0.0000001);
+ assertEquals(0.0, successFactorDeviationMetric(), 0.0000001);
maintainer.success = false;
maintainer.run();
maintainer.run();
- assertEquals(0.0, successFactorMetric(), 0.0000001);
+ assertEquals(1.0, successFactorDeviationMetric(), 0.0000001);
maintainer.success = true;
maintainer.run();
- assertEquals(1.0, successFactorMetric(), 0.0000001);
+ assertEquals(0.0, successFactorDeviationMetric(), 0.0000001);
}
private long consecutiveFailuresMetric() {
@@ -54,10 +54,10 @@ public class ControllerMaintainerTest {
"maintenance.consecutiveFailures").get().longValue();
}
- private long successFactorMetric() {
+ private long successFactorDeviationMetric() {
MetricsMock metrics = (MetricsMock) tester.controller().metric();
return metrics.getMetric((context) -> "TestControllerMaintainer".equals(context.get("job")),
- "maintenance.successFactor").get().longValue();
+ "maintenance.successFactorDeviation").get().longValue();
}
private static class TestControllerMaintainer extends ControllerMaintainer {
@@ -73,7 +73,7 @@ public class ControllerMaintainerTest {
@Override
protected double maintain() {
executions.incrementAndGet();
- return success ? 1.0 : 0.0;
+ return success ? 0.0 : 1.0;
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
index 934e15ad623..49cf8c634ba 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/EndpointCertificateMaintainerTest.java
@@ -49,7 +49,7 @@ public class EndpointCertificateMaintainerTest {
@Test
void old_and_unused_cert_is_deleted() {
tester.curator().writeEndpointCertificateMetadata(ApplicationId.defaultId(), exampleMetadata);
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
assertTrue(tester.curator().readEndpointCertificateMetadata(ApplicationId.defaultId()).isEmpty());
}
@@ -57,7 +57,7 @@ public class EndpointCertificateMaintainerTest {
void unused_but_recently_used_cert_is_not_deleted() {
EndpointCertificateMetadata recentlyRequestedCert = exampleMetadata.withLastRequested(tester.clock().instant().minusSeconds(3600).getEpochSecond());
tester.curator().writeEndpointCertificateMetadata(ApplicationId.defaultId(), recentlyRequestedCert);
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
assertEquals(Optional.of(recentlyRequestedCert), tester.curator().readEndpointCertificateMetadata(ApplicationId.defaultId()));
}
@@ -69,7 +69,7 @@ public class EndpointCertificateMaintainerTest {
secretStore.setSecret(exampleMetadata.keyName(), "foo", 1);
secretStore.setSecret(exampleMetadata.certName(), "bar", 1);
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
var updatedCert = Optional.of(recentlyRequestedCert.withLastRefreshed(tester.clock().instant().getEpochSecond()).withVersion(1));
@@ -90,7 +90,7 @@ public class EndpointCertificateMaintainerTest {
deploymentContext.submit(applicationPackage).runJob(systemTest).runJob(stagingTest).runJob(productionUsWest1);
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
var metadata = tester.curator().readEndpointCertificateMetadata(appId).orElseThrow();
tester.controller().serviceRegistry().endpointCertificateProvider().certificateDetails(metadata.rootRequestId()); // cert should not be deleted, the app is deployed!
}
@@ -110,7 +110,7 @@ public class EndpointCertificateMaintainerTest {
var originalMetadata = tester.curator().readEndpointCertificateMetadata(appId).orElseThrow();
// cert should not be deleted, the app is deployed!
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
assertEquals(tester.curator().readEndpointCertificateMetadata(appId), Optional.of(originalMetadata));
tester.controller().serviceRegistry().endpointCertificateProvider().certificateDetails(originalMetadata.rootRequestId());
@@ -121,7 +121,7 @@ public class EndpointCertificateMaintainerTest {
tester.controller().serviceRegistry().endpointCertificateProvider().requestCaSignedCertificate(appId, originalMetadata.requestedDnsSans(), Optional.of(originalMetadata));
// We should now pick up the new key and cert version + uuid, but not force trigger deployment yet
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
deploymentContext.assertNotRunning(productionUsWest1);
var updatedMetadata = tester.curator().readEndpointCertificateMetadata(appId).orElseThrow();
assertNotEquals(originalMetadata.leafRequestId().orElseThrow(), updatedMetadata.leafRequestId().orElseThrow());
@@ -130,7 +130,7 @@ public class EndpointCertificateMaintainerTest {
// after another 4 days, we should force trigger deployment if it hasn't already happened
tester.clock().advance(Duration.ofDays(4).plusSeconds(1));
deploymentContext.assertNotRunning(productionUsWest1);
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
deploymentContext.assertRunning(productionUsWest1);
}
@@ -156,7 +156,7 @@ public class EndpointCertificateMaintainerTest {
ApplicationId unknown = ApplicationId.fromSerializedForm("applicationid:is:unknown");
endpointCertificateProvider.requestCaSignedCertificate(unknown, List.of("a", "b", "c"), Optional.empty()); // Unknown to controller!
- assertEquals(1.0, maintainer.maintain(), 0.0000001);
+ assertEquals(0.0, maintainer.maintain(), 0.0000001);
assertTrue(endpointCertificateProvider.dnsNamesOf(unknown).isEmpty());
assertTrue(endpointCertificateProvider.listCertificates().isEmpty());
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
index 11110d6edaa..96c1d7c545d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/UpgraderTest.java
@@ -5,7 +5,6 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.zone.ZoneId;
import com.yahoo.test.ManualClock;
-import com.yahoo.vespa.hosted.controller.api.integration.deployment.JobType;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RevisionId;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.application.Change;
@@ -27,7 +26,6 @@ import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
-import java.util.OptionalInt;
import java.util.Set;
import java.util.stream.Collectors;
@@ -856,10 +854,10 @@ public class UpgraderTest {
// Create an application with pinned platform version.
var context = tester.newDeploymentContext().submit().deploy();
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPlatformPin());
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
assertEquals(3, context.instance().deployments().size());
// Application does not upgrade.
@@ -867,21 +865,21 @@ public class UpgraderTest {
tester.controllerTester().upgradeSystem(version1);
tester.upgrader().maintain();
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
// New application package is deployed.
context.submit().deploy();
assertFalse(context.instance().change().hasTargets());
- assertTrue(context.instance().change().isPinned());
+ assertTrue(context.instance().change().isPlatformPinned());
// Application upgrades to new version when pin is removed.
tester.deploymentTrigger().cancelChange(context.instanceId(), PIN);
tester.upgrader().maintain();
assertTrue(context.instance().change().hasTargets());
- assertFalse(context.instance().change().isPinned());
+ assertFalse(context.instance().change().isPlatformPinned());
// Application is pinned to new version, and upgrade is therefore not cancelled, even though confidence is broken.
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.empty().withPlatformPin());
tester.upgrader().maintain();
tester.triggerJobs();
assertEquals(version1, context.instance().change().platform().get());
@@ -890,7 +888,7 @@ public class UpgraderTest {
context.runJob(systemTest).runJob(stagingTest).runJob(productionUsCentral1)
.timeOutUpgrade(productionUsWest1);
tester.deploymentTrigger().cancelChange(context.instanceId(), ALL);
- tester.deploymentTrigger().forceChange(context.instanceId(), Change.of(version0).withPin());
+ tester.deploymentTrigger().forceChange(context.instanceId(), Change.of(version0).withPlatformPin());
assertEquals(version0, context.instance().change().platform().get());
// Application downgrades to pinned version.
@@ -913,7 +911,7 @@ public class UpgraderTest {
// Keep app 1 on current version
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
tester.controller().applications().store(app.with(app1.instance().name(),
- instance -> instance.withChange(instance.change().withPin()))));
+ instance -> instance.withChange(instance.change().withPlatformPin()))));
// New version is released
Version version1 = Version.fromString("6.2");
@@ -935,7 +933,7 @@ public class UpgraderTest {
// App 1 is unpinned and upgrades to latest 6
tester.controller().applications().lockApplicationIfPresent(app1.application().id(), app ->
tester.controller().applications().store(app.with(app1.instance().name(),
- instance -> instance.withChange(instance.change().withoutPin()))));
+ instance -> instance.withChange(instance.change().withoutPlatformPin()))));
tester.upgrader().maintain();
assertEquals(version1,
app1.instance().change().platform().orElseThrow(),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
index 52bd8e9c618..39bf61df9ed 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/maintenance/VcmrMaintainerTest.java
@@ -58,7 +58,7 @@ public class VcmrMaintainerTest {
@Test
void recycle_hosts_after_completion() {
var vcmrReport = new VcmrReport();
- vcmrReport.addVcmr("id123", ZonedDateTime.now(), ZonedDateTime.now());
+ vcmrReport.addVcmr(new ChangeRequestSource("aws", "id123", "url", ChangeRequestSource.Status.WAITING_FOR_APPROVAL , ZonedDateTime.now(), ZonedDateTime.now()));
var parkedNode = createNode(host1, NodeType.host, Node.State.parked, true);
var failedNode = createNode(host2, NodeType.host, Node.State.failed, false);
var reports = vcmrReport.toNodeReports();
@@ -181,7 +181,7 @@ public class VcmrMaintainerTest {
activeNode = nodeRepo.list(zoneId, NodeFilter.all().hostnames(host2)).get(0);
var report = VcmrReport.fromReports(activeNode.reports());
var reportAdded = report.getVcmrs().stream()
- .filter(vcmr -> vcmr.getId().equals(changeRequestId))
+ .filter(vcmr -> vcmr.id().equals(changeRequestId))
.count() == 1;
assertTrue(reportAdded);
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
index 589fc25700f..b71d3cf838b 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/persistence/ApplicationSerializerTest.java
@@ -101,16 +101,17 @@ public class ApplicationSerializerTest {
Optional.empty(),
Optional.of("best commit"),
Optional.of("hash1"),
+ Optional.of(Instant.ofEpochMilli(777)),
true,
false,
Optional.of("~(˘▾˘)~"),
3);
assertEquals("https://github/org/repo/tree/commit1", applicationVersion1.sourceUrl().get());
- ApplicationVersion applicationVersion2 = ApplicationVersion.from(RevisionId.forDevelopment(31, new JobId(id1, DeploymentContext.productionUsEast3)),
- new SourceRevision("repo1", "branch1", "commit1"), "a@b",
- Version.fromString("6.3.1"),
- Instant.ofEpochMilli(496));
+ RevisionId id = RevisionId.forDevelopment(31, new JobId(id1, DeploymentContext.productionUsEast3));
+ SourceRevision source = new SourceRevision("repo1", "branch1", "commit1");
+ Version compileVersion = Version.fromString("6.3.1");
+ ApplicationVersion applicationVersion2 = new ApplicationVersion(id, Optional.of(source), Optional.of("a@b"), Optional.of(compileVersion), Optional.empty(), Optional.of(Instant.ofEpochMilli(496)), Optional.empty(), Optional.empty(), Optional.empty(), Optional.empty(), true, false, Optional.empty(), 0);
Instant activityAt = Instant.parse("2018-06-01T10:15:30.00Z");
deployments.add(new Deployment(zone1, CloudAccount.empty, applicationVersion1.id(), Version.fromString("1.2.3"), Instant.ofEpochMilli(3),
DeploymentMetrics.none, DeploymentActivity.none, QuotaUsage.none, OptionalDouble.empty()));
@@ -143,7 +144,7 @@ public class ApplicationSerializerTest {
Map.of(),
List.of(),
RotationStatus.EMPTY,
- Change.of(Version.fromString("6.7")).withPin()));
+ Change.of(Version.fromString("6.7")).withPlatformPin().withRevisionPin()));
Application original = new Application(TenantAndApplicationId.from(id1),
Instant.now().truncatedTo(ChronoUnit.MILLIS),
@@ -174,6 +175,7 @@ public class ApplicationSerializerTest {
assertEquals(original.revisions().last().get().sourceUrl(), serialized.revisions().last().get().sourceUrl());
assertEquals(original.revisions().last().get().commit(), serialized.revisions().last().get().commit());
assertEquals(original.revisions().last().get().bundleHash(), serialized.revisions().last().get().bundleHash());
+ assertEquals(original.revisions().last().get().obsoleteAt(), serialized.revisions().last().get().obsoleteAt());
assertEquals(original.revisions().last().get().hasPackage(), serialized.revisions().last().get().hasPackage());
assertEquals(original.revisions().last().get().shouldSkip(), serialized.revisions().last().get().shouldSkip());
assertEquals(original.revisions().last().get().description(), serialized.revisions().last().get().description());
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 9a34989aeff..c6d68bc5d9d 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
@@ -11,6 +11,7 @@ import com.yahoo.config.provision.ApplicationName;
import com.yahoo.config.provision.AthenzService;
import com.yahoo.config.provision.Environment;
import com.yahoo.config.provision.InstanceName;
+import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.RegionName;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.zone.RoutingMethod;
@@ -40,6 +41,7 @@ import com.yahoo.vespa.hosted.controller.api.identifiers.UserId;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.ApplicationAction;
import com.yahoo.vespa.hosted.controller.api.integration.athenz.AthenzDbMock;
import com.yahoo.vespa.hosted.controller.api.integration.configserver.ConfigServerException;
+import com.yahoo.vespa.hosted.controller.api.integration.configserver.Node;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.ApplicationVersion;
import com.yahoo.vespa.hosted.controller.api.integration.deployment.RunId;
import com.yahoo.vespa.hosted.controller.api.integration.organization.Contact;
@@ -55,6 +57,7 @@ import com.yahoo.vespa.hosted.controller.deployment.DeploymentContext;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTester;
import com.yahoo.vespa.hosted.controller.deployment.DeploymentTrigger;
import com.yahoo.vespa.hosted.controller.integration.ConfigServerMock;
+import com.yahoo.vespa.hosted.controller.integration.NodeRepositoryMock;
import com.yahoo.vespa.hosted.controller.integration.ZoneApiMock;
import com.yahoo.vespa.hosted.controller.metric.ApplicationMetrics;
import com.yahoo.vespa.hosted.controller.notification.Notification;
@@ -90,6 +93,7 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
+import java.util.function.BiFunction;
import java.util.function.Supplier;
import static com.yahoo.application.container.handler.Request.Method.DELETE;
@@ -510,6 +514,42 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/content/bar/file.json?query=param", GET).userIdentity(USER_ID),
"{\"path\":\"/bar/file.json\"}");
+ // Drop documents
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/environment/prod/region/us-central-1/drop-documents", POST)
+ .userIdentity(USER_ID),
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Drop documents status is only available for manually deployed environments\"}", 400);
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", POST)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Triggered drop documents for tenant2.application1.default in dev.us-east-1\"}");
+
+ ZoneId zone = ZoneId.from("dev", "us-east-1");
+ ApplicationId application = ApplicationId.from("tenant2", "application1", "default");
+ BiFunction<Integer, String, Node> nodeBuilder = (index, dropDocumentsReport) -> Node.builder().hostname("node" + index + ".dev.us-east-1.test")
+ .state(Node.State.active).type(NodeType.tenant).owner(application).clusterId("c1").clusterType(Node.ClusterType.content)
+ .reports(dropDocumentsReport == null ? Map.of() : Map.of("dropDocuments", dropDocumentsReport)).build();
+ NodeRepositoryMock nodeRepository = deploymentTester.controllerTester().serviceRegistry().configServer().nodeRepository();
+
+ // 2 nodes, neither ever dropped any documents
+ nodeRepository.putNodes(zone, List.of(nodeBuilder.apply(1, null), nodeBuilder.apply(2, null)));
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", GET).userIdentity(USER_ID),
+ "{}");
+
+ // 1 node previously dropped documents, 1 node without any report
+ nodeRepository.putNodes(zone, List.of(nodeBuilder.apply(1, "{\"droppedAt\":1,\"readiedAt\":2,\"startedAt\":3}"), nodeBuilder.apply(2, null)));
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", GET).userIdentity(USER_ID),
+ "{\"lastDropped\":2}");
+
+ nodeRepository.putNodes(zone, List.of(nodeBuilder.apply(1, "{}"), nodeBuilder.apply(2, null)));
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", GET).userIdentity(USER_ID),
+ "{\"error-code\":\"CONFLICT\",\"message\":\"Last dropping of documents may have failed to clear all documents due to concurrent topology changes, consider retrying\"}", 409);
+
+ nodeRepository.putNodes(zone, List.of(nodeBuilder.apply(1, "{}"), nodeBuilder.apply(2, "{\"droppedAt\":1}")));
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", GET).userIdentity(USER_ID),
+ "{\"progress\":{\"total\":2,\"dropped\":1,\"started\":0}}");
+
+ nodeRepository.putNodes(zone, List.of(nodeBuilder.apply(1, "{\"startedAt\":3}"), nodeBuilder.apply(2, "{\"readiedAt\":1}")));
+ tester.assertResponse(request("/application/v4/tenant/tenant2/application/application1/instance/default/environment/dev/region/us-east-1/drop-documents", GET).userIdentity(USER_ID),
+ "{\"progress\":{\"total\":2,\"dropped\":2,\"started\":1}}");
updateMetrics();
@@ -541,24 +581,22 @@ public class ApplicationApiTest extends ControllerContainerTest {
"{\"message\":\"No deployment in progress for tenant1.application1.instance1 at this time\"}");
// POST pinning to a given version to an application
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin", POST)
.userIdentity(USER_ID)
.data("6.1.0"),
"{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
assertTrue(tester.controller().auditLogger().readLog().entries().stream()
- .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin?")),
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin?")),
"Action is logged to audit log");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE only the pin to a given version
- tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform-pin", DELETE)
.userIdentity(USER_ID),
"{\"message\":\"Changed deployment from 'pin to 6.1' to 'upgrade to 6.1' for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":false}");
// POST pinning again
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", POST)
@@ -566,14 +604,14 @@ public class ApplicationApiTest extends ControllerContainerTest {
.data("6.1"),
"{\"message\":\"Triggered pin to 6.1 for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"platform\":\"6.1\",\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE only the version, but leave the pin
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/platform", DELETE)
.userIdentity(USER_ID),
"{\"message\":\"Changed deployment from 'pin to 6.1' to 'pin to current platform' for tenant1.application1.instance1\"}");
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
- .userIdentity(USER_ID), "{\"pinned\":true}");
+ .userIdentity(USER_ID), "{\"pinned\":true,\"platform-pinned\":true,\"application-pinned\":false}");
// DELETE also the pin to a given version
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/pin", DELETE)
@@ -582,6 +620,32 @@ public class ApplicationApiTest extends ControllerContainerTest {
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
.userIdentity(USER_ID), "{}");
+ // POST pinning to a given revision to an application
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin", POST)
+ .userIdentity(USER_ID)
+ .data(""),
+ "{\"message\":\"Triggered pin to build 1 for tenant1.application1.instance1\"}");
+ assertTrue(tester.controller().auditLogger().readLog().entries().stream()
+ .anyMatch(entry -> entry.resource().equals("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin?")),
+ "Action is logged to audit log");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{\"application\":\"build 1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":true}");
+
+ // DELETE only the pin to a given revision
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application-pin", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'pin to build 1' to 'revision change to build 1' for tenant1.application1.instance1\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{\"application\":\"build 1\",\"pinned\":false,\"platform-pinned\":false,\"application-pinned\":false}");
+
+ // DELETE deploying to a given revision
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying/application", DELETE)
+ .userIdentity(USER_ID),
+ "{\"message\":\"Changed deployment from 'revision change to build 1' to 'no change' for tenant1.application1.instance1\"}");
+ tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/deploying", GET)
+ .userIdentity(USER_ID), "{}");
+
+
// POST a pause to a production job
tester.assertResponse(request("/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1/pause", POST)
.userIdentity(USER_ID),
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview-2.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview-2.json
index a02fb1fb375..6793553faca 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview-2.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview-2.json
@@ -9,6 +9,7 @@
"declared": true,
"instance": "default",
"readyAt": 0,
+ "delayCause": null,
"deploying": {
"application": {
"build": 3,
@@ -155,6 +156,7 @@
"declared": false,
"instance": "default",
"readyAt": 0,
+ "delayCause": null,
"jobName": "system-test",
"url": "https://some.url:43/instance/default/job/system-test",
"environment": "test",
@@ -344,6 +346,7 @@
"readyAt": 15153000,
"delayedUntil": 15153000,
"coolingDownUntil": 15153000,
+ "delayCause": "coolingDown",
"jobName": "staging-test",
"url": "https://some.url:43/instance/default/job/staging-test",
"environment": "staging",
@@ -777,6 +780,8 @@
"declared": true,
"instance": "default",
"readyAt": 14403000,
+ "delayedUntil": 14403000,
+ "delayCause": "running",
"jobName": "production-us-central-1",
"url": "https://some.url:43/instance/default/job/production-us-central-1",
"environment": "prod",
@@ -902,6 +907,7 @@
],
"declared": true,
"instance": "default",
+ "delayCause": "notReady",
"jobName": "test-us-central-1",
"url": "https://some.url:43/instance/default/job/test-us-central-1",
"environment": "prod",
@@ -1042,6 +1048,7 @@
],
"declared": true,
"instance": "default",
+ "delayCause": "notReady",
"jobName": "production-us-west-1",
"url": "https://some.url:43/instance/default/job/production-us-west-1",
"environment": "prod",
@@ -1150,6 +1157,7 @@
],
"declared": true,
"instance": "default",
+ "delayCause": "notReady",
"jobName": "production-us-east-3",
"url": "https://some.url:43/instance/default/job/production-us-east-3",
"environment": "prod",
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
index 35dd6fc5398..0b7c64c72a5 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/responses/deployment-overview.json
@@ -9,6 +9,7 @@
"declared": true,
"instance": "instance1",
"readyAt": 0,
+ "delayCause": null,
"deploying": {
"application": {
"build": 4,
@@ -47,6 +48,14 @@
"sourceUrl": "repository1/tree/commit1",
"commit": "commit1"
}
+ },
+ {
+ "application": {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1"
+ }
}
],
"blockers": [ ]
@@ -59,6 +68,8 @@
"declared": false,
"instance": "instance1",
"readyAt": 0,
+ "delayedUntil": 0,
+ "delayCause": "running",
"jobName": "system-test",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/system-test",
"environment": "test",
@@ -187,6 +198,8 @@
"declared": false,
"instance": "instance1",
"readyAt": 0,
+ "delayedUntil": 0,
+ "delayCause": "running",
"jobName": "staging-test",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/staging-test",
"environment": "staging",
@@ -348,6 +361,7 @@
],
"declared": true,
"instance": "instance1",
+ "delayCause": "unverified",
"jobName": "production-us-central-1",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-central-1",
"environment": "prod",
@@ -405,6 +419,7 @@
],
"declared": true,
"instance": "instance1",
+ "delayCause": "notReady",
"jobName": "production-us-west-1",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-west-1",
"environment": "prod",
@@ -462,6 +477,7 @@
],
"declared": true,
"instance": "instance1",
+ "delayCause": "notReady",
"jobName": "production-us-east-3",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance1/job/production-us-east-3",
"environment": "prod",
@@ -547,6 +563,7 @@
],
"declared": true,
"instance": "instance2",
+ "delayCause": "notReady",
"deploying": {
"application": {
"build": 4,
@@ -585,6 +602,14 @@
"sourceUrl": "repository1/tree/commit1",
"commit": "commit1"
}
+ },
+ {
+ "application": {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1"
+ }
}
],
"blockers": [ ]
@@ -598,6 +623,7 @@
],
"declared": true,
"instance": "instance2",
+ "delayCause": "unverified",
"jobName": "production-us-central-1",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance2/job/production-us-central-1",
"environment": "prod",
@@ -624,6 +650,7 @@
],
"declared": true,
"instance": "instance2",
+ "delayCause": "notReady",
"jobName": "production-us-west-1",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance2/job/production-us-west-1",
"environment": "prod",
@@ -650,6 +677,7 @@
],
"declared": true,
"instance": "instance2",
+ "delayCause": "notReady",
"jobName": "production-us-east-3",
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1/instance/instance2/job/production-us-east-3",
"environment": "prod",
@@ -697,6 +725,15 @@
"description": "my best commit yet",
"risk": 9001,
"deployable": false
+ },
+ {
+ "build": 1,
+ "compileVersion": "6.1.0",
+ "sourceUrl": "repository1/tree/commit1",
+ "commit": "commit1",
+ "description": "my best commit yet",
+ "risk": 9001,
+ "deployable": true
}
]
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
index 7ee5f6db9b9..c942a7ad63d 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/DeploymentApiTest.java
@@ -18,6 +18,7 @@ import com.yahoo.vespa.hosted.controller.versions.VespaVersion;
import org.junit.jupiter.api.Test;
import java.io.File;
+import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -76,7 +77,7 @@ public class DeploymentApiTest extends ControllerContainerTest {
deploymentTester.upgrader().maintain();
deploymentTester.triggerJobs();
productionApp.runJob(DeploymentContext.systemTest).runJob(DeploymentContext.stagingTest).runJob(DeploymentContext.productionUsWest1);
- failingApp.failDeployment(DeploymentContext.systemTest).failDeployment(DeploymentContext.stagingTest);
+ failingApp.failDeployment(DeploymentContext.systemTest).failDeployment(DeploymentContext.stagingTest).timeOutConvergence(DeploymentContext.stagingTest);
deploymentTester.upgrader().maintain();
deploymentTester.triggerJobs();
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
index 51398daa1d4..ac43fbf2a80 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/deployment/responses/root.json
@@ -37,16 +37,17 @@
"instance": "default",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
{
- "name": "system-test",
- "coolingDownUntil": 1600000000000
+ "name": "system-test"
},
{
"name": "staging-test",
- "coolingDownUntil": 1600000000000
+ "coolingDownUntil": 1600022201500
},
{
"name": "production-us-west-1"
@@ -79,6 +80,8 @@
"instance": "i2",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -141,7 +144,7 @@
"url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1",
"upgradePolicy": "default",
"failing": "staging-test",
- "status": "error"
+ "status": "installationFailed"
}
],
"productionApplications": [
@@ -165,14 +168,6 @@
"running": "system-test"
},
{
- "tenant": "tenant1",
- "application": "application1",
- "instance": "default",
- "url": "http://localhost:8080/application/v4/tenant/tenant1/application/application1",
- "upgradePolicy": "default",
- "running": "staging-test"
- },
- {
"tenant": "tenant2",
"application": "application2",
"instance": "i2",
@@ -188,17 +183,18 @@
"instance": "default",
"upgrading": true,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
{
"name": "system-test",
- "coolingDownUntil": 1600000000000,
"pending": "application"
},
{
"name": "staging-test",
- "coolingDownUntil": 1600000000000,
+ "coolingDownUntil": 1600022201500,
"pending": "platform"
},
{
@@ -222,15 +218,10 @@
},
"staging-test": {
"failing": {
- "number": 2,
- "start": 1600000000000,
- "end": 1600000000000,
- "status": "error"
- },
- "running": {
"number": 3,
"start": 1600000000000,
- "status": "running"
+ "end": 1600014401000,
+ "status": "installationFailed"
}
}
},
@@ -250,15 +241,10 @@
},
"staging-test": {
"failing": {
- "number": 2,
- "start": 1600000000000,
- "end": 1600000000000,
- "status": "error"
- },
- "running": {
"number": 3,
"start": 1600000000000,
- "status": "running"
+ "end": 1600014401000,
+ "status": "installationFailed"
}
}
}
@@ -269,6 +255,8 @@
"instance": "i1",
"upgrading": false,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -289,15 +277,15 @@
"system-test": {
"failing": {
"number": 3,
- "start": 1600000000000,
- "end": 1600000000000,
+ "start": 1600014401000,
+ "end": 1600014401000,
"status": "error"
}
},
"staging-test": {
"running": {
"number": 3,
- "start": 1600000000000,
+ "start": 1600014401000,
"status": "running"
}
},
@@ -329,6 +317,8 @@
"instance": "i2",
"upgrading": true,
"pinned": false,
+ "platformPinned": false,
+ "revisionPinned": false,
"upgradePolicy": "default",
"compileVersion": "6.1.0",
"jobs": [
@@ -341,7 +331,7 @@
"production-us-west-1": {
"running": {
"number": 2,
- "start": 1600000000000,
+ "start": 1600014401000,
"status": "running"
}
}
@@ -350,7 +340,7 @@
"production-us-west-1": {
"running": {
"number": 2,
- "start": 1600000000000,
+ "start": 1600014401000,
"status": "running"
}
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
index 43ad01fc5c2..168a1345c39 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/routing/rotation/RotationRepositoryTest.java
@@ -18,7 +18,6 @@ import org.junit.jupiter.api.Test;
import java.net.URI;
import java.util.List;
import java.util.Set;
-import java.util.stream.Collectors;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -155,7 +154,7 @@ public class RotationRepositoryTest {
var application2 = tester.newDeploymentContext("tenant2", "app2", "default");
application2.submit(applicationPackage).deploy();
assertEquals(List.of(new RotationId("foo-1")), rotationIds(application2.instance().rotations()));
- assertEquals("https://cd.app2.tenant2.global.vespa.oath.cloud/",
+ assertEquals("https://cd.app2.tenant2.global.cd.vespa.oath.cloud/",
tester.controller().routing().readDeclaredEndpointsOf(application2.instanceId()).primary().get().url().toString());
}
diff --git a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
index f08e92a515d..7afa5c7f44a 100644
--- a/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
+++ b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/versions/VersionStatusTest.java
@@ -349,6 +349,13 @@ public class VersionStatusTest {
assertEquals(Confidence.high, confidence(tester.controller(), version0), "Confidence remains unchanged for version0: High");
assertEquals(VespaVersion.Confidence.high, confidence(tester.controller(), version2), "90% of defaults deployed successfully: High");
+ // Canary failing a new revision does not affect confidence
+ canary0.submit(canaryPolicy).failDeployment(systemTest);
+ tester.controllerTester().computeVersionStatus();
+ assertEquals(Confidence.high, confidence(tester.controller(), version0), "Confidence remains unchanged for version0: High");
+ assertEquals(VespaVersion.Confidence.high, confidence(tester.controller(), version2), "90% of defaults deployed successfully: High");
+ canary0.deploy();
+
// A new version is released, all canaries upgrade successfully, but enough "default" apps fail to mark version
// as broken
Version version3 = new Version("6.5");
diff --git a/controller-server/src/test/resources/application-packages/with-certificate.zip b/controller-server/src/test/resources/application-packages/with-certificate.zip
new file mode 100644
index 00000000000..1540b96c7ef
--- /dev/null
+++ b/controller-server/src/test/resources/application-packages/with-certificate.zip
Binary files differ
diff --git a/default_build_settings.cmake b/default_build_settings.cmake
index c82bb91accc..2785a98a396 100644
--- a/default_build_settings.cmake
+++ b/default_build_settings.cmake
@@ -4,11 +4,11 @@ include(VespaExtendedDefaultBuildSettings OPTIONAL)
function(setup_vespa_default_build_settings_darwin)
message("-- Setting up default build settings for darwin")
- set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS_PREFIX}/lib" "/usr/local/opt/bison/lib" "/usr/local/opt/flex/lib" "/usr/local/opt/icu4c/lib" "/usr/local/opt/openssl@1.1/lib" "/usr/local/opt/openblas/lib")
- list(APPEND DEFAULT_EXTRA_LINK_DIRECTORY "/usr/local/lib")
+ set(DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_DEPS_PREFIX}/lib" "${VESPA_HOMEBREW_PREFIX}/opt/bison/lib" "${VESPA_HOMEBREW_PREFIX}/opt/flex/lib" "${VESPA_HOMEBREW_PREFIX}/opt/icu4c/lib" "${VESPA_HOMEBREW_PREFIX}/opt/openssl@1.1/lib" "${VESPA_HOMEBREW_PREFIX}/opt/openblas/lib")
+ list(APPEND DEFAULT_EXTRA_LINK_DIRECTORY "${VESPA_HOMEBREW_PREFIX}/lib")
set(DEFAULT_EXTRA_LINK_DIRECTORY "${DEFAULT_EXTRA_LINK_DIRECTORY}" PARENT_SCOPE)
- set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS_PREFIX}/include" "/usr/local/opt/flex/include" "/usr/local/opt/icu4c/include" "/usr/local/opt/openssl@1.1/include" "/usr/local/opt/openblas/include")
- list(APPEND DEFAULT_EXTRA_INCLUDE_DIRECTORY "/usr/local/include")
+ set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_DEPS_PREFIX}/include" "${VESPA_HOMEBREW_PREFIX}/opt/flex/include" "${VESPA_HOMEBREW_PREFIX}/opt/icu4c/include" "${VESPA_HOMEBREW_PREFIX}/opt/openssl@1.1/include" "${VESPA_HOMEBREW_PREFIX}/opt/openblas/include")
+ list(APPEND DEFAULT_EXTRA_INCLUDE_DIRECTORY "${VESPA_HOMEBREW_PREFIX}/include")
set(DEFAULT_EXTRA_INCLUDE_DIRECTORY "${DEFAULT_EXTRA_INCLUDE_DIRECTORY}" PARENT_SCOPE)
endfunction()
@@ -84,7 +84,7 @@ endfunction()
function(vespa_use_default_cmake_prefix_path)
set(DEFAULT_CMAKE_PREFIX_PATH ${VESPA_DEPS_PREFIX})
if (APPLE)
- list(APPEND DEFAULT_CMAKE_PREFIX_PATH "/usr/local/opt/bison" "/usr/local/opt/flex" "/usr/local/opt/openssl@1.1" "/usr/local/opt/openblas" "/usr/local/opt/icu4c")
+ list(APPEND DEFAULT_CMAKE_PREFIX_PATH "${VESPA_HOMEBREW_PREFIX}/opt/bison" "${VESPA_HOMEBREW_PREFIX}/opt/flex" "${VESPA_HOMEBREW_PREFIX}/opt/openssl@1.1" "${VESPA_HOMEBREW_PREFIX}/opt/openblas" "${VESPA_HOMEBREW_PREFIX}/opt/icu4c")
endif()
message("-- DEFAULT_CMAKE_PREFIX_PATH is ${DEFAULT_CMAKE_PREFIX_PATH}")
if(NOT DEFINED CMAKE_PREFIX_PATH)
@@ -203,8 +203,8 @@ function(vespa_use_default_cxx_compiler)
unset(DEFAULT_CMAKE_CXX_COMPILER)
if(NOT DEFINED VESPA_COMPILER_VARIANT OR VESPA_COMPILER_VARIANT STREQUAL "gcc")
if(APPLE)
- set(DEFAULT_CMAKE_C_COMPILER "/usr/local/bin/gcc-12")
- set(DEFAULT_CMAKE_CXX_COMPILER "/usr/local/bin/g++-12")
+ set(DEFAULT_CMAKE_C_COMPILER "${VESPA_HOMEBREW_PREFIX}/bin/gcc-12")
+ set(DEFAULT_CMAKE_CXX_COMPILER "${VESPA_HOMEBREW_PREFIX}/bin/g++-12")
elseif(VESPA_OS_DISTRO_COMBINED STREQUAL "amzn 2")
set(DEFAULT_CMAKE_C_COMPILER "/usr/bin/gcc10-gcc")
set(DEFAULT_CMAKE_CXX_COMPILER "/usr/bin/gcc10-g++")
@@ -215,8 +215,8 @@ function(vespa_use_default_cxx_compiler)
endif()
elseif(VESPA_COMPILER_VARIANT STREQUAL "clang")
if(APPLE)
- set(DEFAULT_CMAKE_C_COMPILER, "/usr/local/opt/llvm/bin/clang")
- set(DEFAULT_CMAKE_CXX_COMPILER "/usr/local/opt/llvm/bin/clang++")
+ set(DEFAULT_CMAKE_C_COMPILER, "${VESPA_HOMEBREW_PREFIX}/opt/llvm/bin/clang")
+ set(DEFAULT_CMAKE_CXX_COMPILER "${VESPA_HOMEBREW_PREFIX}/opt/llvm/bin/clang++")
elseif(EXISTS "/usr/bin/clang" AND EXISTS "/usr/bin/clang++")
set(DEFAULT_CMAKE_C_COMPILER "/usr/bin/clang")
set(DEFAULT_CMAKE_CXX_COMPILER "/usr/bin/clang++")
diff --git a/docprocs/src/test/cfg/ilscripts.cfg b/docprocs/src/test/cfg/ilscripts.cfg
index c58028f1056..6e4c75f46a7 100644
--- a/docprocs/src/test/cfg/ilscripts.cfg
+++ b/docprocs/src/test/cfg/ilscripts.cfg
@@ -11,3 +11,4 @@ ilscript[0].content[2] "input song | attribute song"
ilscript[0].content[4] "input artist . " ". input title | index combined"
ilscript[0].content[5] "(input artist || "") . " " . (input title || "") | index combinedWithFallback"
+
diff --git a/document/abi-spec.json b/document/abi-spec.json
index 5f520f1a4d1..f3ed7c389a1 100644
--- a/document/abi-spec.json
+++ b/document/abi-spec.json
@@ -387,7 +387,9 @@
"public void <init>(com.yahoo.document.DocumentPut, com.yahoo.document.Document)",
"public boolean equals(java.lang.Object)",
"public int hashCode()",
- "public java.lang.String toString()"
+ "public java.lang.String toString()",
+ "public void setCreateIfNonExistent(boolean)",
+ "public boolean getCreateIfNonExistent()"
],
"fields" : [ ]
},
@@ -3549,4 +3551,4 @@
"protected com.yahoo.document.update.ValueUpdate$ValueUpdateClassID valueUpdateClassID"
]
}
-} \ No newline at end of file
+}
diff --git a/document/src/main/java/com/yahoo/document/DocumentPut.java b/document/src/main/java/com/yahoo/document/DocumentPut.java
index bfadaa8d4da..f91ef9a9ee2 100644
--- a/document/src/main/java/com/yahoo/document/DocumentPut.java
+++ b/document/src/main/java/com/yahoo/document/DocumentPut.java
@@ -2,6 +2,7 @@
package com.yahoo.document;
import java.util.Objects;
+import com.yahoo.api.annotations.Beta;
/**
* @author Vegard Sjonfjell
@@ -9,6 +10,7 @@ import java.util.Objects;
public class DocumentPut extends DocumentOperation {
private final Document document;
+ private boolean createIfNonExistent;
public DocumentPut(Document document) {
this.document = document;
@@ -38,6 +40,7 @@ public class DocumentPut extends DocumentOperation {
public DocumentPut(DocumentPut other) {
super(other);
this.document = new Document(other.getDocument());
+ createIfNonExistent = other.createIfNonExistent;
}
/**
@@ -46,6 +49,7 @@ public class DocumentPut extends DocumentOperation {
public DocumentPut(DocumentPut other, Document newDocument) {
super(other);
this.document = newDocument;
+ createIfNonExistent = other.createIfNonExistent;
}
@Override
@@ -54,6 +58,7 @@ public class DocumentPut extends DocumentOperation {
if (o == null || getClass() != o.getClass()) return false;
DocumentPut that = (DocumentPut) o;
return document.equals(that.document) &&
+ (createIfNonExistent == that.createIfNonExistent) &&
Objects.equals(getCondition(), that.getCondition());
}
@@ -67,4 +72,13 @@ public class DocumentPut extends DocumentOperation {
return "put of document " + getId();
}
+ @Beta
+ public void setCreateIfNonExistent(boolean value) {
+ createIfNonExistent = value;
+ }
+
+ @Beta
+ public boolean getCreateIfNonExistent() {
+ return createIfNonExistent;
+ }
}
diff --git a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
index 110564bea46..795f8e93187 100644
--- a/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
+++ b/document/src/main/java/com/yahoo/document/json/JsonSerializationHelper.java
@@ -5,7 +5,6 @@ import com.fasterxml.jackson.core.JsonGenerator;
import com.yahoo.document.DataType;
import com.yahoo.document.DocumentId;
import com.yahoo.document.Field;
-import com.yahoo.document.PositionDataType;
import com.yahoo.document.PrimitiveDataType;
import com.yahoo.document.datatypes.Array;
import com.yahoo.document.datatypes.BoolFieldValue;
@@ -41,7 +40,6 @@ import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Iterator;
import java.util.Map;
-import java.util.Set;
/**
* @author Steinar Knutsen
@@ -49,7 +47,7 @@ import java.util.Set;
*/
public class JsonSerializationHelper {
- private final static Base64.Encoder base64Encoder = Base64.getEncoder(); // Important: _basic_ format
+ private final static Base64.Encoder base64Encoder = Base64.getEncoder().withoutPadding(); // Important: _basic_ format
static class JsonSerializationException extends RuntimeException {
public JsonSerializationException(Exception base) {
@@ -166,8 +164,7 @@ public class JsonSerializationHelper {
public static void serializeStructField(FieldWriter fieldWriter, JsonGenerator generator, FieldBase field, Struct value) {
DataType dt = value.getDataType();
- if (dt instanceof GeoPosType) {
- var gpt = (GeoPosType)dt;
+ if (dt instanceof GeoPosType gpt) {
if (gpt.renderJsonAsVespa8()) {
serializeGeoPos(generator, field, value, gpt);
return;
diff --git a/document/src/main/java/com/yahoo/document/serialization/DocumentUpdateFlags.java b/document/src/main/java/com/yahoo/document/serialization/DocumentUpdateFlags.java
index e3510676148..11ded80ed2a 100644
--- a/document/src/main/java/com/yahoo/document/serialization/DocumentUpdateFlags.java
+++ b/document/src/main/java/com/yahoo/document/serialization/DocumentUpdateFlags.java
@@ -23,7 +23,7 @@ public class DocumentUpdateFlags {
}
public void setCreateIfNonExistent(boolean value) {
flags &= ~1; // clear flag
- flags |= value ? 1 : 0; // set flag
+ flags |= value ? (byte)1 : (byte)0; // set flag
}
public int injectInto(int value) {
return extractValue(value) | (flags << 28);
diff --git a/document/src/main/java/com/yahoo/document/serialization/XmlSerializationHelper.java b/document/src/main/java/com/yahoo/document/serialization/XmlSerializationHelper.java
index 9c1df0cd6c7..d35693f785f 100644
--- a/document/src/main/java/com/yahoo/document/serialization/XmlSerializationHelper.java
+++ b/document/src/main/java/com/yahoo/document/serialization/XmlSerializationHelper.java
@@ -34,6 +34,8 @@ import java.util.Map;
@SuppressWarnings("removal")
public class XmlSerializationHelper {
+ private final static Base64.Encoder base64Encoder = Base64.getEncoder().withoutPadding();
+
public static void printArrayXml(Array array, XmlStream xml) {
List<FieldValue> lst = array.getValues();
for (FieldValue value : lst) {
@@ -98,7 +100,7 @@ public class XmlSerializationHelper {
public static void printRawXml(Raw r, XmlStream xml) {
xml.addAttribute("binaryencoding", "base64");
- xml.addContent(Base64.getEncoder().encodeToString(r.getByteBuffer().array()));
+ xml.addContent(base64Encoder.encodeToString(r.getByteBuffer().array()));
}
public static void printStringXml(StringFieldValue s, XmlStream xml) {
@@ -106,7 +108,7 @@ public class XmlSerializationHelper {
if (containsNonPrintableCharactersString(content)) {
byte[] bytecontent = Utf8.toBytes(content);
xml.addAttribute("binaryencoding", "base64");
- xml.addContent(Base64.getEncoder().encodeToString(bytecontent));
+ xml.addContent(base64Encoder.encodeToString(bytecontent));
} else {
xml.addContent(content);
}
diff --git a/document/src/test/java/com/yahoo/document/DocumentTestCase.java b/document/src/test/java/com/yahoo/document/DocumentTestCase.java
index 33b77cb1878..4470865b636 100644
--- a/document/src/test/java/com/yahoo/document/DocumentTestCase.java
+++ b/document/src/test/java/com/yahoo/document/DocumentTestCase.java
@@ -52,7 +52,7 @@ public class DocumentTestCase extends DocumentTestCaseBase {
" <mailid>emailfromalicetobob&amp;someone</mailid>\n" +
" <date>-2013512400</date>\n" +
" <attachmentcount>2</attachmentcount>\n" +
- " <rawfield binaryencoding=\"base64\">AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiYw==</rawfield>\n";
+ " <rawfield binaryencoding=\"base64\">AAECAwQFBgcICQoLDA0ODxAREhMUFRYXGBkaGxwdHh8gISIjJCUmJygpKissLS4vMDEyMzQ1Njc4OTo7PD0+P0BBQkNERUZHSElKS0xNTk9QUVJTVFVWV1hZWltcXV5fYGFiYw</rawfield>\n";
private static final String SERTEST_DOC_AS_XML_WEIGHT1 =
" <weightedfield>\n" +
diff --git a/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java b/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
index 08a5c9a124c..af7469de31b 100644
--- a/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
+++ b/document/src/test/java/com/yahoo/document/json/DocumentUpdateJsonSerializerTest.java
@@ -504,7 +504,7 @@ public class DocumentUpdateJsonSerializerTest {
" 'update': 'DOCUMENT_ID',",
" 'fields': {",
" 'raw_field': {",
- " 'assign': 'RG9uJ3QgYmVsaWV2ZSBoaXMgbGllcw=='",
+ " 'assign': 'RG9uJ3QgYmVsaWV2ZSBoaXMgbGllcw'",
" }",
" }",
"}"
diff --git a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
index 0c130ab9a42..a761a9adfb6 100644
--- a/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
+++ b/document/src/test/java/com/yahoo/document/json/JsonReaderTestCase.java
@@ -668,7 +668,7 @@ public class JsonReaderTestCase {
@Test
public void testRaw() throws IOException {
String base64 = new String(new JsonStringEncoder().quoteAsString(
- Base64.getEncoder().encodeToString(Utf8.toBytes("smoketest"))));
+ Base64.getEncoder().withoutPadding().encodeToString(Utf8.toBytes("smoketest"))));
String s = fieldStringFromBase64RawContent(base64);
assertEquals("smoketest", s);
}
diff --git a/document/src/test/java/com/yahoo/document/json/JsonWriterTestCase.java b/document/src/test/java/com/yahoo/document/json/JsonWriterTestCase.java
index eab33afc3e4..4f15a2fe368 100644
--- a/document/src/test/java/com/yahoo/document/json/JsonWriterTestCase.java
+++ b/document/src/test/java/com/yahoo/document/json/JsonWriterTestCase.java
@@ -291,7 +291,7 @@ public class JsonWriterTestCase {
String payload = new String(
new JsonStringEncoder().quoteAsString(
"c3RyaW5nIGxvbmcgZW5vdWdoIHRvIGVtaXQgbW9yZSB0aGFuIDc2IGJhc2U2NCBjaGFyYWN0ZXJzIGFuZC" +
- "B3aGljaCBzaG91bGQgY2VydGFpbmx5IG5vdCBiZSBuZXdsaW5lLWRlbGltaXRlZCE="));
+ "B3aGljaCBzaG91bGQgY2VydGFpbmx5IG5vdCBiZSBuZXdsaW5lLWRlbGltaXRlZCE"));
String docId = "id:unittest:testraw::whee";
diff --git a/document/src/vespa/document/select/parse_utils.cpp b/document/src/vespa/document/select/parse_utils.cpp
index 95461442349..4c116d5bff4 100644
--- a/document/src/vespa/document/select/parse_utils.cpp
+++ b/document/src/vespa/document/select/parse_utils.cpp
@@ -24,7 +24,7 @@ parse_i64(const char* str, size_t len, int64_t& out) {
}
bool
parse_double(const char* str, size_t len, double& out) {
-#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000
+#if defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000
// Temporary workaround that also handles underflow (cf. issue 3081)
// until libc++ supports std::from_chars for double
char *str_end = const_cast<char*>(str) + len;
diff --git a/documentapi/abi-spec.json b/documentapi/abi-spec.json
index 37b8eb6a75d..73ad4b1d121 100644
--- a/documentapi/abi-spec.json
+++ b/documentapi/abi-spec.json
@@ -1828,8 +1828,6 @@
"public static final int MESSAGE_CREATEVISITOR",
"public static final int MESSAGE_DESTROYVISITOR",
"public static final int MESSAGE_VISITORINFO",
- "public static final int MESSAGE_SEARCHRESULT",
- "public static final int MESSAGE_DOCUMENTSUMMARY",
"public static final int MESSAGE_MAPVISITOR",
"public static final int MESSAGE_GETBUCKETSTATE",
"public static final int MESSAGE_STATBUCKET",
@@ -1846,8 +1844,6 @@
"public static final int REPLY_CREATEVISITOR",
"public static final int REPLY_DESTROYVISITOR",
"public static final int REPLY_VISITORINFO",
- "public static final int REPLY_SEARCHRESULT",
- "public static final int REPLY_DOCUMENTSUMMARY",
"public static final int REPLY_MAPVISITOR",
"public static final int REPLY_GETBUCKETSTATE",
"public static final int REPLY_STATBUCKET",
@@ -2093,21 +2089,6 @@
],
"fields" : [ ]
},
- "com.yahoo.documentapi.messagebus.protocol.DocumentSummaryMessage" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.VisitorMessage",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "public void setDocumentSummary(com.yahoo.vdslib.DocumentSummary)",
- "public com.yahoo.vdslib.DocumentSummary getResult()",
- "public com.yahoo.documentapi.messagebus.protocol.DocumentReply createReply()",
- "public int getType()"
- ],
- "fields" : [ ]
- },
"com.yahoo.documentapi.messagebus.protocol.DocumentrouteselectorpolicyConfig$Builder" : {
"superClass" : "java.lang.Object",
"interfaces" : [
@@ -2458,7 +2439,9 @@
"public long getSequenceId()",
"public int getType()",
"public com.yahoo.document.TestAndSetCondition getCondition()",
- "public void setCondition(com.yahoo.document.TestAndSetCondition)"
+ "public void setCondition(com.yahoo.document.TestAndSetCondition)",
+ "public void setCreateIfNonExistent(boolean)",
+ "public boolean getCreateIfNonExistent()"
],
"fields" : [ ]
},
@@ -2670,32 +2653,6 @@
],
"fields" : [ ]
},
- "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentSummaryMessageFactory" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentMessageFactory",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "protected com.yahoo.documentapi.messagebus.protocol.DocumentMessage doDecode(com.yahoo.document.serialization.DocumentDeserializer)",
- "protected boolean doEncode(com.yahoo.documentapi.messagebus.protocol.DocumentMessage, com.yahoo.document.serialization.DocumentSerializer)"
- ],
- "fields" : [ ]
- },
- "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentSummaryReplyFactory" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentReplyFactory",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "protected com.yahoo.documentapi.messagebus.protocol.DocumentReply doDecode(com.yahoo.document.serialization.DocumentDeserializer)",
- "protected boolean doEncode(com.yahoo.documentapi.messagebus.protocol.DocumentReply, com.yahoo.document.serialization.DocumentSerializer)"
- ],
- "fields" : [ ]
- },
"com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$EmptyBucketsMessageFactory" : {
"superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentMessageFactory",
"interfaces" : [ ],
@@ -2934,32 +2891,6 @@
],
"fields" : [ ]
},
- "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$SearchResultMessageFactory" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentMessageFactory",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "protected com.yahoo.documentapi.messagebus.protocol.DocumentMessage doDecode(com.yahoo.document.serialization.DocumentDeserializer)",
- "protected boolean doEncode(com.yahoo.documentapi.messagebus.protocol.DocumentMessage, com.yahoo.document.serialization.DocumentSerializer)"
- ],
- "fields" : [ ]
- },
- "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$SearchResultReplyFactory" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentReplyFactory",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "protected com.yahoo.documentapi.messagebus.protocol.DocumentReply doDecode(com.yahoo.document.serialization.DocumentDeserializer)",
- "protected boolean doEncode(com.yahoo.documentapi.messagebus.protocol.DocumentReply, com.yahoo.document.serialization.DocumentSerializer)"
- ],
- "fields" : [ ]
- },
"com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$StatBucketMessageFactory" : {
"superClass" : "com.yahoo.documentapi.messagebus.protocol.RoutableFactories60$DocumentMessageFactory",
"interfaces" : [ ],
@@ -3093,21 +3024,6 @@
],
"fields" : [ ]
},
- "com.yahoo.documentapi.messagebus.protocol.SearchResultMessage" : {
- "superClass" : "com.yahoo.documentapi.messagebus.protocol.VisitorMessage",
- "interfaces" : [ ],
- "attributes" : [
- "public"
- ],
- "methods" : [
- "public void <init>()",
- "public com.yahoo.vdslib.SearchResult getResult()",
- "public void setSearchResult(com.yahoo.vdslib.SearchResult)",
- "public com.yahoo.documentapi.messagebus.protocol.DocumentReply createReply()",
- "public int getType()"
- ],
- "fields" : [ ]
- },
"com.yahoo.documentapi.messagebus.protocol.SlobrokPolicy" : {
"superClass" : "java.lang.Object",
"interfaces" : [
@@ -3291,4 +3207,4 @@
],
"fields" : [ ]
}
-} \ No newline at end of file
+}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
index 0ff578b64d7..6185437a48f 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentProtocol.java
@@ -50,8 +50,9 @@ public class DocumentProtocol implements Protocol {
public static final int MESSAGE_CREATEVISITOR = DOCUMENT_MESSAGE + 7;
public static final int MESSAGE_DESTROYVISITOR = DOCUMENT_MESSAGE + 8;
public static final int MESSAGE_VISITORINFO = DOCUMENT_MESSAGE + 9;
- public static final int MESSAGE_SEARCHRESULT = DOCUMENT_MESSAGE + 11;
- public static final int MESSAGE_DOCUMENTSUMMARY = DOCUMENT_MESSAGE + 14;
+ // SearchResult and DocumentSummary messages were replaced by QueryResult message in 2010.
+ // public static final int MESSAGE_SEARCHRESULT = DOCUMENT_MESSAGE + 11;
+ // public static final int MESSAGE_DOCUMENTSUMMARY = DOCUMENT_MESSAGE + 14;
public static final int MESSAGE_MAPVISITOR = DOCUMENT_MESSAGE + 15;
public static final int MESSAGE_GETBUCKETSTATE = DOCUMENT_MESSAGE + 18;
public static final int MESSAGE_STATBUCKET = DOCUMENT_MESSAGE + 19;
@@ -70,8 +71,9 @@ public class DocumentProtocol implements Protocol {
public static final int REPLY_CREATEVISITOR = DOCUMENT_REPLY + 7;
public static final int REPLY_DESTROYVISITOR = DOCUMENT_REPLY + 8;
public static final int REPLY_VISITORINFO = DOCUMENT_REPLY + 9;
- public static final int REPLY_SEARCHRESULT = DOCUMENT_REPLY + 11;
- public static final int REPLY_DOCUMENTSUMMARY = DOCUMENT_REPLY + 14;
+ // SearchResult and DocumentSummary replies were replaced by QueryResult reply in 2010.
+ // public static final int REPLY_SEARCHRESULT = DOCUMENT_REPLY + 11;
+ // public static final int REPLY_DOCUMENTSUMMARY = DOCUMENT_REPLY + 14;
public static final int REPLY_MAPVISITOR = DOCUMENT_REPLY + 15;
public static final int REPLY_GETBUCKETSTATE = DOCUMENT_REPLY + 18;
public static final int REPLY_STATBUCKET = DOCUMENT_REPLY + 19;
@@ -282,7 +284,6 @@ public class DocumentProtocol implements Protocol {
putRoutableFactory(MESSAGE_CREATEVISITOR, new RoutableFactories60.CreateVisitorMessageFactory(), from6);
putRoutableFactory(MESSAGE_DESTROYVISITOR, new RoutableFactories60.DestroyVisitorMessageFactory(), from6);
putRoutableFactory(MESSAGE_DOCUMENTLIST, new RoutableFactories60.DocumentListMessageFactory(), from6);
- putRoutableFactory(MESSAGE_DOCUMENTSUMMARY, new RoutableFactories60.DocumentSummaryMessageFactory(), from6);
putRoutableFactory(MESSAGE_EMPTYBUCKETS, new RoutableFactories60.EmptyBucketsMessageFactory(), from6);
putRoutableFactory(MESSAGE_GETBUCKETLIST, new RoutableFactories60.GetBucketListMessageFactory(), from6);
putRoutableFactory(MESSAGE_GETBUCKETSTATE, new RoutableFactories60.GetBucketStateMessageFactory(), from6);
@@ -292,7 +293,6 @@ public class DocumentProtocol implements Protocol {
putRoutableFactory(MESSAGE_QUERYRESULT, new RoutableFactories60.QueryResultMessageFactory(), from6);
putRoutableFactory(MESSAGE_REMOVEDOCUMENT, new RoutableFactories60.RemoveDocumentMessageFactory(), from6);
putRoutableFactory(MESSAGE_REMOVELOCATION, new RoutableFactories60.RemoveLocationMessageFactory(), from6);
- putRoutableFactory(MESSAGE_SEARCHRESULT, new RoutableFactories60.SearchResultMessageFactory(), from6);
putRoutableFactory(MESSAGE_STATBUCKET, new RoutableFactories60.StatBucketMessageFactory(), from6);
putRoutableFactory(MESSAGE_UPDATEDOCUMENT, new RoutableFactories60.UpdateDocumentMessageFactory(), from6);
putRoutableFactory(MESSAGE_VISITORINFO, new RoutableFactories60.VisitorInfoMessageFactory(), from6);
@@ -300,7 +300,6 @@ public class DocumentProtocol implements Protocol {
putRoutableFactory(REPLY_DESTROYVISITOR, new RoutableFactories60.DestroyVisitorReplyFactory(), from6);
putRoutableFactory(REPLY_DOCUMENTIGNORED, new RoutableFactories60.DocumentIgnoredReplyFactory(), from6);
putRoutableFactory(REPLY_DOCUMENTLIST, new RoutableFactories60.DocumentListReplyFactory(), from6);
- putRoutableFactory(REPLY_DOCUMENTSUMMARY, new RoutableFactories60.DocumentSummaryReplyFactory(), from6);
putRoutableFactory(REPLY_EMPTYBUCKETS, new RoutableFactories60.EmptyBucketsReplyFactory(), from6);
putRoutableFactory(REPLY_GETBUCKETLIST, new RoutableFactories60.GetBucketListReplyFactory(), from6);
putRoutableFactory(REPLY_GETBUCKETSTATE, new RoutableFactories60.GetBucketStateReplyFactory(), from6);
@@ -310,7 +309,6 @@ public class DocumentProtocol implements Protocol {
putRoutableFactory(REPLY_QUERYRESULT, new RoutableFactories60.QueryResultReplyFactory(), from6);
putRoutableFactory(REPLY_REMOVEDOCUMENT, new RoutableFactories60.RemoveDocumentReplyFactory(), from6);
putRoutableFactory(REPLY_REMOVELOCATION, new RoutableFactories60.RemoveLocationReplyFactory(), from6);
- putRoutableFactory(REPLY_SEARCHRESULT, new RoutableFactories60.SearchResultReplyFactory(), from6);
putRoutableFactory(REPLY_STATBUCKET, new RoutableFactories60.StatBucketReplyFactory(), from6);
putRoutableFactory(REPLY_UPDATEDOCUMENT, new RoutableFactories60.UpdateDocumentReplyFactory(), from6);
putRoutableFactory(REPLY_UPDATEDOCUMENT, new RoutableFactories60.UpdateDocumentReplyFactory(), from6);
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentSummaryMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentSummaryMessage.java
deleted file mode 100644
index 4866579a977..00000000000
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/DocumentSummaryMessage.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.documentapi.messagebus.protocol;
-
-import com.yahoo.vdslib.DocumentSummary;
-
-public class DocumentSummaryMessage extends VisitorMessage {
-
- private DocumentSummary documentSummary = null;
-
- public void setDocumentSummary(DocumentSummary summary) {
- documentSummary = summary;
- }
-
- public DocumentSummary getResult() {
- return documentSummary;
- }
-
- @Override
- public DocumentReply createReply() {
- return new VisitorReply(DocumentProtocol.REPLY_DOCUMENTSUMMARY);
- }
-
- @Override
- public int getType() {
- return DocumentProtocol.MESSAGE_DOCUMENTSUMMARY;
- }
-}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java
index 4482e5bfc47..3e04b2ab669 100755
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/PutDocumentMessage.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.documentapi.messagebus.protocol;
+import com.yahoo.api.annotations.Beta;
import com.yahoo.document.DocumentPut;
import com.yahoo.document.TestAndSetCondition;
import com.yahoo.document.serialization.DocumentDeserializer;
@@ -129,4 +130,14 @@ public class PutDocumentMessage extends TestAndSetMessage {
put.setCondition(condition);
}
+ @Beta
+ public void setCreateIfNonExistent(boolean value) {
+ put.setCreateIfNonExistent(value);
+ }
+
+ @Beta
+ public boolean getCreateIfNonExistent() {
+ deserialize();
+ return put.getCreateIfNonExistent();
+ }
}
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
index 099839672a2..3824da32d4e 100644
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
+++ b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
@@ -344,34 +344,6 @@ public abstract class RoutableFactories60 {
}
}
- public static class DocumentSummaryMessageFactory extends DocumentMessageFactory {
-
- @Override
- protected DocumentMessage doDecode(DocumentDeserializer buf) {
- DocumentSummaryMessage msg = new DocumentSummaryMessage();
- msg.setDocumentSummary(new DocumentSummary(buf));
- return msg;
- }
-
- @Override
- protected boolean doEncode(DocumentMessage obj, DocumentSerializer buf) {
- return false; // not supported
- }
- }
-
- public static class DocumentSummaryReplyFactory extends DocumentReplyFactory {
-
- @Override
- protected DocumentReply doDecode(DocumentDeserializer buf) {
- return new VisitorReply(DocumentProtocol.REPLY_DOCUMENTSUMMARY);
- }
-
- @Override
- protected boolean doEncode(DocumentReply obj, DocumentSerializer buf) {
- return true;
- }
- }
-
public static class EmptyBucketsMessageFactory extends DocumentMessageFactory {
@Override
@@ -607,6 +579,10 @@ public abstract class RoutableFactories60 {
msg.setDocumentPut(new DocumentPut(Document.createDocument(buf)));
msg.setTimestamp(buf.getLong(null));
decodeTasCondition(msg, buf);
+ if (buf.getBuf().hasRemaining()) {
+ byte value = buf.getBuf().get();
+ msg.setCreateIfNonExistent(value != 0);
+ }
}
@Override
@@ -627,6 +603,11 @@ public abstract class RoutableFactories60 {
msg.getDocumentPut().getDocument().serialize(buf);
buf.putLong(null, msg.getTimestamp());
encodeTasCondition(buf, (TestAndSetMessage) obj);
+ if (msg.getCreateIfNonExistent()) {
+ buf.getBuf().put((byte)1);
+ } else {
+ buf.getBuf().put((byte)0);
+ }
}
return true;
}
@@ -719,21 +700,6 @@ public abstract class RoutableFactories60 {
}
}
- public static class SearchResultMessageFactory extends DocumentMessageFactory {
-
- @Override
- protected DocumentMessage doDecode(DocumentDeserializer buf) {
- SearchResultMessage msg = new SearchResultMessage();
- msg.setSearchResult(new SearchResult(buf));
- return msg;
- }
-
- @Override
- protected boolean doEncode(DocumentMessage obj, DocumentSerializer buf) {
- return false; // not supported
- }
- }
-
public static class QueryResultMessageFactory extends DocumentMessageFactory {
@Override
@@ -750,19 +716,6 @@ public abstract class RoutableFactories60 {
}
}
- public static class SearchResultReplyFactory extends DocumentReplyFactory {
-
- @Override
- protected DocumentReply doDecode(DocumentDeserializer buf) {
- return new VisitorReply(DocumentProtocol.REPLY_SEARCHRESULT);
- }
-
- @Override
- protected boolean doEncode(DocumentReply obj, DocumentSerializer buf) {
- return true;
- }
- }
-
public static class QueryResultReplyFactory extends DocumentReplyFactory {
@Override
diff --git a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/SearchResultMessage.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/SearchResultMessage.java
deleted file mode 100644
index 570aafd49e6..00000000000
--- a/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/SearchResultMessage.java
+++ /dev/null
@@ -1,27 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.documentapi.messagebus.protocol;
-
-import com.yahoo.vdslib.SearchResult;
-
-public class SearchResultMessage extends VisitorMessage {
-
- private SearchResult searchResult = null;
-
- public SearchResult getResult() {
- return searchResult;
- }
-
- public void setSearchResult(SearchResult result) {
- searchResult = result;
- }
-
- @Override
- public DocumentReply createReply() {
- return new VisitorReply(DocumentProtocol.REPLY_SEARCHRESULT);
- }
-
- @Override
- public int getType() {
- return DocumentProtocol.MESSAGE_SEARCHRESULT;
- }
-}
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
index 35f8e7cf0c8..22650fcdbf8 100644
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/Messages60TestCase.java
@@ -20,7 +20,6 @@ import com.yahoo.documentapi.messagebus.protocol.DocumentListMessage;
import com.yahoo.documentapi.messagebus.protocol.DocumentProtocol;
import com.yahoo.documentapi.messagebus.protocol.DocumentReply;
import com.yahoo.documentapi.messagebus.protocol.DocumentState;
-import com.yahoo.documentapi.messagebus.protocol.DocumentSummaryMessage;
import com.yahoo.documentapi.messagebus.protocol.EmptyBucketsMessage;
import com.yahoo.documentapi.messagebus.protocol.GetBucketListMessage;
import com.yahoo.documentapi.messagebus.protocol.GetBucketListReply;
@@ -34,7 +33,6 @@ import com.yahoo.documentapi.messagebus.protocol.QueryResultMessage;
import com.yahoo.documentapi.messagebus.protocol.RemoveDocumentMessage;
import com.yahoo.documentapi.messagebus.protocol.RemoveDocumentReply;
import com.yahoo.documentapi.messagebus.protocol.RemoveLocationMessage;
-import com.yahoo.documentapi.messagebus.protocol.SearchResultMessage;
import com.yahoo.documentapi.messagebus.protocol.StatBucketMessage;
import com.yahoo.documentapi.messagebus.protocol.StatBucketReply;
import com.yahoo.documentapi.messagebus.protocol.UpdateDocumentMessage;
@@ -69,7 +67,6 @@ public class Messages60TestCase extends MessagesTestBase {
out.put(DocumentProtocol.MESSAGE_CREATEVISITOR, new testCreateVisitorMessage());
out.put(DocumentProtocol.MESSAGE_DESTROYVISITOR, new testDestroyVisitorMessage());
out.put(DocumentProtocol.MESSAGE_DOCUMENTLIST, new testDocumentListMessage());
- out.put(DocumentProtocol.MESSAGE_DOCUMENTSUMMARY, new testDocumentSummaryMessage());
out.put(DocumentProtocol.MESSAGE_EMPTYBUCKETS, new testEmptyBucketsMessage());
out.put(DocumentProtocol.MESSAGE_GETBUCKETLIST, new testGetBucketListMessage());
out.put(DocumentProtocol.MESSAGE_GETBUCKETSTATE, new testGetBucketStateMessage());
@@ -79,7 +76,6 @@ public class Messages60TestCase extends MessagesTestBase {
out.put(DocumentProtocol.MESSAGE_QUERYRESULT, new testQueryResultMessage());
out.put(DocumentProtocol.MESSAGE_REMOVEDOCUMENT, new testRemoveDocumentMessage());
out.put(DocumentProtocol.MESSAGE_REMOVELOCATION, new testRemoveLocationMessage());
- out.put(DocumentProtocol.MESSAGE_SEARCHRESULT, new testSearchResultMessage());
out.put(DocumentProtocol.MESSAGE_STATBUCKET, new testStatBucketMessage());
out.put(DocumentProtocol.MESSAGE_UPDATEDOCUMENT, new testUpdateDocumentMessage());
out.put(DocumentProtocol.MESSAGE_VISITORINFO, new testVisitorInfoMessage());
@@ -87,7 +83,6 @@ public class Messages60TestCase extends MessagesTestBase {
out.put(DocumentProtocol.REPLY_DESTROYVISITOR, new testDestroyVisitorReply());
out.put(DocumentProtocol.REPLY_DOCUMENTIGNORED, new testDocumentIgnoredReply());
out.put(DocumentProtocol.REPLY_DOCUMENTLIST, new testDocumentListReply());
- out.put(DocumentProtocol.REPLY_DOCUMENTSUMMARY, new testDocumentSummaryReply());
out.put(DocumentProtocol.REPLY_EMPTYBUCKETS, new testEmptyBucketsReply());
out.put(DocumentProtocol.REPLY_GETBUCKETLIST, new testGetBucketListReply());
out.put(DocumentProtocol.REPLY_GETBUCKETSTATE, new testGetBucketStateReply());
@@ -97,7 +92,6 @@ public class Messages60TestCase extends MessagesTestBase {
out.put(DocumentProtocol.REPLY_QUERYRESULT, new testQueryResultReply());
out.put(DocumentProtocol.REPLY_REMOVEDOCUMENT, new testRemoveDocumentReply());
out.put(DocumentProtocol.REPLY_REMOVELOCATION, new testRemoveLocationReply());
- out.put(DocumentProtocol.REPLY_SEARCHRESULT, new testSearchResultReply());
out.put(DocumentProtocol.REPLY_STATBUCKET, new testStatBucketReply());
out.put(DocumentProtocol.REPLY_UPDATEDOCUMENT, new testUpdateDocumentReply());
out.put(DocumentProtocol.REPLY_VISITORINFO, new testVisitorInfoReply());
@@ -324,14 +318,6 @@ public class Messages60TestCase extends MessagesTestBase {
}
}
- public class testDocumentSummaryReply implements RunnableTest {
-
- @Override
- public void run() {
- testVisitorReply("DocumentSummaryReply", DocumentProtocol.REPLY_DOCUMENTSUMMARY);
- }
- }
-
public class testEmptyBucketsReply implements RunnableTest {
@Override
@@ -392,65 +378,6 @@ public class Messages60TestCase extends MessagesTestBase {
}
}
- public class testDocumentSummaryMessage implements RunnableTest {
-
- @Override
- public void run() {
- Routable routable = deserialize("DocumentSummaryMessage-1", DocumentProtocol.MESSAGE_DOCUMENTSUMMARY, Language.CPP);
- assertTrue(routable instanceof DocumentSummaryMessage);
-
- DocumentSummaryMessage msg = (DocumentSummaryMessage) routable;
- assertEquals(0, msg.getResult().getSummaryCount());
-
- routable = deserialize("DocumentSummaryMessage-2", DocumentProtocol.MESSAGE_DOCUMENTSUMMARY, Language.CPP);
- assertTrue(routable instanceof DocumentSummaryMessage);
-
- msg = (DocumentSummaryMessage) routable;
- assertEquals(2, msg.getResult().getSummaryCount());
- com.yahoo.vdslib.DocumentSummary.Summary s = msg.getResult().getSummary(0);
- assertEquals("doc1", s.getDocId());
- byte[] b = s.getSummary();
- assertEquals(8, b.length);
- byte[] c = {'s', 'u', 'm', 'm', 'a', 'r', 'y', '1'};
- for (int i = 0; i < b.length; i++) {
- assertEquals(c[i], b[i]);
- }
-
- s = msg.getResult().getSummary(1);
- assertEquals("aoc17", s.getDocId());
- b = s.getSummary();
- assertEquals(9, b.length);
- byte[] d = {'s', 'u', 'm', 'm', 'a', 'r', 'y', '4', '5'};
- for (int i = 0; i < b.length; i++) {
- assertEquals(d[i], b[i]);
- }
- routable = deserialize("DocumentSummaryMessage-3", DocumentProtocol.MESSAGE_DOCUMENTSUMMARY, Language.CPP);
- assertTrue(routable instanceof DocumentSummaryMessage);
-
- msg = (DocumentSummaryMessage) routable;
- assertEquals(2, msg.getResult().getSummaryCount());
-
- s = msg.getResult().getSummary(0);
- assertEquals("aoc17", s.getDocId());
- b = s.getSummary();
- assertEquals(9, b.length);
- byte[] e = {'s', 'u', 'm', 'm', 'a', 'r', 'y', '4', '5'};
- for (int i = 0; i < b.length; i++) {
- assertEquals(e[i], b[i]);
- }
-
- s = msg.getResult().getSummary(1);
- assertEquals("doc1", s.getDocId());
- b = s.getSummary();
- assertEquals(8, b.length);
- byte[] f = {'s', 'u', 'm', 'm', 'a', 'r', 'y', '1'};
- for (int i = 0; i < b.length; i++) {
- assertEquals(f[i], b[i]);
- }
- }
- }
-
-
public class testGetDocumentMessage implements RunnableTest {
@Override
@@ -521,81 +448,29 @@ public class Messages60TestCase extends MessagesTestBase {
}
}
- public class testSearchResultMessage implements RunnableTest {
-
- @Override
- public void run() throws Exception {
- Routable routable = deserialize("SearchResultMessage-1", DocumentProtocol.MESSAGE_SEARCHRESULT, Language.CPP);
- assertTrue(routable instanceof SearchResultMessage);
-
- SearchResultMessage msg = (SearchResultMessage)routable;
- assertEquals(0, msg.getResult().getHitCount());
-
- routable = deserialize("SearchResultMessage-2", DocumentProtocol.MESSAGE_SEARCHRESULT, Language.CPP);
- assertTrue(routable instanceof SearchResultMessage);
-
- msg = (SearchResultMessage)routable;
- assertEquals(2, msg.getResult().getHitCount());
- com.yahoo.vdslib.SearchResult.Hit h = msg.getResult().getHit(0);
- assertEquals(89.0, h.getRank(), 1E-6);
- assertEquals("doc1", h.getDocId());
- h = msg.getResult().getHit(1);
- assertEquals(109.0, h.getRank(), 1E-6);
- assertEquals("doc17", h.getDocId());
-
- routable = deserialize("SearchResultMessage-3", DocumentProtocol.MESSAGE_SEARCHRESULT, Language.CPP);
- assertTrue(routable instanceof SearchResultMessage);
-
- msg = (SearchResultMessage)routable;
- assertEquals(2, msg.getResult().getHitCount());
- h = msg.getResult().getHit(0);
- assertEquals(109.0, h.getRank(), 1E-6);
- assertEquals("doc17", h.getDocId());
- h = msg.getResult().getHit(1);
- assertEquals(89.0, h.getRank(), 1E-6);
- assertEquals("doc1", h.getDocId());
+ private static String CONDITION_STRING = "There's just one condition";
- routable = deserialize("SearchResultMessage-4", DocumentProtocol.MESSAGE_SEARCHRESULT, Language.CPP);
- assertTrue(routable instanceof SearchResultMessage);
+ public class testPutDocumentMessage implements RunnableTest {
- msg = (SearchResultMessage)routable;
- assertEquals(3, msg.getResult().getHitCount());
- h = msg.getResult().getHit(0);
- assertTrue(h instanceof SearchResult.HitWithSortBlob);
- assertEquals(89.0, h.getRank(), 1E-6);
- assertEquals("doc1", h.getDocId());
- byte[] b = ((SearchResult.HitWithSortBlob)h).getSortBlob();
- assertEquals(9, b.length);
- byte[] e = { 's', 'o', 'r', 't', 'd', 'a', 't', 'a', '2' };
- for (int i = 0; i < b.length; i++) {
- assertEquals(e[i], b[i]);
- }
- h = msg.getResult().getHit(1);
- assertTrue(h instanceof SearchResult.HitWithSortBlob);
- assertEquals(109.0, h.getRank(), 1E-6);
- assertEquals("doc17", h.getDocId());
- b = ((SearchResult.HitWithSortBlob)h).getSortBlob();
- assertEquals(9, b.length);
- byte[] d = { 's', 'o', 'r', 't', 'd', 'a', 't', 'a', '1' };
- for (int i = 0; i < b.length; i++) {
- assertEquals(d[i], b[i]);
- }
- h = msg.getResult().getHit(2);
- assertTrue(h instanceof SearchResult.HitWithSortBlob);
- assertEquals(90.0, h.getRank(), 1E-6);
- assertEquals("doc18", h.getDocId());
- b = ((SearchResult.HitWithSortBlob)h).getSortBlob();
- assertEquals(9, b.length);
- byte[] c = { 's', 'o', 'r', 't', 'd', 'a', 't', 'a', '3' };
- for (int i = 0; i < b.length; i++) {
- assertEquals(c[i], b[i]);
+ void verifyCreateIfNonExistentFlag() {
+ var msg = new PutDocumentMessage(new DocumentPut(new Document(protocol.getDocumentTypeManager().getDocumentType("testdoc"), "id:ns:testdoc::")));
+ msg.setCreateIfNonExistent(true);
+ int size_of_create_if_non_existent_flag = 1;
+ int expected_serialized_size = BASE_MESSAGE_LENGTH + 45 + serializedLength(msg.getCondition().getSelection()) + size_of_create_if_non_existent_flag;
+ assertEquals(expected_serialized_size, serialize("PutDocumentMessage-create", msg));
+ assertEquals(expected_serialized_size - 1, serialize("PutDocumentMessage-create-truncate", msg, data -> DataTamper.truncate(data, 1)));
+ assertEquals(expected_serialized_size + 1, serialize("PutDocumentMessage-create-pad", msg, data -> DataTamper.pad(data, 1)));
+ for (Language lang: LANGUAGES) {
+ var decoded = (PutDocumentMessage)deserialize("PutDocumentMessage-create", DocumentProtocol.MESSAGE_PUTDOCUMENT, lang);
+ var decoded_trunc = (PutDocumentMessage)deserialize("PutDocumentMessage-create-truncate", DocumentProtocol.MESSAGE_PUTDOCUMENT, lang);
+ var decoded_pad = (PutDocumentMessage)deserialize("PutDocumentMessage-create-pad", DocumentProtocol.MESSAGE_PUTDOCUMENT, lang);
+ assertEquals(true, decoded.getCreateIfNonExistent());
+ assertEquals(false, decoded_trunc.getCreateIfNonExistent());
+ assertEquals(true, decoded_pad.getCreateIfNonExistent());
+ assertTrue(decoded.getDocumentPut().equals(decoded_pad.getDocumentPut()));
+ assertFalse(decoded.getDocumentPut().equals(decoded_trunc.getDocumentPut()));
}
}
- }
-
- private static String CONDITION_STRING = "There's just one condition";
-
- public class testPutDocumentMessage implements RunnableTest {
@Override
public void run() {
@@ -604,7 +479,9 @@ public class Messages60TestCase extends MessagesTestBase {
msg.setTimestamp(666);
msg.setCondition(new TestAndSetCondition(CONDITION_STRING));
- assertEquals(BASE_MESSAGE_LENGTH + 45 + serializedLength(msg.getCondition().getSelection()), serialize("PutDocumentMessage", msg));
+ int size_of_create_if_non_existent_flag = 1;
+ int expected_serialized_size = BASE_MESSAGE_LENGTH + 45 + serializedLength(msg.getCondition().getSelection()) + size_of_create_if_non_existent_flag;
+ assertEquals(expected_serialized_size, serialize("PutDocumentMessage", msg));
for (Language lang : LANGUAGES) {
final PutDocumentMessage deserializedMsg = (PutDocumentMessage)deserialize("PutDocumentMessage", DocumentProtocol.MESSAGE_PUTDOCUMENT, lang);
@@ -612,7 +489,9 @@ public class Messages60TestCase extends MessagesTestBase {
assertEquals(msg.getDocumentPut().getDocument().getId().toString(), deserializedMsg.getDocumentPut().getDocument().getId().toString());
assertEquals(msg.getTimestamp(), deserializedMsg.getTimestamp());
assertEquals(msg.getCondition().getSelection(), deserializedMsg.getCondition().getSelection());
+ assertEquals(false, deserializedMsg.getCreateIfNonExistent());
}
+ verifyCreateIfNonExistentFlag();
}
}
@@ -726,14 +605,6 @@ public class Messages60TestCase extends MessagesTestBase {
}
}
- public class testSearchResultReply implements RunnableTest {
-
- @Override
- public void run() {
- testVisitorReply("SearchResultReply", DocumentProtocol.REPLY_SEARCHRESULT);
- }
- }
-
public class testStatBucketReply implements RunnableTest {
@Override
diff --git a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
index f43449646be..71cae9d136a 100755
--- a/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
+++ b/documentapi/src/test/java/com/yahoo/documentapi/messagebus/protocol/test/MessagesTestBase.java
@@ -94,17 +94,37 @@ public abstract class MessagesTestBase {
return Arrays.equals(existingData, dataToWrite);
}
+ @FunctionalInterface
+ public interface DataTamper {
+ byte[] tamperWith(byte[] data);
+ static byte[] truncate(byte[] data, int bytes) {
+ int newLength = data.length - bytes;
+ assertTrue(newLength > 0);
+ byte[] res = new byte[newLength];
+ System.arraycopy(data, 0, res, 0, newLength);
+ return res;
+ }
+ static byte[] pad(byte[] data, int bytes) {
+ int newLength = data.length + bytes;
+ byte[] res = new byte[newLength];
+ System.arraycopy(data, 0, res, 0, data.length);
+ return res;
+ }
+ }
+
/**
* Writes the content of the given routable to the given file.
*
* @param filename The name of the file to write to.
* @param routable The routable to serialize.
+ * @param tamper allows tampering with serialized data
* @return The size of the written file.
*/
- public int serialize(String filename, Routable routable) {
+ public int serialize(String filename, Routable routable, DataTamper tamper) {
Version version = version();
String path = getPath(version + "-java-" + filename + ".dat");
byte[] data = protocol.encode(version, routable);
+ data = tamper.tamperWith(data);
assertNotNull(data);
assertTrue(data.length > 0);
try {
@@ -122,6 +142,9 @@ public abstract class MessagesTestBase {
assertEquals(routable.getType(), protocol.decode(version, data).getType());
return data.length;
}
+ public int serialize(String filename, Routable routable) {
+ return serialize(filename, routable, data -> data);
+ }
/**
* Reads the content of the given file and creates a corresponding routable.
diff --git a/documentapi/src/tests/messages/messages60test.cpp b/documentapi/src/tests/messages/messages60test.cpp
index 258e8821e0c..12cecefb072 100644
--- a/documentapi/src/tests/messages/messages60test.cpp
+++ b/documentapi/src/tests/messages/messages60test.cpp
@@ -15,6 +15,17 @@
using document::DataType;
using document::DocumentTypeRepo;
+template <typename T>
+struct Unwrap {
+ mbus::Routable::UP value;
+ const T *ptr = nullptr;
+ explicit Unwrap(mbus::Routable::UP value_in) : value(std::move(value_in)) {
+ ptr = dynamic_cast<T*>(value.get());
+ ASSERT_TRUE(ptr != nullptr);
+ }
+ const T *operator->() const noexcept { return ptr; }
+};
+
///////////////////////////////////////////////////////////////////////////////
//
// Setup
@@ -28,7 +39,6 @@ Messages60Test::Messages60Test()
putTest(DocumentProtocol::MESSAGE_CREATEVISITOR, TEST_METHOD(Messages60Test::testCreateVisitorMessage));
putTest(DocumentProtocol::MESSAGE_DESTROYVISITOR, TEST_METHOD(Messages60Test::testDestroyVisitorMessage));
putTest(DocumentProtocol::MESSAGE_DOCUMENTLIST, TEST_METHOD(Messages60Test::testDocumentListMessage));
- putTest(DocumentProtocol::MESSAGE_DOCUMENTSUMMARY, TEST_METHOD(Messages60Test::testDocumentSummaryMessage));
putTest(DocumentProtocol::MESSAGE_EMPTYBUCKETS, TEST_METHOD(Messages60Test::testEmptyBucketsMessage));
putTest(DocumentProtocol::MESSAGE_GETBUCKETLIST, TEST_METHOD(Messages60Test::testGetBucketListMessage));
putTest(DocumentProtocol::MESSAGE_GETBUCKETSTATE, TEST_METHOD(Messages60Test::testGetBucketStateMessage));
@@ -38,7 +48,6 @@ Messages60Test::Messages60Test()
putTest(DocumentProtocol::MESSAGE_QUERYRESULT, TEST_METHOD(Messages60Test::testQueryResultMessage));
putTest(DocumentProtocol::MESSAGE_REMOVEDOCUMENT, TEST_METHOD(Messages60Test::testRemoveDocumentMessage));
putTest(DocumentProtocol::MESSAGE_REMOVELOCATION, TEST_METHOD(Messages60Test::testRemoveLocationMessage));
- putTest(DocumentProtocol::MESSAGE_SEARCHRESULT, TEST_METHOD(Messages60Test::testSearchResultMessage));
putTest(DocumentProtocol::MESSAGE_STATBUCKET, TEST_METHOD(Messages60Test::testStatBucketMessage));
putTest(DocumentProtocol::MESSAGE_UPDATEDOCUMENT, TEST_METHOD(Messages60Test::testUpdateDocumentMessage));
putTest(DocumentProtocol::MESSAGE_VISITORINFO, TEST_METHOD(Messages60Test::testVisitorInfoMessage));
@@ -47,7 +56,6 @@ Messages60Test::Messages60Test()
putTest(DocumentProtocol::REPLY_DESTROYVISITOR, TEST_METHOD(Messages60Test::testDestroyVisitorReply));
putTest(DocumentProtocol::REPLY_DOCUMENTIGNORED, TEST_METHOD(Messages60Test::testDocumentIgnoredReply));
putTest(DocumentProtocol::REPLY_DOCUMENTLIST, TEST_METHOD(Messages60Test::testDocumentListReply));
- putTest(DocumentProtocol::REPLY_DOCUMENTSUMMARY, TEST_METHOD(Messages60Test::testDocumentSummaryReply));
putTest(DocumentProtocol::REPLY_EMPTYBUCKETS, TEST_METHOD(Messages60Test::testEmptyBucketsReply));
putTest(DocumentProtocol::REPLY_GETBUCKETLIST, TEST_METHOD(Messages60Test::testGetBucketListReply));
putTest(DocumentProtocol::REPLY_GETBUCKETSTATE, TEST_METHOD(Messages60Test::testGetBucketStateReply));
@@ -57,7 +65,6 @@ Messages60Test::Messages60Test()
putTest(DocumentProtocol::REPLY_QUERYRESULT, TEST_METHOD(Messages60Test::testQueryResultReply));
putTest(DocumentProtocol::REPLY_REMOVEDOCUMENT, TEST_METHOD(Messages60Test::testRemoveDocumentReply));
putTest(DocumentProtocol::REPLY_REMOVELOCATION, TEST_METHOD(Messages60Test::testRemoveLocationReply));
- putTest(DocumentProtocol::REPLY_SEARCHRESULT, TEST_METHOD(Messages60Test::testSearchResultReply));
putTest(DocumentProtocol::REPLY_STATBUCKET, TEST_METHOD(Messages60Test::testStatBucketReply));
putTest(DocumentProtocol::REPLY_UPDATEDOCUMENT, TEST_METHOD(Messages60Test::testUpdateDocumentReply));
putTest(DocumentProtocol::REPLY_VISITORINFO, TEST_METHOD(Messages60Test::testVisitorInfoReply));
@@ -258,65 +265,6 @@ Messages60Test::testRemoveLocationMessage()
bool
-Messages60Test::testDocumentSummaryMessage()
-{
- DocumentSummaryMessage srm;
- EXPECT_EQUAL(srm.hasSequenceId(), false);
- EXPECT_EQUAL(srm.getSummaryCount(), size_t(0));
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(12), serialize("DocumentSummaryMessage-1", srm));
-
- mbus::Routable::UP routable = deserialize("DocumentSummaryMessage-1", DocumentProtocol::MESSAGE_DOCUMENTSUMMARY, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- DocumentSummaryMessage * dm = static_cast<DocumentSummaryMessage *>(routable.get());
- EXPECT_EQUAL(dm->getSummaryCount(), size_t(0));
-
- srm.addSummary("doc1", "summary1", 8);
- srm.addSummary("aoc17", "summary45", 9);
-
- const void *summary(NULL);
- const char *docId(NULL);
- size_t sz(0);
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 52u, serialize("DocumentSummaryMessage-2", srm));
- routable = deserialize("DocumentSummaryMessage-2", DocumentProtocol::MESSAGE_DOCUMENTSUMMARY, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<DocumentSummaryMessage *>(routable.get());
- EXPECT_EQUAL(dm->getSummaryCount(), size_t(2));
- dm->getSummary(0, docId, summary, sz);
- EXPECT_EQUAL(sz, 8u);
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- EXPECT_EQUAL(memcmp("summary1", summary, sz), 0);
- dm->getSummary(1, docId, summary, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(strcmp("aoc17", docId), 0);
- EXPECT_EQUAL(memcmp("summary45", summary, sz), 0);
-
- srm.sort();
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 52u, serialize("DocumentSummaryMessage-3", srm));
- routable = deserialize("DocumentSummaryMessage-3", DocumentProtocol::MESSAGE_DOCUMENTSUMMARY, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<DocumentSummaryMessage *>(routable.get());
- EXPECT_EQUAL(dm->getSummaryCount(), size_t(2));
- dm->getSummary(0, docId, summary, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(strcmp("aoc17", docId), 0);
- EXPECT_EQUAL(memcmp("summary45", summary, sz), 0);
- dm->getSummary(1, docId, summary, sz);
- EXPECT_EQUAL(sz, 8u);
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- EXPECT_EQUAL(memcmp("summary1", summary, sz), 0);
- return true;
-}
-
-bool
Messages60Test::testGetDocumentMessage()
{
GetDocumentMessage tmp(document::DocumentId("id:ns:testdoc::"), "foo bar");
@@ -399,10 +347,12 @@ Messages60Test::testPutDocumentMessage()
EXPECT_EQUAL(sizeof(vespalib::string), sizeof(TestAndSetCondition));
EXPECT_EQUAL(112u, sizeof(DocumentMessage));
EXPECT_EQUAL(sizeof(TestAndSetCondition) + sizeof(DocumentMessage), sizeof(TestAndSetMessage));
- EXPECT_EQUAL(sizeof(TestAndSetMessage) + 24, sizeof(PutDocumentMessage));
+ EXPECT_EQUAL(sizeof(TestAndSetMessage) + 32, sizeof(PutDocumentMessage));
+ int size_of_create_if_non_existent_flag = 1;
EXPECT_EQUAL(MESSAGE_BASE_LENGTH +
45u +
- serializedLength(msg.getCondition().getSelection()),
+ serializedLength(msg.getCondition().getSelection()) +
+ size_of_create_if_non_existent_flag,
serialize("PutDocumentMessage", msg));
for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
@@ -413,11 +363,34 @@ Messages60Test::testPutDocumentMessage()
EXPECT_EQUAL(msg.getDocument().getType().getName(), deserializedMsg.getDocument().getType().getName());
EXPECT_EQUAL(msg.getDocument().getId().toString(), deserializedMsg.getDocument().getId().toString());
EXPECT_EQUAL(msg.getTimestamp(), deserializedMsg.getTimestamp());
- EXPECT_EQUAL(71u, deserializedMsg.getApproxSize());
+ EXPECT_EQUAL(72u, deserializedMsg.getApproxSize());
EXPECT_EQUAL(msg.getCondition().getSelection(), deserializedMsg.getCondition().getSelection());
+ EXPECT_EQUAL(false, deserializedMsg.get_create_if_non_existent());
}
}
+ //-------------------------------------------------------------------------
+
+ PutDocumentMessage msg2(createDoc(getTypeRepo(), "testdoc", "id:ns:testdoc::"));
+ msg2.set_create_if_non_existent(true);
+ uint32_t expected_message_size = MESSAGE_BASE_LENGTH + 45u +
+ serializedLength(msg2.getCondition().getSelection()) +
+ size_of_create_if_non_existent_flag;
+ auto trunc1 = [](mbus::Blob x) noexcept { return truncate(std::move(x), 1); };
+ auto pad1 = [](mbus::Blob x) noexcept { return pad(std::move(x), 1); };
+ EXPECT_EQUAL(expected_message_size, serialize("PutDocumentMessage-create", msg2));
+ EXPECT_EQUAL(expected_message_size - 1, serialize("PutDocumentMessage-create-truncate", msg2, trunc1));
+ EXPECT_EQUAL(expected_message_size + 1, serialize("PutDocumentMessage-create-pad", msg2, pad1));
+
+ for (uint32_t lang = 0; lang < NUM_LANGUAGES; ++lang) {
+ auto decoded = Unwrap<PutDocumentMessage>(deserialize("PutDocumentMessage-create", DocumentProtocol::MESSAGE_PUTDOCUMENT, lang));
+ auto decoded_trunc = Unwrap<PutDocumentMessage>(deserialize("PutDocumentMessage-create-truncate", DocumentProtocol::MESSAGE_PUTDOCUMENT, lang));
+ auto decoded_pad = Unwrap<PutDocumentMessage>(deserialize("PutDocumentMessage-create-pad", DocumentProtocol::MESSAGE_PUTDOCUMENT, lang));
+ EXPECT_EQUAL(true, decoded->get_create_if_non_existent());
+ EXPECT_EQUAL(false, decoded_trunc->get_create_if_non_existent());
+ EXPECT_EQUAL(true, decoded_pad->get_create_if_non_existent());
+ }
+
return true;
}
@@ -525,133 +498,6 @@ Messages60Test::testRemoveDocumentReply()
}
bool
-Messages60Test::testSearchResultMessage()
-{
- SearchResultMessage srm;
- EXPECT_EQUAL(srm.getSequenceId(), 0u);
- EXPECT_EQUAL(srm.getHitCount(), 0u);
- EXPECT_EQUAL(srm.getAggregatorList().getSerializedSize(), 4u);
- EXPECT_EQUAL(srm.vdslib::SearchResult::getSerializedSize(), 20u);
- EXPECT_EQUAL(srm.getSerializedSize(), 20u);
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + size_t(24), serialize("SearchResultMessage-1", srm));
-
- mbus::Routable::UP routable = deserialize("SearchResultMessage-1", DocumentProtocol::MESSAGE_SEARCHRESULT, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- SearchResultMessage * dm = static_cast<SearchResultMessage *>(routable.get());
- EXPECT_EQUAL(dm->getSequenceId(), size_t(0));
- EXPECT_EQUAL(dm->getHitCount(), size_t(0));
-
- srm.addHit(0, "doc1", 89);
- srm.addHit(1, "doc17", 109);
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 55u, serialize("SearchResultMessage-2", srm));
- routable = deserialize("SearchResultMessage-2", DocumentProtocol::MESSAGE_SEARCHRESULT, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<SearchResultMessage *>(routable.get());
- EXPECT_EQUAL(dm->getHitCount(), size_t(2));
- const char *docId;
- SearchResultMessage::RankType rank;
- dm->getHit(0, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- dm->getHit(1, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
- EXPECT_EQUAL(strcmp("doc17", docId), 0);
-
- srm.sort();
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 55u, serialize("SearchResultMessage-3", srm));
- routable = deserialize("SearchResultMessage-3", DocumentProtocol::MESSAGE_SEARCHRESULT, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<SearchResultMessage *>(routable.get());
- EXPECT_EQUAL(dm->getHitCount(), size_t(2));
- dm->getHit(0, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
- EXPECT_EQUAL(strcmp("doc17", docId), 0);
- dm->getHit(1, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
-
- SearchResultMessage srm2;
- srm2.addHit(0, "doc1", 89, "sortdata2", 9);
- srm2.addHit(1, "doc17", 109, "sortdata1", 9);
- srm2.addHit(2, "doc18", 90, "sortdata3", 9);
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 108u, serialize("SearchResultMessage-4", srm2));
- routable = deserialize("SearchResultMessage-4", DocumentProtocol::MESSAGE_SEARCHRESULT, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<SearchResultMessage *>(routable.get());
- EXPECT_EQUAL(dm->getHitCount(), size_t(3));
- dm->getHit(0, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- dm->getHit(1, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
- EXPECT_EQUAL(strcmp("doc17", docId), 0);
- dm->getHit(2, docId, rank);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
- EXPECT_EQUAL(strcmp("doc18", docId), 0);
-
- srm2.sort();
- const void *buf;
- size_t sz;
- srm2.getHit(0, docId, rank);
- srm2.getSortBlob(0, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
- EXPECT_EQUAL(strcmp("doc17", docId), 0);
- srm2.getHit(1, docId, rank);
- srm2.getSortBlob(1, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- srm2.getHit(2, docId, rank);
- srm2.getSortBlob(2, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
- EXPECT_EQUAL(strcmp("doc18", docId), 0);
-
- EXPECT_EQUAL(MESSAGE_BASE_LENGTH + 108u, serialize("SearchResultMessage-5", srm2));
- routable = deserialize("SearchResultMessage-5", DocumentProtocol::MESSAGE_SEARCHRESULT, LANG_CPP);
- if (!EXPECT_TRUE(routable)) {
- return false;
- }
- dm = static_cast<SearchResultMessage *>(routable.get());
- EXPECT_EQUAL(dm->getHitCount(), size_t(3));
- dm->getHit(0, docId, rank);
- dm->getSortBlob(0, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata1", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(109));
- EXPECT_EQUAL(strcmp("doc17", docId), 0);
- dm->getHit(1, docId, rank);
- dm->getSortBlob(1, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata2", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(89));
- EXPECT_EQUAL(strcmp("doc1", docId), 0);
- dm->getHit(2, docId, rank);
- dm->getSortBlob(2, buf, sz);
- EXPECT_EQUAL(sz, 9u);
- EXPECT_EQUAL(memcmp("sortdata3", buf, sz), 0);
- EXPECT_EQUAL(rank, SearchResultMessage::RankType(90));
- EXPECT_EQUAL(strcmp("doc18", docId), 0);
- return true;
-}
-
-bool
Messages60Test::testUpdateDocumentMessage()
{
const DocumentTypeRepo & repo = getTypeRepo();
@@ -877,12 +723,6 @@ Messages60Test::testDocumentListReply()
}
bool
-Messages60Test::testDocumentSummaryReply()
-{
- return tryVisitorReply("DocumentSummaryReply", DocumentProtocol::REPLY_DOCUMENTSUMMARY);
-}
-
-bool
Messages60Test::testGetDocumentReply()
{
document::Document::SP doc =
@@ -911,12 +751,6 @@ Messages60Test::testMapVisitorReply()
}
bool
-Messages60Test::testSearchResultReply()
-{
- return tryVisitorReply("SearchResultReply", DocumentProtocol::REPLY_SEARCHRESULT);
-}
-
-bool
Messages60Test::testStatBucketReply()
{
StatBucketReply msg;
diff --git a/documentapi/src/tests/messages/messages60test.h b/documentapi/src/tests/messages/messages60test.h
index 1eb3e8e248f..4a2a3f98fad 100644
--- a/documentapi/src/tests/messages/messages60test.h
+++ b/documentapi/src/tests/messages/messages60test.h
@@ -24,7 +24,6 @@ public:
bool testDocumentListMessage();
bool testDocumentListReply();
bool testDocumentSummaryMessage();
- bool testDocumentSummaryReply();
bool testEmptyBucketsMessage();
bool testEmptyBucketsReply();
bool testGetBucketListMessage();
@@ -44,7 +43,6 @@ public:
bool testRemoveLocationMessage();
bool testRemoveLocationReply();
bool testSearchResultMessage();
- bool testSearchResultReply();
bool testStatBucketMessage();
bool testStatBucketReply();
bool testUpdateDocumentMessage();
diff --git a/documentapi/src/tests/messages/testbase.cpp b/documentapi/src/tests/messages/testbase.cpp
index db2c08704e1..ddce9f82a5d 100644
--- a/documentapi/src/tests/messages/testbase.cpp
+++ b/documentapi/src/tests/messages/testbase.cpp
@@ -76,6 +76,24 @@ TestBase::putTest(uint32_t type, TEST_METHOD_PT test)
return *this;
}
+mbus::Blob
+TestBase::truncate(mbus::Blob data, size_t bytes)
+{
+ ASSERT_GREATER(data.size(), bytes);
+ mbus::Blob res(data.size() - bytes);
+ memcpy(res.data(), data.data(), res.size());
+ return res;
+}
+
+mbus::Blob
+TestBase::pad(mbus::Blob data, size_t bytes)
+{
+ mbus::Blob res(data.size() + bytes);
+ memset(res.data(), 0, res.size());
+ memcpy(res.data(), data.data(), data.size());
+ return res;
+}
+
bool
TestBase::testCoverage(const std::vector<uint32_t> &expected, const std::vector<uint32_t> &actual, bool report) const
{
@@ -119,13 +137,13 @@ bool TestBase::file_content_is_unchanged(const string& filename, const mbus::Blo
}
uint32_t
-TestBase::serialize(const string &filename, const mbus::Routable &routable)
+TestBase::serialize(const string &filename, const mbus::Routable &routable, Tamper tamper)
{
const vespalib::Version version = getVersion();
string path = getPath(version.toString() + "-cpp-" + filename + ".dat");
LOG(info, "Serializing to '%s'..", path.c_str());
- mbus::Blob blob = _protocol.encode(version, routable);
+ mbus::Blob blob = tamper(_protocol.encode(version, routable));
if (file_content_is_unchanged(path, blob)) {
LOG(info, "Serialization for '%s' is unchanged; not overwriting it", path.c_str());
} else if (!EXPECT_TRUE(writeFile(path, blob))) {
@@ -213,5 +231,3 @@ TestBase::readFile(const string &filename) const
return blob;
}
-
-
diff --git a/documentapi/src/tests/messages/testbase.h b/documentapi/src/tests/messages/testbase.h
index bf384fc2fb9..a2cd5ee5649 100644
--- a/documentapi/src/tests/messages/testbase.h
+++ b/documentapi/src/tests/messages/testbase.h
@@ -43,13 +43,20 @@ protected:
int Main() override;
public:
+ using Tamper = std::function<mbus::Blob(mbus::Blob)>;
+ static mbus::Blob truncate(mbus::Blob data, size_t bytes);
+ static mbus::Blob pad(mbus::Blob data, size_t bytes);
+
const document::DocumentTypeRepo &getTypeRepo() { return *_repo; }
std::shared_ptr<const document::DocumentTypeRepo> &getTypeRepoSp() { return _repo; }
bool testCoverage(const std::vector<uint32_t> &expected, const std::vector<uint32_t> &actual, bool report = false) const;
bool writeFile(const string &filename, const mbus::Blob& blob) const;
mbus::Blob readFile(const string &filename) const;
- uint32_t serialize(const string &filename, const mbus::Routable &routable);
+ uint32_t serialize(const string &filename, const mbus::Routable &routable, Tamper tamper);
+ uint32_t serialize(const string &filename, const mbus::Routable &routable) {
+ return serialize(filename, routable, [](auto x)noexcept{ return x; });
+ }
mbus::Routable::UP deserialize(const string &filename, uint32_t classId, uint32_t lang);
void dump(const mbus::Blob &blob) const;
diff --git a/documentapi/src/vespa/documentapi/documentapi.h b/documentapi/src/vespa/documentapi/documentapi.h
index 311199c94cd..b784a63a642 100644
--- a/documentapi/src/vespa/documentapi/documentapi.h
+++ b/documentapi/src/vespa/documentapi/documentapi.h
@@ -11,9 +11,7 @@
#include <vespa/documentapi/messagebus/messages/updatedocumentreply.h>
#include <vespa/documentapi/messagebus/messages/feedreply.h>
#include <vespa/documentapi/messagebus/messages/updatedocumentmessage.h>
-#include <vespa/documentapi/messagebus/messages/searchresultmessage.h>
#include <vespa/documentapi/messagebus/messages/visitor.h>
-#include <vespa/documentapi/messagebus/messages/documentsummarymessage.h>
#include <vespa/documentapi/messagebus/messages/wrongdistributionreply.h>
#include <vespa/documentapi/messagebus/messages/getbucketlistmessage.h>
#include <vespa/documentapi/messagebus/messages/getbucketlistreply.h>
diff --git a/documentapi/src/vespa/documentapi/messagebus/documentprotocol.cpp b/documentapi/src/vespa/documentapi/messagebus/documentprotocol.cpp
index d751d11177c..1eb50bba714 100644
--- a/documentapi/src/vespa/documentapi/messagebus/documentprotocol.cpp
+++ b/documentapi/src/vespa/documentapi/messagebus/documentprotocol.cpp
@@ -49,7 +49,6 @@ DocumentProtocol::DocumentProtocol(std::shared_ptr<const DocumentTypeRepo> repo,
putRoutableFactory(MESSAGE_CREATEVISITOR, std::make_shared<RoutableFactories60::CreateVisitorMessageFactory>(), from6);
putRoutableFactory(MESSAGE_DESTROYVISITOR, std::make_shared<RoutableFactories60::DestroyVisitorMessageFactory>(), from6);
putRoutableFactory(MESSAGE_DOCUMENTLIST, std::make_shared<RoutableFactories60::DocumentListMessageFactory>(*_repo), from6);
- putRoutableFactory(MESSAGE_DOCUMENTSUMMARY, std::make_shared<RoutableFactories60::DocumentSummaryMessageFactory>(), from6);
putRoutableFactory(MESSAGE_EMPTYBUCKETS, std::make_shared<RoutableFactories60::EmptyBucketsMessageFactory>(), from6);
putRoutableFactory(MESSAGE_GETBUCKETLIST, std::make_shared<RoutableFactories60::GetBucketListMessageFactory>(), from6);
putRoutableFactory(MESSAGE_GETBUCKETSTATE, std::make_shared<RoutableFactories60::GetBucketStateMessageFactory>(), from6);
@@ -59,7 +58,6 @@ DocumentProtocol::DocumentProtocol(std::shared_ptr<const DocumentTypeRepo> repo,
putRoutableFactory(MESSAGE_QUERYRESULT, std::make_shared<RoutableFactories60::QueryResultMessageFactory>(), from6);
putRoutableFactory(MESSAGE_REMOVEDOCUMENT, std::make_shared<RoutableFactories60::RemoveDocumentMessageFactory>(), from6);
putRoutableFactory(MESSAGE_REMOVELOCATION, std::make_shared<RoutableFactories60::RemoveLocationMessageFactory>(*_repo), from6);
- putRoutableFactory(MESSAGE_SEARCHRESULT, std::make_shared<RoutableFactories60::SearchResultMessageFactory>(), from6);
putRoutableFactory(MESSAGE_STATBUCKET, std::make_shared<RoutableFactories60::StatBucketMessageFactory>(), from6);
putRoutableFactory(MESSAGE_UPDATEDOCUMENT, std::make_shared<RoutableFactories60::UpdateDocumentMessageFactory>(*_repo), from6);
putRoutableFactory(MESSAGE_VISITORINFO, std::make_shared<RoutableFactories60::VisitorInfoMessageFactory>(), from6);
@@ -67,7 +65,6 @@ DocumentProtocol::DocumentProtocol(std::shared_ptr<const DocumentTypeRepo> repo,
putRoutableFactory(REPLY_DESTROYVISITOR, std::make_shared<RoutableFactories60::DestroyVisitorReplyFactory>(), from6);
putRoutableFactory(REPLY_DOCUMENTIGNORED, std::make_shared<RoutableFactories60::DocumentIgnoredReplyFactory>(), from6);
putRoutableFactory(REPLY_DOCUMENTLIST, std::make_shared<RoutableFactories60::DocumentListReplyFactory>(), from6);
- putRoutableFactory(REPLY_DOCUMENTSUMMARY, std::make_shared<RoutableFactories60::DocumentSummaryReplyFactory>(), from6);
putRoutableFactory(REPLY_EMPTYBUCKETS, std::make_shared<RoutableFactories60::EmptyBucketsReplyFactory>(), from6);
putRoutableFactory(REPLY_GETBUCKETLIST, std::make_shared<RoutableFactories60::GetBucketListReplyFactory>(), from6);
putRoutableFactory(REPLY_GETBUCKETSTATE, std::make_shared<RoutableFactories60::GetBucketStateReplyFactory>(), from6);
@@ -77,7 +74,6 @@ DocumentProtocol::DocumentProtocol(std::shared_ptr<const DocumentTypeRepo> repo,
putRoutableFactory(REPLY_QUERYRESULT, std::make_shared<RoutableFactories60::QueryResultReplyFactory>(), from6);
putRoutableFactory(REPLY_REMOVEDOCUMENT, std::make_shared<RoutableFactories60::RemoveDocumentReplyFactory>(), from6);
putRoutableFactory(REPLY_REMOVELOCATION, std::make_shared<RoutableFactories60::RemoveLocationReplyFactory>(), from6);
- putRoutableFactory(REPLY_SEARCHRESULT, std::make_shared<RoutableFactories60::SearchResultReplyFactory>(), from6);
putRoutableFactory(REPLY_STATBUCKET, std::make_shared<RoutableFactories60::StatBucketReplyFactory>(), from6);
putRoutableFactory(REPLY_UPDATEDOCUMENT, std::make_shared<RoutableFactories60::UpdateDocumentReplyFactory>(), from6);
putRoutableFactory(REPLY_VISITORINFO, std::make_shared<RoutableFactories60::VisitorInfoReplyFactory>(), from6);
diff --git a/documentapi/src/vespa/documentapi/messagebus/documentprotocol.h b/documentapi/src/vespa/documentapi/messagebus/documentprotocol.h
index 9f0d7253335..d91d355c567 100644
--- a/documentapi/src/vespa/documentapi/messagebus/documentprotocol.h
+++ b/documentapi/src/vespa/documentapi/messagebus/documentprotocol.h
@@ -55,9 +55,10 @@ public:
MESSAGE_CREATEVISITOR = DOCUMENT_MESSAGE + 7,
MESSAGE_DESTROYVISITOR = DOCUMENT_MESSAGE + 8,
MESSAGE_VISITORINFO = DOCUMENT_MESSAGE + 9,
- MESSAGE_SEARCHRESULT = DOCUMENT_MESSAGE + 11,
+ // SearchResult and DocumentSummary messages were replaced by QueryResult message in 2010.
+ // MESSAGE_SEARCHRESULT = DOCUMENT_MESSAGE + 11,
//MESSAGE_MULTIOPERATION = DOCUMENT_MESSAGE + 13,
- MESSAGE_DOCUMENTSUMMARY = DOCUMENT_MESSAGE + 14,
+ // MESSAGE_DOCUMENTSUMMARY = DOCUMENT_MESSAGE + 14,
MESSAGE_MAPVISITOR = DOCUMENT_MESSAGE + 15,
MESSAGE_GETBUCKETSTATE = DOCUMENT_MESSAGE + 18,
MESSAGE_STATBUCKET = DOCUMENT_MESSAGE + 19,
@@ -78,9 +79,10 @@ public:
REPLY_CREATEVISITOR = DOCUMENT_REPLY + 7,
REPLY_DESTROYVISITOR = DOCUMENT_REPLY + 8,
REPLY_VISITORINFO = DOCUMENT_REPLY + 9,
- REPLY_SEARCHRESULT = DOCUMENT_REPLY + 11,
+ // SearchResult and DocumentSummary replies were replaced by QueryResult reply in 2010.
+ // REPLY_SEARCHRESULT = DOCUMENT_REPLY + 11,
//REPLY_MULTIOPERATION = DOCUMENT_REPLY + 13,
- REPLY_DOCUMENTSUMMARY = DOCUMENT_REPLY + 14,
+ // REPLY_DOCUMENTSUMMARY = DOCUMENT_REPLY + 14,
REPLY_MAPVISITOR = DOCUMENT_REPLY + 15,
REPLY_GETBUCKETSTATE = DOCUMENT_REPLY + 18,
REPLY_STATBUCKET = DOCUMENT_REPLY + 19,
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/CMakeLists.txt b/documentapi/src/vespa/documentapi/messagebus/messages/CMakeLists.txt
index ddbe66fc22a..d906166b6df 100644
--- a/documentapi/src/vespa/documentapi/messagebus/messages/CMakeLists.txt
+++ b/documentapi/src/vespa/documentapi/messagebus/messages/CMakeLists.txt
@@ -5,7 +5,6 @@ vespa_add_library(documentapi_documentapimessages OBJECT
documentmessage.cpp
documentreply.cpp
documentstate.cpp
- documentsummarymessage.cpp
emptybucketsmessage.cpp
feedanswer.cpp
feedmessage.cpp
@@ -21,7 +20,6 @@ vespa_add_library(documentapi_documentapimessages OBJECT
removedocumentmessage.cpp
removedocumentreply.cpp
removelocationmessage.cpp
- searchresultmessage.cpp
statbucketmessage.cpp
statbucketreply.cpp
testandsetmessage.cpp
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.cpp b/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.cpp
deleted file mode 100644
index 6be241b2e1d..00000000000
--- a/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "documentsummarymessage.h"
-
-using vdslib::DocumentSummary;
-
-namespace documentapi {
-
-DocumentSummaryMessage::DocumentSummaryMessage(const DocumentSummary & sr) :
- VisitorMessage(),
- DocumentSummary(sr)
-{
- // empty
-}
-
-DocumentSummaryMessage::DocumentSummaryMessage() :
- VisitorMessage(),
- DocumentSummary()
-{
- // empty
-}
-
-DocumentReply::UP
-DocumentSummaryMessage::doCreateReply() const
-{
- return DocumentReply::UP(new VisitorReply(DocumentProtocol::REPLY_DOCUMENTSUMMARY));
-}
-
-uint32_t
-DocumentSummaryMessage::getApproxSize() const
-{
- return DocumentSummary::getSerializedSize();
-}
-
-uint32_t
-DocumentSummaryMessage::getType() const
-{
- return DocumentProtocol::MESSAGE_DOCUMENTSUMMARY;
-}
-
-}
-
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.h b/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.h
deleted file mode 100644
index 2c8149ab058..00000000000
--- a/documentapi/src/vespa/documentapi/messagebus/messages/documentsummarymessage.h
+++ /dev/null
@@ -1,38 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-
-#include "visitor.h"
-#include <vespa/vdslib/container/documentsummary.h>
-
-namespace documentapi {
-
-class DocumentSummaryMessage : public VisitorMessage,
- public vdslib::DocumentSummary {
-protected:
- // Implements VisitorMessage.
- DocumentReply::UP doCreateReply() const override;
-
-public:
- /**
- * Convenience typedef.
- */
- using UP = std::unique_ptr<DocumentSummaryMessage>;
- using SP = std::shared_ptr<DocumentSummaryMessage>;
-
- /**
- * Constructs a new document message with no content.
- */
- DocumentSummaryMessage();
-
- /**
- * Constructs a new document message with summary comment.
- *
- * @param summary The document summary to contain.
- */
- DocumentSummaryMessage(const vdslib::DocumentSummary &summary);
- uint32_t getApproxSize() const override;
- uint32_t getType() const override;
- string toString() const override { return "documentsummarymessage"; }
-};
-
-}
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/putdocumentmessage.h b/documentapi/src/vespa/documentapi/messagebus/messages/putdocumentmessage.h
index 5a7f8acb60a..a1fabd38c3c 100644
--- a/documentapi/src/vespa/documentapi/messagebus/messages/putdocumentmessage.h
+++ b/documentapi/src/vespa/documentapi/messagebus/messages/putdocumentmessage.h
@@ -11,6 +11,7 @@ private:
using DocumentSP = std::shared_ptr<document::Document>;
DocumentSP _document;
uint64_t _time;
+ bool _create_if_non_existent = false;
protected:
DocumentReply::UP doCreateReply() const override;
@@ -65,7 +66,9 @@ public:
uint64_t getSequenceId() const override;
uint32_t getType() const override;
string toString() const override { return "putdocumentmessage"; }
+
+ void set_create_if_non_existent(bool value) noexcept { _create_if_non_existent = value; }
+ bool get_create_if_non_existent() const noexcept { return _create_if_non_existent; }
};
}
-
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.cpp b/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.cpp
deleted file mode 100644
index 8e25e70d749..00000000000
--- a/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.cpp
+++ /dev/null
@@ -1,42 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "searchresultmessage.h"
-
-using vdslib::SearchResult;
-
-namespace documentapi {
-
-SearchResultMessage::SearchResultMessage() :
- VisitorMessage(),
- SearchResult()
-{
- // empty
-}
-
-SearchResultMessage::SearchResultMessage(SearchResult &&result) :
- VisitorMessage(),
- SearchResult(std::move(result))
-{
- // empty
-}
-
-DocumentReply::UP
-SearchResultMessage::doCreateReply() const
-{
- return std::make_unique<VisitorReply>(DocumentProtocol::REPLY_SEARCHRESULT);
-}
-
-uint32_t
-SearchResultMessage::getApproxSize() const
-{
- return SearchResult::getSerializedSize();
-}
-
-uint32_t
-SearchResultMessage::getType() const
-{
- return DocumentProtocol::MESSAGE_SEARCHRESULT;
-}
-
-}
-
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.h b/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.h
deleted file mode 100644
index d22f8197534..00000000000
--- a/documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.h
+++ /dev/null
@@ -1,26 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-
-#include "visitor.h"
-#include <vespa/vdslib/container/searchresult.h>
-
-namespace documentapi {
-
-class SearchResultMessage : public VisitorMessage,
- public vdslib::SearchResult {
-protected:
- DocumentReply::UP doCreateReply() const override;
-
-public:
- using UP = std::unique_ptr<SearchResultMessage>;
- using SP = std::shared_ptr<SearchResultMessage>;
-
- SearchResultMessage();
- SearchResultMessage(vdslib::SearchResult &&result);
-
- uint32_t getApproxSize() const override;
- uint32_t getType() const override;
- string toString() const override { return "searchresultmessage"; }
-};
-
-}
diff --git a/documentapi/src/vespa/documentapi/messagebus/messages/testandsetcondition.h b/documentapi/src/vespa/documentapi/messagebus/messages/testandsetcondition.h
index c06c1767414..1e0bfda986c 100644
--- a/documentapi/src/vespa/documentapi/messagebus/messages/testandsetcondition.h
+++ b/documentapi/src/vespa/documentapi/messagebus/messages/testandsetcondition.h
@@ -27,6 +27,10 @@ public:
const vespalib::string & getSelection() const { return _selection; }
bool isPresent() const noexcept { return !_selection.empty(); }
+
+ bool operator==(const TestAndSetCondition& rhs) const noexcept {
+ return (_selection == rhs._selection);
+ }
};
}
diff --git a/documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp b/documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp
index 0757db9f5fc..508bb25c907 100644
--- a/documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp
+++ b/documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp
@@ -258,37 +258,6 @@ RoutableFactories60::DocumentListReplyFactory::doEncode(const DocumentReply &, v
}
DocumentMessage::UP
-RoutableFactories60::DocumentSummaryMessageFactory::doDecode(document::ByteBuffer &buf) const
-{
- auto msg = std::make_unique<DocumentSummaryMessage>();
-
- msg->deserialize(buf);
-
- return msg;
-}
-
-bool
-RoutableFactories60::DocumentSummaryMessageFactory::doEncode(const DocumentMessage &obj, vespalib::GrowableByteBuffer &buf) const
-{
- const DocumentSummaryMessage &msg = static_cast<const DocumentSummaryMessage&>(obj);
- msg.serialize(buf);
-
- return true;
-}
-
-DocumentReply::UP
-RoutableFactories60::DocumentSummaryReplyFactory::doDecode(document::ByteBuffer &) const
-{
- return std::make_unique<VisitorReply>(DocumentProtocol::REPLY_DOCUMENTSUMMARY);
-}
-
-bool
-RoutableFactories60::DocumentSummaryReplyFactory::doEncode(const DocumentReply &, vespalib::GrowableByteBuffer &) const
-{
- return true;
-}
-
-DocumentMessage::UP
RoutableFactories60::EmptyBucketsMessageFactory::doDecode(document::ByteBuffer &buf) const
{
auto msg = std::make_unique<EmptyBucketsMessage>();
@@ -532,6 +501,11 @@ RoutableFactories60::PutDocumentMessageFactory::decodeInto(PutDocumentMessage &
msg.setDocument(decodeDocument(_repo, buf));
msg.setTimestamp(static_cast<uint64_t>(decodeLong(buf)));
decodeTasCondition(msg, buf);
+ if (buf.getRemaining() > 0) {
+ uint8_t value = 0;
+ buf.getByte(value);
+ msg.set_create_if_non_existent(value != 0);
+ }
}
bool
@@ -544,7 +518,12 @@ RoutableFactories60::PutDocumentMessageFactory::doEncode(const DocumentMessage &
buf.putBytes(stream.peek(), stream.size());
buf.putLong(static_cast<int64_t>(msg.getTimestamp()));
encodeTasCondition(buf, msg);
-
+ if (msg.get_create_if_non_existent()) {
+ buf.putByte(1);
+ } else {
+ buf.putByte(0);
+ }
+
return true;
}
@@ -632,23 +611,6 @@ RoutableFactories60::RemoveLocationReplyFactory::doEncode(const DocumentReply &,
}
DocumentMessage::UP
-RoutableFactories60::SearchResultMessageFactory::doDecode(document::ByteBuffer &buf) const
-{
- auto msg = std::make_unique<SearchResultMessage>();
- msg->deserialize(buf);
- return msg;
-}
-
-bool
-RoutableFactories60::SearchResultMessageFactory::doEncode(const DocumentMessage &obj, vespalib::GrowableByteBuffer &buf) const
-{
- const auto & msg = static_cast<const SearchResultMessage&>(obj);
- msg.serialize(buf);
-
- return true;
-}
-
-DocumentMessage::UP
RoutableFactories60::QueryResultMessageFactory::doDecode(document::ByteBuffer &buf) const
{
auto msg = std::make_unique<QueryResultMessage>();
@@ -670,18 +632,6 @@ RoutableFactories60::QueryResultMessageFactory::doEncode(const DocumentMessage &
}
DocumentReply::UP
-RoutableFactories60::SearchResultReplyFactory::doDecode(document::ByteBuffer &) const
-{
- return std::make_unique<VisitorReply>(DocumentProtocol::REPLY_SEARCHRESULT);
-}
-
-bool
-RoutableFactories60::SearchResultReplyFactory::doEncode(const DocumentReply &, vespalib::GrowableByteBuffer &) const
-{
- return true;
-}
-
-DocumentReply::UP
RoutableFactories60::QueryResultReplyFactory::doDecode(document::ByteBuffer &) const
{
return std::make_unique<VisitorReply>(DocumentProtocol::REPLY_QUERYRESULT);
diff --git a/documentapi/src/vespa/documentapi/messagebus/routablefactories60.h b/documentapi/src/vespa/documentapi/messagebus/routablefactories60.h
index b618d92a145..c0cbc4868eb 100644
--- a/documentapi/src/vespa/documentapi/messagebus/routablefactories60.h
+++ b/documentapi/src/vespa/documentapi/messagebus/routablefactories60.h
@@ -172,16 +172,6 @@ public:
DocumentReply::UP doDecode(document::ByteBuffer &buf) const override;
bool doEncode(const DocumentReply &reply, vespalib::GrowableByteBuffer &buf) const override;
};
- class DocumentSummaryMessageFactory : public DocumentMessageFactory {
- protected:
- DocumentMessage::UP doDecode(document::ByteBuffer &buf) const override;
- bool doEncode(const DocumentMessage &msg, vespalib::GrowableByteBuffer &buf) const override;
- };
- class DocumentSummaryReplyFactory : public DocumentReplyFactory {
- protected:
- DocumentReply::UP doDecode(document::ByteBuffer &buf) const override;
- bool doEncode(const DocumentReply &reply, vespalib::GrowableByteBuffer &buf) const override;
- };
class EmptyBucketsMessageFactory : public DocumentMessageFactory {
protected:
DocumentMessage::UP doDecode(document::ByteBuffer &buf) const override;
@@ -282,16 +272,6 @@ public:
DocumentReply::UP doDecode(document::ByteBuffer &buf) const override;
bool doEncode(const DocumentReply &reply, vespalib::GrowableByteBuffer &buf) const override;
};
- class SearchResultMessageFactory : public DocumentMessageFactory {
- protected:
- DocumentMessage::UP doDecode(document::ByteBuffer &buf) const override;
- bool doEncode(const DocumentMessage &msg, vespalib::GrowableByteBuffer &buf) const override;
- };
- class SearchResultReplyFactory : public DocumentReplyFactory {
- protected:
- DocumentReply::UP doDecode(document::ByteBuffer &buf) const override;
- bool doEncode(const DocumentReply &reply, vespalib::GrowableByteBuffer &buf) const override;
- };
class StatBucketMessageFactory : public DocumentMessageFactory {
virtual bool encodeBucketSpace(vespalib::stringref bucketSpace, vespalib::GrowableByteBuffer& buf) const;
virtual string decodeBucketSpace(document::ByteBuffer&) const;
diff --git a/documentapi/test/crosslanguagefiles/.gitattributes b/documentapi/test/crosslanguagefiles/.gitattributes
new file mode 100644
index 00000000000..8697fef7d46
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/.gitattributes
@@ -0,0 +1 @@
+*.dat binary
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-1.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-1.dat
deleted file mode 100644
index 0107dd5f350..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-1.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-2.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-2.dat
deleted file mode 100644
index 57187093f28..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-2.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-3.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-3.dat
deleted file mode 100644
index 6a516d38d17..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryMessage-3.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryReply.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryReply.dat
deleted file mode 100644
index 16b1e4bc4ef..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-DocumentSummaryReply.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-pad.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-pad.dat
new file mode 100644
index 00000000000..bbe9bede8d6
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-pad.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-truncate.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-truncate.dat
new file mode 100644
index 00000000000..344d0a4b58c
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create-truncate.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create.dat
new file mode 100644
index 00000000000..7f7c55b2b88
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage-create.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage.dat
index 706a002c8c9..93f85f7e419 100644
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage.dat
+++ b/documentapi/test/crosslanguagefiles/6.221-cpp-PutDocumentMessage.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-1.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-1.dat
deleted file mode 100644
index 988f9fdab1f..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-1.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-2.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-2.dat
deleted file mode 100644
index ac277d09643..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-2.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-3.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-3.dat
deleted file mode 100644
index 03b49c8a0ac..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-3.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-4.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-4.dat
deleted file mode 100644
index d52e574ea44..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-4.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-5.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-5.dat
deleted file mode 100644
index e68654e9941..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultMessage-5.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultReply.dat b/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultReply.dat
deleted file mode 100644
index cce9c6f8d14..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-cpp-SearchResultReply.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-DocumentSummaryReply.dat b/documentapi/test/crosslanguagefiles/6.221-java-DocumentSummaryReply.dat
deleted file mode 100644
index 16b1e4bc4ef..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-java-DocumentSummaryReply.dat
+++ /dev/null
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-pad.dat b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-pad.dat
new file mode 100644
index 00000000000..bbe9bede8d6
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-pad.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-truncate.dat b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-truncate.dat
new file mode 100644
index 00000000000..344d0a4b58c
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create-truncate.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create.dat b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create.dat
new file mode 100644
index 00000000000..7f7c55b2b88
--- /dev/null
+++ b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage-create.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage.dat b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage.dat
index 706a002c8c9..93f85f7e419 100644
--- a/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage.dat
+++ b/documentapi/test/crosslanguagefiles/6.221-java-PutDocumentMessage.dat
Binary files differ
diff --git a/documentapi/test/crosslanguagefiles/6.221-java-SearchResultReply.dat b/documentapi/test/crosslanguagefiles/6.221-java-SearchResultReply.dat
deleted file mode 100644
index cce9c6f8d14..00000000000
--- a/documentapi/test/crosslanguagefiles/6.221-java-SearchResultReply.dat
+++ /dev/null
Binary files differ
diff --git a/fat-model-dependencies/pom.xml b/fat-model-dependencies/pom.xml
index b58533b32e9..0ae2c68e6a8 100644
--- a/fat-model-dependencies/pom.xml
+++ b/fat-model-dependencies/pom.xml
@@ -96,6 +96,10 @@
<groupId>ai.djl.huggingface</groupId>
<artifactId>tokenizers</artifactId>
</exclusion>
+ <exclusion>
+ <groupId>com.theokanning.openai-gpt3-java</groupId>
+ <artifactId>service</artifactId>
+ </exclusion>
</exclusions>
</dependency>
<dependency>
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
index e5aa47fe5c9..b37fe02226b 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReceiver.java
@@ -130,7 +130,7 @@ public class FileReceiver {
moveFileToDestination(inprogressFile, file);
} else {
decompressedDir = Files.createTempDirectory(tmpDir.toPath(), "archive").toFile();
- log.log(Level.FINE, () -> "compression type to use=" + compressionType);
+ log.log(Level.FINEST, () -> "compression type to use=" + compressionType);
new FileReferenceCompressor(fileType, compressionType).decompress(inprogressFile, decompressedDir);
moveFileToDestination(decompressedDir, fileReferenceDir);
}
@@ -230,7 +230,7 @@ public class FileReceiver {
}
private void receiveFileMeta(Request req) {
- log.log(Level.FINE, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(Level.FINEST, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
FileReference reference = new FileReference(req.parameters().get(0).asString());
String fileName = req.parameters().get(1).asString();
Type type = FileReferenceData.Type.valueOf(req.parameters().get(2).asString());
@@ -281,7 +281,7 @@ public class FileReceiver {
}
private void receiveFileEof(Request req) {
- log.log(Level.FINE, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
+ log.log(Level.FINEST, () -> "Received method call '" + req.methodName() + "' with parameters : " + req.parameters());
FileReference reference = new FileReference(req.parameters().get(0).asString());
int sessionId = req.parameters().get(1).asInt32();
long xxhash = req.parameters().get(2).asInt64();
diff --git a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceCompressor.java b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceCompressor.java
index 8d6f9ea1af3..5ab1841486e 100644
--- a/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceCompressor.java
+++ b/filedistribution/src/main/java/com/yahoo/vespa/filedistribution/FileReferenceCompressor.java
@@ -61,7 +61,7 @@ public class FileReferenceCompressor {
}
public void decompress(File inputFile, File outputDir) throws IOException {
- log.log(Level.FINE, () -> "Decompressing '" + inputFile + "' into '" + outputDir + "'");
+ log.log(Level.FINEST, () -> "Decompressing '" + inputFile + "' into '" + outputDir + "'");
try (ArchiveInputStream ais = new TarArchiveInputStream(decompressedInputStream(inputFile))) {
decompress(ais, outputDir);
} catch (IllegalArgumentException e) {
@@ -121,7 +121,7 @@ public class FileReferenceCompressor {
private OutputStream compressedOutputStream(File outputFile) throws IOException {
switch (type) {
case compressed:
- log.log(Level.FINE, () -> "Compressing with compression type " + compressionType);
+ log.log(Level.FINEST, () -> "Compressing with compression type " + compressionType);
return switch (compressionType) {
case gzip -> new GZIPOutputStream(new FileOutputStream(outputFile));
case lz4 -> new LZ4BlockOutputStream(new FileOutputStream(outputFile));
@@ -137,7 +137,7 @@ public class FileReferenceCompressor {
private InputStream decompressedInputStream(File inputFile) throws IOException {
switch (type) {
case compressed:
- log.log(Level.FINE, () -> "Decompressing with compression type " + compressionType);
+ log.log(Level.FINEST, () -> "Decompressing with compression type " + compressionType);
return switch (compressionType) {
case gzip -> new GZIPInputStream(new FileInputStream(inputFile));
case lz4 -> new LZ4BlockInputStream(new FileInputStream(inputFile));
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 299d8c5d1e3..001a0fc835c 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -48,16 +48,9 @@ public class Flags {
private static volatile TreeMap<FlagId, FlagDefinition> flags = new TreeMap<>();
- public static final UnboundBooleanFlag RECONFIGURE_ALB_TARGETS = defineFeatureFlag(
- "reconfigure-alb-targets", false,
- List.of("bjormel"), "2023-03-24", "2023-04-30",
- "Reconfigure ALB targets",
- "Takes effect on next config server container start",
- ZONE_ID);
-
public static final UnboundBooleanFlag DROP_CACHES = defineFeatureFlag(
"drop-caches", false,
- List.of("hakonhall", "baldersheim"), "2023-03-06", "2023-04-05",
+ List.of("hakonhall", "baldersheim"), "2023-03-06", "2023-06-05",
"Drop caches on tenant hosts",
"Takes effect on next tick",
ZONE_ID,
@@ -379,12 +372,43 @@ public class Flags {
ZONE_ID);
public static final UnboundLongFlag ZOOKEEPER_BARRIER_WAIT_FOR_ALL_TIMEOUT = defineLongFlag(
- "zookeeper-barrier-wait-for-all-timeout", 1,
- List.of("hmusum"), "2023-03-28", "2023-04-28",
+ "zookeeper-barrier-wait-for-all-timeout", 5,
+ List.of("hmusum"), "2023-03-28", "2023-05-28",
"Time to wait for all barrier members after getting response from quorum number of member",
"Takes effect on next config server container start",
ZONE_ID);
+ public static final UnboundBooleanFlag NODE_ADMIN_TENANT_SERVICE_REGISTRY = defineFeatureFlag(
+ "node-admin-tenant-service-registry", false,
+ List.of("olaa"), "2023-04-12", "2023-06-12",
+ "Whether AthenzCredentialsMaintainer in node-admin should create tenant service identity certificate",
+ "Takes effect on next tick",
+ ZONE_ID, HOSTNAME, VESPA_VERSION, APPLICATION_ID
+ );
+
+ public static final UnboundBooleanFlag ENABLE_CROWDSTRIKE = defineFeatureFlag(
+ "enable-crowdstrike", true, List.of("andreer"), "2023-04-13", "2023-06-13",
+ "Whether to enable CrowdStrike.", "Takes effect on next host admin tick",
+ HOSTNAME);
+
+ public static final UnboundBooleanFlag ALLOW_MORE_THAN_ONE_CONTENT_GROUP_DOWN = defineFeatureFlag(
+ "allow-more-than-one-content-group-down", false, List.of("hmusum"), "2023-04-14", "2023-06-14",
+ "Whether to enable possible configuration of letting more than one content group down",
+ "Takes effect at redeployment",
+ ZONE_ID, APPLICATION_ID);
+
+ public static final UnboundBooleanFlag FAIL_DEPLOYMENT_ON_MISSING_CERTIFICATE_FILE = defineFeatureFlag(
+ "fail-on-missing-certificate-file", false, List.of("hmusum"), "2023-04-21", "2023-05-21",
+ "Whether to fail in controller when a submitted application package has no certificate files",
+ "Takes effect at redeployment",
+ ZONE_ID);
+
+ public static final UnboundBooleanFlag NEW_IDDOC_LAYOUT = defineFeatureFlag(
+ "new_iddoc_layout", false, List.of("tokle", "bjorncs", "olaa"), "2023-04-24", "2023-05-31",
+ "Whether to use new identity document lauoyt",
+ "Takes effect on node reboot",
+ HOSTNAME);
+
/** WARNING: public for testing: All flags should be defined in {@link Flags}. */
public static UnboundBooleanFlag defineFeatureFlag(String flagId, boolean defaultValue, List<String> owners,
String createdAt, String expiresAt, String description,
diff --git a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
index 336edf74e8f..f5fbc26e099 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/PermanentFlags.java
@@ -347,6 +347,12 @@ public class PermanentFlags {
"Takes effect immediately",
TENANT_ID);
+ public static final UnboundIntFlag KEEP_FILE_REFERENCES_ON_TENANT_NODES = defineIntFlag(
+ "keep-file-references-on-tenant-nodes", 21,
+ "How many days to keep file references on tenant nodes (based on last modification time)",
+ "Takes effect on restart of Docker container",
+ ZONE_ID, APPLICATION_ID
+ );
private PermanentFlags() {}
diff --git a/functions.cmake b/functions.cmake
index 7fa0b0db954..7f217867314 100644
--- a/functions.cmake
+++ b/functions.cmake
@@ -746,6 +746,14 @@ function(vespa_detect_build_platform)
elseif(APPLE)
set(OS_DISTRO "darwin")
set(OS_DISTRO_VERSION ${CMAKE_SYSTEM_VERSION})
+ if(EXISTS "/opt/homebrew/bin/brew")
+ set(VESPA_HOMEBREW_PREFIX "/opt/homebrew")
+ elseif(EXISTS "/usr/local/bin/brew")
+ set(VESPA_HOMEBREW_PREFIX "/usr/local")
+ else()
+ message(FATAL_ERROR "-- Cannot determine homebrew prefix")
+ endif()
+ set(VESPA_HOMEBREW_PREFIX ${VESPA_HOMEBREW_PREFIX} PARENT_SCOPE)
endif()
if(OS_DISTRO)
set(VESPA_OS_DISTRO ${OS_DISTRO} PARENT_SCOPE)
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionConverter.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionConverter.java
index ccad9d6d08b..231f6fb7598 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionConverter.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ExpressionConverter.java
@@ -1,127 +1,22 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.indexinglanguage;
-import com.yahoo.collections.Pair;
-import com.yahoo.vespa.indexinglanguage.expressions.*;
-
-import java.lang.reflect.InvocationTargetException;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.List;
-import java.util.Map;
+import com.yahoo.vespa.indexinglanguage.expressions.Expression;
/**
* @author Simon Thoresen Hult
*/
-@SuppressWarnings({ "UnusedDeclaration" })
public abstract class ExpressionConverter implements Cloneable {
- public final Expression convert(Expression exp) {
- if (exp == null) {
- return null;
- }
- if (shouldConvert(exp)) {
- return doConvert(exp);
- }
- if (!(exp instanceof CompositeExpression)) {
- return exp;
- }
- try {
- // The class.getMethod here takes 8% of the cpu time in reading the SSBE application package
- // TODO: Implement double dispatch through visitor instead?
- return (Expression)ExpressionConverter.class.getMethod("innerConvert", exp.getClass()).invoke(this, exp);
- } catch (IllegalAccessException | NoSuchMethodException e) {
- throw new UnsupportedOperationException(exp.getClass().getName(), e);
- } catch (InvocationTargetException e) {
- Throwable t = e.getTargetException();
- throw t instanceof RuntimeException ? (RuntimeException)t : new RuntimeException(t);
- }
- }
-
- public Expression innerConvert(ArithmeticExpression exp) {
- return new ArithmeticExpression(convert(exp.getLeftHandSide()),
- exp.getOperator(),
- convert(exp.getRightHandSide()));
- }
-
- public Expression innerConvert(CatExpression exp) {
- List<Expression> lst = new LinkedList<>();
- for (Expression innerExp : exp) {
- Expression next = convert(innerExp);
- if (next != null) {
- lst.add(next);
- }
- }
- return new CatExpression(lst);
- }
-
- public Expression innerConvert(ChoiceExpression exp) {
- var convertedInnerExpressions = exp.asList().stream().map(inner -> convert(inner)).toList();
- return new ChoiceExpression(convertedInnerExpressions);
- }
-
- public Expression innerConvert(ForEachExpression exp) {
- return new ForEachExpression(convert(exp.getInnerExpression()));
- }
-
- public Expression innerConvert(GuardExpression exp) {
- return new GuardExpression(convert(exp.getInnerExpression()));
- }
-
- public Expression innerConvert(IfThenExpression exp) {
- return new IfThenExpression(branch().convert(exp.getLeftHandSide()),
- exp.getComparator(),
- branch().convert(exp.getRightHandSide()),
- branch().convert(exp.getIfTrueExpression()),
- branch().convert(exp.getIfFalseExpression()));
- }
-
- public Expression innerConvert(ParenthesisExpression exp) {
- return new ParenthesisExpression(convert(exp.getInnerExpression()));
- }
-
- public Expression innerConvert(ScriptExpression exp) {
- List<StatementExpression> lst = new LinkedList<>();
- for (Expression innerExp : exp) {
- StatementExpression next = (StatementExpression)branch().convert(innerExp);
- if (next != null) {
- lst.add(next);
- }
- }
- return new ScriptExpression(lst);
- }
-
- public Expression innerConvert(SelectInputExpression exp) {
- List<Pair<String, Expression>> cases = new LinkedList<>();
- for (Pair<String, Expression> pair : exp.getCases()) {
- cases.add(new Pair<>(pair.getFirst(), branch().convert(pair.getSecond())));
- }
- return new SelectInputExpression(cases);
- }
-
- public Expression innerConvert(StatementExpression exp) {
- List<Expression> lst = new LinkedList<>();
- for (Expression innerExp : exp) {
- Expression next = convert(innerExp);
- if (next != null) {
- lst.add(next);
- }
- }
- return new StatementExpression(lst);
- }
-
- public Expression innerConvert(SwitchExpression exp) {
- Map<String, Expression> cases = new HashMap<>();
- for (Map.Entry<String, Expression> entry : exp.getCases().entrySet()) {
- Expression next = branch().convert(entry.getValue());
- if (next != null) {
- cases.put(entry.getKey(), next);
- }
- }
- return new SwitchExpression(cases, branch().convert(exp.getDefaultExpression()));
+ public final Expression convert(Expression expression) {
+ if (expression == null) return null;
+ if (shouldConvert(expression))
+ return doConvert(expression);
+ else
+ return expression.convertChildren(this);
}
- protected ExpressionConverter branch() {
+ public ExpressionConverter branch() {
return this;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ValueTransformProvider.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ValueTransformProvider.java
index bbd8c5ebcb8..623f940b06b 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ValueTransformProvider.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/ValueTransformProvider.java
@@ -20,7 +20,7 @@ public abstract class ValueTransformProvider extends ExpressionConverter {
}
@Override
- protected final ExpressionConverter branch() {
+ public final ExpressionConverter branch() {
return clone();
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
index e4bc2dae965..b7ee444975f 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ArithmeticExpression.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
import com.yahoo.document.NumericDataType;
import com.yahoo.document.datatypes.*;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -55,6 +56,12 @@ public final class ArithmeticExpression extends CompositeExpression {
this.rhs = rhs;
}
+ @Override
+ public ArithmeticExpression convertChildren(ExpressionConverter converter) {
+ // TODO: branch()?
+ return new ArithmeticExpression(converter.convert(lhs), op, converter.convert(rhs));
+ }
+
public Expression getLeftHandSide() {
return lhs;
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
index 4c14c633fbf..564ab015e10 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CatExpression.java
@@ -9,6 +9,7 @@ import com.yahoo.document.datatypes.Array;
import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.document.datatypes.WeightedSet;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import java.util.*;
@@ -26,6 +27,11 @@ public final class CatExpression extends ExpressionList<Expression> {
}
@Override
+ public CatExpression convertChildren(ExpressionConverter converter) {
+ return new CatExpression(convertChildList(converter));
+ }
+
+ @Override
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
DataType inputType = input != null ? input.getDataType() : null;
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
index 4f83cbfdd8c..5dbb9292a9d 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceExpression.java
@@ -3,6 +3,7 @@ package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import java.util.Arrays;
import java.util.Collection;
@@ -32,6 +33,11 @@ public class ChoiceExpression extends ExpressionList<Expression> {
}
@Override
+ public ChoiceExpression convertChildren(ExpressionConverter converter) {
+ return new ChoiceExpression(asList().stream().map(choice -> converter.branch().convert(choice)).toList());
+ }
+
+ @Override
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
for (Expression expression : this) {
@@ -44,9 +50,9 @@ public class ChoiceExpression extends ExpressionList<Expression> {
@Override
protected void doVerify(VerificationContext context) {
DataType input = context.getValueType();
+ context.setValueType(input);
for (Expression exp : this)
context.setValueType(input).execute(exp);
- context.setValueType(input);
}
private static DataType resolveInputType(Collection<? extends Expression> list) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpression.java
index 27e5524f4ad..8c00aad6bb0 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/CompositeExpression.java
@@ -2,12 +2,16 @@
package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
/**
* @author Simon Thoresen Hult
*/
public abstract class CompositeExpression extends Expression {
+ @Override
+ public abstract CompositeExpression convertChildren(ExpressionConverter converter);
+
protected CompositeExpression(DataType inputType) {
super(inputType);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
index bf8201ee7ee..f498b871096 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/Expression.java
@@ -35,6 +35,12 @@ public abstract class Expression extends Selectable {
this.inputType = inputType;
}
+ /**
+ * Returns an expression where the children of this has been converted using the given converter.
+ * This default implementation returns this as it has no children.
+ */
+ public Expression convertChildren(ExpressionConverter converter) { return this; }
+
/** Sets the document type and field the statement this expression is part of will write to */
public void setStatementOutput(DocumentType documentType, Field field) {}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
index e2ff1de7126..57de66f80a0 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ExpressionList.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -11,6 +12,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
+import java.util.Objects;
/**
* @author Simon Thoresen Hult
@@ -26,6 +28,10 @@ public abstract class ExpressionList<T extends Expression> extends CompositeExpr
}
}
+ protected List<Expression> convertChildList(ExpressionConverter converter) {
+ return asList().stream().map(converter::convert).filter(Objects::nonNull).toList();
+ }
+
@Override
public void setStatementOutput(DocumentType documentType, Field field) {
for (Expression expression : expressions)
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
index 0f3a445bcb9..3053a391823 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ForEachExpression.java
@@ -6,6 +6,7 @@ import com.yahoo.document.datatypes.Array;
import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.Struct;
import com.yahoo.document.datatypes.WeightedSet;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.indexinglanguage.FieldValueConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -27,6 +28,11 @@ public final class ForEachExpression extends CompositeExpression {
}
@Override
+ public ForEachExpression convertChildren(ExpressionConverter converter) {
+ return new ForEachExpression(converter.convert(exp));
+ }
+
+ @Override
public void setStatementOutput(DocumentType documentType, Field field) {
exp.setStatementOutput(documentType, field);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GuardExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GuardExpression.java
index da7cfcdcaee..38a05c3056c 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GuardExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/GuardExpression.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.indexinglanguage.ExpressionVisitor;
import com.yahoo.vespa.indexinglanguage.UpdateAdapter;
import com.yahoo.vespa.objects.ObjectOperation;
@@ -28,6 +29,11 @@ public final class GuardExpression extends CompositeExpression {
}
@Override
+ public GuardExpression convertChildren(ExpressionConverter converter) {
+ return new GuardExpression(converter.convert(exp));
+ }
+
+ @Override
public void setStatementOutput(DocumentType documentType, Field field) {
exp.setStatementOutput(documentType, field);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
index 8a29c8e8645..f05795aa234 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/IfThenExpression.java
@@ -6,6 +6,7 @@ import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.NumericFieldValue;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -57,6 +58,15 @@ public final class IfThenExpression extends CompositeExpression {
}
@Override
+ public IfThenExpression convertChildren(ExpressionConverter converter) {
+ return new IfThenExpression(converter.branch().convert(lhs),
+ cmp,
+ converter.branch().convert(rhs),
+ converter.branch().convert(ifTrue),
+ converter.branch().convert(ifFalse));
+ }
+
+ @Override
public void setStatementOutput(DocumentType documentType, Field field) {
lhs.setStatementOutput(documentType, field);
rhs.setStatementOutput(documentType, field);
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisExpression.java
index 60b059f3ef1..6e476f5f7e4 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ParenthesisExpression.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -24,6 +25,11 @@ public class ParenthesisExpression extends CompositeExpression {
}
@Override
+ public ParenthesisExpression convertChildren(ExpressionConverter converter) {
+ return new ParenthesisExpression(converter.convert(innerExp));
+ }
+
+ @Override
public void setStatementOutput(DocumentType documentType, Field field) {
innerExp.setStatementOutput(documentType, field);
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
index f0c37960a99..1a640c9924e 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/ScriptExpression.java
@@ -6,6 +6,7 @@ import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.language.Linguistics;
import com.yahoo.language.process.Embedder;
import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.indexinglanguage.ScriptParser;
import com.yahoo.vespa.indexinglanguage.ScriptParserContext;
import com.yahoo.vespa.indexinglanguage.parser.IndexingInput;
@@ -17,6 +18,7 @@ import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
/**
* @author Simon Thoresen Hult
@@ -36,6 +38,14 @@ public final class ScriptExpression extends ExpressionList<StatementExpression>
}
@Override
+ public ScriptExpression convertChildren(ExpressionConverter converter) {
+ return new ScriptExpression(asList().stream()
+ .map(child -> (StatementExpression)converter.branch().convert(child))
+ .filter(Objects::nonNull)
+ .toList());
+ }
+
+ @Override
protected void doExecute(ExecutionContext context) {
FieldValue input = context.getValue();
for (StatementExpression statement : this) {
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
index 212b60525f9..bb8111f358e 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SelectInputExpression.java
@@ -6,10 +6,12 @@ import com.yahoo.document.DataType;
import com.yahoo.document.DocumentType;
import com.yahoo.document.Field;
import com.yahoo.document.datatypes.FieldValue;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
import java.util.Arrays;
+import java.util.Collection;
import java.util.Collections;
import java.util.List;
@@ -32,6 +34,13 @@ public final class SelectInputExpression extends CompositeExpression {
}
@Override
+ public SelectInputExpression convertChildren(ExpressionConverter converter) {
+ return new SelectInputExpression(cases.stream()
+ .map(c -> new Pair<>(c.getFirst(), converter.branch().convert(c.getSecond())))
+ .toList());
+ }
+
+ @Override
public void setStatementOutput(DocumentType documentType, Field field) {
for (var casePair : cases)
casePair.getSecond().setStatementOutput(documentType, field);
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
index 8516ddb883d..75f206ef47d 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/StatementExpression.java
@@ -5,6 +5,7 @@ import com.yahoo.document.DataType;
import com.yahoo.language.Linguistics;
import com.yahoo.language.process.Embedder;
import com.yahoo.language.simple.SimpleLinguistics;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.indexinglanguage.ScriptParser;
import com.yahoo.vespa.indexinglanguage.ScriptParserContext;
import com.yahoo.vespa.indexinglanguage.parser.IndexingInput;
@@ -15,6 +16,7 @@ import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
+import java.util.Objects;
import java.util.stream.Collectors;
/**
@@ -45,6 +47,14 @@ public final class StatementExpression extends ExpressionList<Expression> {
public List<String> getInputFields() { return inputFields; }
@Override
+ public StatementExpression convertChildren(ExpressionConverter converter) {
+ return new StatementExpression(asList().stream()
+ .map(child -> converter.convert(child))
+ .filter(Objects::nonNull)
+ .toList());
+ }
+
+ @Override
protected void doExecute(ExecutionContext context) {
for (Expression expression : this) {
context.execute(expression);
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
index 86913d8c1ba..c7cf7066483 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/SwitchExpression.java
@@ -7,6 +7,7 @@ import com.yahoo.document.Field;
import com.yahoo.document.datatypes.FieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.text.StringUtilities;
+import com.yahoo.vespa.indexinglanguage.ExpressionConverter;
import com.yahoo.vespa.objects.ObjectOperation;
import com.yahoo.vespa.objects.ObjectPredicate;
@@ -32,6 +33,17 @@ public final class SwitchExpression extends CompositeExpression {
this.cases.putAll(cases);
}
+ @Override
+ public SwitchExpression convertChildren(ExpressionConverter converter) {
+ var convertedCases = new LinkedHashMap<String, Expression>();
+ for (var entry : cases.entrySet()) {
+ var converted = converter.branch().convert(entry.getValue());
+ if (converted != null)
+ convertedCases.put(entry.getKey(), converted);
+ }
+ return new SwitchExpression(convertedCases, converter.branch().convert(defaultExp));
+ }
+
public boolean isEmpty() {
return defaultExp == null && cases.isEmpty();
}
diff --git a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/VerificationContext.java b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/VerificationContext.java
index c667a0019c2..fb1338b8b65 100644
--- a/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/VerificationContext.java
+++ b/indexinglanguage/src/main/java/com/yahoo/vespa/indexinglanguage/expressions/VerificationContext.java
@@ -11,7 +11,7 @@ import java.util.Map;
*/
public class VerificationContext implements FieldTypeAdapter, Cloneable {
- private final Map<String, DataType> variables = new HashMap<String, DataType>();
+ private final Map<String, DataType> variables = new HashMap<>();
private final FieldTypeAdapter adapter;
private DataType value;
private String outputField;
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
index f1e1be0ae41..8aeaa084e1b 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/ExpressionConverterTestCase.java
@@ -2,7 +2,6 @@
package com.yahoo.vespa.indexinglanguage;
import com.yahoo.collections.Pair;
-import com.yahoo.document.DataType;
import com.yahoo.document.datatypes.IntegerFieldValue;
import com.yahoo.language.simple.SimpleLinguistics;
import com.yahoo.vespa.indexinglanguage.expressions.ArithmeticExpression;
@@ -11,9 +10,7 @@ import com.yahoo.vespa.indexinglanguage.expressions.Base64DecodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.Base64EncodeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.CatExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ClearStateExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.CompositeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.EchoExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.ExecutionContext;
import com.yahoo.vespa.indexinglanguage.expressions.Expression;
import com.yahoo.vespa.indexinglanguage.expressions.ForEachExpression;
import com.yahoo.vespa.indexinglanguage.expressions.GetFieldExpression;
@@ -54,7 +51,6 @@ import com.yahoo.vespa.indexinglanguage.expressions.ToStringExpression;
import com.yahoo.vespa.indexinglanguage.expressions.ToWsetExpression;
import com.yahoo.vespa.indexinglanguage.expressions.TokenizeExpression;
import com.yahoo.vespa.indexinglanguage.expressions.TrimExpression;
-import com.yahoo.vespa.indexinglanguage.expressions.VerificationContext;
import com.yahoo.vespa.indexinglanguage.expressions.ZCurveExpression;
import com.yahoo.vespa.indexinglanguage.linguistics.AnnotatorConfig;
import org.junit.Test;
@@ -73,7 +69,6 @@ import static org.junit.Assert.fail;
*/
public class ExpressionConverterTestCase {
- @SuppressWarnings("unchecked")
@Test
public void requireThatAllExpressionTypesCanBeTraversed() {
assertConvertable(new ArithmeticExpression(new InputExpression("foo"), ArithmeticExpression.Operator.ADD,
@@ -167,16 +162,6 @@ public class ExpressionConverterTestCase {
}
@Test
- public void requireThatUnknownCompositeThrows() {
- try {
- new MyTraverser().convert(new MyComposite());
- fail();
- } catch (UnsupportedOperationException e) {
- assertEquals(NoSuchMethodException.class, e.getCause().getClass());
- }
- }
-
- @Test
public void requireThatConversionExceptionCanBeThrown() {
final RuntimeException expectedCause = new RuntimeException();
try {
@@ -254,24 +239,4 @@ public class ExpressionConverterTestCase {
}
}
- private static class MyComposite extends CompositeExpression {
-
- MyComposite() {
- super(null);
- }
- @Override
- protected void doExecute(ExecutionContext context) {
-
- }
-
- @Override
- protected void doVerify(VerificationContext context) {
-
- }
-
- @Override
- public DataType createdOutputType() {
- return null;
- }
- }
}
diff --git a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceTestCase.java b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceTestCase.java
index 7ece841e9b7..e6d5c550e93 100644
--- a/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceTestCase.java
+++ b/indexinglanguage/src/test/java/com/yahoo/vespa/indexinglanguage/expressions/ChoiceTestCase.java
@@ -2,7 +2,9 @@
package com.yahoo.vespa.indexinglanguage.expressions;
import com.yahoo.document.DataType;
+import com.yahoo.document.Document;
import com.yahoo.document.Field;
+import com.yahoo.document.datatypes.LongFieldValue;
import com.yahoo.document.datatypes.StringFieldValue;
import com.yahoo.language.Linguistics;
import com.yahoo.language.process.Embedder;
@@ -44,6 +46,7 @@ public class ChoiceTestCase {
var adapter = new SimpleTestAdapter(new Field("foo", DataType.STRING), new Field("bar", DataType.STRING));
adapter.setValue("foo", new StringFieldValue("foo1"));
adapter.setValue("bar", new StringFieldValue("bar1"));
+ choice.verify(adapter);
ExecutionContext context = new ExecutionContext(adapter);
choice.execute(context);
assertEquals("foo1", context.getValue().getWrappedValue());
@@ -51,6 +54,28 @@ public class ChoiceTestCase {
}
@Test
+ public void testChoiceWithConstant() throws ParseException {
+ var choice = parse("input timestamp || 99999999L | attribute timestamp");
+
+ { // value is set
+ var adapter = new SimpleTestAdapter(new Field("timestamp", DataType.LONG));
+ choice.verify(adapter);
+ adapter.setValue("timestamp", new LongFieldValue(34));
+ ExecutionContext context = new ExecutionContext(adapter);
+ choice.execute(context);
+ assertEquals(34L, context.getValue().getWrappedValue());
+ }
+
+ { // fallback to default
+ var adapter = new SimpleTestAdapter(new Field("timestamp", DataType.LONG));
+ choice.verify(adapter);
+ ExecutionContext context = new ExecutionContext(adapter);
+ choice.execute(context);
+ assertEquals(99999999L, context.getValue().getWrappedValue());
+ }
+ }
+
+ @Test
public void testIllegalChoiceExpression() throws ParseException {
try {
parse("input (foo || 99999999) | attribute");
diff --git a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesIT.java b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesIT.java
index e9aba0893f9..a2aade05059 100644
--- a/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesIT.java
+++ b/jdisc_core/src/test/java/com/yahoo/jdisc/core/ExportPackagesIT.java
@@ -62,8 +62,8 @@ public class ExportPackagesIT {
String expectedValue = expectedProperties.getProperty(ExportPackages.EXPORT_PACKAGES);
assertNotNull(expectedValue, "Missing exportPackages property in file.");
- Set<String> actualPackages = getPackages(actualValue);
- Set<String> expectedPackages = getPackages(expectedValue);
+ Set<String> actualPackages = removeNewPackageOnJava20(removeJavaVersion(getPackages(actualValue)));
+ Set<String> expectedPackages = removeNewPackageOnJava20(removeJavaVersion(getPackages(expectedValue)));
if (!actualPackages.equals(expectedPackages)) {
StringBuilder message = getDiff(actualPackages, expectedPackages);
message.append("\n\nIf this test fails due to an intentional change in exported packages, run the following command:\n")
@@ -73,6 +73,17 @@ public class ExportPackagesIT {
}
}
+ private static Set<String> removeJavaVersion(Set<String> packages) {
+ return packages.stream().map(p -> p.replaceAll(".JavaSE_\\d+", "")).collect(Collectors.toSet());
+ }
+
+ private static Set<String> removeNewPackageOnJava20(Set<String> packages) {
+ return packages.stream()
+ .filter(p -> ! p.contains("java.lang.foreign"))
+ .filter(p -> ! p.contains("com.sun.jna"))
+ .collect(Collectors.toSet());
+ }
+
private static StringBuilder getDiff(Set<String> actual, Set<String> expected) {
StringBuilder sb = new StringBuilder();
Set<String> onlyInActual = onlyInSet1(actual, expected);
diff --git a/jdisc_core/src/test/resources/exportPackages.properties b/jdisc_core/src/test/resources/exportPackages.properties
index b49f91842e3..8af15fe591d 100644
--- a/jdisc_core/src/test/resources/exportPackages.properties
+++ b/jdisc_core/src/test/resources/exportPackages.properties
@@ -1,3 +1,3 @@
#generated by com.yahoo.jdisc.core.ExportPackages
-#Wed Jul 20 02:55:26 CEST 2022
-exportPackages=org.osgi.framework; version\="1.10.0", org.osgi.framework.connect; version\="1.0.0", org.osgi.framework.dto; uses\:\="org.osgi.dto"; version\="1.8.0", org.osgi.framework.hooks.bundle; uses\:\="org.osgi.framework"; version\="1.1.0", org.osgi.framework.hooks.resolver; uses\:\="org.osgi.framework.wiring"; version\="1.0.0", org.osgi.framework.hooks.service; uses\:\="org.osgi.framework"; version\="1.1.0", org.osgi.framework.hooks.weaving; uses\:\="org.osgi.framework.wiring"; version\="1.1.0", org.osgi.framework.launch; uses\:\="org.osgi.framework"; version\="1.2.0", org.osgi.framework.namespace; uses\:\="org.osgi.resource"; version\="1.2.0", org.osgi.framework.startlevel; uses\:\="org.osgi.framework"; version\="1.0.0", org.osgi.framework.startlevel.dto; uses\:\="org.osgi.dto"; version\="1.0.0", org.osgi.framework.wiring; uses\:\="org.osgi.framework,org.osgi.resource"; version\="1.2.0", org.osgi.framework.wiring.dto; uses\:\="org.osgi.dto,org.osgi.resource.dto"; version\="1.3.0", org.osgi.resource; version\="1.0.1", org.osgi.resource.dto; uses\:\="org.osgi.dto"; version\="1.0.1", org.osgi.service.packageadmin; uses\:\="org.osgi.framework"; version\="1.2.1", org.osgi.service.startlevel; uses\:\="org.osgi.framework"; version\="1.1.1", org.osgi.service.url; version\="1.0.1", org.osgi.service.resolver; uses\:\="org.osgi.resource"; version\="1.1.1", org.osgi.util.tracker; uses\:\="org.osgi.framework"; version\="1.5.3", org.osgi.dto; version\="1.1.1", org.osgi.service.condition; version\="1.0.0", javax.security.auth.callback; version\="0.0.0.JavaSE_017", javax.security.auth.login; version\="0.0.0.JavaSE_017", javax.net.ssl; version\="0.0.0.JavaSE_017", java.lang.constant; version\="0.0.0.JavaSE_017", java.security.interfaces; version\="0.0.0.JavaSE_017", java.text.spi; version\="0.0.0.JavaSE_017", java.nio.channels.spi; version\="0.0.0.JavaSE_017", java.math; version\="0.0.0.JavaSE_017", java.nio.file; version\="0.0.0.JavaSE_017", java.util.concurrent.atomic; version\="0.0.0.JavaSE_017", java.security.cert; version\="0.0.0.JavaSE_017", java.security.spec; version\="0.0.0.JavaSE_017", java.nio.channels; version\="0.0.0.JavaSE_017", java.time.chrono; version\="0.0.0.JavaSE_017", javax.crypto; version\="0.0.0.JavaSE_017", java.time.zone; version\="0.0.0.JavaSE_017", java.nio.charset; version\="0.0.0.JavaSE_017", java.io; version\="0.0.0.JavaSE_017", java.util.spi; version\="0.0.0.JavaSE_017", java.net; version\="0.0.0.JavaSE_017", javax.security.cert; version\="0.0.0.JavaSE_017", java.lang.annotation; version\="0.0.0.JavaSE_017", javax.security.auth.spi; version\="0.0.0.JavaSE_017", java.util.concurrent; version\="0.0.0.JavaSE_017", java.nio.charset.spi; version\="0.0.0.JavaSE_017", javax.crypto.interfaces; version\="0.0.0.JavaSE_017", java.util; version\="0.0.0.JavaSE_017", java.security; version\="0.0.0.JavaSE_017", java.nio.file.spi; version\="0.0.0.JavaSE_017", java.nio; version\="0.0.0.JavaSE_017", java.util.jar; version\="0.0.0.JavaSE_017", javax.security.auth; version\="0.0.0.JavaSE_017", java.lang.ref; version\="0.0.0.JavaSE_017", java.util.regex; version\="0.0.0.JavaSE_017", java.net.spi; version\="0.0.0.JavaSE_017", java.lang.module; version\="0.0.0.JavaSE_017", java.lang.invoke; version\="0.0.0.JavaSE_017", java.time.format; version\="0.0.0.JavaSE_017", java.util.concurrent.locks; version\="0.0.0.JavaSE_017", java.time.temporal; version\="0.0.0.JavaSE_017", java.util.zip; version\="0.0.0.JavaSE_017", java.nio.file.attribute; version\="0.0.0.JavaSE_017", java.util.random; version\="0.0.0.JavaSE_017", java.text; version\="0.0.0.JavaSE_017", javax.crypto.spec; version\="0.0.0.JavaSE_017", java.util.stream; version\="0.0.0.JavaSE_017", java.time; version\="0.0.0.JavaSE_017", java.lang; version\="0.0.0.JavaSE_017", java.lang.runtime; version\="0.0.0.JavaSE_017", java.util.function; version\="0.0.0.JavaSE_017", javax.net; version\="0.0.0.JavaSE_017", javax.security.auth.x500; version\="0.0.0.JavaSE_017", java.lang.reflect; version\="0.0.0.JavaSE_017", javax.lang.model; version\="0.0.0.JavaSE_017", javax.annotation.processing; version\="0.0.0.JavaSE_017", javax.lang.model.element; version\="0.0.0.JavaSE_017", javax.tools; version\="0.0.0.JavaSE_017", javax.lang.model.type; version\="0.0.0.JavaSE_017", javax.lang.model.util; version\="0.0.0.JavaSE_017", java.awt.datatransfer; version\="0.0.0.JavaSE_017", java.awt.desktop; version\="0.0.0.JavaSE_017", javax.swing.plaf.synth; version\="0.0.0.JavaSE_017", java.beans; version\="0.0.0.JavaSE_017", javax.swing.text.html.parser; version\="0.0.0.JavaSE_017", javax.swing.text.rtf; version\="0.0.0.JavaSE_017", java.awt.font; version\="0.0.0.JavaSE_017", javax.imageio; version\="0.0.0.JavaSE_017", java.awt.im.spi; version\="0.0.0.JavaSE_017", java.applet; version\="0.0.0.JavaSE_017", javax.sound.midi; version\="0.0.0.JavaSE_017", java.awt.dnd; version\="0.0.0.JavaSE_017", javax.swing.text; version\="0.0.0.JavaSE_017", javax.swing.plaf.basic; version\="0.0.0.JavaSE_017", javax.swing.undo; version\="0.0.0.JavaSE_017", javax.swing.plaf; version\="0.0.0.JavaSE_017", javax.swing.filechooser; version\="0.0.0.JavaSE_017", javax.imageio.event; version\="0.0.0.JavaSE_017", javax.sound.sampled; version\="0.0.0.JavaSE_017", javax.print.attribute; version\="0.0.0.JavaSE_017", javax.print; version\="0.0.0.JavaSE_017", javax.swing.plaf.nimbus; version\="0.0.0.JavaSE_017", javax.accessibility; version\="0.0.0.JavaSE_017", java.awt.event; version\="0.0.0.JavaSE_017", javax.swing.text.html; version\="0.0.0.JavaSE_017", javax.imageio.spi; version\="0.0.0.JavaSE_017", javax.swing.border; version\="0.0.0.JavaSE_017", javax.sound.sampled.spi; version\="0.0.0.JavaSE_017", javax.imageio.plugins.bmp; version\="0.0.0.JavaSE_017", java.awt.color; version\="0.0.0.JavaSE_017", java.awt.geom; version\="0.0.0.JavaSE_017", javax.imageio.plugins.jpeg; version\="0.0.0.JavaSE_017", javax.swing.tree; version\="0.0.0.JavaSE_017", javax.imageio.plugins.tiff; version\="0.0.0.JavaSE_017", java.awt.print; version\="0.0.0.JavaSE_017", java.awt.image; version\="0.0.0.JavaSE_017", javax.imageio.metadata; version\="0.0.0.JavaSE_017", javax.swing.table; version\="0.0.0.JavaSE_017", javax.sound.midi.spi; version\="0.0.0.JavaSE_017", javax.print.attribute.standard; version\="0.0.0.JavaSE_017", javax.swing.colorchooser; version\="0.0.0.JavaSE_017", javax.swing; version\="0.0.0.JavaSE_017", java.awt.image.renderable; version\="0.0.0.JavaSE_017", javax.swing.plaf.multi; version\="0.0.0.JavaSE_017", java.awt.im; version\="0.0.0.JavaSE_017", javax.print.event; version\="0.0.0.JavaSE_017", javax.swing.plaf.metal; version\="0.0.0.JavaSE_017", java.beans.beancontext; version\="0.0.0.JavaSE_017", java.awt; version\="0.0.0.JavaSE_017", javax.imageio.stream; version\="0.0.0.JavaSE_017", javax.swing.event; version\="0.0.0.JavaSE_017", java.lang.instrument; version\="0.0.0.JavaSE_017", java.util.logging; version\="0.0.0.JavaSE_017", javax.management.timer; version\="0.0.0.JavaSE_017", javax.management; version\="0.0.0.JavaSE_017", javax.management.relation; version\="0.0.0.JavaSE_017", javax.management.loading; version\="0.0.0.JavaSE_017", javax.management.openmbean; version\="0.0.0.JavaSE_017", java.lang.management; version\="0.0.0.JavaSE_017", javax.management.remote; version\="0.0.0.JavaSE_017", javax.management.monitor; version\="0.0.0.JavaSE_017", javax.management.modelmbean; version\="0.0.0.JavaSE_017", javax.management.remote.rmi; version\="0.0.0.JavaSE_017", javax.naming.event; version\="0.0.0.JavaSE_017", javax.naming.ldap.spi; version\="0.0.0.JavaSE_017", javax.naming; version\="0.0.0.JavaSE_017", javax.naming.spi; version\="0.0.0.JavaSE_017", javax.naming.ldap; version\="0.0.0.JavaSE_017", javax.naming.directory; version\="0.0.0.JavaSE_017", java.net.http; version\="0.0.0.JavaSE_017", java.util.prefs; version\="0.0.0.JavaSE_017", java.rmi.registry; version\="0.0.0.JavaSE_017", javax.rmi.ssl; version\="0.0.0.JavaSE_017", java.rmi.dgc; version\="0.0.0.JavaSE_017", java.rmi; version\="0.0.0.JavaSE_017", java.rmi.server; version\="0.0.0.JavaSE_017", javax.script; version\="0.0.0.JavaSE_017", org.ietf.jgss; version\="0.0.0.JavaSE_017", javax.security.auth.kerberos; version\="0.0.0.JavaSE_017", javax.security.sasl; version\="0.0.0.JavaSE_017", javax.smartcardio; version\="0.0.0.JavaSE_017", java.sql; version\="0.0.0.JavaSE_017", javax.sql; version\="0.0.0.JavaSE_017", javax.sql.rowset.spi; version\="0.0.0.JavaSE_017", javax.sql.rowset.serial; version\="0.0.0.JavaSE_017", javax.sql.rowset; version\="0.0.0.JavaSE_017", javax.transaction.xa; version\="0.0.0.JavaSE_017", javax.xml; version\="0.0.0.JavaSE_017", javax.xml.transform.sax; version\="0.0.0.JavaSE_017", javax.xml.datatype; version\="0.0.0.JavaSE_017", javax.xml.catalog; version\="0.0.0.JavaSE_017", org.w3c.dom.traversal; version\="0.0.0.JavaSE_017", javax.xml.stream.events; version\="0.0.0.JavaSE_017", javax.xml.stream; version\="0.0.0.JavaSE_017", org.xml.sax; version\="0.0.0.JavaSE_017", javax.xml.transform; version\="0.0.0.JavaSE_017", javax.xml.xpath; version\="0.0.0.JavaSE_017", org.w3c.dom.events; version\="0.0.0.JavaSE_017", org.w3c.dom.ranges; version\="0.0.0.JavaSE_017", org.w3c.dom.ls; version\="0.0.0.JavaSE_017", javax.xml.stream.util; version\="0.0.0.JavaSE_017", javax.xml.namespace; version\="0.0.0.JavaSE_017", javax.xml.transform.stax; version\="0.0.0.JavaSE_017", org.xml.sax.helpers; version\="0.0.0.JavaSE_017", org.w3c.dom.views; version\="0.0.0.JavaSE_017", org.w3c.dom.bootstrap; version\="0.0.0.JavaSE_017", org.w3c.dom; version\="0.0.0.JavaSE_017", javax.xml.transform.stream; version\="0.0.0.JavaSE_017", javax.xml.transform.dom; version\="0.0.0.JavaSE_017", javax.xml.validation; version\="0.0.0.JavaSE_017", javax.xml.parsers; version\="0.0.0.JavaSE_017", org.xml.sax.ext; version\="0.0.0.JavaSE_017", javax.xml.crypto.dsig.spec; version\="0.0.0.JavaSE_017", javax.xml.crypto.dsig.keyinfo; version\="0.0.0.JavaSE_017", javax.xml.crypto.dsig.dom; version\="0.0.0.JavaSE_017", javax.xml.crypto.dom; version\="0.0.0.JavaSE_017", javax.xml.crypto; version\="0.0.0.JavaSE_017", javax.xml.crypto.dsig; version\="0.0.0.JavaSE_017", com.sun.java.accessibility.util; version\="0.0.0.JavaSE_017", com.sun.tools.attach.spi; version\="0.0.0.JavaSE_017", com.sun.tools.attach; version\="0.0.0.JavaSE_017", com.sun.source.doctree; version\="0.0.0.JavaSE_017", com.sun.source.tree; version\="0.0.0.JavaSE_017", com.sun.source.util; version\="0.0.0.JavaSE_017", com.sun.tools.javac; version\="0.0.0.JavaSE_017", jdk.dynalink.beans; version\="0.0.0.JavaSE_017", jdk.dynalink.linker.support; version\="0.0.0.JavaSE_017", jdk.dynalink.support; version\="0.0.0.JavaSE_017", jdk.dynalink; version\="0.0.0.JavaSE_017", jdk.dynalink.linker; version\="0.0.0.JavaSE_017", com.sun.net.httpserver; version\="0.0.0.JavaSE_017", com.sun.net.httpserver.spi; version\="0.0.0.JavaSE_017", com.sun.jarsigner; version\="0.0.0.JavaSE_017", jdk.security.jarsigner; version\="0.0.0.JavaSE_017", jdk.javadoc.doclet; version\="0.0.0.JavaSE_017", com.sun.tools.jconsole; version\="0.0.0.JavaSE_017", com.sun.jdi.connect; version\="0.0.0.JavaSE_017", com.sun.jdi.event; version\="0.0.0.JavaSE_017", com.sun.jdi.connect.spi; version\="0.0.0.JavaSE_017", com.sun.jdi; version\="0.0.0.JavaSE_017", com.sun.jdi.request; version\="0.0.0.JavaSE_017", jdk.jfr.consumer; version\="0.0.0.JavaSE_017", jdk.jfr; version\="0.0.0.JavaSE_017", jdk.jshell; version\="0.0.0.JavaSE_017", jdk.jshell.execution; version\="0.0.0.JavaSE_017", jdk.jshell.spi; version\="0.0.0.JavaSE_017", jdk.jshell.tool; version\="0.0.0.JavaSE_017", netscape.javascript; version\="0.0.0.JavaSE_017", com.sun.management; version\="0.0.0.JavaSE_017", jdk.management.jfr; version\="0.0.0.JavaSE_017", jdk.net; version\="0.0.0.JavaSE_017", jdk.nio; version\="0.0.0.JavaSE_017", jdk.nio.mapmode; version\="0.0.0.JavaSE_017", com.sun.nio.sctp; version\="0.0.0.JavaSE_017", com.sun.security.auth.module; version\="0.0.0.JavaSE_017", com.sun.security.auth.login; version\="0.0.0.JavaSE_017", com.sun.security.auth; version\="0.0.0.JavaSE_017", com.sun.security.auth.callback; version\="0.0.0.JavaSE_017", com.sun.security.jgss; version\="0.0.0.JavaSE_017", sun.reflect; version\="0.0.0.JavaSE_017", sun.misc; version\="0.0.0.JavaSE_017", com.sun.nio.file; version\="0.0.0.JavaSE_017", jdk.swing.interop; version\="0.0.0.JavaSE_017", org.w3c.dom.stylesheets; version\="0.0.0.JavaSE_017", org.w3c.dom.html; version\="0.0.0.JavaSE_017", org.w3c.dom.xpath; version\="0.0.0.JavaSE_017", org.w3c.dom.css; version\="0.0.0.JavaSE_017", com.yahoo.jdisc, com.yahoo.jdisc.application, com.yahoo.jdisc.handler, com.yahoo.jdisc.service, com.yahoo.jdisc.statistics, com.yahoo.jdisc.refcount, javax.inject;version\=1.0.0, org.aopalliance.intercept, org.aopalliance.aop, com.google.common.annotations;version\="27.1.0",com.google.common.base;version\="27.1.0",com.google.common.cache;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.util.concurrent",com.google.common.collect;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.escape;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.eventbus;version\="27.1.0",com.google.common.graph;version\="27.1.0";uses\:\="com.google.common.collect",com.google.common.hash;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.html;version\="27.1.0";uses\:\="com.google.common.escape",com.google.common.io;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.graph,com.google.common.hash",com.google.common.math;version\="27.1.0",com.google.common.net;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.escape",com.google.common.primitives;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.reflect;version\="27.1.0";uses\:\="com.google.common.collect,com.google.common.io",com.google.common.util.concurrent;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.util.concurrent.internal",com.google.common.xml;version\="27.1.0";uses\:\="com.google.common.escape", com.google.inject;version\="1.4",com.google.inject.binder;version\="1.4",com.google.inject.matcher;version\="1.4",com.google.inject.multibindings;version\="1.4",com.google.inject.name;version\="1.4",com.google.inject.spi;version\="1.4",com.google.inject.util;version\="1.4", org.slf4j;version\=1.7.32, org.slf4j.spi;version\=1.7.32, org.slf4j.helpers;version\=1.7.32, org.slf4j.event;version\=1.7.32, org.slf4j.impl;version\=1.7.32, org.apache.commons.logging;version\=1.2, org.apache.commons.logging.impl;version\=1.2, org.apache.log4j;version\=1.2.17,org.apache.log4j.helpers;version\=1.2.17,org.apache.log4j.spi;version\=1.2.17,org.apache.log4j.xml;version\=1.2.17, com.yahoo.component.annotation;version\="1.0.0", com.yahoo.config;version\=1.0.0, com.yahoo.vespa.defaults;version\=1.0.0, ai.vespa.http;version\=1.0.0,ai.vespa.validation;version\=1.0.0,com.yahoo.binaryprefix;version\=1.0.0,com.yahoo.collections;version\=1.0.0,com.yahoo.compress;version\=1.0.0,com.yahoo.concurrent.classlock;version\=1.0.0,com.yahoo.concurrent.maintenance;version\=1.0.0,com.yahoo.concurrent;version\=1.0.0,com.yahoo.data.access.simple;version\=1.0.0,com.yahoo.data.access.slime;version\=1.0.0,com.yahoo.data.access;version\=1.0.0,com.yahoo.errorhandling;version\=1.0.0,com.yahoo.exception;version\=1.0.0,com.yahoo.geo;version\=1.0.0,com.yahoo.io.reader;version\=1.0.0,com.yahoo.io;version\=1.0.0,com.yahoo.javacc;version\=1.0.0,com.yahoo.lang;version\=1.0.0,com.yahoo.nativec;version\=1.0.0,com.yahoo.net;version\=1.0.0,com.yahoo.path;version\=1.0.0,com.yahoo.protect;version\=1.0.0,com.yahoo.reflection;version\=1.0.0,com.yahoo.slime;version\=1.0.0,com.yahoo.stream;version\=1.0.0,com.yahoo.system.execution;version\=1.0.0,com.yahoo.system;version\=1.0.0,com.yahoo.tensor.evaluation;version\=1.0.0,com.yahoo.tensor.functions;version\=1.0.0,com.yahoo.tensor.serialization;version\=1.0.0,com.yahoo.tensor;version\=1.0.0,com.yahoo.text.internal;version\=1.0.0,com.yahoo.text;version\=1.0.0,com.yahoo.time;version\=1.0.0,com.yahoo.transaction;version\=1.0.0,com.yahoo.vespa.objects;version\=1.0.0,com.yahoo.yolean.chain;version\=1.0.0,com.yahoo.yolean.concurrent;version\=1.0.0,com.yahoo.yolean.function;version\=1.0.0,com.yahoo.yolean.system;version\=1.0.0,com.yahoo.yolean.trace;version\=1.0.0,com.yahoo.yolean;version\=1.0.0, com.yahoo.log.event;version\=1.0.0,com.yahoo.log.impl;version\=1.0.0,com.yahoo.log;version\=1.0.0, javax.xml.bind;version\="2.3";uses\:\="javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.validation,org.w3c.dom,org.xml.sax",javax.xml.bind.annotation;version\="2.3";uses\:\="javax.xml.bind,javax.xml.parsers,javax.xml.transform,javax.xml.transform.dom,org.w3c.dom",javax.xml.bind.annotation.adapters;version\="2.3",javax.xml.bind.attachment;version\="2.3";uses\:\="javax.activation",javax.xml.bind.helpers;version\="2.3";uses\:\="javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.stream,javax.xml.transform,javax.xml.validation,org.w3c.dom,org.xml.sax",javax.xml.bind.util;version\="2.3";uses\:\="javax.xml.bind,javax.xml.transform.sax", com.sun.istack;version\="3.0.5";uses\:\="javax.activation,javax.xml.stream,org.xml.sax,org.xml.sax.helpers",com.sun.istack.localization;version\="3.0.5",com.sun.istack.logging;version\="3.0.5",com.sun.xml.bind;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.annotation;version\="2.3.0",com.sun.xml.bind.api;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.api.impl;version\="2.3.0",com.sun.xml.bind.marshaller;uses\:\="javax.xml.parsers,org.w3c.dom,org.xml.sax,org.xml.sax.helpers";version\="2.3.0",com.sun.xml.bind.unmarshaller;uses\:\="com.sun.xml.bind.v2.runtime.unmarshaller,javax.xml.bind,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.util;version\="2.3.0",com.sun.xml.bind.v2;version\="2.3.0",com.sun.xml.bind.v2.model.annotation;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.core;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.impl,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.runtime,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.namespace,javax.xml.transform";version\="2.3.0",com.sun.xml.bind.v2.model.impl;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.nav";version\="2.3.0",com.sun.xml.bind.v2.model.nav;uses\:\="com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.util;uses\:\="javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.runtime;uses\:\="com.sun.xml.bind.v2.model.annotation,javax.activation,javax.xml.bind,javax.xml.bind.annotation.adapters";version\="2.3.0",com.sun.xml.bind.v2.runtime.unmarshaller;uses\:\="javax.xml.bind,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.schemagen.episode;uses\:\="com.sun.xml.txw2,com.sun.xml.txw2.annotation";version\="2.3.0",com.sun.xml.bind.v2.util;uses\:\="javax.xml.parsers,javax.xml.transform,javax.xml.validation,javax.xml.xpath";version\="2.3.0",com.sun.xml.txw2;uses\:\="com.sun.xml.txw2.output,javax.xml.namespace";version\="2.3.0",com.sun.xml.txw2.annotation;version\="2.3.0",com.sun.xml.txw2.output;uses\:\="com.sun.xml.txw2,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.sax,javax.xml.transform.stream,org.w3c.dom,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers";version\="2.3.0", com.sun.xml.bind;uses\:\="com.sun.xml.bind.v2.runtime.reflect,javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.datatype,javax.xml.namespace,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.api;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,javax.xml.bind,javax.xml.bind.attachment,javax.xml.namespace,javax.xml.stream,javax.xml.transform,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.marshaller;version\="2.3.0",com.sun.xml.bind.unmarshaller;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.util;uses\:\="com.sun.xml.bind,javax.xml.bind.helpers,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.annotation,javax.xml.bind";version\="2.3.0",com.sun.xml.bind.v2.bytecode;version\="2.3.0",com.sun.xml.bind.v2.model.annotation;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.impl;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,javax.activation,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.model.runtime;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,javax.xml.bind,javax.xml.namespace,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime;uses\:\="com.sun.istack,com.sun.xml.bind.api,com.sun.xml.bind.marshaller,com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime.output,com.sun.xml.bind.v2.runtime.property,com.sun.xml.bind.v2.runtime.unmarshaller,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.bind.helpers,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.sax,javax.xml.validation,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.output;uses\:\="com.sun.xml.bind.marshaller,com.sun.xml.bind.v2.runtime,com.sun.xml.fastinfoset.stax,javax.xml.stream,org.jvnet.staxex,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.property;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,com.sun.xml.bind.v2.runtime.unmarshaller,com.sun.xml.bind.v2.util,javax.xml.namespace,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.reflect;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.unmarshaller,javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.reflect.opt;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.unmarshaller;uses\:\="com.sun.xml.bind,com.sun.xml.bind.api,com.sun.xml.bind.unmarshaller,com.sun.xml.bind.util,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.output,com.sun.xml.bind.v2.runtime.reflect,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.bind.helpers,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.sax,javax.xml.validation,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.schemagen;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.txw2.output,javax.xml.bind,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.schemagen.xmlschema;uses\:\="com.sun.xml.txw2,com.sun.xml.txw2.annotation,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.util;uses\:\="com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.unmarshaller,javax.activation,javax.xml.namespace,javax.xml.transform.stream,org.xml.sax";version\="2.3.0", javax.activation;uses\:\="com.sun.activation.registries";version\="1.2",com.sun.activation.viewers;uses\:\="javax.activation";version\="1.2.0",com.sun.activation.registries;version\="1.2.0"
+#Tue Apr 25 14:00:54 CEST 2023
+exportPackages=org.osgi.framework; version\="1.10.0", org.osgi.framework.connect; version\="1.0.0", org.osgi.framework.dto; uses\:\="org.osgi.dto"; version\="1.8.0", org.osgi.framework.hooks.bundle; uses\:\="org.osgi.framework"; version\="1.1.0", org.osgi.framework.hooks.resolver; uses\:\="org.osgi.framework.wiring"; version\="1.0.0", org.osgi.framework.hooks.service; uses\:\="org.osgi.framework"; version\="1.1.0", org.osgi.framework.hooks.weaving; uses\:\="org.osgi.framework.wiring"; version\="1.1.0", org.osgi.framework.launch; uses\:\="org.osgi.framework"; version\="1.2.0", org.osgi.framework.namespace; uses\:\="org.osgi.resource"; version\="1.2.0", org.osgi.framework.startlevel; uses\:\="org.osgi.framework"; version\="1.0.0", org.osgi.framework.startlevel.dto; uses\:\="org.osgi.dto"; version\="1.0.0", org.osgi.framework.wiring; uses\:\="org.osgi.framework,org.osgi.resource"; version\="1.2.0", org.osgi.framework.wiring.dto; uses\:\="org.osgi.dto,org.osgi.resource.dto"; version\="1.3.0", org.osgi.resource; version\="1.0.1", org.osgi.resource.dto; uses\:\="org.osgi.dto"; version\="1.0.1", org.osgi.service.packageadmin; uses\:\="org.osgi.framework"; version\="1.2.1", org.osgi.service.startlevel; uses\:\="org.osgi.framework"; version\="1.1.1", org.osgi.service.url; version\="1.0.1", org.osgi.service.resolver; uses\:\="org.osgi.resource"; version\="1.1.1", org.osgi.util.tracker; uses\:\="org.osgi.framework"; version\="1.5.3", org.osgi.dto; version\="1.1.1", org.osgi.service.condition; version\="1.0.0", javax.crypto.interfaces; version\="0.0.0.JavaSE_020", java.util; version\="0.0.0.JavaSE_020", java.security; version\="0.0.0.JavaSE_020", java.nio.file.spi; version\="0.0.0.JavaSE_020", java.nio; version\="0.0.0.JavaSE_020", java.util.jar; version\="0.0.0.JavaSE_020", javax.security.auth; version\="0.0.0.JavaSE_020", java.lang.ref; version\="0.0.0.JavaSE_020", java.util.regex; version\="0.0.0.JavaSE_020", java.net.spi; version\="0.0.0.JavaSE_020", java.lang.module; version\="0.0.0.JavaSE_020", java.lang.invoke; version\="0.0.0.JavaSE_020", java.time.format; version\="0.0.0.JavaSE_020", java.util.concurrent.locks; version\="0.0.0.JavaSE_020", java.time.temporal; version\="0.0.0.JavaSE_020", java.util.zip; version\="0.0.0.JavaSE_020", java.nio.file.attribute; version\="0.0.0.JavaSE_020", java.util.random; version\="0.0.0.JavaSE_020", java.text; version\="0.0.0.JavaSE_020", javax.crypto.spec; version\="0.0.0.JavaSE_020", java.util.stream; version\="0.0.0.JavaSE_020", java.time; version\="0.0.0.JavaSE_020", java.lang; version\="0.0.0.JavaSE_020", java.lang.runtime; version\="0.0.0.JavaSE_020", java.util.function; version\="0.0.0.JavaSE_020", javax.net; version\="0.0.0.JavaSE_020", javax.security.auth.x500; version\="0.0.0.JavaSE_020", java.lang.reflect; version\="0.0.0.JavaSE_020", javax.security.auth.callback; version\="0.0.0.JavaSE_020", javax.security.auth.login; version\="0.0.0.JavaSE_020", javax.net.ssl; version\="0.0.0.JavaSE_020", java.lang.constant; version\="0.0.0.JavaSE_020", java.security.interfaces; version\="0.0.0.JavaSE_020", java.text.spi; version\="0.0.0.JavaSE_020", java.nio.channels.spi; version\="0.0.0.JavaSE_020", java.math; version\="0.0.0.JavaSE_020", java.nio.file; version\="0.0.0.JavaSE_020", java.util.concurrent.atomic; version\="0.0.0.JavaSE_020", java.security.cert; version\="0.0.0.JavaSE_020", java.security.spec; version\="0.0.0.JavaSE_020", java.nio.channels; version\="0.0.0.JavaSE_020", java.time.chrono; version\="0.0.0.JavaSE_020", javax.crypto; version\="0.0.0.JavaSE_020", java.time.zone; version\="0.0.0.JavaSE_020", java.nio.charset; version\="0.0.0.JavaSE_020", java.lang.foreign; version\="0.0.0.JavaSE_020", java.io; version\="0.0.0.JavaSE_020", java.util.spi; version\="0.0.0.JavaSE_020", java.net; version\="0.0.0.JavaSE_020", javax.security.cert; version\="0.0.0.JavaSE_020", java.lang.annotation; version\="0.0.0.JavaSE_020", javax.security.auth.spi; version\="0.0.0.JavaSE_020", java.util.concurrent; version\="0.0.0.JavaSE_020", java.nio.charset.spi; version\="0.0.0.JavaSE_020", javax.tools; version\="0.0.0.JavaSE_020", javax.lang.model.type; version\="0.0.0.JavaSE_020", javax.lang.model.util; version\="0.0.0.JavaSE_020", javax.lang.model; version\="0.0.0.JavaSE_020", javax.annotation.processing; version\="0.0.0.JavaSE_020", javax.lang.model.element; version\="0.0.0.JavaSE_020", java.awt.datatransfer; version\="0.0.0.JavaSE_020", javax.accessibility; version\="0.0.0.JavaSE_020", java.awt.event; version\="0.0.0.JavaSE_020", javax.swing.text.html; version\="0.0.0.JavaSE_020", javax.imageio.spi; version\="0.0.0.JavaSE_020", javax.swing.border; version\="0.0.0.JavaSE_020", javax.sound.sampled.spi; version\="0.0.0.JavaSE_020", javax.imageio.plugins.bmp; version\="0.0.0.JavaSE_020", java.awt.color; version\="0.0.0.JavaSE_020", java.awt.geom; version\="0.0.0.JavaSE_020", javax.imageio.plugins.jpeg; version\="0.0.0.JavaSE_020", javax.swing.tree; version\="0.0.0.JavaSE_020", javax.imageio.plugins.tiff; version\="0.0.0.JavaSE_020", java.awt.print; version\="0.0.0.JavaSE_020", java.awt.image; version\="0.0.0.JavaSE_020", javax.imageio.metadata; version\="0.0.0.JavaSE_020", javax.swing.table; version\="0.0.0.JavaSE_020", javax.sound.midi.spi; version\="0.0.0.JavaSE_020", javax.print.attribute.standard; version\="0.0.0.JavaSE_020", javax.swing.colorchooser; version\="0.0.0.JavaSE_020", javax.swing; version\="0.0.0.JavaSE_020", java.awt.image.renderable; version\="0.0.0.JavaSE_020", javax.swing.plaf.multi; version\="0.0.0.JavaSE_020", java.awt.im; version\="0.0.0.JavaSE_020", javax.print.event; version\="0.0.0.JavaSE_020", javax.swing.plaf.metal; version\="0.0.0.JavaSE_020", java.beans.beancontext; version\="0.0.0.JavaSE_020", java.awt; version\="0.0.0.JavaSE_020", javax.imageio.stream; version\="0.0.0.JavaSE_020", javax.swing.event; version\="0.0.0.JavaSE_020", java.awt.desktop; version\="0.0.0.JavaSE_020", javax.swing.plaf.synth; version\="0.0.0.JavaSE_020", java.beans; version\="0.0.0.JavaSE_020", javax.swing.text.html.parser; version\="0.0.0.JavaSE_020", javax.swing.text.rtf; version\="0.0.0.JavaSE_020", java.awt.font; version\="0.0.0.JavaSE_020", javax.imageio; version\="0.0.0.JavaSE_020", java.awt.im.spi; version\="0.0.0.JavaSE_020", java.applet; version\="0.0.0.JavaSE_020", javax.sound.midi; version\="0.0.0.JavaSE_020", java.awt.dnd; version\="0.0.0.JavaSE_020", javax.swing.text; version\="0.0.0.JavaSE_020", javax.swing.plaf.basic; version\="0.0.0.JavaSE_020", javax.swing.undo; version\="0.0.0.JavaSE_020", javax.swing.plaf; version\="0.0.0.JavaSE_020", javax.swing.filechooser; version\="0.0.0.JavaSE_020", javax.imageio.event; version\="0.0.0.JavaSE_020", javax.sound.sampled; version\="0.0.0.JavaSE_020", javax.print.attribute; version\="0.0.0.JavaSE_020", javax.print; version\="0.0.0.JavaSE_020", javax.swing.plaf.nimbus; version\="0.0.0.JavaSE_020", java.lang.instrument; version\="0.0.0.JavaSE_020", java.util.logging; version\="0.0.0.JavaSE_020", javax.management.remote; version\="0.0.0.JavaSE_020", javax.management.monitor; version\="0.0.0.JavaSE_020", javax.management.modelmbean; version\="0.0.0.JavaSE_020", javax.management.timer; version\="0.0.0.JavaSE_020", javax.management; version\="0.0.0.JavaSE_020", javax.management.relation; version\="0.0.0.JavaSE_020", javax.management.loading; version\="0.0.0.JavaSE_020", javax.management.openmbean; version\="0.0.0.JavaSE_020", java.lang.management; version\="0.0.0.JavaSE_020", javax.management.remote.rmi; version\="0.0.0.JavaSE_020", javax.naming; version\="0.0.0.JavaSE_020", javax.naming.spi; version\="0.0.0.JavaSE_020", javax.naming.event; version\="0.0.0.JavaSE_020", javax.naming.ldap.spi; version\="0.0.0.JavaSE_020", javax.naming.ldap; version\="0.0.0.JavaSE_020", javax.naming.directory; version\="0.0.0.JavaSE_020", java.net.http; version\="0.0.0.JavaSE_020", java.util.prefs; version\="0.0.0.JavaSE_020", javax.rmi.ssl; version\="0.0.0.JavaSE_020", java.rmi.dgc; version\="0.0.0.JavaSE_020", java.rmi; version\="0.0.0.JavaSE_020", java.rmi.server; version\="0.0.0.JavaSE_020", java.rmi.registry; version\="0.0.0.JavaSE_020", javax.script; version\="0.0.0.JavaSE_020", javax.security.auth.kerberos; version\="0.0.0.JavaSE_020", org.ietf.jgss; version\="0.0.0.JavaSE_020", javax.security.sasl; version\="0.0.0.JavaSE_020", javax.smartcardio; version\="0.0.0.JavaSE_020", java.sql; version\="0.0.0.JavaSE_020", javax.sql; version\="0.0.0.JavaSE_020", javax.sql.rowset.spi; version\="0.0.0.JavaSE_020", javax.sql.rowset.serial; version\="0.0.0.JavaSE_020", javax.sql.rowset; version\="0.0.0.JavaSE_020", javax.transaction.xa; version\="0.0.0.JavaSE_020", org.w3c.dom.events; version\="0.0.0.JavaSE_020", org.w3c.dom.ranges; version\="0.0.0.JavaSE_020", org.w3c.dom.ls; version\="0.0.0.JavaSE_020", javax.xml.stream.util; version\="0.0.0.JavaSE_020", javax.xml.namespace; version\="0.0.0.JavaSE_020", javax.xml.transform.stax; version\="0.0.0.JavaSE_020", org.xml.sax.helpers; version\="0.0.0.JavaSE_020", org.w3c.dom.views; version\="0.0.0.JavaSE_020", org.w3c.dom.bootstrap; version\="0.0.0.JavaSE_020", org.w3c.dom; version\="0.0.0.JavaSE_020", javax.xml.transform.stream; version\="0.0.0.JavaSE_020", javax.xml.transform.dom; version\="0.0.0.JavaSE_020", javax.xml.validation; version\="0.0.0.JavaSE_020", javax.xml.parsers; version\="0.0.0.JavaSE_020", org.xml.sax.ext; version\="0.0.0.JavaSE_020", javax.xml; version\="0.0.0.JavaSE_020", javax.xml.transform.sax; version\="0.0.0.JavaSE_020", javax.xml.datatype; version\="0.0.0.JavaSE_020", javax.xml.catalog; version\="0.0.0.JavaSE_020", org.w3c.dom.traversal; version\="0.0.0.JavaSE_020", javax.xml.stream.events; version\="0.0.0.JavaSE_020", javax.xml.stream; version\="0.0.0.JavaSE_020", org.xml.sax; version\="0.0.0.JavaSE_020", javax.xml.transform; version\="0.0.0.JavaSE_020", javax.xml.xpath; version\="0.0.0.JavaSE_020", javax.xml.crypto; version\="0.0.0.JavaSE_020", javax.xml.crypto.dsig; version\="0.0.0.JavaSE_020", javax.xml.crypto.dsig.spec; version\="0.0.0.JavaSE_020", javax.xml.crypto.dsig.keyinfo; version\="0.0.0.JavaSE_020", javax.xml.crypto.dsig.dom; version\="0.0.0.JavaSE_020", javax.xml.crypto.dom; version\="0.0.0.JavaSE_020", com.sun.java.accessibility.util; version\="0.0.0.JavaSE_020", com.sun.tools.attach; version\="0.0.0.JavaSE_020", com.sun.tools.attach.spi; version\="0.0.0.JavaSE_020", com.sun.source.tree; version\="0.0.0.JavaSE_020", com.sun.source.util; version\="0.0.0.JavaSE_020", com.sun.tools.javac; version\="0.0.0.JavaSE_020", com.sun.source.doctree; version\="0.0.0.JavaSE_020", jdk.dynalink.support; version\="0.0.0.JavaSE_020", jdk.dynalink; version\="0.0.0.JavaSE_020", jdk.dynalink.linker; version\="0.0.0.JavaSE_020", jdk.dynalink.beans; version\="0.0.0.JavaSE_020", jdk.dynalink.linker.support; version\="0.0.0.JavaSE_020", com.sun.net.httpserver; version\="0.0.0.JavaSE_020", com.sun.net.httpserver.spi; version\="0.0.0.JavaSE_020", com.sun.jarsigner; version\="0.0.0.JavaSE_020", jdk.security.jarsigner; version\="0.0.0.JavaSE_020", jdk.javadoc.doclet; version\="0.0.0.JavaSE_020", com.sun.tools.jconsole; version\="0.0.0.JavaSE_020", com.sun.jdi; version\="0.0.0.JavaSE_020", com.sun.jdi.request; version\="0.0.0.JavaSE_020", com.sun.jdi.connect; version\="0.0.0.JavaSE_020", com.sun.jdi.event; version\="0.0.0.JavaSE_020", com.sun.jdi.connect.spi; version\="0.0.0.JavaSE_020", jdk.jfr.consumer; version\="0.0.0.JavaSE_020", jdk.jfr; version\="0.0.0.JavaSE_020", jdk.jshell.spi; version\="0.0.0.JavaSE_020", jdk.jshell.tool; version\="0.0.0.JavaSE_020", jdk.jshell; version\="0.0.0.JavaSE_020", jdk.jshell.execution; version\="0.0.0.JavaSE_020", netscape.javascript; version\="0.0.0.JavaSE_020", com.sun.management; version\="0.0.0.JavaSE_020", jdk.management.jfr; version\="0.0.0.JavaSE_020", jdk.net; version\="0.0.0.JavaSE_020", jdk.nio; version\="0.0.0.JavaSE_020", jdk.nio.mapmode; version\="0.0.0.JavaSE_020", com.sun.nio.sctp; version\="0.0.0.JavaSE_020", com.sun.security.auth.login; version\="0.0.0.JavaSE_020", com.sun.security.auth; version\="0.0.0.JavaSE_020", com.sun.security.auth.callback; version\="0.0.0.JavaSE_020", com.sun.security.auth.module; version\="0.0.0.JavaSE_020", com.sun.security.jgss; version\="0.0.0.JavaSE_020", sun.reflect; version\="0.0.0.JavaSE_020", sun.misc; version\="0.0.0.JavaSE_020", com.sun.nio.file; version\="0.0.0.JavaSE_020", jdk.swing.interop; version\="0.0.0.JavaSE_020", org.w3c.dom.xpath; version\="0.0.0.JavaSE_020", org.w3c.dom.css; version\="0.0.0.JavaSE_020", org.w3c.dom.stylesheets; version\="0.0.0.JavaSE_020", org.w3c.dom.html; version\="0.0.0.JavaSE_020", com.yahoo.jdisc, com.yahoo.jdisc.application, com.yahoo.jdisc.handler, com.yahoo.jdisc.service, com.yahoo.jdisc.statistics, com.yahoo.jdisc.refcount, javax.inject;version\=1.0.0, org.aopalliance.intercept, org.aopalliance.aop, com.google.common.annotations;version\="27.1.0",com.google.common.base;version\="27.1.0",com.google.common.cache;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.util.concurrent",com.google.common.collect;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.escape;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.eventbus;version\="27.1.0",com.google.common.graph;version\="27.1.0";uses\:\="com.google.common.collect",com.google.common.hash;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.html;version\="27.1.0";uses\:\="com.google.common.escape",com.google.common.io;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.graph,com.google.common.hash",com.google.common.math;version\="27.1.0",com.google.common.net;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.escape",com.google.common.primitives;version\="27.1.0";uses\:\="com.google.common.base",com.google.common.reflect;version\="27.1.0";uses\:\="com.google.common.collect,com.google.common.io",com.google.common.util.concurrent;version\="27.1.0";uses\:\="com.google.common.base,com.google.common.collect,com.google.common.util.concurrent.internal",com.google.common.xml;version\="27.1.0";uses\:\="com.google.common.escape", com.google.inject;version\="1.4",com.google.inject.binder;version\="1.4",com.google.inject.matcher;version\="1.4",com.google.inject.multibindings;version\="1.4",com.google.inject.name;version\="1.4",com.google.inject.spi;version\="1.4",com.google.inject.util;version\="1.4", org.slf4j;version\=1.7.32, org.slf4j.spi;version\=1.7.32, org.slf4j.helpers;version\=1.7.32, org.slf4j.event;version\=1.7.32, org.slf4j.impl;version\=1.7.32, org.apache.commons.logging;version\=1.2, org.apache.commons.logging.impl;version\=1.2, com.sun.jna;version\=5.11.0, com.sun.jna.ptr;version\=5.11.0, com.sun.jna.win32;version\=5.11.0, org.apache.log4j;version\=1.2.17,org.apache.log4j.helpers;version\=1.2.17,org.apache.log4j.spi;version\=1.2.17,org.apache.log4j.xml;version\=1.2.17, com.yahoo.component.annotation;version\="1.0.0", com.yahoo.config;version\=1.0.0, com.yahoo.vespa.defaults;version\=1.0.0, ai.vespa.http;version\=1.0.0,ai.vespa.llm.completion;version\=1.0.0,ai.vespa.llm.test;version\=1.0.0,ai.vespa.llm;version\=1.0.0,ai.vespa.validation;version\=1.0.0,com.yahoo.binaryprefix;version\=1.0.0,com.yahoo.collections;version\=1.0.0,com.yahoo.compress;version\=1.0.0,com.yahoo.concurrent.classlock;version\=1.0.0,com.yahoo.concurrent.maintenance;version\=1.0.0,com.yahoo.concurrent;version\=1.0.0,com.yahoo.data.access.simple;version\=1.0.0,com.yahoo.data.access.slime;version\=1.0.0,com.yahoo.data.access;version\=1.0.0,com.yahoo.errorhandling;version\=1.0.0,com.yahoo.exception;version\=1.0.0,com.yahoo.geo;version\=1.0.0,com.yahoo.io.reader;version\=1.0.0,com.yahoo.io;version\=1.0.0,com.yahoo.javacc;version\=1.0.0,com.yahoo.lang;version\=1.0.0,com.yahoo.nativec;version\=1.0.0,com.yahoo.net;version\=1.0.0,com.yahoo.path;version\=1.0.0,com.yahoo.protect;version\=1.0.0,com.yahoo.reflection;version\=1.0.0,com.yahoo.slime;version\=1.0.0,com.yahoo.stream;version\=1.0.0,com.yahoo.system.execution;version\=1.0.0,com.yahoo.system;version\=1.0.0,com.yahoo.tensor.evaluation;version\=1.0.0,com.yahoo.tensor.functions;version\=1.0.0,com.yahoo.tensor.serialization;version\=1.0.0,com.yahoo.tensor;version\=1.0.0,com.yahoo.text.internal;version\=1.0.0,com.yahoo.text;version\=1.0.0,com.yahoo.time;version\=1.0.0,com.yahoo.transaction;version\=1.0.0,com.yahoo.vespa.objects;version\=1.0.0,com.yahoo.yolean.chain;version\=1.0.0,com.yahoo.yolean.concurrent;version\=1.0.0,com.yahoo.yolean.function;version\=1.0.0,com.yahoo.yolean.system;version\=1.0.0,com.yahoo.yolean.trace;version\=1.0.0,com.yahoo.yolean;version\=1.0.0, com.yahoo.log.event;version\=1.0.0,com.yahoo.log.impl;version\=1.0.0,com.yahoo.log;version\=1.0.0, javax.xml.bind;version\="2.3";uses\:\="javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.validation,org.w3c.dom,org.xml.sax",javax.xml.bind.annotation;version\="2.3";uses\:\="javax.xml.bind,javax.xml.parsers,javax.xml.transform,javax.xml.transform.dom,org.w3c.dom",javax.xml.bind.annotation.adapters;version\="2.3",javax.xml.bind.attachment;version\="2.3";uses\:\="javax.activation",javax.xml.bind.helpers;version\="2.3";uses\:\="javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.stream,javax.xml.transform,javax.xml.validation,org.w3c.dom,org.xml.sax",javax.xml.bind.util;version\="2.3";uses\:\="javax.xml.bind,javax.xml.transform.sax", com.sun.istack;version\="3.0.5";uses\:\="javax.activation,javax.xml.stream,org.xml.sax,org.xml.sax.helpers",com.sun.istack.localization;version\="3.0.5",com.sun.istack.logging;version\="3.0.5",com.sun.xml.bind;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.annotation;version\="2.3.0",com.sun.xml.bind.api;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.api.impl;version\="2.3.0",com.sun.xml.bind.marshaller;uses\:\="javax.xml.parsers,org.w3c.dom,org.xml.sax,org.xml.sax.helpers";version\="2.3.0",com.sun.xml.bind.unmarshaller;uses\:\="com.sun.xml.bind.v2.runtime.unmarshaller,javax.xml.bind,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.util;version\="2.3.0",com.sun.xml.bind.v2;version\="2.3.0",com.sun.xml.bind.v2.model.annotation;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.core;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.impl,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.runtime,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.namespace,javax.xml.transform";version\="2.3.0",com.sun.xml.bind.v2.model.impl;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.nav";version\="2.3.0",com.sun.xml.bind.v2.model.nav;uses\:\="com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.util;uses\:\="javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.runtime;uses\:\="com.sun.xml.bind.v2.model.annotation,javax.activation,javax.xml.bind,javax.xml.bind.annotation.adapters";version\="2.3.0",com.sun.xml.bind.v2.runtime.unmarshaller;uses\:\="javax.xml.bind,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.schemagen.episode;uses\:\="com.sun.xml.txw2,com.sun.xml.txw2.annotation";version\="2.3.0",com.sun.xml.bind.v2.util;uses\:\="javax.xml.parsers,javax.xml.transform,javax.xml.validation,javax.xml.xpath";version\="2.3.0",com.sun.xml.txw2;uses\:\="com.sun.xml.txw2.output,javax.xml.namespace";version\="2.3.0",com.sun.xml.txw2.annotation;version\="2.3.0",com.sun.xml.txw2.output;uses\:\="com.sun.xml.txw2,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.dom,javax.xml.transform.sax,javax.xml.transform.stream,org.w3c.dom,org.xml.sax,org.xml.sax.ext,org.xml.sax.helpers";version\="2.3.0", com.sun.xml.bind;uses\:\="com.sun.xml.bind.v2.runtime.reflect,javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.datatype,javax.xml.namespace,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.api;uses\:\="com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,javax.xml.bind,javax.xml.bind.attachment,javax.xml.namespace,javax.xml.stream,javax.xml.transform,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.marshaller;version\="2.3.0",com.sun.xml.bind.unmarshaller;uses\:\="org.xml.sax";version\="2.3.0",com.sun.xml.bind.util;uses\:\="com.sun.xml.bind,javax.xml.bind.helpers,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.annotation,javax.xml.bind";version\="2.3.0",com.sun.xml.bind.v2.bytecode;version\="2.3.0",com.sun.xml.bind.v2.model.annotation;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.runtime";version\="2.3.0",com.sun.xml.bind.v2.model.impl;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,javax.activation,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.model.runtime;uses\:\="com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,javax.xml.bind,javax.xml.namespace,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime;uses\:\="com.sun.istack,com.sun.xml.bind.api,com.sun.xml.bind.marshaller,com.sun.xml.bind.v2.model.annotation,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime.output,com.sun.xml.bind.v2.runtime.property,com.sun.xml.bind.v2.runtime.unmarshaller,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.bind.helpers,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.sax,javax.xml.validation,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.output;uses\:\="com.sun.xml.bind.marshaller,com.sun.xml.bind.v2.runtime,com.sun.xml.fastinfoset.stax,javax.xml.stream,org.jvnet.staxex,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.property;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,com.sun.xml.bind.v2.runtime.unmarshaller,com.sun.xml.bind.v2.util,javax.xml.namespace,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.reflect;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.unmarshaller,javax.xml.bind,javax.xml.bind.annotation.adapters,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.reflect.opt;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.runtime,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.reflect,javax.xml.stream,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.runtime.unmarshaller;uses\:\="com.sun.xml.bind,com.sun.xml.bind.api,com.sun.xml.bind.unmarshaller,com.sun.xml.bind.util,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.output,com.sun.xml.bind.v2.runtime.reflect,javax.activation,javax.xml.bind,javax.xml.bind.annotation,javax.xml.bind.annotation.adapters,javax.xml.bind.attachment,javax.xml.bind.helpers,javax.xml.namespace,javax.xml.stream,javax.xml.transform,javax.xml.transform.sax,javax.xml.validation,org.w3c.dom,org.xml.sax";version\="2.3.0",com.sun.xml.bind.v2.schemagen;uses\:\="com.sun.xml.bind.api,com.sun.xml.bind.v2.model.core,com.sun.xml.bind.v2.model.nav,com.sun.xml.txw2.output,javax.xml.bind,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.schemagen.xmlschema;uses\:\="com.sun.xml.txw2,com.sun.xml.txw2.annotation,javax.xml.namespace";version\="2.3.0",com.sun.xml.bind.v2.util;uses\:\="com.sun.xml.bind.v2.runtime,com.sun.xml.bind.v2.runtime.unmarshaller,javax.activation,javax.xml.namespace,javax.xml.transform.stream,org.xml.sax";version\="2.3.0", javax.activation;uses\:\="com.sun.activation.registries";version\="1.2",com.sun.activation.viewers;uses\:\="javax.activation";version\="1.2.0",com.sun.activation.registries;version\="1.2.0"
diff --git a/model-integration/abi-spec.json b/model-integration/abi-spec.json
index 6f31cf5a2e6..d3c472778e6 100644
--- a/model-integration/abi-spec.json
+++ b/model-integration/abi-spec.json
@@ -1 +1,50 @@
-{ } \ No newline at end of file
+{
+ "ai.vespa.llm.generation.Generator" : {
+ "superClass" : "com.yahoo.component.AbstractComponent",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(ai.vespa.modelintegration.evaluator.OnnxRuntime, com.yahoo.llm.GeneratorConfig)",
+ "public java.lang.String generate(java.lang.String, ai.vespa.llm.generation.GeneratorOptions)",
+ "public java.lang.String generate(java.lang.String)",
+ "public void deconstruct()"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.generation.GeneratorOptions$SearchMethod" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static ai.vespa.llm.generation.GeneratorOptions$SearchMethod[] values()",
+ "public static ai.vespa.llm.generation.GeneratorOptions$SearchMethod valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum ai.vespa.llm.generation.GeneratorOptions$SearchMethod GREEDY",
+ "public static final enum ai.vespa.llm.generation.GeneratorOptions$SearchMethod CONTRASTIVE",
+ "public static final enum ai.vespa.llm.generation.GeneratorOptions$SearchMethod BEAM",
+ "public static final enum ai.vespa.llm.generation.GeneratorOptions$SearchMethod SAMPLE"
+ ]
+ },
+ "ai.vespa.llm.generation.GeneratorOptions" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public ai.vespa.llm.generation.GeneratorOptions$SearchMethod getSearchMethod()",
+ "public ai.vespa.llm.generation.GeneratorOptions setSearchMethod(ai.vespa.llm.generation.GeneratorOptions$SearchMethod)",
+ "public int getMaxLength()",
+ "public ai.vespa.llm.generation.GeneratorOptions setMaxLength(int)"
+ ],
+ "fields" : [ ]
+ }
+} \ No newline at end of file
diff --git a/model-integration/src/main/java/ai/vespa/embedding/BertBaseEmbedder.java b/model-integration/src/main/java/ai/vespa/embedding/BertBaseEmbedder.java
index b40e2b5be72..bf56d233f89 100644
--- a/model-integration/src/main/java/ai/vespa/embedding/BertBaseEmbedder.java
+++ b/model-integration/src/main/java/ai/vespa/embedding/BertBaseEmbedder.java
@@ -32,10 +32,9 @@ import java.util.Map;
*/
public class BertBaseEmbedder extends AbstractComponent implements Embedder {
- private final static int TOKEN_CLS = 101; // [CLS]
- private final static int TOKEN_SEP = 102; // [SEP]
-
private final int maxTokens;
+ private final int startSequenceToken;
+ private final int endSequenceToken;
private final String inputIdsName;
private final String attentionMaskName;
private final String tokenTypeIdsName;
@@ -48,6 +47,8 @@ public class BertBaseEmbedder extends AbstractComponent implements Embedder {
@Inject
public BertBaseEmbedder(OnnxRuntime onnx, BertBaseEmbedderConfig config) {
maxTokens = config.transformerMaxTokens();
+ startSequenceToken = config.transformerStartSequenceToken();
+ endSequenceToken = config.transformerEndSequenceToken();
inputIdsName = config.transformerInputIds();
attentionMaskName = config.transformerAttentionMask();
tokenTypeIdsName = config.transformerTokenTypeIds();
@@ -98,7 +99,7 @@ public class BertBaseEmbedder extends AbstractComponent implements Embedder {
if (!type.dimensions().get(0).isIndexed()) {
throw new IllegalArgumentException("Error in embedding to type '" + type + "': dimension should be indexed.");
}
- List<Integer> tokens = embedWithSeperatorTokens(text, context, maxTokens);
+ List<Integer> tokens = embedWithSeparatorTokens(text, context, maxTokens);
return embedTokens(tokens, type);
}
@@ -109,6 +110,7 @@ public class BertBaseEmbedder extends AbstractComponent implements Embedder {
Tensor attentionMask = createAttentionMask(inputSequence);
Tensor tokenTypeIds = createTokenTypeIds(inputSequence);
+
Map<String, Tensor> inputs;
if (!"".equals(tokenTypeIdsName)) {
inputs = Map.of(inputIdsName, inputSequence.expand("d0"),
@@ -138,14 +140,14 @@ public class BertBaseEmbedder extends AbstractComponent implements Embedder {
return builder.build();
}
- private List<Integer> embedWithSeperatorTokens(String text, Context context, int maxLength) {
+ private List<Integer> embedWithSeparatorTokens(String text, Context context, int maxLength) {
List<Integer> tokens = new ArrayList<>();
- tokens.add(TOKEN_CLS);
+ tokens.add(startSequenceToken);
tokens.addAll(embed(text, context));
- tokens.add(TOKEN_SEP);
+ tokens.add(endSequenceToken);
if (tokens.size() > maxLength) {
tokens = tokens.subList(0, maxLength-1);
- tokens.add(TOKEN_SEP);
+ tokens.add(endSequenceToken);
}
return tokens;
}
diff --git a/model-integration/src/main/java/ai/vespa/llm/Generator.java b/model-integration/src/main/java/ai/vespa/llm/generation/Generator.java
index 973b5ac2899..f20925b86ee 100644
--- a/model-integration/src/main/java/ai/vespa/llm/Generator.java
+++ b/model-integration/src/main/java/ai/vespa/llm/generation/Generator.java
@@ -1,4 +1,4 @@
-package ai.vespa.llm;
+package ai.vespa.llm.generation;
import ai.vespa.modelintegration.evaluator.OnnxEvaluator;
import ai.vespa.modelintegration.evaluator.OnnxEvaluatorOptions;
@@ -13,6 +13,7 @@ import com.yahoo.tensor.IndexedTensor;
import com.yahoo.tensor.PartialAddress;
import com.yahoo.tensor.Tensor;
import com.yahoo.tensor.TensorType;
+import com.yahoo.api.annotations.Beta;
import java.util.ArrayList;
import java.util.List;
@@ -27,6 +28,7 @@ import java.util.Map;
*
* @author lesters
*/
+@Beta
public class Generator extends AbstractComponent {
private final static int TOKEN_EOS = 1; // end of sequence
diff --git a/model-integration/src/main/java/ai/vespa/llm/GeneratorOptions.java b/model-integration/src/main/java/ai/vespa/llm/generation/GeneratorOptions.java
index 743bb7c2f27..79b466e5a74 100644
--- a/model-integration/src/main/java/ai/vespa/llm/GeneratorOptions.java
+++ b/model-integration/src/main/java/ai/vespa/llm/generation/GeneratorOptions.java
@@ -1,5 +1,8 @@
-package ai.vespa.llm;
+package ai.vespa.llm.generation;
+import com.yahoo.api.annotations.Beta;
+
+@Beta
public class GeneratorOptions {
public enum SearchMethod {
diff --git a/model-integration/src/main/java/ai/vespa/llm/generation/package-info.java b/model-integration/src/main/java/ai/vespa/llm/generation/package-info.java
new file mode 100644
index 00000000000..ed3adb2f59e
--- /dev/null
+++ b/model-integration/src/main/java/ai/vespa/llm/generation/package-info.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package ai.vespa.llm.generation;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
+
+/**
+ * API for generating text with language models.
+ */ \ No newline at end of file
diff --git a/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluatorOptions.java b/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluatorOptions.java
index 1ed219a8560..a980ca984ec 100644
--- a/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluatorOptions.java
+++ b/model-integration/src/main/java/ai/vespa/modelintegration/evaluator/OnnxEvaluatorOptions.java
@@ -7,6 +7,9 @@ import ai.onnxruntime.OrtSession;
import java.util.Objects;
+import static ai.onnxruntime.OrtSession.SessionOptions.ExecutionMode.PARALLEL;
+import static ai.onnxruntime.OrtSession.SessionOptions.ExecutionMode.SEQUENTIAL;
+
/**
* Session options for ONNX Runtime evaluation
*
@@ -24,9 +27,10 @@ public class OnnxEvaluatorOptions {
public OnnxEvaluatorOptions() {
// Defaults:
optimizationLevel = OrtSession.SessionOptions.OptLevel.ALL_OPT;
- executionMode = OrtSession.SessionOptions.ExecutionMode.SEQUENTIAL;
- interOpThreads = 1;
- intraOpThreads = Math.max(1, (int) Math.ceil(((double) Runtime.getRuntime().availableProcessors()) / 4));
+ executionMode = SEQUENTIAL;
+ int quarterVcpu = Math.max(1, (int) Math.ceil(Runtime.getRuntime().availableProcessors() / 4d));
+ interOpThreads = quarterVcpu;
+ intraOpThreads = quarterVcpu;
gpuDeviceNumber = -1;
gpuDeviceRequired = false;
}
@@ -35,7 +39,7 @@ public class OnnxEvaluatorOptions {
OrtSession.SessionOptions options = new OrtSession.SessionOptions();
options.setOptimizationLevel(optimizationLevel);
options.setExecutionMode(executionMode);
- options.setInterOpNumThreads(interOpThreads);
+ options.setInterOpNumThreads(executionMode == PARALLEL ? interOpThreads : 1);
options.setIntraOpNumThreads(intraOpThreads);
if (loadCuda) {
options.addCUDA(gpuDeviceNumber);
@@ -47,7 +51,7 @@ public class OnnxEvaluatorOptions {
if ("parallel".equalsIgnoreCase(mode)) {
executionMode = OrtSession.SessionOptions.ExecutionMode.PARALLEL;
} else if ("sequential".equalsIgnoreCase(mode)) {
- executionMode = OrtSession.SessionOptions.ExecutionMode.SEQUENTIAL;
+ executionMode = SEQUENTIAL;
}
}
diff --git a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/OrderedTensorType.java b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/OrderedTensorType.java
index ceb9a27924d..eee60d56c55 100644
--- a/model-integration/src/main/java/ai/vespa/rankingexpression/importer/OrderedTensorType.java
+++ b/model-integration/src/main/java/ai/vespa/rankingexpression/importer/OrderedTensorType.java
@@ -96,14 +96,14 @@ public class OrderedTensorType {
* so that they are correctly laid out in memory for Vespa.
* Used when importing tensors.
*/
- public int toDirectIndex(int index) {
+ public long toDirectIndex(int index) {
if (dimensions.size() == 0) {
return 0;
}
if (dimensionMap == null) {
throw new IllegalArgumentException("Dimension map is not available");
}
- int directIndex = 0;
+ long directIndex = 0;
long rest = index;
for (int i = 0; i < dimensions.size(); ++i) {
long address = rest / innerSizesOriginal[i];
diff --git a/model-integration/src/main/resources/configdefinitions/embedding.bert-base-embedder.def b/model-integration/src/main/resources/configdefinitions/embedding.bert-base-embedder.def
index 14d953eeef9..ef42d81e1fe 100644
--- a/model-integration/src/main/resources/configdefinitions/embedding.bert-base-embedder.def
+++ b/model-integration/src/main/resources/configdefinitions/embedding.bert-base-embedder.def
@@ -17,6 +17,10 @@ transformerInputIds string default=input_ids
transformerAttentionMask string default=attention_mask
transformerTokenTypeIds string default=token_type_ids
+# special token ids
+transformerStartSequenceToken int default=101
+transformerEndSequenceToken int default=102
+
# Output name
transformerOutput string default=output_0
diff --git a/model-integration/src/test/java/ai/vespa/llm/GeneratorTest.java b/model-integration/src/test/java/ai/vespa/llm/generation/GeneratorTest.java
index c22902b344f..8c9b961f4a8 100644
--- a/model-integration/src/test/java/ai/vespa/llm/GeneratorTest.java
+++ b/model-integration/src/test/java/ai/vespa/llm/generation/GeneratorTest.java
@@ -1,5 +1,7 @@
-package ai.vespa.llm;
+package ai.vespa.llm.generation;
+import ai.vespa.llm.generation.Generator;
+import ai.vespa.llm.generation.GeneratorOptions;
import ai.vespa.modelintegration.evaluator.OnnxRuntime;
import com.yahoo.config.ModelReference;
import com.yahoo.llm.GeneratorConfig;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java
index 7367a254b4a..92cccf86ecb 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoreDumpMetadata.java
@@ -4,6 +4,7 @@ package com.yahoo.vespa.hosted.node.admin.configserver.cores;
import com.yahoo.config.provision.DockerImage;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.List;
import java.util.Objects;
import java.util.Optional;
@@ -12,7 +13,11 @@ import java.util.Optional;
* @author hakonhall
*/
public class CoreDumpMetadata {
+ public enum Type { CORE_DUMP, JVM_HEAP, OOM }
+
+ private Type type;
private String binPath;
+ private Instant created;
private List<String> backtrace;
private List<String> backtraceAllThreads;
private Path coreDumpPath;
@@ -24,30 +29,36 @@ public class CoreDumpMetadata {
public CoreDumpMetadata() {}
- public Optional<String> binPath() { return Optional.ofNullable(binPath); };
- public Optional<List<String>> backtrace() { return Optional.ofNullable(backtrace); };
- public Optional<List<String>> backtraceAllThreads() { return Optional.ofNullable(backtraceAllThreads); };
- public Optional<Path> coredumpPath() { return Optional.ofNullable(coreDumpPath); };
+ public Optional<Type> type() { return Optional.ofNullable(type); }
+ public Optional<String> binPath() { return Optional.ofNullable(binPath); }
+ public Optional<Instant> created() { return Optional.ofNullable(created); }
+ public Optional<List<String>> backtrace() { return Optional.ofNullable(backtrace); }
+ public Optional<List<String>> backtraceAllThreads() { return Optional.ofNullable(backtraceAllThreads); }
+ public Optional<Path> coredumpPath() { return Optional.ofNullable(coreDumpPath); }
public Optional<String> decryptionToken() { return Optional.ofNullable(decryptionToken); }
- public Optional<String> kernelVersion() { return Optional.ofNullable(kernelVersion); };
- public Optional<String> cpuMicrocodeVersion() { return Optional.ofNullable(cpuMicrocodeVersion); };
- public Optional<DockerImage> dockerImage() { return Optional.ofNullable(dockerImage); };
- public Optional<String> vespaVersion() { return Optional.ofNullable(vespaVersion); };
+ public Optional<String> kernelVersion() { return Optional.ofNullable(kernelVersion); }
+ public Optional<String> cpuMicrocodeVersion() { return Optional.ofNullable(cpuMicrocodeVersion); }
+ public Optional<DockerImage> dockerImage() { return Optional.ofNullable(dockerImage); }
+ public Optional<String> vespaVersion() { return Optional.ofNullable(vespaVersion); }
- public CoreDumpMetadata setBinPath(String binPath) { this.binPath = binPath; return this; };
- public CoreDumpMetadata setBacktrace(List<String> backtrace) { this.backtrace = backtrace; return this; };
- public CoreDumpMetadata setBacktraceAllThreads(List<String> backtraceAllThreads) { this.backtraceAllThreads = backtraceAllThreads; return this; };
- public CoreDumpMetadata setCoreDumpPath(Path coreDumpPath) { this.coreDumpPath = coreDumpPath; return this; };
+ public CoreDumpMetadata setType(Type type) { this.type = type; return this; }
+ public CoreDumpMetadata setBinPath(String binPath) { this.binPath = binPath; return this; }
+ public CoreDumpMetadata setCreated(Instant created) { this.created = created; return this; }
+ public CoreDumpMetadata setBacktrace(List<String> backtrace) { this.backtrace = backtrace; return this; }
+ public CoreDumpMetadata setBacktraceAllThreads(List<String> backtraceAllThreads) { this.backtraceAllThreads = backtraceAllThreads; return this; }
+ public CoreDumpMetadata setCoreDumpPath(Path coreDumpPath) { this.coreDumpPath = coreDumpPath; return this; }
public CoreDumpMetadata setDecryptionToken(String decryptionToken) { this.decryptionToken = decryptionToken; return this; }
- public CoreDumpMetadata setKernelVersion(String kernelVersion) { this.kernelVersion = kernelVersion; return this; };
- public CoreDumpMetadata setCpuMicrocodeVersion(String cpuMicrocodeVersion) { this.cpuMicrocodeVersion = cpuMicrocodeVersion; return this; };
- public CoreDumpMetadata setDockerImage(DockerImage dockerImage) { this.dockerImage = dockerImage; return this; };
- public CoreDumpMetadata setVespaVersion(String vespaVersion) { this.vespaVersion = vespaVersion; return this; };
+ public CoreDumpMetadata setKernelVersion(String kernelVersion) { this.kernelVersion = kernelVersion; return this; }
+ public CoreDumpMetadata setCpuMicrocodeVersion(String cpuMicrocodeVersion) { this.cpuMicrocodeVersion = cpuMicrocodeVersion; return this; }
+ public CoreDumpMetadata setDockerImage(DockerImage dockerImage) { this.dockerImage = dockerImage; return this; }
+ public CoreDumpMetadata setVespaVersion(String vespaVersion) { this.vespaVersion = vespaVersion; return this; }
@Override
public String toString() {
return "CoreDumpMetadata{" +
- "binPath=" + binPath +
+ "type=" + type +
+ ", binPath=" + binPath +
+ ", created=" + created +
", backtrace=" + backtrace +
", backtraceAllThreads=" + backtraceAllThreads +
", coreDumpPath=" + coreDumpPath +
@@ -64,7 +75,9 @@ public class CoreDumpMetadata {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
CoreDumpMetadata metadata = (CoreDumpMetadata) o;
- return Objects.equals(binPath, metadata.binPath) &&
+ return type == metadata.type &&
+ Objects.equals(binPath, metadata.binPath) &&
+ Objects.equals(created, metadata.created) &&
Objects.equals(backtrace, metadata.backtrace) &&
Objects.equals(backtraceAllThreads, metadata.backtraceAllThreads) &&
Objects.equals(coreDumpPath, metadata.coreDumpPath) &&
@@ -77,7 +90,7 @@ public class CoreDumpMetadata {
@Override
public int hashCode() {
- return Objects.hash(binPath, backtrace, backtraceAllThreads, coreDumpPath, decryptionToken, kernelVersion,
+ return Objects.hash(type, binPath, created, backtrace, backtraceAllThreads, coreDumpPath, decryptionToken, kernelVersion,
cpuMicrocodeVersion, dockerImage, vespaVersion);
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java
index 27cf28b8e1e..a9620ebabc2 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/bindings/ReportCoreDumpRequest.java
@@ -14,6 +14,7 @@ import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.NoSuchFileException;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.List;
import java.util.Optional;
@@ -31,6 +32,8 @@ public class ReportCoreDumpRequest {
public List<String> backtrace;
public List<String> backtrace_all_threads;
+ public Long created;
+ public String type;
public String bin_path;
public String coredump_path;
public String cpu_microcode_version;
@@ -44,7 +47,9 @@ public class ReportCoreDumpRequest {
/** Fill this from metadata and return this. */
@JsonIgnore
public ReportCoreDumpRequest fillFrom(CoreDumpMetadata metadata) {
+ metadata.type().ifPresent(type -> this.type = type.name());
metadata.binPath().ifPresent(binPath -> this.bin_path = binPath);
+ metadata.created().ifPresent(created -> this.created = created.toEpochMilli());
metadata.backtrace().ifPresent(backtrace -> this.backtrace = List.copyOf(backtrace));
metadata.backtraceAllThreads().ifPresent(backtraceAllThreads -> this.backtrace_all_threads = List.copyOf(backtraceAllThreads));
metadata.coredumpPath().ifPresent(coredumpPath -> this.coredump_path = coredumpPath.toString());
@@ -58,7 +63,9 @@ public class ReportCoreDumpRequest {
@JsonIgnore
public void populateMetadata(CoreDumpMetadata metadata, FileSystem fileSystem) {
+ if (type != null) metadata.setType(CoreDumpMetadata.Type.valueOf(type));
if (bin_path != null) metadata.setBinPath(bin_path);
+ if (created != null) metadata.setCreated(Instant.ofEpochMilli(created));
if (backtrace != null) metadata.setBacktrace(backtrace);
if (backtrace_all_threads != null) metadata.setBacktraceAllThreads(backtrace_all_threads);
if (coredump_path != null) metadata.setCoreDumpPath(fileSystem.getPath(coredump_path));
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/DropDocumentsReport.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/DropDocumentsReport.java
new file mode 100644
index 00000000000..0d88f10ebf9
--- /dev/null
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/configserver/noderepository/reports/DropDocumentsReport.java
@@ -0,0 +1,55 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.hosted.node.admin.configserver.noderepository.reports;
+
+import com.fasterxml.jackson.annotation.JsonGetter;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+/**
+ * @author freva
+ */
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public class DropDocumentsReport extends BaseReport {
+ private static final String REPORT_ID = "dropDocuments";
+ private static final String DROPPED_AT_FIELD = "droppedAt";
+ private static final String READIED_AT_FIELD = "readiedAt";
+ private static final String STARTED_AT_FIELD = "startedAt";
+
+ private final Long droppedAt;
+ private final Long readiedAt;
+ private final Long startedAt;
+
+ public DropDocumentsReport(@JsonProperty(CREATED_FIELD) Long createdMillisOrNull,
+ @JsonProperty(DROPPED_AT_FIELD) Long droppedAtOrNull,
+ @JsonProperty(READIED_AT_FIELD) Long readiedAtOrNull,
+ @JsonProperty(STARTED_AT_FIELD) Long startedAtOrNull) {
+ super(createdMillisOrNull, null);
+ this.droppedAt = droppedAtOrNull;
+ this.readiedAt = readiedAtOrNull;
+ this.startedAt = startedAtOrNull;
+ }
+
+ @JsonGetter(DROPPED_AT_FIELD)
+ public Long droppedAt() { return droppedAt; }
+
+ @JsonGetter(READIED_AT_FIELD)
+ public Long readiedAt() { return readiedAt; }
+
+ @JsonGetter(STARTED_AT_FIELD)
+ public Long startedAt() { return startedAt; }
+
+ public DropDocumentsReport withDroppedAt(long droppedAt) {
+ return new DropDocumentsReport(getCreatedMillisOrNull(), droppedAt, readiedAt, startedAt);
+ }
+
+ public DropDocumentsReport withStartedAt(long startedAt) {
+ return new DropDocumentsReport(getCreatedMillisOrNull(), droppedAt, readiedAt, startedAt);
+ }
+
+ public static String reportId() {
+ return REPORT_ID;
+ }
+
+}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroup.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroup.java
deleted file mode 100644
index b98ad7a11bc..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroup.java
+++ /dev/null
@@ -1,90 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.container;
-
-import com.yahoo.collections.Pair;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-
-import java.io.IOException;
-import java.util.Arrays;
-import java.util.Map;
-import java.util.Optional;
-import java.util.OptionalInt;
-
-import static com.yahoo.vespa.hosted.node.admin.container.ContainerStatsCollector.userHzToMicroSeconds;
-
-/**
- * Read and write interface to the CGroup of a podman container.
- *
- * @author freva
- */
-public interface CGroup {
-
- /**
- * Returns quota and period values used for CPU scheduling. This serves as hard cap on CPU usage by allowing
- * the CGroup to use up to {@code quota} each {@code period}. If uncapped, quota will be negative.
- *
- * @param containerId full container ID.
- * @return CPU quota and period for the given container. Empty if CGroup for this container is not found.
- */
- Optional<Pair<Integer, Integer>> cpuQuotaPeriod(ContainerId containerId);
-
- /** @return number of shares allocated to this CGroup for purposes of CPU time scheduling, empty if CGroup not found */
- OptionalInt cpuShares(ContainerId containerId);
-
- /** Update CPU quota and period for the given container ID, set quota to -1 value for unlimited */
- boolean updateCpuQuotaPeriod(NodeAgentContext context, ContainerId containerId, int cpuQuotaUs, int periodUs);
-
- boolean updateCpuShares(NodeAgentContext context, ContainerId containerId, int shares);
-
- Map<CpuStatField, Long> cpuStats(ContainerId containerId) throws IOException;
-
- /** @return Maximum amount of memory that can be used by the cgroup and its descendants. */
- long memoryLimitInBytes(ContainerId containerId) throws IOException;
-
- /** @return The total amount of memory currently being used by the cgroup and its descendants. */
- long memoryUsageInBytes(ContainerId containerId) throws IOException;
-
- /** @return Number of bytes used to cache filesystem data, including tmpfs and shared memory. */
- long memoryCacheInBytes(ContainerId containerId) throws IOException;
-
- enum CpuStatField {
- TOTAL_USAGE_USEC(null/* in a dedicated file */, "usage_usec"),
- USER_USAGE_USEC("user", "user_usec"),
- SYSTEM_USAGE_USEC("system", "system_usec"),
- TOTAL_PERIODS("nr_periods", "nr_periods"),
- THROTTLED_PERIODS("nr_throttled", "nr_throttled"),
- THROTTLED_TIME_USEC("throttled_time", "throttled_usec");
-
- private final String v1Name;
- private final String v2Name;
- CpuStatField(String v1Name, String v2Name) {
- this.v1Name = v1Name;
- this.v2Name = v2Name;
- }
-
- long parseValueV1(String value) {
- long longValue = Long.parseLong(value);
- return switch (this) {
- case THROTTLED_TIME_USEC, TOTAL_USAGE_USEC -> longValue / 1000; // Value in ns
- case USER_USAGE_USEC, SYSTEM_USAGE_USEC -> userHzToMicroSeconds(longValue);
- default -> longValue;
- };
- }
-
- long parseValueV2(String value) {
- return Long.parseLong(value);
- }
-
- static Optional<CpuStatField> fromV1Field(String name) {
- return Arrays.stream(values())
- .filter(field -> name.equals(field.v1Name))
- .findFirst();
- }
-
- static Optional<CpuStatField> fromV2Field(String name) {
- return Arrays.stream(values())
- .filter(field -> name.equals(field.v2Name))
- .findFirst();
- }
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1.java
deleted file mode 100644
index 7607858ec85..00000000000
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1.java
+++ /dev/null
@@ -1,129 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.container;
-
-import com.yahoo.collections.Pair;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Optional;
-import java.util.OptionalInt;
-import java.util.logging.Logger;
-import java.util.stream.Stream;
-
-import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.parseLong;
-
-/**
- * Read and write interface to the CGroup V1 of a Podman container.
- *
- * @see <a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v1/index.html">CGroups V1</a>
- * @author freva
- */
-public class CGroupV1 implements CGroup {
-
- private static final Logger logger = Logger.getLogger(CGroupV1.class.getName());
-
- private final FileSystem fileSystem;
-
- public CGroupV1(FileSystem fileSystem) {
- this.fileSystem = fileSystem;
- }
-
- @Override
- public Optional<Pair<Integer, Integer>> cpuQuotaPeriod(ContainerId containerId) {
- OptionalInt quota = readCgroupsCpuInt(cfsQuotaPath(containerId));
- if (quota.isEmpty()) return Optional.empty();
- OptionalInt period = readCgroupsCpuInt(cfsPeriodPath(containerId));
- if (period.isEmpty()) return Optional.empty();
- return Optional.of(new Pair<>(quota.getAsInt(), period.getAsInt()));
- }
-
- @Override
- public OptionalInt cpuShares(ContainerId containerId) {
- return readCgroupsCpuInt(sharesPath(containerId));
- }
-
- @Override
- public boolean updateCpuQuotaPeriod(NodeAgentContext context, ContainerId containerId, int cpuQuotaUs, int periodUs) {
- return writeCgroupsCpuInt(context, cfsQuotaPath(containerId), cpuQuotaUs) |
- writeCgroupsCpuInt(context, cfsPeriodPath(containerId), periodUs);
- }
-
- @Override
- public boolean updateCpuShares(NodeAgentContext context, ContainerId containerId, int shares) {
- return writeCgroupsCpuInt(context, sharesPath(containerId), shares);
- }
-
- @Override
- public Map<CpuStatField, Long> cpuStats(ContainerId containerId) throws IOException {
- Map<CpuStatField, Long> stats = new HashMap<>();
- stats.put(CpuStatField.TOTAL_USAGE_USEC, parseLong(cpuacctPath(containerId).resolve("cpuacct.usage")) / 1000);
- Stream.concat(Files.readAllLines(cpuacctPath(containerId).resolve("cpuacct.stat")).stream(),
- Files.readAllLines(cpuacctPath(containerId).resolve("cpu.stat")).stream())
- .forEach(line -> {
- String[] parts = line.split("\\s+");
- if (parts.length != 2) return;
- CpuStatField.fromV1Field(parts[0]).ifPresent(field -> stats.put(field, field.parseValueV1(parts[1])));
- });
- return stats;
- }
-
- @Override
- public long memoryLimitInBytes(ContainerId containerId) throws IOException {
- return parseLong(memoryPath(containerId).resolve("memory.limit_in_bytes"));
- }
-
- @Override
- public long memoryUsageInBytes(ContainerId containerId) throws IOException {
- return parseLong(memoryPath(containerId).resolve("memory.usage_in_bytes"));
- }
-
- @Override
- public long memoryCacheInBytes(ContainerId containerId) throws IOException {
- return parseLong(memoryPath(containerId).resolve("memory.stat"), "cache");
- }
-
- private Path cpuacctPath(ContainerId containerId) {
- return fileSystem.getPath("/sys/fs/cgroup/cpuacct/machine.slice/libpod-" + containerId + ".scope");
- }
-
- private Path cpuPath(ContainerId containerId) {
- return fileSystem.getPath("/sys/fs/cgroup/cpu/machine.slice/libpod-" + containerId + ".scope");
- }
-
- private Path memoryPath(ContainerId containerId) {
- return fileSystem.getPath("/sys/fs/cgroup/memory/machine.slice/libpod-" + containerId + ".scope");
- }
-
- private UnixPath cfsQuotaPath(ContainerId containerId) {
- return new UnixPath(cpuPath(containerId).resolve("cpu.cfs_quota_us"));
- }
-
- private UnixPath cfsPeriodPath(ContainerId containerId) {
- return new UnixPath(cpuPath(containerId).resolve("cpu.cfs_period_us"));
- }
-
- private UnixPath sharesPath(ContainerId containerId) {
- return new UnixPath(cpuPath(containerId).resolve("cpu.shares"));
- }
-
- private static OptionalInt readCgroupsCpuInt(UnixPath unixPath) {
- return unixPath.readUtf8FileIfExists()
- .map(s -> OptionalInt.of(Integer.parseInt(s.strip())))
- .orElseGet(OptionalInt::empty);
- }
-
- private static boolean writeCgroupsCpuInt(NodeAgentContext context, UnixPath unixPath, int value) {
- int currentValue = readCgroupsCpuInt(unixPath).orElseThrow();
- if (currentValue == value) return false;
-
- context.recordSystemModification(logger, "Updating " + unixPath + " from " + currentValue + " to " + value);
- unixPath.writeUtf8File(Integer.toString(value));
- return true;
- }
-}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2.java
index 0c86829b96d..3cb34e066ff 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2.java
@@ -2,6 +2,7 @@
package com.yahoo.vespa.hosted.node.admin.container;
import com.yahoo.collections.Pair;
+import com.yahoo.vespa.defaults.Defaults;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
@@ -9,6 +10,7 @@ import java.io.IOException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
@@ -17,23 +19,48 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
/**
- * Read and write interface to the CGroup V2 of a Podman container.
+ * Read and write interface to the cgroup v2 of a Podman container.
*
* @see <a href="https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html">CGroups V2</a>
* @author freva
*/
-public class CGroupV2 implements CGroup {
+public class CGroupV2 {
private static final Logger logger = Logger.getLogger(CGroupV2.class.getName());
private static final String MAX = "max";
- private final FileSystem fileSystem;
+ private final Path rootCgroupPath;
public CGroupV2(FileSystem fileSystem) {
- this.fileSystem = fileSystem;
- }
-
- @Override
+ this.rootCgroupPath = fileSystem.getPath("/sys/fs/cgroup");
+ }
+
+ /**
+ * Wraps {@code command} to ensure it is executed in the given cgroup.
+ *
+ * <p>WARNING: This method must be called only after vespa-cgexec has been installed.</p>
+ *
+ * @param cgroup The cgroup to execute the command in, e.g. /sys/fs/cgroup/system.slice/wireguard.scope.
+ * @param command The command to execute in the cgroup.
+ * @see #cgroupRootPath()
+ * @see #cgroupPath(ContainerId)
+ */
+ public String[] wrapForExecutionIn(Path cgroup, String... command) {
+ String[] fullCommand = new String[3 + command.length];
+ fullCommand[0] = Defaults.getDefaults().vespaHome() + "/bin/vespa-cgexec";
+ fullCommand[1] = "-g";
+ fullCommand[2] = cgroup.toString();
+ System.arraycopy(command, 0, fullCommand, 3, command.length);
+ return fullCommand;
+ }
+
+ /**
+ * Returns quota and period values used for CPU scheduling. This serves as hard cap on CPU usage by allowing
+ * the CGroupV2 to use up to {@code quota} each {@code period}. If uncapped, quota will be negative.
+ *
+ * @param containerId full container ID.
+ * @return CPU quota and period for the given container. Empty if CGroupV2 for this container is not found.
+ */
public Optional<Pair<Integer, Integer>> cpuQuotaPeriod(ContainerId containerId) {
return cpuMaxPath(containerId).readUtf8FileIfExists()
.map(s -> {
@@ -42,60 +69,89 @@ public class CGroupV2 implements CGroup {
});
}
- @Override
+ /** @return number of shares allocated to this CGroupV2 for purposes of CPU time scheduling, empty if CGroupV2 not found */
public OptionalInt cpuShares(ContainerId containerId) {
return cpuWeightPath(containerId).readUtf8FileIfExists()
.map(s -> OptionalInt.of(weightToShares(Integer.parseInt(s.strip()))))
.orElseGet(OptionalInt::empty);
}
- @Override
+ /** Update CPU quota and period for the given container ID, set quota to -1 value for unlimited */
public boolean updateCpuQuotaPeriod(NodeAgentContext context, ContainerId containerId, int cpuQuotaUs, int periodUs) {
String wanted = String.format("%s %d", cpuQuotaUs < 0 ? MAX : cpuQuotaUs, periodUs);
return writeCGroupsValue(context, cpuMaxPath(containerId), wanted);
}
- @Override
public boolean updateCpuShares(NodeAgentContext context, ContainerId containerId, int shares) {
return writeCGroupsValue(context, cpuWeightPath(containerId), Integer.toString(sharesToWeight(shares)));
}
- @Override
+ enum CpuStatField {
+ TOTAL_USAGE_USEC("usage_usec"),
+ USER_USAGE_USEC("user_usec"),
+ SYSTEM_USAGE_USEC("system_usec"),
+ TOTAL_PERIODS("nr_periods"),
+ THROTTLED_PERIODS("nr_throttled"),
+ THROTTLED_TIME_USEC("throttled_usec");
+
+ private final String name;
+
+ CpuStatField(String name) {
+ this.name = name;
+ }
+
+ long parseValue(String value) {
+ return Long.parseLong(value);
+ }
+
+ static Optional<CpuStatField> fromField(String fieldName) {
+ return Arrays.stream(values())
+ .filter(field -> fieldName.equals(field.name))
+ .findFirst();
+ }
+ }
+
public Map<CpuStatField, Long> cpuStats(ContainerId containerId) throws IOException {
- return Files.readAllLines(cgroupRoot(containerId).resolve("cpu.stat")).stream()
- .map(line -> line.split("\\s+"))
- .filter(parts -> parts.length == 2)
- .flatMap(parts -> CpuStatField.fromV2Field(parts[0]).stream().map(field -> new Pair<>(field, field.parseValueV2(parts[1]))))
- .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
+ return Files.readAllLines(cgroupPath(containerId).resolve("cpu.stat")).stream()
+ .map(line -> line.split("\\s+"))
+ .filter(parts -> parts.length == 2)
+ .flatMap(parts -> CpuStatField.fromField(parts[0]).stream().map(field -> new Pair<>(field, field.parseValue(parts[1]))))
+ .collect(Collectors.toMap(Pair::getFirst, Pair::getSecond));
}
- @Override
+ /** @return Maximum amount of memory that can be used by the cgroup and its descendants. */
public long memoryLimitInBytes(ContainerId containerId) throws IOException {
- String limit = Files.readString(cgroupRoot(containerId).resolve("memory.max")).strip();
+ String limit = Files.readString(cgroupPath(containerId).resolve("memory.max")).strip();
return MAX.equals(limit) ? -1L : Long.parseLong(limit);
}
- @Override
+ /** @return The total amount of memory currently being used by the cgroup and its descendants. */
public long memoryUsageInBytes(ContainerId containerId) throws IOException {
- return parseLong(cgroupRoot(containerId).resolve("memory.current"));
+ return parseLong(cgroupPath(containerId).resolve("memory.current"));
}
- @Override
+ /** @return Number of bytes used to cache filesystem data, including tmpfs and shared memory. */
public long memoryCacheInBytes(ContainerId containerId) throws IOException {
- return parseLong(cgroupRoot(containerId).resolve("memory.stat"), "file");
+ return parseLong(cgroupPath(containerId).resolve("memory.stat"), "file");
+ }
+
+ /** Returns the cgroup v2 mount point path (/sys/fs/cgroup). */
+ public Path cgroupRootPath() {
+ return rootCgroupPath;
}
- private Path cgroupRoot(ContainerId containerId) {
+ /** Returns the cgroup directory of the Podman container, and which appears as the root cgroup within the container. */
+ public Path cgroupPath(ContainerId containerId) {
// crun path, runc path is without the 'container' directory
- return fileSystem.getPath("/sys/fs/cgroup/machine.slice/libpod-" + containerId + ".scope/container");
+ return rootCgroupPath.resolve("machine.slice/libpod-" + containerId + ".scope/container");
}
private UnixPath cpuMaxPath(ContainerId containerId) {
- return new UnixPath(cgroupRoot(containerId).resolve("cpu.max"));
+ return new UnixPath(cgroupPath(containerId).resolve("cpu.max"));
}
private UnixPath cpuWeightPath(ContainerId containerId) {
- return new UnixPath(cgroupRoot(containerId).resolve("cpu.weight"));
+ return new UnixPath(cgroupPath(containerId).resolve("cpu.weight"));
}
private static boolean writeCGroupsValue(NodeAgentContext context, UnixPath unixPath, String value) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/Container.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/Container.java
index fb789874acf..e76a46b1c3b 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/Container.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/Container.java
@@ -51,6 +51,16 @@ public class Container extends PartialContainer {
}
@Override
+ public String toString() {
+ return "Container{" +
+ "hostname='" + hostname + '\'' +
+ ", resources=" + resources +
+ ", conmonPid=" + conmonPid +
+ ", networks=" + networks +
+ '}';
+ }
+
+ @Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
index f131aca2db0..ce2a6bb22ac 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerOperations.java
@@ -33,7 +33,7 @@ public class ContainerOperations {
private final ContainerImagePruner imagePruner;
private final ContainerStatsCollector containerStatsCollector;
- public ContainerOperations(ContainerEngine containerEngine, CGroup cgroup, FileSystem fileSystem) {
+ public ContainerOperations(ContainerEngine containerEngine, CGroupV2 cgroup, FileSystem fileSystem) {
this.containerEngine = Objects.requireNonNull(containerEngine);
this.imageDownloader = new ContainerImageDownloader(containerEngine);
this.imagePruner = new ContainerImagePruner(containerEngine, Clock.systemUTC());
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java
index c17f98b9c9d..870809123a9 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollector.java
@@ -27,15 +27,15 @@ import java.util.stream.Stream;
class ContainerStatsCollector {
private final ContainerEngine containerEngine;
- private final CGroup cgroup;
+ private final CGroupV2 cgroup;
private final FileSystem fileSystem;
private final int onlineCpus;
- ContainerStatsCollector(ContainerEngine containerEngine, CGroup cgroup, FileSystem fileSystem) {
+ ContainerStatsCollector(ContainerEngine containerEngine, CGroupV2 cgroup, FileSystem fileSystem) {
this(containerEngine, cgroup, fileSystem, Runtime.getRuntime().availableProcessors());
}
- ContainerStatsCollector(ContainerEngine containerEngine, CGroup cgroup, FileSystem fileSystem, int onlineCpus) {
+ ContainerStatsCollector(ContainerEngine containerEngine, CGroupV2 cgroup, FileSystem fileSystem, int onlineCpus) {
this.containerEngine = Objects.requireNonNull(containerEngine);
this.cgroup = Objects.requireNonNull(cgroup);
this.fileSystem = Objects.requireNonNull(fileSystem);
@@ -83,14 +83,14 @@ class ContainerStatsCollector {
}
private ContainerStats.CpuStats collectCpuStats(ContainerId containerId) throws IOException {
- Map<CGroup.CpuStatField, Long> cpuStats = cgroup.cpuStats(containerId);
+ Map<CGroupV2.CpuStatField, Long> cpuStats = cgroup.cpuStats(containerId);
return new ContainerStats.CpuStats(onlineCpus,
systemCpuUsage(),
- cpuStats.get(CGroup.CpuStatField.TOTAL_USAGE_USEC),
- cpuStats.get(CGroup.CpuStatField.SYSTEM_USAGE_USEC),
- cpuStats.get(CGroup.CpuStatField.THROTTLED_TIME_USEC),
- cpuStats.get(CGroup.CpuStatField.TOTAL_PERIODS),
- cpuStats.get(CGroup.CpuStatField.THROTTLED_PERIODS));
+ cpuStats.get(CGroupV2.CpuStatField.TOTAL_USAGE_USEC),
+ cpuStats.get(CGroupV2.CpuStatField.SYSTEM_USAGE_USEC),
+ cpuStats.get(CGroupV2.CpuStatField.THROTTLED_TIME_USEC),
+ cpuStats.get(CGroupV2.CpuStatField.TOTAL_PERIODS),
+ cpuStats.get(CGroupV2.CpuStatField.THROTTLED_PERIODS));
}
private ContainerStats.MemoryStats collectMemoryStats(ContainerId containerId) throws IOException {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/ContainerWireguardTask.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/ContainerWireguardTask.java
index 073e4263492..858b3d647fc 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/ContainerWireguardTask.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/ContainerWireguardTask.java
@@ -1,5 +1,6 @@
package com.yahoo.vespa.hosted.node.admin.maintenance;
+import com.yahoo.vespa.hosted.node.admin.container.ContainerId;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
/**
@@ -9,6 +10,6 @@ import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
*/
public interface ContainerWireguardTask {
- void converge(NodeAgentContext context);
+ void converge(NodeAgentContext context, ContainerId containerId);
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java
index 28773767d24..5d4628b41b6 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollector.java
@@ -5,6 +5,7 @@ import com.yahoo.vespa.hosted.node.admin.configserver.cores.CoreDumpMetadata;
import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
@@ -96,17 +97,19 @@ public class CoreCollector {
}
CoreDumpMetadata collect(NodeAgentContext context, ContainerPath coredumpPath) {
- var metadata = new CoreDumpMetadata();
+ var metadata = new CoreDumpMetadata()
+ .setCreated(new UnixPath(coredumpPath).getLastModifiedTime());
if (JAVA_HEAP_DUMP_PATTERN.matcher(coredumpPath.getFileName().toString()).find()) {
- metadata.setBinPath("java")
+ metadata.setType(CoreDumpMetadata.Type.JVM_HEAP)
+ .setBinPath("java")
.setBacktrace(List.of("Heap dump, no backtrace available"));
return metadata;
}
try {
String binPath = readBinPath(context, coredumpPath);
- metadata.setBinPath(binPath);
+ metadata.setType(CoreDumpMetadata.Type.CORE_DUMP).setBinPath(binPath);
if (Path.of(binPath).getFileName().toString().equals("java")) {
metadata.setBacktraceAllThreads(readJstack(context, coredumpPath, binPath));
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
index 15be7accb7d..3ab1fdf211b 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
@@ -1,6 +1,8 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.hosted.node.admin.maintenance.identity;
+import com.yahoo.component.Version;
+import com.yahoo.config.provision.ApplicationId;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyUtils;
import com.yahoo.security.Pkcs10Csr;
@@ -13,12 +15,17 @@ import com.yahoo.vespa.athenz.client.zts.ZtsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClientException;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.client.CsrGenerator;
import com.yahoo.vespa.athenz.identityprovider.client.DefaultIdentityDocumentClient;
import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
import com.yahoo.vespa.athenz.utils.SiaUtils;
+import com.yahoo.vespa.flags.BooleanFlag;
+import com.yahoo.vespa.flags.FetchVector;
+import com.yahoo.vespa.flags.FlagSource;
+import com.yahoo.vespa.flags.Flags;
import com.yahoo.vespa.hosted.node.admin.component.ConfigServerInfo;
import com.yahoo.vespa.hosted.node.admin.container.ContainerName;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
@@ -47,6 +54,9 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
+import static com.yahoo.vespa.hosted.node.admin.maintenance.identity.AthenzCredentialsMaintainer.IdentityType.NODE;
+import static com.yahoo.vespa.hosted.node.admin.maintenance.identity.AthenzCredentialsMaintainer.IdentityType.TENANT;
+
/**
* A maintainer that is responsible for providing and refreshing Athenz credentials for a container.
*
@@ -68,7 +78,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private final String certificateDnsSuffix;
private final ServiceIdentityProvider hostIdentityProvider;
private final IdentityDocumentClient identityDocumentClient;
- private final boolean useInternalZts;
+ private final BooleanFlag tenantServiceIdentityFlag;
+ private final BooleanFlag useNewIdentityDocumentLayout;
// Used as an optimization to ensure ZTS is not DDoS'ed on continuously failing refresh attempts
private final Map<ContainerName, Instant> lastRefreshAttempt = new ConcurrentHashMap<>();
@@ -78,7 +89,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
ConfigServerInfo configServerInfo,
String certificateDnsSuffix,
ServiceIdentityProvider hostIdentityProvider,
- boolean useInternalZts,
+ FlagSource flagSource,
Clock clock) {
this.ztsEndpoint = ztsEndpoint;
this.ztsTrustStorePath = ztsTrustStorePath;
@@ -89,24 +100,43 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
hostIdentityProvider,
new AthenzIdentityVerifier(Set.of(configServerInfo.getConfigServerIdentity())));
this.clock = clock;
- this.useInternalZts = useInternalZts;
+ this.tenantServiceIdentityFlag = Flags.NODE_ADMIN_TENANT_SERVICE_REGISTRY.bindTo(flagSource);
+ this.useNewIdentityDocumentLayout = Flags.NEW_IDDOC_LAYOUT.bindTo(flagSource);
}
public boolean converge(NodeAgentContext context) {
+ var modified = false;
+ modified |= maintain(context, NODE);
+
+ if (context.zone().getSystemName().isPublic())
+ return modified;
+
+ if (shouldWriteTenantServiceIdentity(context))
+ modified |= maintain(context, TENANT);
+ else
+ modified |= deleteTenantCredentials(context);
+ return modified;
+ }
+
+ private boolean maintain(NodeAgentContext context, IdentityType identityType) {
if (context.isDisabled(NodeAgentTask.CredentialsMaintainer)) return false;
try {
context.log(logger, Level.FINE, "Checking certificate");
- ContainerPath containerSiaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa());
- ContainerPath privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(containerSiaDirectory, context.identity());
- ContainerPath certificateFile = (ContainerPath) SiaUtils.getCertificateFile(containerSiaDirectory, context.identity());
- ContainerPath identityDocumentFile = containerSiaDirectory.resolve("vespa-node-identity-document.json");
+ ContainerPath siaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa());
+ ContainerPath identityDocumentFile = siaDirectory.resolve(identityType.getIdentityDocument());
+ Optional<AthenzIdentity> optionalAthenzIdentity = getAthenzIdentity(context, identityType, identityDocumentFile);
+ if (optionalAthenzIdentity.isEmpty())
+ return false;
+ AthenzIdentity athenzIdentity = optionalAthenzIdentity.get();
+ ContainerPath privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(siaDirectory, athenzIdentity);
+ ContainerPath certificateFile = (ContainerPath) SiaUtils.getCertificateFile(siaDirectory, athenzIdentity);
if (!Files.exists(privateKeyFile) || !Files.exists(certificateFile) || !Files.exists(identityDocumentFile)) {
context.log(logger, "Certificate/private key/identity document file does not exist");
Files.createDirectories(privateKeyFile.getParent());
Files.createDirectories(certificateFile.getParent());
Files.createDirectories(identityDocumentFile.getParent());
- registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile);
+ registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
return true;
}
@@ -114,13 +144,13 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
Instant now = clock.instant();
Instant expiry = certificate.getNotAfter().toInstant();
var doc = EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile);
- if (doc.outdated()) {
+ if (refreshIdentityDocument(doc, context)) {
context.log(logger, "Identity document is outdated (version=%d)", doc.documentVersion());
- registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile);
+ registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
return true;
} else if (isCertificateExpired(expiry, now)) {
context.log(logger, "Certificate has expired (expiry=%s)", expiry.toString());
- registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile);
+ registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, athenzIdentity);
return true;
}
@@ -134,7 +164,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
return false;
} else {
lastRefreshAttempt.put(context.containerName(), now);
- refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc);
+ refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc.identityDocument(), identityType, athenzIdentity);
return true;
}
}
@@ -145,6 +175,11 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
}
}
+ private boolean refreshIdentityDocument(SignedIdentityDocument signedIdentityDocument, NodeAgentContext context) {
+ int expectedVersion = documentVersion(context);
+ return signedIdentityDocument.outdated() || signedIdentityDocument.documentVersion() != expectedVersion;
+ }
+
public void clearCredentials(NodeAgentContext context) {
FileFinder.files(context.paths().of(CONTAINER_SIA_DIRECTORY))
.deleteRecursively(context);
@@ -171,6 +206,23 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
return "node-certificate";
}
+ private boolean deleteTenantCredentials(NodeAgentContext context) {
+ var siaDirectory = context.paths().of(CONTAINER_SIA_DIRECTORY, context.users().vespa());
+ var identityDocumentFile = siaDirectory.resolve(TENANT.getIdentityDocument());
+ if (!Files.exists(identityDocumentFile)) return false;
+ return getAthenzIdentity(context, TENANT, identityDocumentFile).map(athenzIdentity -> {
+ var privateKeyFile = (ContainerPath) SiaUtils.getPrivateKeyFile(siaDirectory, athenzIdentity);
+ var certificateFile = (ContainerPath) SiaUtils.getCertificateFile(siaDirectory, athenzIdentity);
+ try {
+ return Files.deleteIfExists(identityDocumentFile) ||
+ Files.deleteIfExists(privateKeyFile) ||
+ Files.deleteIfExists(certificateFile);
+ } catch (IOException e) {
+ throw new UncheckedIOException(e);
+ }
+ }).orElse(false);
+ }
+
private boolean shouldRefreshCredentials(Duration age) {
return age.compareTo(REFRESH_PERIOD) >= 0;
}
@@ -182,12 +234,13 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
now)) > 0;
}
- private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile) {
+ private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile, IdentityType identityType, AthenzIdentity identity) {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- SignedIdentityDocument doc = identityDocumentClient.getNodeIdentityDocument(context.hostname().value());
+ SignedIdentityDocument signedDoc = signedIdentityDocument(context, identityType);
+ IdentityDocument doc = signedDoc.identityDocument();
CsrGenerator csrGenerator = new CsrGenerator(certificateDnsSuffix, doc.providerService().getFullName());
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
- context.identity(), doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
+ identity, doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
// Allow all zts hosts while removing SIS
HostnameVerifier ztsHostNameVerifier = (hostname, sslSession) -> true;
@@ -195,10 +248,10 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
InstanceIdentity instanceIdentity =
ztsClient.registerInstance(
doc.providerService(),
- context.identity(),
- EntityBindingsMapper.toAttestationData(doc),
+ identity,
+ EntityBindingsMapper.toAttestationData(signedDoc),
csr);
- EntityBindingsMapper.writeSignedIdentityDocumentToFile(identityDocumentFile, doc);
+ EntityBindingsMapper.writeSignedIdentityDocumentToFile(identityDocumentFile, signedDoc);
writePrivateKeyAndCertificate(privateKeyFile, keyPair.getPrivate(), certificateFile, instanceIdentity.certificate());
context.log(logger, "Instance successfully registered and credentials written to file");
}
@@ -207,18 +260,18 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
/**
* Return zts url from identity document, fallback to ztsEndpoint
*/
- private URI ztsEndpoint(SignedIdentityDocument doc) {
+ private URI ztsEndpoint(IdentityDocument doc) {
return Optional.ofNullable(doc.ztsUrl())
.filter(s -> !s.isBlank())
.map(URI::create)
.orElse(ztsEndpoint);
}
private void refreshIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile,
- ContainerPath identityDocumentFile, SignedIdentityDocument doc) {
+ ContainerPath identityDocumentFile, IdentityDocument doc, IdentityType identityType, AthenzIdentity identity) {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
CsrGenerator csrGenerator = new CsrGenerator(certificateDnsSuffix, doc.providerService().getFullName());
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
- context.identity(), doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
+ identity, doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
SSLContext containerIdentitySslContext = new SslContextBuilder().withKeyStore(privateKeyFile, certificateFile)
.withTrustStore(ztsTrustStorePath)
@@ -231,7 +284,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
InstanceIdentity instanceIdentity =
ztsClient.refreshInstance(
doc.providerService(),
- context.identity(),
+ identity,
doc.providerUniqueId().asDottedString(),
csr);
writePrivateKeyAndCertificate(privateKeyFile, keyPair.getPrivate(), certificateFile, instanceIdentity.certificate());
@@ -239,7 +292,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
} catch (ZtsClientException e) {
if (e.getErrorCode() == 403 && e.getDescription().startsWith("Certificate revoked")) {
context.log(logger, Level.SEVERE, "Certificate cannot be refreshed as it is revoked by ZTS - re-registering the instance now", e);
- registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile);
+ registerIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, identityType, identity);
} else {
throw e;
}
@@ -272,4 +325,62 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private static boolean isCertificateExpired(Instant expiry, Instant now) {
return now.isAfter(expiry.minus(EXPIRY_MARGIN));
}
+
+ private SignedIdentityDocument signedIdentityDocument(NodeAgentContext context, IdentityType identityType) {
+ return switch (identityType) {
+ case NODE -> identityDocumentClient.getNodeIdentityDocument(context.hostname().value(), documentVersion(context));
+ case TENANT -> identityDocumentClient.getTenantIdentityDocument(context.hostname().value(), documentVersion(context)).get();
+ };
+ }
+
+ private Optional<AthenzIdentity> getAthenzIdentity(NodeAgentContext context, IdentityType identityType, ContainerPath identityDocumentFile) {
+ return switch (identityType) {
+ case NODE -> Optional.of(context.identity());
+ case TENANT -> getTenantIdentity(context, identityDocumentFile);
+ };
+ }
+
+ private Optional<AthenzIdentity> getTenantIdentity(NodeAgentContext context, ContainerPath identityDocumentFile) {
+ if (Files.exists(identityDocumentFile)) {
+ return Optional.of(EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile).identityDocument().serviceIdentity());
+ } else {
+ return identityDocumentClient.getTenantIdentityDocument(context.hostname().value(), documentVersion(context))
+ .map(doc -> doc.identityDocument().serviceIdentity());
+ }
+ }
+
+ private boolean shouldWriteTenantServiceIdentity(NodeAgentContext context) {
+ var version = context.node().currentVespaVersion()
+ .orElse(context.node().wantedVespaVersion().orElse(Version.emptyVersion));
+ var appId = context.node().owner().orElse(ApplicationId.defaultId());
+ return tenantServiceIdentityFlag
+ .with(FetchVector.Dimension.VESPA_VERSION, version.toFullString())
+ .with(FetchVector.Dimension.APPLICATION_ID, appId.serializedForm())
+ .value();
+ }
+
+ /*
+ Get the document version to ask for
+ */
+ private int documentVersion(NodeAgentContext context) {
+ return useNewIdentityDocumentLayout
+ .with(FetchVector.Dimension.HOSTNAME, context.hostname().value())
+ .value()
+ ? SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION
+ : SignedIdentityDocument.LEGACY_DEFAULT_DOCUMENT_VERSION;
+ }
+
+ enum IdentityType {
+ NODE("vespa-node-identity-document.json"),
+ TENANT("vespa-tenant-identity-document.json");
+
+ private String identityDocument;
+ IdentityType(String identityDocument) {
+ this.identityDocument = identityDocument;
+ }
+
+ public String getIdentityDocument() {
+ return identityDocument;
+ }
+ }
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfo.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfo.java
index 3a12191a0de..c743f1c8c85 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfo.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfo.java
@@ -71,11 +71,14 @@ public class SyncFileInfo {
if (filename.startsWith("vespa.log")) {
dir = "logs/vespa/";
compression = Compression.ZSTD;
- minDurationBetweenSync = filename.length() == 9 ? rotatedOnly ? Duration.ofHours(1) : Duration.ZERO : null;
+ if (filename.length() == 9) {
+ if (!rotatedOnly) remoteFilename = "vespa.log-" + DATE_TIME_FORMATTER.format(new UnixPath(logFile).getLastModifiedTime());
+ minDurationBetweenSync = rotatedOnly ? Duration.ofHours(1) : Duration.ZERO;
+ }
} else if (filename.startsWith("zookeeper.") && filename.endsWith(".log")) {
compression = Compression.ZSTD;
dir = "logs/zookeeper/";
- remoteFilename = filename.endsWith(".0.log") ? "zookeeper.log" :
+ remoteFilename = rotatedOnly && filename.endsWith(".0.log") ? "zookeeper.log" :
"zookeeper.log-" + DATE_TIME_FORMATTER.format(new UnixPath(logFile).getLastModifiedTime());
minDurationBetweenSync = filename.endsWith(".0.log") ? rotatedOnly ? Duration.ofHours(1) : Duration.ZERO : null;
} else {
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 20359410321..025a04a15d6 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
@@ -16,6 +16,7 @@ import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeMembers
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeRepository;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeState;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.reports.DropDocumentsReport;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.container.Container;
@@ -29,6 +30,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.identity.CredentialsMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.VespaServiceDumper;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.FileFinder;
import java.time.Clock;
import java.time.Duration;
@@ -228,6 +230,12 @@ public class NodeAgentImpl implements NodeAgent {
changed = true;
}
+ Optional<DropDocumentsReport> report = context.node().reports().getReport(DropDocumentsReport.reportId(), DropDocumentsReport.class);
+ if (report.isPresent() && report.get().startedAt() == null && report.get().readiedAt() != null) {
+ newNodeAttributes.withReport(DropDocumentsReport.reportId(), report.get().withStartedAt(clock.millis()).toJsonNode());
+ changed = true;
+ }
+
if (changed) {
context.log(logger, "Publishing new set of attributes to node repo: %s -> %s",
currentNodeAttributes, newNodeAttributes);
@@ -433,6 +441,21 @@ public class NodeAgentImpl implements NodeAgent {
.orElse(false);
}
+ private void dropDocsIfNeeded(NodeAgentContext context, Optional<Container> container) {
+ Optional<DropDocumentsReport> report = context.node().reports()
+ .getReport(DropDocumentsReport.reportId(), DropDocumentsReport.class);
+ if (report.isEmpty() || report.get().readiedAt() != null) return;
+
+ if (report.get().droppedAt() == null) {
+ container.ifPresent(c -> removeContainer(context, c, List.of("Dropping documents"), true));
+ FileFinder.from(context.paths().underVespaHome("var/db/vespa/search")).deleteRecursively(context);
+ nodeRepository.updateNodeAttributes(context.node().hostname(),
+ new NodeAttributes().withReport(DropDocumentsReport.reportId(), report.get().withDroppedAt(clock.millis()).toJsonNode()));
+ }
+
+ throw ConvergenceException.ofTransient("Documents already dropped, waiting for signal to start the container");
+ }
+
public void converge(NodeAgentContext context) {
try {
doConverge(context);
@@ -483,30 +506,24 @@ public class NodeAgentImpl implements NodeAgent {
storageMaintainer.cleanDiskIfFull(context);
storageMaintainer.handleCoreDumpsForContainer(context, container, false);
- // TODO: this is a workaround for restarting wireguard as early as possible after host-admin has been down.
- var runOrdinaryWireguardTasks = true;
- if (container.isPresent() && container.get().state().isRunning()) {
- wireguardTasks.forEach(task -> task.converge(context));
- runOrdinaryWireguardTasks = false;
- }
-
if (downloadImageIfNeeded(context, container)) {
context.log(logger, "Waiting for image to download " + context.node().wantedDockerImage().get().asString());
return;
}
+ dropDocsIfNeeded(context, container);
container = removeContainerIfNeededUpdateContainerState(context, container);
credentialsMaintainers.forEach(maintainer -> maintainer.converge(context));
if (container.isEmpty()) {
containerState = STARTING;
container = Optional.of(startContainer(context));
containerState = UNKNOWN;
- runOrdinaryWireguardTasks = true;
} else {
container = Optional.of(updateContainerIfNeeded(context, container.get()));
}
aclMaintainer.ifPresent(maintainer -> maintainer.converge(context));
- if (runOrdinaryWireguardTasks) wireguardTasks.forEach(task -> task.converge(context));
+ final Optional<Container> finalContainer = container;
+ wireguardTasks.forEach(task -> task.converge(context, finalContainer.get().id()));
startServicesIfNeeded(context);
resumeNodeIfNeeded(context);
if (healthChecker.isPresent()) {
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddressesImpl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddressesImpl.java
index 1b5e6c57f5c..b7a1cb8a16e 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddressesImpl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/network/IPAddressesImpl.java
@@ -1,6 +1,7 @@
// Copyright Yahoo. 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.network;
+import java.io.UncheckedIOException;
import java.net.InetAddress;
import java.net.UnknownHostException;
@@ -14,7 +15,7 @@ public class IPAddressesImpl implements IPAddresses {
try {
return InetAddress.getAllByName(hostname);
} catch (UnknownHostException e) {
- throw new RuntimeException(e);
+ throw new UncheckedIOException(e);
}
}
}
diff --git a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
index c7d34a12f43..9662d4184df 100644
--- a/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
+++ b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/task/util/systemd/SystemCtl.java
@@ -91,7 +91,7 @@ public class SystemCtl {
public String getServiceProperty(TaskContext context, String unit, String property) {
return newCommandLine(context)
.add("systemctl", "show", "--property", property, "--value", unit + ".service")
- .execute()
+ .executeSilently()
.getOutput();
}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java
index f49dd2e705b..b35f4d6c790 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/configserver/cores/CoresTest.java
@@ -16,6 +16,7 @@ import org.mockito.ArgumentCaptor;
import java.nio.file.FileSystem;
import java.nio.file.Path;
+import java.time.Instant;
import java.util.List;
import java.util.Optional;
@@ -40,6 +41,8 @@ class CoresTest {
private final HostName hostname = HostName.of("foo.com");
private final String id = "5c987afb-347a-49ee-a0c5-bef56bbddeb0";
private final CoreDumpMetadata metadata = new CoreDumpMetadata()
+ .setType(CoreDumpMetadata.Type.OOM)
+ .setCreated(Instant.ofEpochMilli(12345678))
.setKernelVersion("4.18.0-372.26.1.el8_6.x86_64")
.setCpuMicrocodeVersion("0x1000065")
.setCoreDumpPath(fileSystem.getPath("/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813"))
@@ -83,9 +86,11 @@ class CoresTest {
"bin_path": "/usr/bin/java",
"coredump_path": "/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813",
"cpu_microcode_version": "0x1000065",
+ "created": 12345678,
"decryption_token": "987def",
"docker_image": "us-central1-docker.pkg.dev/vespa-external-cd/vespa-cloud/vespa/cloud-tenant-rhel8:8.68.8",
"kernel_version": "4.18.0-372.26.1.el8_6.x86_64",
+ "type": "OOM",
"vespa_version": "8.68.8"
}""",
JsonTestHelper.normalize(uncheck(() -> mapper.writeValueAsString(bodyJsonPojoCaptor.getValue()))));
@@ -128,9 +133,11 @@ class CoresTest {
"bin_path": "/usr/bin/java",
"coredump_path": "/data/vespa/processed-coredumps/h7641a/5c987afb-347a-49ee-a0c5-bef56bbddeb0/dump_java.core.813",
"cpu_microcode_version": "0x1000065",
+ "created": 12345678,
"decryption_token": "987def",
"docker_image": "us-central1-docker.pkg.dev/vespa-external-cd/vespa-cloud/vespa/cloud-tenant-rhel8:8.68.8",
"kernel_version": "4.18.0-372.26.1.el8_6.x86_64",
+ "type": "OOM",
"vespa_version": "8.68.8"
}""",
JsonTestHelper.normalize(new UnixPath(path).readUtf8File()));
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1Test.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1Test.java
deleted file mode 100644
index f25001d77cd..00000000000
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV1Test.java
+++ /dev/null
@@ -1,100 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.vespa.hosted.node.admin.container;
-
-import com.yahoo.collections.Pair;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
-import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
-import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
-import com.yahoo.vespa.test.file.TestFileSystem;
-import org.junit.jupiter.api.Test;
-
-import java.io.IOException;
-import java.nio.file.FileSystem;
-import java.util.Map;
-import java.util.Optional;
-import java.util.OptionalInt;
-
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.SYSTEM_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_TIME_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.USER_USAGE_USEC;
-import static org.junit.jupiter.api.Assertions.assertEquals;
-import static org.junit.jupiter.api.Assertions.assertFalse;
-import static org.junit.jupiter.api.Assertions.assertTrue;
-
-/**
- * @author freva
- */
-public class CGroupV1Test {
-
- private static final ContainerId containerId = new ContainerId("4aec78cc");
-
- private final FileSystem fileSystem = TestFileSystem.create();
- private final CGroup cgroup = new CGroupV1(fileSystem);
- private final NodeAgentContext context = NodeAgentContextImpl.builder("node123.yahoo.com").fileSystem(fileSystem).build();
-
- @Test
- public void updates_cpu_quota_and_period() {
- assertEquals(Optional.empty(), cgroup.cpuQuotaPeriod(containerId));
-
- UnixPath cpu = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/cpu/machine.slice/libpod-4aec78cc.scope")).createDirectories();
- cpu.resolve("cpu.cfs_period_us").writeUtf8File("123456\n");
- cpu.resolve("cpu.cfs_quota_us").writeUtf8File("-1\n");
- assertEquals(Optional.of(new Pair<>(-1, 123456)), cgroup.cpuQuotaPeriod(containerId));
-
- cpu.resolve("cpu.cfs_quota_us").writeUtf8File("456\n");
- assertEquals(Optional.of(new Pair<>(456, 123456)), cgroup.cpuQuotaPeriod(containerId));
-
- assertFalse(cgroup.updateCpuQuotaPeriod(context, containerId, 456, 123456));
-
- assertTrue(cgroup.updateCpuQuotaPeriod(context, containerId, 654, 123456));
- assertEquals(Optional.of(new Pair<>(654, 123456)), cgroup.cpuQuotaPeriod(containerId));
- }
-
- @Test
- public void updates_cpu_shares() {
- assertEquals(OptionalInt.empty(), cgroup.cpuShares(containerId));
-
- UnixPath cpuPath = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/cpu/machine.slice/libpod-4aec78cc.scope")).createDirectories();
- cpuPath.resolve("cpu.shares").writeUtf8File("987\n");
- assertEquals(OptionalInt.of(987), cgroup.cpuShares(containerId));
-
- assertFalse(cgroup.updateCpuShares(context, containerId, 987));
-
- assertTrue(cgroup.updateCpuShares(context, containerId, 789));
- assertEquals(OptionalInt.of(789), cgroup.cpuShares(containerId));
- }
-
- @Test
- public void reads_cpu_stats() throws IOException {
- UnixPath cpuacctPath = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/cpuacct/machine.slice/libpod-4aec78cc.scope")).createDirectories();
- cpuacctPath.resolve("cpuacct.usage").writeUtf8File("91623711445\n");
- cpuacctPath.resolve("cpuacct.stat").writeUtf8File("user 7463\n" +
- "system 1741\n");
- cpuacctPath.resolve("cpu.stat").writeUtf8File("nr_periods 2361\n" +
- "nr_throttled 342\n" +
- "throttled_time 131033468519\n");
-
- assertEquals(Map.of(TOTAL_USAGE_USEC, 91623711L, SYSTEM_USAGE_USEC, 17410000L, USER_USAGE_USEC, 74630000L,
- TOTAL_PERIODS, 2361L, THROTTLED_PERIODS, 342L, THROTTLED_TIME_USEC, 131033468L), cgroup.cpuStats(containerId));
- }
-
- @Test
- public void reads_memory_metrics() throws IOException {
- UnixPath memoryPath = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/memory/machine.slice/libpod-4aec78cc.scope")).createDirectories();
- memoryPath.resolve("memory.usage_in_bytes").writeUtf8File("2525093888\n");
- assertEquals(2525093888L, cgroup.memoryUsageInBytes(containerId));
-
- memoryPath.resolve("memory.limit_in_bytes").writeUtf8File("4322885632\n");
- assertEquals(4322885632L, cgroup.memoryLimitInBytes(containerId));
-
- memoryPath.resolve("memory.stat").writeUtf8File("cache 296828928\n" +
- "rss 2152587264\n" +
- "rss_huge 1107296256\n" +
- "shmem 135168\n" +
- "mapped_file 270336\n");
- assertEquals(296828928L, cgroup.memoryCacheInBytes(containerId));
- }
-}
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2Test.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2Test.java
index 909979342ea..789f31f75c6 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2Test.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/CGroupV2Test.java
@@ -14,12 +14,12 @@ import java.util.Map;
import java.util.Optional;
import java.util.OptionalInt;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.SYSTEM_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_TIME_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.USER_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.SYSTEM_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.THROTTLED_PERIODS;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.THROTTLED_TIME_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.TOTAL_PERIODS;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.TOTAL_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.USER_USAGE_USEC;
import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.sharesToWeight;
import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.weightToShares;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -34,7 +34,7 @@ public class CGroupV2Test {
private static final ContainerId containerId = new ContainerId("4aec78cc");
private final FileSystem fileSystem = TestFileSystem.create();
- private final CGroup cgroup = new CGroupV2(fileSystem);
+ private final CGroupV2 cgroup = new CGroupV2(fileSystem);
private final NodeAgentContext context = NodeAgentContextImpl.builder("node123.yahoo.com").fileSystem(fileSystem).build();
private final UnixPath cgroupRoot = new UnixPath(fileSystem.getPath("/sys/fs/cgroup/machine.slice/libpod-4aec78cc.scope/container")).createDirectories();
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java
index f852eb6235d..72c5d016a47 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/container/ContainerStatsCollectorTest.java
@@ -17,12 +17,12 @@ import java.util.List;
import java.util.Map;
import java.util.Optional;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.SYSTEM_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.THROTTLED_TIME_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_PERIODS;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.TOTAL_USAGE_USEC;
-import static com.yahoo.vespa.hosted.node.admin.container.CGroup.CpuStatField.USER_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.SYSTEM_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.THROTTLED_PERIODS;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.THROTTLED_TIME_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.TOTAL_PERIODS;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.TOTAL_USAGE_USEC;
+import static com.yahoo.vespa.hosted.node.admin.container.CGroupV2.CpuStatField.USER_USAGE_USEC;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.eq;
@@ -37,7 +37,7 @@ public class ContainerStatsCollectorTest {
private final TestTerminal testTerminal = new TestTerminal();
private final ContainerEngineMock containerEngine = new ContainerEngineMock(testTerminal);
private final FileSystem fileSystem = TestFileSystem.create();
- private final CGroup cgroup = mock(CGroup.class);
+ private final CGroupV2 cgroup = mock(CGroupV2.class);
private final NodeAgentContext context = NodeAgentContextImpl.builder(NodeSpec.Builder.testSpec("c1").build())
.fileSystem(TestFileSystem.create())
.build();
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
index 4fa18c71da0..b4a35d6012c 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/coredump/CoreCollectorTest.java
@@ -5,11 +5,13 @@ import com.yahoo.vespa.hosted.node.admin.configserver.cores.CoreDumpMetadata;
import com.yahoo.vespa.hosted.node.admin.container.ContainerOperations;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContext;
import com.yahoo.vespa.hosted.node.admin.nodeagent.NodeAgentContextImpl;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
import com.yahoo.vespa.hosted.node.admin.task.util.fs.ContainerPath;
import com.yahoo.vespa.hosted.node.admin.task.util.process.CommandResult;
import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.jupiter.api.Test;
+import java.time.Instant;
import java.util.List;
import static com.yahoo.vespa.hosted.node.admin.maintenance.coredump.CoreCollector.GDB_PATH_RHEL8;
@@ -22,12 +24,18 @@ import static org.mockito.Mockito.when;
* @author freva
*/
public class CoreCollectorTest {
+ private static final Instant CORE_CREATED = Instant.ofEpochMilli(2233445566L);
+
private final ContainerOperations docker = mock(ContainerOperations.class);
private final CoreCollector coreCollector = new CoreCollector(docker);
private final NodeAgentContext context = NodeAgentContextImpl.builder("container-123.domain.tld")
.fileSystem(TestFileSystem.create()).build();
- private final ContainerPath TEST_CORE_PATH = context.paths().of("/tmp/core.1234");
+ private final ContainerPath TEST_CORE_PATH = (ContainerPath) new UnixPath(context.paths().of("/tmp/core.1234"))
+ .createParents()
+ .createNewFile()
+ .setLastModifiedTime(CORE_CREATED)
+ .toPath();
private final String TEST_BIN_PATH = "/usr/bin/program";
private final List<String> GDB_BACKTRACE = List.of("[New Thread 2703]",
"Core was generated by `/usr/bin/program\'.", "Program terminated with signal 11, Segmentation fault.",
@@ -143,6 +151,8 @@ public class CoreCollectorTest {
String.join("\n", GDB_BACKTRACE));
var expected = new CoreDumpMetadata().setBinPath(TEST_BIN_PATH)
+ .setCreated(CORE_CREATED)
+ .setType(CoreDumpMetadata.Type.CORE_DUMP)
.setBacktrace(GDB_BACKTRACE)
.setBacktraceAllThreads(GDB_BACKTRACE);
assertEquals(expected, coreCollector.collect(context, TEST_CORE_PATH));
@@ -156,7 +166,7 @@ public class CoreCollectorTest {
mockExec(new String[]{GDB_PATH_RHEL8 + " -n -ex set print frame-arguments none -ex bt -batch /usr/bin/program /tmp/core.1234"},
"", "Failure");
- var expected = new CoreDumpMetadata().setBinPath(TEST_BIN_PATH);
+ var expected = new CoreDumpMetadata().setBinPath(TEST_BIN_PATH).setCreated(CORE_CREATED).setType(CoreDumpMetadata.Type.CORE_DUMP);
assertEquals(expected, coreCollector.collect(context, TEST_CORE_PATH));
}
@@ -174,6 +184,8 @@ public class CoreCollectorTest {
jstack);
var expected = new CoreDumpMetadata().setBinPath(jdkPath)
+ .setCreated(CORE_CREATED)
+ .setType(CoreDumpMetadata.Type.CORE_DUMP)
.setBacktraceAllThreads(List.of(jstack));
assertEquals(expected, coreCollector.collect(context, TEST_CORE_PATH));
}
@@ -181,9 +193,14 @@ public class CoreCollectorTest {
@Test
void metadata_for_java_heap_dump() {
var expected = new CoreDumpMetadata().setBinPath("java")
+ .setType(CoreDumpMetadata.Type.JVM_HEAP)
+ .setCreated(CORE_CREATED)
.setBacktrace(List.of("Heap dump, no backtrace available"));
- assertEquals(expected, coreCollector.collect(context, context.paths().of("/dump_java_pid123.hprof")));
+ assertEquals(expected, coreCollector.collect(context, (ContainerPath) new UnixPath(context.paths().of("/dump_java_pid123.hprof"))
+ .createNewFile()
+ .setLastModifiedTime(CORE_CREATED)
+ .toPath()));
}
private void mockExec(String[] cmd, String output) {
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfoTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfoTest.java
index 3c91a9f32d1..b7aee6706b1 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfoTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/maintenance/sync/SyncFileInfoTest.java
@@ -74,8 +74,9 @@ public class SyncFileInfoTest {
@Test
void vespa_logs() {
+ new UnixPath(vespaLogPath1).createParents().createNewFile().setLastModifiedTime(Instant.parse("2022-05-09T14:22:11Z"));
assertForLogFile(vespaLogPath1, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/vespa/vespa.log.zst", ZSTD, Duration.ofHours(1), true);
- assertForLogFile(vespaLogPath1, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/vespa/vespa.log.zst", ZSTD, Duration.ZERO, false);
+ assertForLogFile(vespaLogPath1, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/vespa/vespa.log-2022-05-09.14-22-11.zst", ZSTD, Duration.ZERO, false);
assertForLogFile(vespaLogPath2, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/vespa/vespa.log-2021-02-12.zst", ZSTD, true);
assertForLogFile(vespaLogPath2, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/vespa/vespa.log-2021-02-12.zst", ZSTD, false);
@@ -83,8 +84,9 @@ public class SyncFileInfoTest {
@Test
void zookeeper_logs() {
+ new UnixPath(zkLogPath0).createParents().createNewFile().setLastModifiedTime(Instant.parse("2022-05-13T13:13:45Z"));
assertForLogFile(zkLogPath0, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/zookeeper/zookeeper.log.zst", ZSTD, Duration.ofHours(1), true);
- assertForLogFile(zkLogPath0, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/zookeeper/zookeeper.log.zst", ZSTD, Duration.ZERO, false);
+ assertForLogFile(zkLogPath0, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/zookeeper/zookeeper.log-2022-05-13.13-13-45.zst", ZSTD, Duration.ZERO, false);
new UnixPath(zkLogPath1).createParents().createNewFile().setLastModifiedTime(Instant.parse("2022-05-09T14:22:11Z"));
assertForLogFile(zkLogPath1, "s3://vespa-data-bucket/vespa/music/main/h432a/logs/zookeeper/zookeeper.log-2022-05-09.14-22-11.zst", ZSTD, true);
diff --git a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
index b8b72308bdd..2db5314dbf2 100644
--- a/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
+++ b/node-admin/src/test/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImplTest.java
@@ -14,6 +14,7 @@ import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeReposit
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeSpec;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.NodeState;
import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.OrchestratorStatus;
+import com.yahoo.vespa.hosted.node.admin.configserver.noderepository.reports.DropDocumentsReport;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.Orchestrator;
import com.yahoo.vespa.hosted.node.admin.configserver.orchestrator.OrchestratorException;
import com.yahoo.vespa.hosted.node.admin.container.Container;
@@ -27,6 +28,7 @@ import com.yahoo.vespa.hosted.node.admin.maintenance.acl.AclMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.identity.CredentialsMaintainer;
import com.yahoo.vespa.hosted.node.admin.maintenance.servicedump.VespaServiceDumper;
import com.yahoo.vespa.hosted.node.admin.nodeadmin.ConvergenceException;
+import com.yahoo.vespa.hosted.node.admin.task.util.file.UnixPath;
import com.yahoo.vespa.test.file.TestFileSystem;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
@@ -38,8 +40,11 @@ import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.Optional;
+import java.util.function.BiFunction;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
@@ -739,6 +744,56 @@ public class NodeAgentImplTest {
inOrder.verify(orchestrator, times(1)).resume(eq(hostName));
}
+ @Test
+ void drop_all_documents() {
+ InOrder inOrder = inOrder(orchestrator, nodeRepository);
+ BiFunction<NodeState, DropDocumentsReport, NodeSpec> specBuilder = (state, report) -> (report == null ?
+ nodeBuilder(state) : nodeBuilder(state).report(DropDocumentsReport.reportId(), report.toJsonNode()))
+ .wantedDockerImage(dockerImage).currentDockerImage(dockerImage)
+ .build();
+ NodeAgentImpl nodeAgent = makeNodeAgent(dockerImage, true, Duration.ofSeconds(30));
+
+ NodeAgentContext context = createContext(specBuilder.apply(NodeState.active, null));
+ UnixPath indexPath = new UnixPath(context.paths().underVespaHome("var/db/vespa/search/cluster.foo/0/doc")).createParents().createNewFile();
+ mockGetContainer(dockerImage, ContainerResources.from(2, 2, 16), true);
+ assertTrue(indexPath.exists());
+
+ // Initially no changes, index is not dropped
+ nodeAgent.converge(context);
+ assertTrue(indexPath.exists());
+ inOrder.verifyNoMoreInteractions();
+
+ context = createContext(specBuilder.apply(NodeState.active, new DropDocumentsReport(1L, null, null, null)));
+ nodeAgent.converge(context);
+ verify(containerOperations).removeContainer(eq(context), any());
+ assertFalse(indexPath.exists());
+ inOrder.verify(nodeRepository).updateNodeAttributes(eq(hostName), eq(new NodeAttributes().withReport(DropDocumentsReport.reportId(), new DropDocumentsReport(1L, clock.millis(), null, null).toJsonNode())));
+ inOrder.verifyNoMoreInteractions();
+
+ // After droppedAt and before readiedAt are set, we cannot proceed
+ mockGetContainer(null, false);
+ context = createContext(specBuilder.apply(NodeState.active, new DropDocumentsReport(1L, 2L, null, null)));
+ nodeAgent.converge(context);
+ verify(containerOperations, never()).removeContainer(eq(context), any());
+ verify(containerOperations, never()).startContainer(eq(context));
+ inOrder.verifyNoMoreInteractions();
+
+ context = createContext(specBuilder.apply(NodeState.active, new DropDocumentsReport(1L, 2L, 3L, null)));
+ nodeAgent.converge(context);
+ verify(containerOperations).startContainer(eq(context));
+ inOrder.verifyNoMoreInteractions();
+
+ mockGetContainer(dockerImage, ContainerResources.from(0, 2, 16), true);
+ clock.advance(Duration.ofSeconds(31));
+ nodeAgent.converge(context);
+ verify(containerOperations, times(1)).startContainer(eq(context));
+ verify(containerOperations, never()).removeContainer(eq(context), any());
+ inOrder.verify(nodeRepository).updateNodeAttributes(eq(hostName), eq(new NodeAttributes()
+ .withRebootGeneration(0)
+ .withReport(DropDocumentsReport.reportId(), new DropDocumentsReport(1L, 2L, 3L, clock.millis()).toJsonNode())));
+ inOrder.verifyNoMoreInteractions();
+ }
+
private void verifyThatContainerIsStopped(NodeState nodeState, Optional<ApplicationId> owner) {
NodeSpec.Builder nodeBuilder = nodeBuilder(nodeState)
.type(NodeType.tenant)
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 4c9fab748d1..856d6e07156 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
@@ -59,7 +59,7 @@ public class AutoscalingMaintainer extends NodeRepositoryMaintainer {
failures++;
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
index c8b736cb25b..f3ea326a3c0 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/CapacityChecker.java
@@ -130,7 +130,7 @@ public class CapacityChecker {
Set<String> ipPool = host.ipConfig().pool().asSet();
for (var child : nodeChildren.get(host)) {
hostResources = hostResources.subtract(child.resources().justNumbers());
- occupiedIps += child.ipConfig().primary().stream().filter(ipPool::contains).count();
+ occupiedIps += (int)child.ipConfig().primary().stream().filter(ipPool::contains).count();
}
availableResources.put(host, new AllocationResources(hostResources, host.ipConfig().pool().asSet().size() - occupiedIps));
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DiskReplacer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DiskReplacer.java
index acd5cb61d81..6f2eb726e91 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DiskReplacer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/DiskReplacer.java
@@ -51,6 +51,6 @@ public class DiskReplacer extends NodeRepositoryMaintainer {
log.log(Level.WARNING, "Failed to rebuild " + host.hostname() + ", will retry in " + interval(), e);
}
}
- return this.asSuccessFactor(nodes.size(), failures);
+ return this.asSuccessFactorDeviation(nodes.size(), failures);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java
index 83dadddf76c..a4bc3a1aea5 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostCapacityMaintainer.java
@@ -113,7 +113,7 @@ public class HostCapacityMaintainer extends NodeRepositoryMaintainer {
}
success++;
}
- return asSuccessFactor(attempts, attempts - success);
+ return asSuccessFactorDeviation(attempts, attempts - success);
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostDeprovisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostDeprovisioner.java
index 7ecfc8f7926..0fa16f22061 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostDeprovisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostDeprovisioner.java
@@ -53,7 +53,7 @@ public class HostDeprovisioner extends NodeRepositoryMaintainer {
log.log(Level.WARNING, "Failed to deprovision " + host.hostname() + ", will retry in " + interval(), e);
}
}
- return asSuccessFactor(hosts.size(), failures);
+ return asSuccessFactorDeviation(hosts.size(), failures);
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java
index 86c5a926900..3c77725298d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/HostResumeProvisioner.java
@@ -63,7 +63,7 @@ public class HostResumeProvisioner extends NodeRepositoryMaintainer {
}
}
}
- return asSuccessFactor(hosts.size(), failures);
+ return asSuccessFactorDeviation(hosts.size(), failures);
}
private void setIpConfig(Node host, NodeList children, HostIpConfig hostIpConfig) {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
index f864ab18920..baa2e596b36 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/LoadBalancerExpirer.java
@@ -94,7 +94,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
.collect(Collectors.joining(", ")),
interval()));
}
- return asSuccessFactor(attempts.get(), failed.size());
+ return asSuccessFactorDeviation(attempts.get(), failed.size());
}
/** Remove reals from inactive load balancers */
@@ -131,7 +131,7 @@ public class LoadBalancerExpirer extends NodeRepositoryMaintainer {
interval()),
lastException.get());
}
- return asSuccessFactor(attempts.get(), failed.size());
+ return asSuccessFactorDeviation(attempts.get(), failed.size());
}
/** Patch load balancers matching given filter, while holding lock */
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 5af74214648..e6cfe8ca6b5 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
@@ -176,6 +176,9 @@ public class MetricsReporter extends NodeRepositoryMaintainer {
boolean converged = currentVersion.isPresent() &&
currentVersion.get().equals(wantedVersion);
metric.set("wantToChangeVespaVersion", converged ? 0 : 1, context);
+ if (node.cloudAccount().isEnclave(nodeRepository().zone())) {
+ metric.set("hasWireguardKey", node.wireguardPubKey().isPresent() ? 1 : 0, context);
+ }
} else {
context = getContext(Map.of("state", node.state().name(),
"host", node.hostname()));
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
index afea08711fa..766bc688c62 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailer.java
@@ -7,6 +7,7 @@ import com.yahoo.config.provision.Deployment;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.TransientException;
import com.yahoo.jdisc.Metric;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.transaction.Mutex;
import com.yahoo.vespa.hosted.provision.Node;
import com.yahoo.vespa.hosted.provision.NodeList;
@@ -97,7 +98,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
metric.set(throttlingActiveMetric, throttlingActive, null);
metric.set(throttledHostFailuresMetric, throttledHostFailures, null);
metric.set(throttledNodeFailuresMetric, throttledNodeFailures, null);
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
private Collection<FailingNode> findActiveFailingNodes() {
@@ -109,7 +110,7 @@ public class NodeFailer extends NodeRepositoryMaintainer {
for (Node node : activeNodes) {
Instant graceTimeStart = clock().instant().minus(nodeRepository().nodes().suspended(node) ? suspendedDownTimeLimit : downTimeLimit);
- if (node.isDown() && node.history().hasEventBefore(History.Event.Type.down, graceTimeStart) && !applicationSuspended(node)) {
+ if (node.isDown() && node.history().hasEventBefore(History.Event.Type.down, graceTimeStart) && !applicationSuspended(node) && !undergoingCmr(node)) {
// Allow a grace period after node re-activation
if (!node.history().hasEventAfter(History.Event.Type.activated, graceTimeStart))
failingNodes.add(new FailingNode(node, "Node has been down longer than " + downTimeLimit));
@@ -157,6 +158,19 @@ public class NodeFailer extends NodeRepositoryMaintainer {
}
}
+ private boolean undergoingCmr(Node node) {
+ return node.reports().getReport("vcmr")
+ .map(report ->
+ SlimeUtils.entriesStream(report.getInspector().field("upcoming"))
+ .anyMatch(cmr -> {
+ var startTime = cmr.field("plannedStartTime").asLong();
+ var endTime = cmr.field("plannedEndTime").asLong();
+ var now = clock().instant().getEpochSecond();
+ return now > startTime && now < endTime;
+ })
+ ).orElse(false);
+ }
+
/** Is the node and all active children suspended? */
private boolean allSuspended(Node node, NodeList activeNodes) {
if (!nodeRepository().nodes().suspended(node)) return false;
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
index 781debe26a0..979a2771082 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeHealthTracker.java
@@ -77,7 +77,7 @@ public class NodeHealthTracker extends NodeRepositoryMaintainer {
failures.add(1);
}
});
- return asSuccessFactor(attempts.get(), failures.get());
+ return asSuccessFactorDeviation(attempts.get(), failures.get());
}
/**
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
index b299369db1a..e28dac1c915 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainer.java
@@ -55,10 +55,10 @@ public class NodeMetricsDbMaintainer extends NodeRepositoryMaintainer {
nodeRepository().metricsDb().gc();
- return asSuccessFactor(attempts, failures.get());
+ return asSuccessFactorDeviation(attempts, failures.get());
}
catch (InterruptedException e) {
- return asSuccessFactor(attempts, failures.get());
+ return asSuccessFactorDeviation(attempts, failures.get());
}
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
index 3c00e3b708d..b8d37c7eb5c 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/NodeRepositoryMaintainer.java
@@ -59,7 +59,7 @@ public abstract class NodeRepositoryMaintainer extends Maintainer {
@Override
public void completed(String job, double successFactor, long duration) {
var context = metric.createContext(Map.of("job", job));
- metric.set("maintenance.successFactor", successFactor, context);
+ metric.set("maintenance.successFactorDeviation", successFactor, context);
metric.set("maintenance.duration", duration, context);
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
index af368934188..4071559d841 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/maintenance/ScalingSuggestionsMaintainer.java
@@ -48,7 +48,7 @@ public class ScalingSuggestionsMaintainer extends NodeRepositoryMaintainer {
failures++;
}
}
- return asSuccessFactor(attempts, failures);
+ return asSuccessFactorDeviation(attempts, failures);
}
private Applications applications() {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
index 6dc3b2b3193..fd6b15609d6 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/Nodes.java
@@ -5,6 +5,7 @@ import com.yahoo.collections.ListMap;
import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationTransaction;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.NodeType;
import com.yahoo.config.provision.Zone;
@@ -42,6 +43,8 @@ import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
+import static com.yahoo.vespa.hosted.provision.restapi.NodePatcher.DROP_DOCUMENTS_REPORT;
+
/**
* The nodes in the node repo and their state transitions
*
@@ -727,6 +730,23 @@ public class Nodes {
return resultingNodes;
}
+ public List<Node> dropDocuments(ApplicationId applicationId, Optional<ClusterSpec.Id> clusterId) {
+ try (Mutex lock = applications.lock(applicationId)) {
+ Instant now = clock.instant();
+ List<Node> nodes = list(Node.State.active, Node.State.reserved)
+ .owner(applicationId)
+ .matching(node -> {
+ ClusterSpec cluster = node.allocation().get().membership().cluster();
+ if (!cluster.type().isContent()) return false;
+ return clusterId.isEmpty() || clusterId.get().equals(cluster.id());
+ })
+ .mapToList(node -> node.with(node.reports().withReport(Report.basicReport(DROP_DOCUMENTS_REPORT, Report.Type.UNSPECIFIED, now, ""))));
+ if (nodes.isEmpty())
+ throw new NoSuchNodeException("No content nodes found for " + applicationId + clusterId.map(id -> " and cluster " + id).orElse(""));
+ return db.writeTo(nodes, Agent.operator, Optional.empty());
+ }
+ }
+
public boolean canAllocateTenantNodeTo(Node host) {
return canAllocateTenantNodeTo(host, zone.cloud().dynamicProvisioning());
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ApplicationFilter.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ApplicationFilter.java
index 2a4e320fd6a..401554f940d 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ApplicationFilter.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/node/filter/ApplicationFilter.java
@@ -36,16 +36,8 @@ public class ApplicationFilter {
public static Predicate<Node> from(String applicationIds) {
return makePredicate(StringUtilities.split(applicationIds).stream()
- .map(ApplicationFilter::toApplicationId)
+ .map(ApplicationId::fromFullString)
.collect(Collectors.toUnmodifiableSet()));
}
- public static ApplicationId toApplicationId(String applicationIdString) {
- String[] parts = applicationIdString.split("\\.");
- if (parts.length != 3)
- throw new IllegalArgumentException("Application id must be on the form tenant.application.instance, got '" +
- applicationIdString + "'");
- return ApplicationId.from(parts[0], parts[1], parts[2]);
- }
-
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
index 691f88a9be3..06c1916dd4f 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/GroupPreparer.java
@@ -21,7 +21,6 @@ import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.logging.Level;
import java.util.logging.Logger;
-import java.util.stream.Collectors;
/**
* Performs preparation of node activation changes for a single host group in an application.
@@ -107,7 +106,7 @@ public class GroupPreparer {
// Offer the nodes on the newly provisioned hosts, this should be enough to cover the deficit
List<NodeCandidate> candidates = provisionedHosts.stream()
.map(host -> NodeCandidate.createNewExclusiveChild(host.generateNode(),
- host.generateHost()))
+ host.generateHost()))
.toList();
allocation.offer(candidates);
};
@@ -124,6 +123,14 @@ public class GroupPreparer {
hosts.forEach(host -> nodeRepository.nodes().deprovision(host.hostname(), Agent.system, nodeRepository.clock().instant()));
throw e;
}
+ } else if (allocation.hostDeficit().isPresent() && requestedNodes.canFail() &&
+ allocation.hasRetiredJustNow() && requestedNodes instanceof NodeSpec.CountNodeSpec cns) {
+ // Non-dynamically provisioned zone with a deficit because we just now retired some nodes.
+ // Try again, but without retiring
+ indices.resetProbe();
+ List<Node> accepted = prepareWithLocks(application, cluster, cns.withoutRetiring(), surplusActiveNodes, indices, wantedGroups);
+ log.warning("Prepared " + application + " " + cluster.id() + " without retirement due to lack of capacity");
+ return accepted;
}
if (! allocation.fulfilled() && requestedNodes.canFail())
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
index 04f64b070b3..b3198a72d1b 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/LoadBalancerProvisioner.java
@@ -109,7 +109,7 @@ public class LoadBalancerProvisioner {
public void activate(Set<ClusterSpec> clusters, NodeList newActive, ApplicationTransaction transaction) {
Map<ClusterSpec.Id, ZoneEndpoint> activatingClusters = clusters.stream()
// .collect(Collectors.toMap(ClusterSpec::id, ClusterSpec::zoneEndpoint));
- // TODO: this dies with combined clusters Ü
+ // TODO: this dies with combined clusters
.collect(groupingBy(LoadBalancerProvisioner::effectiveId,
reducing(ZoneEndpoint.defaultEndpoint,
ClusterSpec::zoneEndpoint,
@@ -193,14 +193,13 @@ public class LoadBalancerProvisioner {
Optional<LoadBalancer> loadBalancer = db.readLoadBalancer(id);
LoadBalancer newLoadBalancer;
LoadBalancer.State fromState = loadBalancer.map(LoadBalancer::state).orElse(null);
- if ( loadBalancer.isPresent()
- && ( ! inAccount(cloudAccount, loadBalancer.get())
- || ! hasCorrectVisibility(loadBalancer.get(), zoneEndpoint))) {
- // We have a load balancer, but with the wrong account or visibility.
- // Load balancer must be removed before we can provision a new one with the wanted visibility
- newLoadBalancer = loadBalancer.get().with(LoadBalancer.State.removable, now);
- }
- else {
+ boolean recreateLoadBalancer = loadBalancer.isPresent() && ( ! inAccount(cloudAccount, loadBalancer.get())
+ || ! hasCorrectVisibility(loadBalancer.get(), zoneEndpoint));
+ if (recreateLoadBalancer) {
+ // We have a load balancer, but with the wrong account or visibility.
+ // Load balancer must be removed before we can provision a new one with the wanted visibility
+ newLoadBalancer = loadBalancer.get().with(LoadBalancer.State.removable, now);
+ } else {
Optional<LoadBalancerInstance> instance = provisionInstance(id, loadBalancer, zoneEndpoint, cloudAccount);
newLoadBalancer = loadBalancer.isEmpty() ? new LoadBalancer(id, instance, LoadBalancer.State.reserved, now)
: loadBalancer.get().with(instance);
@@ -211,8 +210,8 @@ public class LoadBalancerProvisioner {
}
private static boolean hasCorrectVisibility(LoadBalancer newLoadBalancer, ZoneEndpoint zoneEndpoint) {
- return newLoadBalancer.instance().isEmpty()
- || newLoadBalancer.instance().get().settings().isPublicEndpoint() == zoneEndpoint.isPublicEndpoint();
+ return newLoadBalancer.instance().isEmpty() ||
+ newLoadBalancer.instance().get().settings().isPublicEndpoint() == zoneEndpoint.isPublicEndpoint();
}
private void activate(ApplicationTransaction transaction, ClusterSpec.Id cluster, ZoneEndpoint settings, NodeList nodes) {
@@ -320,14 +319,6 @@ public class LoadBalancerProvisioner {
return loadBalancer.instance().isEmpty() || loadBalancer.instance().get().cloudAccount().equals(cloudAccount);
}
- /** Returns whether load balancer has given reals, and settings if specified */
- private static boolean isUpToDate(LoadBalancer loadBalancer, Set<Real> reals, ZoneEndpoint zoneEndpoint) {
- if (loadBalancer.instance().isEmpty())
- throw new IllegalStateException("Expected a load balancer instance to be present, for " + loadBalancer.id());
- return loadBalancer.instance().get().reals().equals(reals)
- && loadBalancer.instance().get().settings().equals(zoneEndpoint);
- }
-
/** Find IP addresses reachable by the load balancer service */
private Set<String> reachableIpAddresses(Node node) {
Set<String> reachable = new LinkedHashSet<>(node.ipConfig().primary());
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java
index c6971f0fe02..3af63125474 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeAllocation.java
@@ -307,10 +307,15 @@ class NodeAllocation {
}
/** Returns true if this allocation was already fulfilled and resulted in no new changes */
- public boolean fulfilledAndNoChanges() {
+ boolean fulfilledAndNoChanges() {
return fulfilled() && reservableNodes().isEmpty() && newNodes().isEmpty();
}
+ /** Returns true if this allocation has retired nodes */
+ boolean hasRetiredJustNow() {
+ return wasRetiredJustNow > 0;
+ }
+
/**
* Returns {@link HostDeficit} describing the host deficit for the given {@link NodeSpec}.
*
@@ -451,7 +456,7 @@ class NodeAllocation {
.toList();
}
- public String allocationFailureDetails() {
+ String allocationFailureDetails() {
List<String> reasons = new ArrayList<>();
if (rejectedDueToExclusivity > 0)
reasons.add("host exclusivity constraints");
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
index 59c089943ab..28d1e7c1c68 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/NodeSpec.java
@@ -3,8 +3,6 @@ package com.yahoo.vespa.hosted.provision.provisioning;
import com.yahoo.config.provision.CloudAccount;
import com.yahoo.config.provision.ClusterSpec;
-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.vespa.hosted.provision.Node;
@@ -79,7 +77,7 @@ public interface NodeSpec {
}
static NodeSpec from(int nodeCount, NodeResources resources, boolean exclusive, boolean canFail, CloudAccount cloudAccount) {
- return new CountNodeSpec(nodeCount, resources, exclusive, canFail, cloudAccount);
+ return new CountNodeSpec(nodeCount, resources, exclusive, canFail, canFail, cloudAccount);
}
static NodeSpec from(NodeType type, CloudAccount cloudAccount) {
@@ -93,14 +91,19 @@ public interface NodeSpec {
private final NodeResources requestedNodeResources;
private final boolean exclusive;
private final boolean canFail;
+ private final boolean considerRetiring;
private final CloudAccount cloudAccount;
- private CountNodeSpec(int count, NodeResources resources, boolean exclusive, boolean canFail, CloudAccount cloudAccount) {
+ private CountNodeSpec(int count, NodeResources resources, boolean exclusive, boolean canFail, boolean considerRetiring, CloudAccount cloudAccount) {
this.count = count;
this.requestedNodeResources = Objects.requireNonNull(resources, "Resources must be specified");
this.exclusive = exclusive;
this.canFail = canFail;
+ this.considerRetiring = considerRetiring;
this.cloudAccount = Objects.requireNonNull(cloudAccount);
+
+ if (!canFail && considerRetiring)
+ throw new IllegalArgumentException("Cannot consider retiring nodes if we cannot fail");
}
@Override
@@ -127,8 +130,7 @@ public interface NodeSpec {
@Override
public boolean considerRetiring() {
- // If we cannot fail we cannot retire as we may end up without sufficient replacement capacity
- return canFail();
+ return considerRetiring;
}
@Override
@@ -143,7 +145,11 @@ public interface NodeSpec {
@Override
public NodeSpec fraction(int divisor) {
- return new CountNodeSpec(count/divisor, requestedNodeResources, exclusive, canFail, cloudAccount);
+ return new CountNodeSpec(count/divisor, requestedNodeResources, exclusive, canFail, considerRetiring, cloudAccount);
+ }
+
+ public NodeSpec withoutRetiring() {
+ return new CountNodeSpec(count, requestedNodeResources, exclusive, canFail, false, cloudAccount);
}
@Override
@@ -185,7 +191,7 @@ public interface NodeSpec {
}
- /** A node spec specifying a node type. This will accept all nodes of this type. */
+ /** A node spec specifying a node type. */
class TypeNodeSpec implements NodeSpec {
private static final Map<NodeType, Integer> WANTED_NODE_COUNT = Map.of(NodeType.config, 3,
@@ -256,7 +262,7 @@ public interface NodeSpec {
}
@Override
- public String toString() { return "request for all nodes of type '" + type + "'"; }
+ public String toString() { return "request for nodes of type '" + type + "'"; }
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java
index d083d81c196..98afc6e7482 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/provisioning/ProvisionedHost.java
@@ -75,8 +75,8 @@ public class ProvisionedHost {
/** Generate {@link Node} instance representing the node running on this physical host */
public Node generateNode() {
return Node.reserve(Set.of(), nodeHostname(), hostHostname, nodeResources, hostType.childNodeType())
- .cloudAccount(cloudAccount)
- .build();
+ .cloudAccount(cloudAccount)
+ .build();
}
public String getId() { return id; }
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java
index bf5b735c4a0..09f947503f6 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/LoadBalancersResponse.java
@@ -10,7 +10,6 @@ import com.yahoo.vespa.hosted.provision.NodeRepository;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancer;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerInstance;
import com.yahoo.vespa.hosted.provision.lb.LoadBalancerList;
-import com.yahoo.vespa.hosted.provision.node.filter.ApplicationFilter;
import java.util.List;
import java.util.Optional;
@@ -32,7 +31,7 @@ public class LoadBalancersResponse extends SlimeJsonResponse {
private Optional<ApplicationId> application() {
return Optional.ofNullable(request.getProperty("application"))
- .map(ApplicationFilter::toApplicationId);
+ .map(ApplicationId::fromFullString);
}
private List<LoadBalancer> loadBalancers() {
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java
index dfe01f5f1c3..4dc48459ec9 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java
@@ -11,8 +11,10 @@ import com.yahoo.config.provision.NodeFlavors;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.TenantName;
import com.yahoo.config.provision.WireguardKey;
+import com.yahoo.slime.Cursor;
import com.yahoo.slime.Inspector;
import com.yahoo.slime.ObjectTraverser;
+import com.yahoo.slime.Slime;
import com.yahoo.slime.SlimeUtils;
import com.yahoo.slime.Type;
import com.yahoo.vespa.hosted.provision.LockedNodeList;
@@ -40,6 +42,7 @@ import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.TreeSet;
+import java.util.stream.Stream;
import static com.yahoo.config.provision.NodeResources.DiskSpeed.fast;
import static com.yahoo.config.provision.NodeResources.DiskSpeed.slow;
@@ -54,9 +57,13 @@ import static com.yahoo.config.provision.NodeResources.StorageType.remote;
*/
public class NodePatcher {
+ // Same as in DropDocumentsReport.java
+ public static final String DROP_DOCUMENTS_REPORT = "dropDocuments";
+
private static final String WANT_TO_RETIRE = "wantToRetire";
private static final String WANT_TO_DEPROVISION = "wantToDeprovision";
private static final String WANT_TO_REBUILD = "wantToRebuild";
+ private static final String REPORTS = "reports";
private static final Set<String> RECURSIVE_FIELDS = Set.of(WANT_TO_RETIRE, WANT_TO_DEPROVISION);
private static final Set<String> IP_CONFIG_FIELDS = Set.of("ipAddresses",
"additionalIpAddresses",
@@ -133,7 +140,29 @@ public class NodePatcher {
throw new IllegalArgumentException("Could not set field '" + name + "'", e);
}
}
- nodeRepository.nodes().write(node, lock);
+ List<Node> nodes = List.of(node);
+ if (node.state() == Node.State.active && isInDocumentsDroppedState(root.field(REPORTS).field(DROP_DOCUMENTS_REPORT))) {
+ NodeList clusterNodes = nodeRepository.nodes()
+ .list(Node.State.active)
+ .except(node)
+ .owner(node.allocation().get().owner())
+ .cluster(node.allocation().get().membership().cluster().id());
+ boolean allNodesDroppedDocuments = clusterNodes.stream().allMatch(cNode ->
+ cNode.reports().getReport(DROP_DOCUMENTS_REPORT).map(report -> isInDocumentsDroppedState(report.getInspector())).orElse(false));
+ if (allNodesDroppedDocuments) {
+ nodes = Stream.concat(nodes.stream(), clusterNodes.stream())
+ .map(cNode -> {
+ Cursor reportRoot = new Slime().setObject();
+ Report report = cNode.reports().getReport(DROP_DOCUMENTS_REPORT).get();
+ report.toSlime(reportRoot);
+ reportRoot.setLong("readiedAt", clock.millis());
+
+ return cNode.with(cNode.reports().withReport(Report.fromSlime(DROP_DOCUMENTS_REPORT, reportRoot)));
+ })
+ .toList();
+ }
+ }
+ nodeRepository.nodes().write(nodes, lock);
}
}
@@ -202,18 +231,15 @@ public class NodePatcher {
.orElseGet(node.status()::wantToRebuild),
Agent.operator,
clock.instant());
- case "reports" :
+ case REPORTS:
return nodeWithPatchedReports(node, value);
- case "id" :
+ case "id":
return node.withId(asString(value));
case "diskGb":
- case "minDiskAvailableGb":
return node.with(node.flavor().with(node.flavor().resources().withDiskGb(value.asDouble())), Agent.operator, clock.instant());
case "memoryGb":
- case "minMainMemoryAvailableGb":
return node.with(node.flavor().with(node.flavor().resources().withMemoryGb(value.asDouble())), Agent.operator, clock.instant());
case "vcpu":
- case "minCpuCores":
return node.with(node.flavor().with(node.flavor().resources().withVcpu(value.asDouble())), Agent.operator, clock.instant());
case "fastDisk":
return node.with(node.flavor().with(node.flavor().resources().with(value.asBool() ? fast : slow)), Agent.operator, clock.instant());
@@ -244,18 +270,12 @@ public class NodePatcher {
}
private Node applyIpconfigField(Node node, String name, Inspector value, LockedNodeList nodes) {
- switch (name) {
- case "ipAddresses" -> {
- return IP.Config.verify(node.with(node.ipConfig().withPrimary(asStringSet(value))), nodes);
- }
- case "additionalIpAddresses" -> {
- return IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withIpAddresses(asStringSet(value)))), nodes);
- }
- case "additionalHostnames" -> {
- return IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withHostnames(asHostnames(value)))), nodes);
- }
- }
- throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field");
+ return switch (name) {
+ case "ipAddresses" -> IP.Config.verify(node.with(node.ipConfig().withPrimary(asStringSet(value))), nodes);
+ case "additionalIpAddresses" -> IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withIpAddresses(asStringSet(value)))), nodes);
+ case "additionalHostnames" -> IP.Config.verify(node.with(node.ipConfig().withPool(node.ipConfig().pool().withHostnames(asHostnames(value)))), nodes);
+ default -> throw new IllegalArgumentException("Could not apply field '" + name + "' on a node: No such modifiable field");
+ };
}
private Node nodeWithPatchedReports(Node node, Inspector reportsInspector) {
@@ -374,4 +394,9 @@ public class NodePatcher {
return Optional.of(field).filter(Inspector::valid).map(this::asBoolean);
}
+ private static boolean isInDocumentsDroppedState(Inspector report) {
+ if (!report.valid()) return false;
+ return report.field("droppedAt").valid() && !report.field("readiedAt").valid();
+ }
+
}
diff --git a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
index eb935ba6a5c..e04d21d3012 100644
--- a/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
+++ b/node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiHandler.java
@@ -5,6 +5,7 @@ import com.yahoo.component.Version;
import com.yahoo.config.provision.ApplicationId;
import com.yahoo.config.provision.ApplicationLockException;
import com.yahoo.config.provision.CloudAccount;
+import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.Flavor;
import com.yahoo.config.provision.HostFilter;
import com.yahoo.config.provision.HostName;
@@ -222,6 +223,11 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler {
}
if (path.matches("/nodes/v2/maintenance/run/{job}")) return runJob(path.get("job"));
if (path.matches("/nodes/v2/upgrade/firmware")) return requestFirmwareCheckResponse();
+ if (path.matches("/nodes/v2/application/{applicationId}/drop-documents")) {
+ int count = nodeRepository.nodes().dropDocuments(ApplicationId.fromFullString(path.get("applicationId")),
+ Optional.ofNullable(request.getProperty("clusterId")).map(ClusterSpec.Id::from)).size();
+ return new MessageResponse("Triggered dropping of documents on " + count + " nodes");
+ }
throw new NotFoundException("Nothing at path '" + request.getUri().getPath() + "'");
}
@@ -482,14 +488,14 @@ public class NodesV2ApiHandler extends ThreadedHttpRequestHandler {
return new SlimeJsonResponse(slime);
}
- private void toSlime(Load load, Cursor object) {
+ private static void toSlime(Load load, Cursor object) {
object.setDouble("cpu", load.cpu());
object.setDouble("memory", load.memory());
object.setDouble("disk", load.disk());
}
/** Returns a copy of the given URI with the host and port from the given URI and the path set to the given path */
- private URI withPath(String newPath, URI uri) {
+ private static URI withPath(String newPath, URI uri) {
try {
return new URI(uri.getScheme(), uri.getUserInfo(), uri.getHost(), uri.getPort(), newPath, null, null);
}
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
index 491485b78fc..c63be6d5dc5 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeFailerTest.java
@@ -6,6 +6,7 @@ import com.yahoo.config.provision.ClusterResources;
import com.yahoo.config.provision.ClusterSpec;
import com.yahoo.config.provision.NodeResources;
import com.yahoo.config.provision.NodeType;
+import com.yahoo.slime.SlimeUtils;
import com.yahoo.vespa.applicationmodel.ServiceInstance;
import com.yahoo.vespa.applicationmodel.ServiceStatus;
import com.yahoo.vespa.hosted.provision.Node;
@@ -627,6 +628,49 @@ public class NodeFailerTest {
assertFalse(badNode(1, 3, 1, 2));
}
+ @Test
+ public void nodes_undergoing_cmr_are_not_failed() {
+ var tester = NodeFailTester.withTwoApplications(6);
+ var clock = tester.clock;
+ var slime = SlimeUtils.jsonToSlime(
+ String.format("""
+ {
+ "upcoming":[{
+ "id": "id-42",
+ "status": "some-status",
+ "plannedStartTime": %d,
+ "plannedEndTime": %d
+ }]
+ }
+ """, clock.instant().getEpochSecond(), clock.instant().plus(Duration.ofMinutes(90)).getEpochSecond())
+ );
+ var cmrReport = Report.fromSlime("vcmr", slime.get());
+ var downHost = tester.nodeRepository.nodes().list(Node.State.active).owner(NodeFailTester.app1).asList().get(1).hostname();
+
+ var node = tester.nodeRepository.nodes().node(downHost).get();
+ tester.nodeRepository.nodes().write(node.with(node.reports().withReport(cmrReport)), () -> {});
+
+ tester.serviceMonitor.setHostDown(downHost);
+ tester.runMaintainers();
+ node = tester.nodeRepository.nodes().node(downHost).get();
+ assertTrue(node.isDown());
+ assertEquals(Node.State.active, node.state());
+
+ // CMR still ongoing, don't fail yet
+ clock.advance(Duration.ofHours(1));
+ tester.runMaintainers();
+ node = tester.nodeRepository.nodes().node(downHost).get();
+ assertTrue(node.isDown());
+ assertEquals(Node.State.active, node.state());
+
+ // No ongoing CMR anymore, host should be failed
+ clock.advance(Duration.ofHours(1));
+ tester.runMaintainers();
+ node = tester.nodeRepository.nodes().node(downHost).get();
+ assertTrue(node.isDown());
+ assertEquals(Node.State.failed, node.state());
+ }
+
private void addServiceInstances(List<ServiceInstance> list, ServiceStatus status, int num) {
for (int i = 0; i < num; ++i) {
ServiceInstance service = mock(ServiceInstance.class);
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainerTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainerTest.java
index c7c6e770fe3..611594cc72e 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainerTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/maintenance/NodeMetricsDbMaintainerTest.java
@@ -44,7 +44,7 @@ public class NodeMetricsDbMaintainerTest {
fetcher,
Duration.ofHours(1),
new TestMetric());
- assertEquals(maintainer.maintain(), 1.0, 0.0000001);
+ assertEquals(maintainer.maintain(), 0.0, 0.0000001);
List<NodeTimeseries> timeseriesList = tester.nodeRepository().metricsDb().getNodeTimeseries(Duration.ofDays(1),
Set.of("host-1.yahoo.com", "host-2.yahoo.com"));
assertEquals(2, timeseriesList.size());
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 978edf3f7e4..2cd0e84c356 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
@@ -757,6 +757,23 @@ public class ProvisioningTest {
}
@Test
+ public void ignore_retirement_if_no_capacity() {
+ ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
+ tester.makeReadyHosts(3, defaultResources).activateTenantHosts();
+
+ ApplicationId application = ProvisioningTester.applicationId();
+ ClusterSpec cluster = ClusterSpec.request(ClusterSpec.Type.content, ClusterSpec.Id.from("music")).vespaVersion("4.5.6").build();
+ tester.activate(application, tester.prepare(application, cluster, 3, 1, defaultResources));
+
+ // Mark the nodes as want to retire
+ NodeList nodes = tester.getNodes(application);
+ tester.patchNodes(nodes.asList(), node -> node.withWantToRetire(true, Agent.system, tester.clock().instant()));
+
+ tester.activate(application, tester.prepare(application, cluster, 3, 1, defaultResources));
+ assertEquals(3, tester.getNodes(application).state(Node.State.active).not().retired().size());
+ }
+
+ @Test
public void highest_node_indexes_are_retired_first() {
ProvisioningTester tester = new ProvisioningTester.Builder().zone(new Zone(Environment.prod, RegionName.from("us-east"))).build();
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java
index fb773f19b8a..0744d82c85b 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/provisioning/VirtualNodeProvisioningTest.java
@@ -34,7 +34,6 @@ import java.util.stream.Stream;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -171,7 +170,7 @@ public class VirtualNodeProvisioningTest {
@Test
public void indistinct_distribution_with_known_ready_nodes() {
ProvisioningTester tester = new ProvisioningTester.Builder().build();
- tester.makeReadyChildren(3, resources1);
+ tester.makeReadyChildren(4, resources1);
int contentNodeCount = 3;
int groups = 1;
@@ -191,13 +190,10 @@ public class VirtualNodeProvisioningTest {
tester.makeReadyChildren(1, resources1, "parentHost1");
tester.makeReadyChildren(2, resources1, "parentHost2");
- NodeAllocationException expectedException = null;
- try {
- tester.prepare(applicationId, contentClusterSpec, contentNodeCount, groups, resources1);
- } catch (NodeAllocationException e) {
- expectedException = e;
- }
- assertNotNull(expectedException);
+ tester.activate(applicationId, tester.prepare(applicationId, contentClusterSpec, contentNodeCount, groups, resources1));
+ nodes = tester.getNodes(applicationId, Node.State.active);
+ assertEquals(4, nodes.size());
+ assertEquals(1, nodes.retired().size());
}
@Test
diff --git a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
index c9e57c22d11..022822fd3ec 100644
--- a/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
+++ b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
@@ -650,6 +650,43 @@ public class NodesV2ApiTest {
}
@Test
+ public void drop_documents() throws IOException {
+ // Initially no reports
+ tester.assertPartialResponse(new Request("http://localhost:8080/nodes/v2/node/test-node-pool-102-2"), "reports", false);
+ tester.assertPartialResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"), "reports", false);
+
+ // Initiating drop documents will set the report on all nodes
+ assertResponse(new Request("http://localhost:8080/nodes/v2/application/tenant3.application3.instance3/drop-documents?clusterId=id3", new byte[0], Request.Method.POST),
+ "{\"message\":\"Triggered dropping of documents on 2 nodes\"}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/test-node-pool-102-2"),
+ "{\"dropDocuments\":{\"createdMillis\":123}}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"),
+ "{\"dropDocuments\":{\"createdMillis\":123}}");
+
+ // Host admin of the first node finishes dropping
+ assertResponse(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com",
+ Utf8.toBytes("{\"reports\": {\"dropDocuments\":{\"createdMillis\":25,\"droppedAt\":36}}}"),
+ Request.Method.PATCH),
+ "{\"message\":\"Updated host4.yahoo.com\"}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"),
+ "{\"dropDocuments\":{\"createdMillis\":25,\"droppedAt\":36}}");
+
+ // Host admin of the second node finishes dropping, node-repo will update report on both nodes to start phase 2
+ assertResponse(new Request("http://localhost:8080/nodes/v2/node/test-node-pool-102-2",
+ Utf8.toBytes("{\"reports\": {\"dropDocuments\":{\"createdMillis\":49,\"droppedAt\":456}}}"),
+ Request.Method.PATCH),
+ "{\"message\":\"Updated test-node-pool-102-2\"}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/test-node-pool-102-2"),
+ "{\"dropDocuments\":{\"createdMillis\":49,\"droppedAt\":456,\"readiedAt\":123}}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"),
+ "{\"dropDocuments\":{\"createdMillis\":25,\"droppedAt\":36,\"readiedAt\":123}}");
+
+ tester.assertResponse(new Request("http://localhost:8080/nodes/v2/application/does.not.exist/drop-documents", new byte[0], Request.Method.POST),
+ 404,
+ "{\"error-code\":\"NOT_FOUND\",\"message\":\"No content nodes found for does.not.exist\"}");
+ }
+
+ @Test
public void test_upgrade() throws IOException {
// Initially, no versions are set
assertResponse(new Request("http://localhost:8080/nodes/v2/upgrade/"), "{\"versions\":{},\"osVersions\":{},\"dockerImages\":{}}");
@@ -906,13 +943,13 @@ public class NodesV2ApiTest {
// Test patching with overrides
tester.assertResponse(new Request("http://localhost:8080/nodes/v2/node/" + host,
- "{\"minDiskAvailableGb\":5432,\"minMainMemoryAvailableGb\":2345}".getBytes(StandardCharsets.UTF_8),
+ "{\"diskGb\":5432,\"memoryGb\":2345}".getBytes(StandardCharsets.UTF_8),
Request.Method.PATCH),
400,
- "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not set field 'minMainMemoryAvailableGb': Can only override disk GB for configured flavor\"}");
+ "{\"error-code\":\"BAD_REQUEST\",\"message\":\"Could not set field 'memoryGb': Can only override disk GB for configured flavor\"}");
assertResponse(new Request("http://localhost:8080/nodes/v2/node/" + host,
- "{\"minDiskAvailableGb\":5432}".getBytes(StandardCharsets.UTF_8),
+ "{\"diskGb\":5432}".getBytes(StandardCharsets.UTF_8),
Request.Method.PATCH),
"{\"message\":\"Updated " + host + "\"}");
tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/" + host),
diff --git a/openai-client/abi-spec.json b/openai-client/abi-spec.json
new file mode 100644
index 00000000000..039ca57fc64
--- /dev/null
+++ b/openai-client/abi-spec.json
@@ -0,0 +1,29 @@
+{
+ "ai.vespa.llm.client.openai.OpenAiClient$Builder" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String)",
+ "public ai.vespa.llm.client.openai.OpenAiClient$Builder model(java.lang.String)",
+ "public ai.vespa.llm.client.openai.OpenAiClient$Builder echo(boolean)",
+ "public ai.vespa.llm.client.openai.OpenAiClient build()"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.client.openai.OpenAiClient" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "ai.vespa.llm.LanguageModel"
+ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public java.util.List complete(ai.vespa.llm.completion.Prompt)"
+ ],
+ "fields" : [ ]
+ }
+} \ No newline at end of file
diff --git a/openai-client/pom.xml b/openai-client/pom.xml
new file mode 100644
index 00000000000..f4362194b58
--- /dev/null
+++ b/openai-client/pom.xml
@@ -0,0 +1,67 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>parent</artifactId>
+ <version>8-SNAPSHOT</version>
+ <relativePath>../parent/pom.xml</relativePath>
+ </parent>
+ <artifactId>openai-client</artifactId>
+ <packaging>container-plugin</packaging>
+ <version>8-SNAPSHOT</version>
+
+ <properties>
+ <openai-gpt3.version>0.12.0</openai-gpt3.version>
+ </properties>
+
+ <dependencies>
+ <dependency>
+ <groupId>com.theokanning.openai-gpt3-java</groupId>
+ <artifactId>service</artifactId>
+ <version>${openai-gpt3.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>annotations</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ <dependency>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>vespajlib</artifactId>
+ <version>${project.version}</version>
+ <scope>provided</scope>
+ </dependency>
+ </dependencies>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>bundle-plugin</artifactId>
+ <extensions>true</extensions>
+ </plugin>
+ <plugin>
+ <groupId>org.apache.maven.plugins</groupId>
+ <artifactId>maven-compiler-plugin</artifactId>
+ <configuration>
+ <!-- openai-gpt3-java produces warnings -->
+ <compilerArgs>
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-rawtypes</arg>
+ <arg>-Xlint:-unchecked</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
+ </configuration>
+ </plugin>
+ <plugin>
+ <groupId>com.yahoo.vespa</groupId>
+ <artifactId>abi-check-plugin</artifactId>
+ </plugin>
+ </plugins>
+ </build>
+
+</project> \ No newline at end of file
diff --git a/openai-client/src/main/java/ai/vespa/llm/client/openai/OpenAiClient.java b/openai-client/src/main/java/ai/vespa/llm/client/openai/OpenAiClient.java
new file mode 100644
index 00000000000..66be5ff1f69
--- /dev/null
+++ b/openai-client/src/main/java/ai/vespa/llm/client/openai/OpenAiClient.java
@@ -0,0 +1,84 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.llm.client.openai;
+
+import ai.vespa.llm.completion.Completion;
+import ai.vespa.llm.LanguageModel;
+import ai.vespa.llm.completion.Prompt;
+import com.theokanning.openai.OpenAiHttpException;
+import com.theokanning.openai.completion.CompletionRequest;
+import com.theokanning.openai.service.OpenAiService;
+import com.yahoo.api.annotations.Beta;
+import com.yahoo.yolean.Exceptions;
+
+import java.util.List;
+
+/**
+ * A client to the OpenAI language model API. Refer to https://platform.openai.com/docs/api-reference/.
+ *
+ * @author bratseth
+ */
+@Beta
+public class OpenAiClient implements LanguageModel {
+
+ private final OpenAiService openAiService;
+ private final String model;
+ private final boolean echo;
+
+ private OpenAiClient(Builder builder) {
+ openAiService = new OpenAiService(builder.token);
+ this.model = builder.model;
+ this.echo = builder.echo;
+ }
+
+ @Override
+ public List<Completion> complete(Prompt prompt) {
+ try {
+ CompletionRequest completionRequest = CompletionRequest.builder()
+ .prompt(prompt.asString())
+ .model(model)
+ .echo(echo)
+ .build();
+ return openAiService.createCompletion(completionRequest).getChoices().stream()
+ .map(c -> new Completion(c.getText(), toFinishReason(c.getFinish_reason()))).toList();
+ }
+ catch (OpenAiHttpException e) {
+ throw new RuntimeException(Exceptions.toMessageString(e));
+ }
+ }
+
+ private Completion.FinishReason toFinishReason(String finishReasonString) {
+ return switch(finishReasonString) {
+ case "length" -> Completion.FinishReason.length;
+ case "stop" -> Completion.FinishReason.stop;
+ default -> throw new IllegalStateException("Unknown OpenAi completion finish reason '" + finishReasonString + "'");
+ };
+ }
+
+ public static class Builder {
+
+ private final String token;
+ private String model = "text-davinci-003";
+ private boolean echo = false;
+
+ public Builder(String token) {
+ this.token = token;
+ }
+
+ /** One of the language models listed at https://platform.openai.com/docs/models */
+ public Builder model(String model) {
+ this.model = model;
+ return this;
+ }
+
+ public Builder echo(boolean echo) {
+ this.echo = echo;
+ return this;
+ }
+
+ public OpenAiClient build() {
+ return new OpenAiClient(this);
+ }
+
+ }
+
+}
diff --git a/openai-client/src/main/java/ai/vespa/llm/client/openai/package-info.java b/openai-client/src/main/java/ai/vespa/llm/client/openai/package-info.java
new file mode 100644
index 00000000000..8b8b99308b0
--- /dev/null
+++ b/openai-client/src/main/java/ai/vespa/llm/client/openai/package-info.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package ai.vespa.llm.client.openai;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
+
+/**
+ * Client to OpenAi's large language models.
+ */ \ No newline at end of file
diff --git a/parent/pom.xml b/parent/pom.xml
index ace6a47d6bc..6b84137b43b 100644
--- a/parent/pom.xml
+++ b/parent/pom.xml
@@ -212,6 +212,7 @@
<java.io.tmpdir>${project.build.directory}</java.io.tmpdir>
</systemPropertyVariables>
<trimStackTrace>false</trimStackTrace>
+ <argLine>-Djava.io.tmpdir=${project.build.directory}</argLine>
</configuration>
</plugin>
<plugin>
@@ -1009,6 +1010,11 @@
<version>${junit.version}</version>
</dependency>
<dependency>
+ <groupId>org.junit.jupiter</groupId>
+ <artifactId>junit-jupiter-params</artifactId>
+ <version>${junit.version}</version>
+ </dependency>
+ <dependency>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
<version>${junit.version}</version>
@@ -1138,7 +1144,7 @@
<felix.log.version>1.0.1</felix.log.version>
<findbugs.version>3.0.2</findbugs.version> <!-- Should be kept in sync with guava -->
<hdrhistogram.version>2.1.12</hdrhistogram.version>
- <jetty.version>11.0.14</jetty.version>
+ <jetty.version>11.0.15</jetty.version>
<jetty-servlet-api.version>5.0.2</jetty-servlet-api.version>
<jjwt.version>0.11.2</jjwt.version>
<jna.version>5.11.0</jna.version>
@@ -1166,7 +1172,7 @@
<netty.version>4.1.86.Final</netty.version>
<netty-tcnative.version>2.0.54.Final</netty-tcnative.version>
<onnxruntime.version>1.13.1</onnxruntime.version> <!-- WARNING: sync cloud-tenant-base-dependencies-enforcer/pom.xml -->
- <org.json.version>20220320</org.json.version>
+ <org.json.version>20230227</org.json.version>
<org.lz4.version>1.8.0</org.lz4.version>
<prometheus.client.version>0.6.0</prometheus.client.version>
<protobuf.version>3.21.7</protobuf.version>
diff --git a/pom.xml b/pom.xml
index c4b6e200e2e..a601e847e2b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -91,7 +91,6 @@
<module>jdisc_core_test</module>
<module>jrt</module>
<module>linguistics</module>
- <module>opennlp-linguistics</module>
<module>linguistics-components</module>
<module>logd</module>
<module>logserver</module>
@@ -102,6 +101,8 @@
<module>model-integration</module>
<module>node-repository</module>
<module>node-admin</module>
+ <module>openai-client</module>
+ <module>opennlp-linguistics</module>
<module>orchestrator-restapi</module>
<module>orchestrator</module>
<module>parent</module>
diff --git a/predicate-search/src/main/java/com/yahoo/search/predicate/benchmarks/ResultMetrics.java b/predicate-search/src/main/java/com/yahoo/search/predicate/benchmarks/ResultMetrics.java
index 11103a2a66a..ef65d9e2efa 100644
--- a/predicate-search/src/main/java/com/yahoo/search/predicate/benchmarks/ResultMetrics.java
+++ b/predicate-search/src/main/java/com/yahoo/search/predicate/benchmarks/ResultMetrics.java
@@ -62,8 +62,8 @@ public class ResultMetrics {
}
private double percentile(double percentile) {
- int targetCount = (int) Math.round(totalQueries * percentile);
- int currentCount = 0;
+ long targetCount = Math.round(totalQueries * percentile);
+ long currentCount = 0;
int index = 0;
while (currentCount < targetCount && index < SLOTS) {
currentCount += latencyHistogram[index];
diff --git a/predicate-search/src/main/java/com/yahoo/search/predicate/index/CachedPostingListCounter.java b/predicate-search/src/main/java/com/yahoo/search/predicate/index/CachedPostingListCounter.java
index e6db1dec7c3..eb8b0b9927b 100644
--- a/predicate-search/src/main/java/com/yahoo/search/predicate/index/CachedPostingListCounter.java
+++ b/predicate-search/src/main/java/com/yahoo/search/predicate/index/CachedPostingListCounter.java
@@ -75,7 +75,7 @@ public class CachedPostingListCounter {
private void countUsingBitVector(byte[] nPostingListsForDocument, int postingListBitmap) {
for (int docId = 0; docId < nDocuments; docId++) {
- nPostingListsForDocument[docId] += Integer.bitCount(bitVector[docId] & postingListBitmap);
+ nPostingListsForDocument[docId] += (byte)Integer.bitCount(bitVector[docId] & postingListBitmap);
}
}
@@ -88,8 +88,7 @@ public class CachedPostingListCounter {
}
public CachedPostingListCounter rebuildCache() {
- MinMaxPriorityQueue<Entry> mostExpensive = MinMaxPriorityQueue
- .maximumSize(32).expectedSize(32).create();
+ MinMaxPriorityQueue<Entry> mostExpensive = MinMaxPriorityQueue.maximumSize(32).expectedSize(32).create();
synchronized (this) {
for (ObjectLongPair<int[]> p : frequency.keyValuesView()) {
mostExpensive.add(new Entry(p.getOne(), p.getTwo()));
diff --git a/screwdriver/update-vespa-version-in-sample-apps.sh b/screwdriver/update-vespa-version-in-sample-apps.sh
index 22a55b0ce20..d3870267f26 100755
--- a/screwdriver/update-vespa-version-in-sample-apps.sh
+++ b/screwdriver/update-vespa-version-in-sample-apps.sh
@@ -12,8 +12,12 @@ fi
readonly VESPA_RELEASE="$1"
export JAVA_HOME=$(dirname $(dirname $(readlink -f /usr/bin/java)))
+BUILD_DIR=$(mktemp -d)
+trap "rm -rf $BUILD_DIR" EXIT
+cd $BUILD_DIR
+
function is_published {
- local TMP_MVN_REPO=/tmp/maven-repo
+ local TMP_MVN_REPO=$BUILD_DIR/maven-repo
echo $TMP_MVN_REPO
mkdir -p $TMP_MVN_REPO
rm -rf $TMP_MVN_REPO/com/yahoo/vespa
diff --git a/searchcore/src/tests/proton/attribute/attribute_initializer/attribute_initializer_test.cpp b/searchcore/src/tests/proton/attribute/attribute_initializer/attribute_initializer_test.cpp
index 52b7a7ce60a..d2798c16065 100644
--- a/searchcore/src/tests/proton/attribute/attribute_initializer/attribute_initializer_test.cpp
+++ b/searchcore/src/tests/proton/attribute/attribute_initializer/attribute_initializer_test.cpp
@@ -7,8 +7,11 @@
#include <vespa/searchcore/proton/test/attribute_utils.h>
#include <vespa/searchlib/attribute/attributefactory.h>
#include <vespa/searchlib/attribute/integerbase.h>
+#include <vespa/searchlib/attribute/stringbase.h>
#include <vespa/searchlib/test/directory_handler.h>
#include <vespa/searchcommon/attribute/config.h>
+#include <vespa/searchcommon/attribute/i_multi_value_attribute.h>
+#include <vespa/vespalib/util/stash.h>
#include <vespa/vespalib/util/threadstackexecutor.h>
#include <vespa/vespalib/testkit/testapp.h>
@@ -18,8 +21,10 @@ LOG_SETUP("attribute_initializer_test");
using search::attribute::Config;
using search::attribute::BasicType;
using search::attribute::CollectionType;
+using search::attribute::IMultiValueAttribute;
using search::SerialNum;
using search::test::DirectoryHandler;
+using vespalib::Stash;
const vespalib::string test_dir = "test_output";
@@ -60,7 +65,7 @@ Config get_int32_wset_fs()
}
void
-saveAttr(const vespalib::string &name, const Config &cfg, SerialNum serialNum, SerialNum createSerialNum)
+saveAttr(const vespalib::string &name, const Config &cfg, SerialNum serialNum, SerialNum createSerialNum, bool mutate_reserved_doc = false)
{
auto diskLayout = AttributeDiskLayout::create(test_dir);
auto dir = diskLayout->createAttributeDir(name);
@@ -81,6 +86,15 @@ saveAttr(const vespalib::string &name, const Config &cfg, SerialNum serialNum, S
iav.append(docId, 10, 1);
iav.append(docId, 11, 1);
}
+ if (mutate_reserved_doc) {
+ av->clearDoc(0u);
+ if (cfg.basicType().type() == BasicType::Type::STRING &&
+ cfg.collectionType().type() == CollectionType::Type::WSET) {
+ auto &sav = dynamic_cast<search::StringAttribute &>(*av);
+ sav.append(0u, "badly", 15);
+ sav.append(0u, "broken", 20);
+ }
+ }
av->save();
writer->markValidSnapshot(serialNum);
}
@@ -250,6 +264,22 @@ TEST("require that saved attribute is ignored when serial num is not set")
EXPECT_EQUAL(1u, av->getNumDocs());
}
+TEST("require that reserved document is reinitialized during load")
+{
+ saveAttr("a", string_wset, 10, 2, true);
+ Fixture f;
+ auto av = f.createInitializer({"a", string_wset}, 5)->init().getAttribute();
+ EXPECT_EQUAL(2u, av->getCreateSerialNum());
+ EXPECT_EQUAL(2u, av->getNumDocs());
+ auto mvav = av->as_multi_value_attribute();
+ ASSERT_TRUE(mvav != nullptr);
+ Stash stash;
+ auto read_view = mvav->make_read_view(IMultiValueAttribute::WeightedSetTag<const char*>(), stash);
+ ASSERT_TRUE(read_view != nullptr);
+ auto reserved_values = read_view->get_values(0u);
+ EXPECT_EQUAL(0u, reserved_values.size());
+}
+
}
TEST_MAIN()
diff --git a/searchcore/src/tests/proton/common/attribute_updater/attribute_updater_test.cpp b/searchcore/src/tests/proton/common/attribute_updater/attribute_updater_test.cpp
index 392dcc256c8..892be2c874f 100644
--- a/searchcore/src/tests/proton/common/attribute_updater/attribute_updater_test.cpp
+++ b/searchcore/src/tests/proton/common/attribute_updater/attribute_updater_test.cpp
@@ -264,7 +264,8 @@ TEST_F("require that array attributes are updated", Fixture)
{
CollectionType ct(CollectionType::ARRAY);
{
- auto vec = AttributeBuilder("in1/aint", Config(BasicType::INT32, ct)).fill_array({{32}, {32}, {32}, {32}, {32}}).get();
+ using IL = AttributeBuilder::IntList;
+ auto vec = AttributeBuilder("in1/aint", Config(BasicType::INT32, ct)).fill_array({IL{32}, {32}, {32}, {32}, {32}}).get();
auto first = std::make_unique<IntFieldValue>(32);
auto second = std::make_unique<IntFieldValue>(64);
auto assign = std::make_unique<ArrayFieldValue>(f.docType->getField("aint").getDataType());
@@ -279,7 +280,8 @@ TEST_F("require that array attributes are updated", Fixture)
EXPECT_TRUE(check(vec, 5, std::vector<WeightedInt>{WeightedInt(32)}));
}
{
- auto vec = AttributeBuilder("in1/afloat", Config(BasicType::FLOAT, ct)).fill_array({{55.5}, {55.5}, {55.5}, {55.5}, {55.5}}).get();
+ using DL = AttributeBuilder::DoubleList;
+ auto vec = AttributeBuilder("in1/afloat", Config(BasicType::FLOAT, ct)).fill_array({DL{55.5}, {55.5}, {55.5}, {55.5}, {55.5}}).get();
auto first = std::make_unique<FloatFieldValue>(55.5f);
auto second = std::make_unique<FloatFieldValue>(77.7f);
auto assign = std::make_unique<ArrayFieldValue>(f.docType->getField("afloat").getDataType());
diff --git a/searchcore/src/tests/proton/matching/matching_test.cpp b/searchcore/src/tests/proton/matching/matching_test.cpp
index fd6f6af730c..4ad386afa3f 100644
--- a/searchcore/src/tests/proton/matching/matching_test.cpp
+++ b/searchcore/src/tests/proton/matching/matching_test.cpp
@@ -16,7 +16,6 @@
#include <vespa/searchlib/aggregation/grouping.h>
#include <vespa/searchlib/aggregation/perdocexpression.h>
#include <vespa/searchlib/attribute/extendableattributes.h>
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/engine/docsumreply.h>
#include <vespa/searchlib/engine/docsumrequest.h>
#include <vespa/searchlib/engine/searchreply.h>
@@ -36,6 +35,7 @@
#include <vespa/eval/eval/value_codec.h>
#include <vespa/vespalib/objects/nbostream.h>
#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vespa/vespalib/util/simple_thread_bundle.h>
#include <vespa/vespalib/util/testclock.h>
#include <vespa/vespalib/stllike/asciistream.h>
@@ -64,6 +64,7 @@ using search::index::schema::DataType;
using storage::spi::Timestamp;
using vespalib::eval::SimpleValue;
using vespalib::eval::TensorSpec;
+using vespalib::FeatureSet;
using vespalib::nbostream;
vespalib::ThreadBundle &ttb() { return vespalib::ThreadBundle::trivial(); }
diff --git a/searchcore/src/tests/proton/server/shared_threading_service/shared_threading_service_test.cpp b/searchcore/src/tests/proton/server/shared_threading_service/shared_threading_service_test.cpp
index 2027ad56768..fe7303692ba 100644
--- a/searchcore/src/tests/proton/server/shared_threading_service/shared_threading_service_test.cpp
+++ b/searchcore/src/tests/proton/server/shared_threading_service/shared_threading_service_test.cpp
@@ -20,7 +20,6 @@ ProtonConfig
make_proton_config(double concurrency, uint32_t indexing_threads = 1)
{
ProtonConfigBuilder builder;
- // This setup requires a minimum of 4 shared threads.
builder.documentdb.push_back(ProtonConfig::Documentdb());
builder.documentdb.push_back(ProtonConfig::Documentdb());
builder.flush.maxconcurrent = 1;
@@ -48,8 +47,10 @@ expect_field_writer_threads(uint32_t exp_threads, uint32_t cpu_cores, uint32_t i
TEST(SharedThreadingServiceConfigTest, shared_threads_are_derived_from_cpu_cores_and_feeding_concurrency)
{
- expect_shared_threads(4, 1);
- expect_shared_threads(4, 6);
+ expect_shared_threads(2, 1);
+ expect_shared_threads(2, 4);
+ expect_shared_threads(3, 5);
+ expect_shared_threads(3, 6);
expect_shared_threads(4, 8);
expect_shared_threads(5, 9);
expect_shared_threads(5, 10);
diff --git a/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp b/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
index d39d2873edb..f6200bb14ee 100644
--- a/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
+++ b/searchcore/src/vespa/searchcore/proton/attribute/attribute_initializer.cpp
@@ -200,6 +200,7 @@ AttributeInitializer::loadAttribute(const AttributeVectorSP &attr,
attr->getBaseFileName().c_str());
return false;
} else {
+ attr->set_reserved_doc_values();
attr->commit(CommitParam(serialNum));
EventLogger::loadAttributeComplete(_documentSubDbName, attr->getName(), timer.elapsed());
}
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.cpp b/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.cpp
index 82e1aa3b57c..430412fc1c0 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.cpp
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.cpp
@@ -137,8 +137,15 @@ SearchContext::getStore() const
SearchContext::SearchContext(QueryTermSimple::UP qTerm, const DocumentMetaStore &toBeSearched)
: search::attribute::SearchContext(toBeSearched),
- _isWord(qTerm->isWord())
+ _isWord(qTerm->isWord()),
+ _docid_limit(toBeSearched.getCommittedDocIdLimit())
{
}
+uint32_t
+SearchContext::get_committed_docid_limit() const noexcept
+{
+ return _docid_limit;
+}
+
}
diff --git a/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.h b/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.h
index ca4b026e2a4..7c88d8f3502 100644
--- a/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.h
+++ b/searchcore/src/vespa/searchcore/proton/documentmetastore/search_context.h
@@ -18,6 +18,7 @@ private:
bool _isWord;
document::GlobalId _gid;
+ uint32_t _docid_limit;
unsigned int approximateHits() const override;
int32_t onFind(DocId docId, int32_t elemId, int32_t &weight) const override;
@@ -30,6 +31,7 @@ private:
public:
SearchContext(std::unique_ptr<search::QueryTermSimple> qTerm, const DocumentMetaStore &toBeSearched);
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.cpp
index fd5c3782b9a..6014df1c2f9 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.cpp
@@ -18,7 +18,6 @@
#include <vespa/log/log.h>
LOG_SETUP(".proton.matching.docsum_matcher");
-using search::FeatureSet;
using search::MatchingElements;
using search::MatchingElementsFields;
using search::fef::FeatureResolver;
@@ -29,6 +28,7 @@ using search::queryeval::IntermediateBlueprint;
using search::queryeval::MatchingElementsSearch;
using search::queryeval::SameElementBlueprint;
using search::queryeval::SearchIterator;
+using vespalib::FeatureSet;
using AttrSearchCtx = search::attribute::ISearchContext;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.h b/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.h
index 006a443e539..bf99a6b1950 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/docsum_matcher.h
@@ -2,9 +2,9 @@
#pragma once
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/common/matching_elements.h>
#include <vespa/searchlib/common/matching_elements_fields.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vector>
#include <memory>
@@ -21,7 +21,7 @@ class SearchSession;
class DocsumMatcher
{
private:
- using FeatureSet = search::FeatureSet;
+ using FeatureSet = vespalib::FeatureSet;
using MatchingElementsFields = search::MatchingElementsFields;
using MatchingElements = search::MatchingElements;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/extract_features.cpp b/searchcore/src/vespa/searchcore/proton/matching/extract_features.cpp
index 8f7970f5717..30958214b72 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/extract_features.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/extract_features.cpp
@@ -12,10 +12,10 @@
#include <vespa/searchlib/queryeval/searchiterator.h>
using vespalib::Doom;
+using vespalib::FeatureSet;
+using vespalib::FeatureValues;
using vespalib::Runnable;
using vespalib::ThreadBundle;
-using search::FeatureSet;
-using search::FeatureValues;
using search::fef::FeatureResolver;
using search::fef::RankProgram;
using search::queryeval::SearchIterator;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/extract_features.h b/searchcore/src/vespa/searchcore/proton/matching/extract_features.h
index 48c3476f164..09da89250a2 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/extract_features.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/extract_features.h
@@ -2,8 +2,8 @@
#pragma once
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/common/stringmap.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vector>
namespace vespalib { class Doom; };
@@ -16,8 +16,8 @@ namespace proton::matching {
class MatchToolsFactory;
struct ExtractFeatures {
- using FeatureSet = search::FeatureSet;
- using FeatureValues = search::FeatureValues;
+ using FeatureSet = vespalib::FeatureSet;
+ using FeatureValues = vespalib::FeatureValues;
using ThreadBundle = vespalib::ThreadBundle;
using SearchIterator = search::queryeval::SearchIterator;
using RankProgram = search::fef::RankProgram;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/match_master.cpp b/searchcore/src/vespa/searchcore/proton/matching/match_master.cpp
index 3a43e9a118e..0bb183d1dc0 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/match_master.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/match_master.cpp
@@ -18,7 +18,7 @@ namespace proton::matching {
using namespace search::fef;
using search::queryeval::SearchIterator;
-using search::FeatureSet;
+using vespalib::FeatureSet;
using vespalib::ThreadBundle;
using vespalib::Issue;
diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
index 236964c2e6b..b393558638d 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
+++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.cpp
@@ -27,7 +27,6 @@ using namespace search::engine;
using namespace search::grouping;
using search::DocumentMetaData;
using search::LidUsageStats;
-using search::FeatureSet;
using search::MatchingElementsFields;
using search::MatchingElements;
using search::attribute::IAttributeContext;
@@ -39,6 +38,7 @@ using search::fef::indexproperties::hitcollector::ArraySize;
using search::queryeval::Blueprint;
using search::queryeval::SearchIterator;
using vespalib::Doom;
+using vespalib::FeatureSet;
using vespalib::make_string_short::fmt;
namespace proton::matching {
diff --git a/searchcore/src/vespa/searchcore/proton/matching/matcher.h b/searchcore/src/vespa/searchcore/proton/matching/matcher.h
index bad56fe1c36..6507ffca2eb 100644
--- a/searchcore/src/vespa/searchcore/proton/matching/matcher.h
+++ b/searchcore/src/vespa/searchcore/proton/matching/matcher.h
@@ -11,13 +11,13 @@
#include "viewresolver.h"
#include <vespa/searchcommon/attribute/i_attribute_functor.h>
#include <vespa/searchlib/fef/blueprintfactory.h>
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/common/matching_elements_fields.h>
#include <vespa/searchlib/common/matching_elements.h>
#include <vespa/searchlib/common/resultset.h>
#include <vespa/searchlib/queryeval/blueprint.h>
#include <vespa/searchlib/query/base.h>
#include <vespa/vespalib/util/clock.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vespa/vespalib/util/thread_bundle.h>
#include <mutex>
@@ -135,7 +135,7 @@ public:
* @param attrCtx abstract view of attribute data
* @return calculated summary features.
**/
- search::FeatureSet::SP
+ vespalib::FeatureSet::SP
getSummaryFeatures(const DocsumRequest & req, ISearchContext & searchCtx,
IAttributeContext & attrCtx, SessionManager &sessionManager) const;
@@ -149,7 +149,7 @@ public:
* @param attrCtx abstract view of attribute data
* @return calculated rank features.
**/
- search::FeatureSet::SP
+ vespalib::FeatureSet::SP
getRankFeatures(const DocsumRequest & req, ISearchContext & searchCtx,
IAttributeContext & attrCtx, SessionManager &sessionManager) const;
diff --git a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service_config.cpp b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service_config.cpp
index 50ef5039e75..c1802b40deb 100644
--- a/searchcore/src/vespa/searchcore/proton/server/shared_threading_service_config.cpp
+++ b/searchcore/src/vespa/searchcore/proton/server/shared_threading_service_config.cpp
@@ -28,10 +28,10 @@ namespace {
uint32_t
derive_shared_threads(const ProtonConfig& cfg, const HwInfo::Cpu& cpu_info)
{
- uint32_t scaled_cores = (uint32_t)std::ceil(cpu_info.cores() * cfg.feeding.concurrency);
+ uint32_t scaled_cores = uint32_t(std::ceil(cpu_info.cores() * cfg.feeding.concurrency));
// We need at least 1 guaranteed free worker in order to ensure progress.
- return std::max(scaled_cores, (uint32_t)cfg.documentdb.size() + cfg.flush.maxconcurrent + 1);
+ return std::max(scaled_cores, uint32_t(cfg.flush.maxconcurrent + 1u));
}
uint32_t
@@ -42,8 +42,8 @@ derive_warmup_threads(const HwInfo::Cpu& cpu_info) {
uint32_t
derive_field_writer_threads(const ProtonConfig& cfg, const HwInfo::Cpu& cpu_info)
{
- uint32_t scaled_cores = (size_t)std::ceil(cpu_info.cores() * cfg.feeding.concurrency);
- uint32_t field_writer_threads = std::max(scaled_cores, (uint32_t)cfg.indexing.threads);
+ uint32_t scaled_cores = size_t(std::ceil(cpu_info.cores() * cfg.feeding.concurrency));
+ uint32_t field_writer_threads = std::max(scaled_cores, uint32_t(cfg.indexing.threads));
// Originally we used at least 3 threads for writing fields:
// - index field inverter
// - index field writer
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java
index ae7d0a67b2f..b0f98685578 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int16ResultNode.java
@@ -80,7 +80,7 @@ public class Int16ResultNode extends NumericResultNode {
@Override
public void add(ResultNode rhs) {
- value += rhs.getInteger();
+ value += (short)rhs.getInteger();
}
@Override
@@ -90,7 +90,7 @@ public class Int16ResultNode extends NumericResultNode {
@Override
public void multiply(ResultNode rhs) {
- value *= rhs.getInteger();
+ value *= (short)rhs.getInteger();
}
@Override
@@ -101,7 +101,7 @@ public class Int16ResultNode extends NumericResultNode {
@Override
public void modulo(ResultNode rhs) {
- value %= rhs.getInteger();
+ value %= (short)rhs.getInteger();
}
@Override
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java
index da31cbc236a..711b8f1bd3f 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int32ResultNode.java
@@ -80,7 +80,7 @@ public class Int32ResultNode extends NumericResultNode {
@Override
public void add(ResultNode rhs) {
- value += rhs.getInteger();
+ value += (int)rhs.getInteger();
}
@Override
@@ -90,7 +90,7 @@ public class Int32ResultNode extends NumericResultNode {
@Override
public void multiply(ResultNode rhs) {
- value *= rhs.getInteger();
+ value *= (int)rhs.getInteger();
}
@Override
@@ -101,7 +101,7 @@ public class Int32ResultNode extends NumericResultNode {
@Override
public void modulo(ResultNode rhs) {
- value %= rhs.getInteger();
+ value %= (int)rhs.getInteger();
}
@Override
@@ -122,7 +122,7 @@ public class Int32ResultNode extends NumericResultNode {
@Override
public Object getNumber() {
- return Integer.valueOf(value);
+ return value;
}
@Override
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java
index ae53cf45a6f..d6706ce1dfe 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/Int8ResultNode.java
@@ -78,7 +78,7 @@ public class Int8ResultNode extends NumericResultNode {
@Override
public void add(ResultNode rhs) {
- value += rhs.getInteger();
+ value += (byte)rhs.getInteger();
}
@Override
@@ -88,7 +88,7 @@ public class Int8ResultNode extends NumericResultNode {
@Override
public void multiply(ResultNode rhs) {
- value *= rhs.getInteger();
+ value *= (byte)rhs.getInteger();
}
@Override
@@ -99,7 +99,7 @@ public class Int8ResultNode extends NumericResultNode {
@Override
public void modulo(ResultNode rhs) {
- value %= rhs.getInteger();
+ value %= (byte)rhs.getInteger();
}
@Override
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java
index 5a0e056f254..d1dc46fc4d0 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/expression/RawResultNode.java
@@ -18,8 +18,8 @@ public class RawResultNode extends SingleResultNode {
// The global class identifier shared with C++.
public static final int classId = registerClass(0x4000 + 54, RawResultNode.class);
- private static RawResultNode negativeInfinity = new RawResultNode();
- private static PositiveInfinityResultNode positiveInfinity = new PositiveInfinityResultNode();
+ private static final RawResultNode negativeInfinity = new RawResultNode();
+ private static final PositiveInfinityResultNode positiveInfinity = new PositiveInfinityResultNode();
// The raw value of this node.
private RawData value = null;
@@ -147,7 +147,7 @@ public class RawResultNode extends SingleResultNode {
@Override
public Object getValue() {
- return getString();
+ return value;
}
@Override
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java
index 2b5efdb1ffe..5b6a53a7019 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/ranking/features/fieldmatch/FieldMatchMetrics.java
@@ -16,7 +16,7 @@ import static java.lang.Math.*;
public final class FieldMatchMetrics implements Cloneable {
/** The calculator creating this - given on initialization */
- private FieldMatchMetricsComputer source;
+ private final FieldMatchMetricsComputer source;
/** The trace accumulated during execution - empty if no tracing */
private final Trace trace = new Trace();
@@ -75,7 +75,7 @@ public final class FieldMatchMetrics implements Cloneable {
currentSequence=0;
segmentStarts.clear();
- queryLength=source.getQuery().getTerms().length;
+ queryLength = source.getQuery().getTerms().length;
}
/** Are these metrics representing a complete match */
@@ -93,7 +93,7 @@ public final class FieldMatchMetrics implements Cloneable {
*/
public float get(String name) {
try {
- Method getter=getClass().getMethod("get" + name.substring(0,1).toUpperCase() + name.substring(1));
+ Method getter = getClass().getMethod("get" + name.substring(0, 1).toUpperCase() + name.substring(1));
return ((Number)getter.invoke(this)).floatValue();
}
catch (NoSuchMethodException e) {
@@ -140,7 +140,7 @@ public final class FieldMatchMetrics implements Cloneable {
* segment or out of order
*/
public float getAbsoluteProximity() {
- if (pairs <1) return 0.1f;
+ if (pairs < 1) return 0.1f;
return proximity/pairs;
}
@@ -151,7 +151,7 @@ public final class FieldMatchMetrics implements Cloneable {
* following each other in sequence, and close to 0 if they are far from each other or out of order
*/
public float getUnweightedProximity() {
- if (pairs <1) return 1f;
+ if (pairs < 1) return 1f;
return unweightedProximity/pairs;
}
@@ -271,33 +271,33 @@ public final class FieldMatchMetrics implements Cloneable {
* <code>queryCompleteness * ( 1 - fieldCompletenessImportance) + fieldCompletenessImportance * fieldCompleteness</code>
*/
public float getCompleteness() {
- float fieldCompletenessImportance=source.getParameters().getFieldCompletenessImportance();
+ float fieldCompletenessImportance = source.getParameters().getFieldCompletenessImportance();
return getQueryCompleteness() * ( 1 - fieldCompletenessImportance) + fieldCompletenessImportance*getFieldCompleteness();
}
/** Returns how well the order of the terms agreed in segments: <code>1-outOfOrder/pairs</code> */
public float getOrderness() {
- if (pairs ==0) return 1f;
+ if (pairs == 0) return 1f;
return 1-(float)outOfOrder/pairs;
}
/** Returns the degree to which different terms are related (occurring in the same segment): <code>1-segments/(matches-1)</code> */
public float getRelatedness() {
- if (matches==0) return 0;
- if (matches==1) return 1;
- return 1-(float)(segments-1)/(matches-1);
+ if (matches == 0) return 0;
+ if (matches == 1) return 1;
+ return 1 - (float)(segments - 1) / (matches - 1);
}
/** Returns <code>longestSequence/matches</code> */
public float getLongestSequenceRatio() {
- if (matches==0) return 0;
- return (float)longestSequence/matches;
+ if (matches == 0) return 0;
+ return (float)longestSequence / matches;
}
/** Returns the closeness of the segments in the field: <code>1-segmentDistance/fieldLength</code> */
public float getSegmentProximity() {
- if (matches==0) return 0;
- return 1-segmentDistance/source.getField().terms().size();
+ if (matches == 0) return 0;
+ return 1 - segmentDistance / source.getField().terms().size();
}
/**
@@ -306,14 +306,14 @@ public final class FieldMatchMetrics implements Cloneable {
* This is absoluteProximity/average connectedness.
*/
public float getProximity() {
- float totalConnectedness=0;
- for (int i=1; i<queryLength; i++) {
- totalConnectedness+=Math.max(0.1,source.getQuery().getTerms()[i].getConnectedness());
+ float totalConnectedness = 0;
+ for (int i = 1; i < queryLength; i++) {
+ totalConnectedness += (float)Math.max(0.1, source.getQuery().getTerms()[i].getConnectedness());
}
- float averageConnectedness=0.1f;
- if (queryLength>1)
- averageConnectedness=totalConnectedness/(queryLength-1);
- return getAbsoluteProximity()/averageConnectedness;
+ float averageConnectedness = 0.1f;
+ if (queryLength > 1)
+ averageConnectedness = totalConnectedness / (queryLength - 1);
+ return getAbsoluteProximity() / averageConnectedness;
}
/**
@@ -378,7 +378,7 @@ public final class FieldMatchMetrics implements Cloneable {
* not only when the metrics are complete, because this metric is used to choose segments during calculation.</p>
*/
float getSegmentationScore() {
- if (segments==0) return 0;
+ if (segments == 0) return 0;
return getAbsoluteProximity() * getExactness() / (segments * segments);
}
@@ -389,7 +389,7 @@ public final class FieldMatchMetrics implements Cloneable {
/** Called once for every match */
void onMatch(int i, int j) {
- if (matches>=source.getField().terms().size()) return;
+ if (matches >= source.getField().terms().size()) return;
matches++;
weight += (float)source.getQuery().getTerms()[i].getWeight() / source.getQuery().getTotalTermWeight();
significance += source.getQuery().getTerms()[i].getSignificance() / source.getQuery().getTotalSignificance();
@@ -418,42 +418,42 @@ public final class FieldMatchMetrics implements Cloneable {
}
/** Called once when this value is calculated, before onComplete */
- void setOccurrence(float occurrence) { this.occurrence=occurrence; }
+ void setOccurrence(float occurrence) { this.occurrence = occurrence; }
/** Called once when this value is calculated, before onComplete */
- void setWeightedOccurrence(float weightedOccurrence) { this.weightedOccurrence=weightedOccurrence; }
+ void setWeightedOccurrence(float weightedOccurrence) { this.weightedOccurrence = weightedOccurrence; }
/** Called once when this value is calculated, before onComplete */
- void setAbsoluteOccurrence(float absoluteOccurrence) { this.absoluteOccurrence=absoluteOccurrence; }
+ void setAbsoluteOccurrence(float absoluteOccurrence) { this.absoluteOccurrence = absoluteOccurrence; }
/** Called once when this value is calculated, before onComplete */
- void setWeightedAbsoluteOccurrence(float weightedAbsoluteOccurrence) { this.weightedAbsoluteOccurrence=weightedAbsoluteOccurrence; }
+ void setWeightedAbsoluteOccurrence(float weightedAbsoluteOccurrence) { this.weightedAbsoluteOccurrence = weightedAbsoluteOccurrence; }
/** Called once when this value is calculated, before onComplete */
- void setSignificantOccurrence(float significantOccurrence) { this.significantOccurrence =significantOccurrence; }
+ void setSignificantOccurrence(float significantOccurrence) { this.significantOccurrence = significantOccurrence; }
/** Called once when matching is complete */
void onComplete() {
// segment distance - calculated from sorted segment starts
- if (segmentStarts.size()<=1) {
- segmentDistance=0;
+ if (segmentStarts.size() <= 1) {
+ segmentDistance = 0;
}
else {
Collections.sort(segmentStarts);
- for (int i=1; i<segmentStarts.size(); i++) {
- segmentDistance+=segmentStarts.get(i)-segmentStarts.get(i-1)+1;
+ for (int i = 1; i < segmentStarts.size(); i++) {
+ segmentDistance += segmentStarts.get(i) - segmentStarts.get(i - 1) + 1;
}
}
- if (head==-1) head=0;
- if (tail==-1) tail=0;
+ if (head == -1) head = 0;
+ if (tail == -1) tail = 0;
}
// Events on pairs ----------
/** Called when <i>any</i> pair is encountered */
void onPair(int i, int j, int previousJ) {
- int distance = j-previousJ-1;
+ int distance = j - previousJ - 1;
if (distance < 0) distance++; // Discontinuity where the two terms are in the same position
if (abs(distance) > source.getParameters().getProximityLimit()) return; // Contribution=0
@@ -463,7 +463,7 @@ public final class FieldMatchMetrics implements Cloneable {
unweightedProximity += pairProximity;
float connectedness = source.getQuery().getTerms()[i].getConnectedness();
- proximity += pow(pairProximity, connectedness/0.1) * max(0.1, connectedness);
+ proximity += (float)pow(pairProximity, connectedness / 0.1) * (float)max(0.1, connectedness);
pairs++;
}
@@ -498,8 +498,8 @@ public final class FieldMatchMetrics implements Cloneable {
@Override
public FieldMatchMetrics clone() {
try {
- FieldMatchMetrics clone=(FieldMatchMetrics)super.clone();
- clone.segmentStarts=new ArrayList<>(segmentStarts);
+ FieldMatchMetrics clone = (FieldMatchMetrics)super.clone();
+ clone.segmentStarts = new ArrayList<>(segmentStarts);
return clone;
}
catch (CloneNotSupportedException e) {
@@ -514,19 +514,19 @@ public final class FieldMatchMetrics implements Cloneable {
public String toStringDump() {
try {
- StringBuilder b=new StringBuilder();
+ StringBuilder b = new StringBuilder();
for (Method m : this.getClass().getDeclaredMethods()) {
if ( ! m.getName().startsWith("get")) continue;
- if (m.getReturnType()!=Integer.TYPE && m.getReturnType()!=Float.TYPE) continue;
- if ( m.getParameterTypes().length!=0 ) continue;
+ if (m.getReturnType() != Integer.TYPE && m.getReturnType() != Float.TYPE) continue;
+ if ( m.getParameterTypes().length != 0 ) continue;
- Object value=m.invoke(this,new Object[0]);
- b.append(m.getName().substring(3,4).toLowerCase() + m.getName().substring(4) + ": " + value + "\n");
+ Object value = m.invoke(this, new Object[0]);
+ b.append(m.getName().substring(3, 4).toLowerCase() + m.getName().substring(4) + ": " + value + "\n");
}
return b.toString();
}
catch (Exception e) {
- throw new RuntimeException("Programming error",e);
+ throw new RuntimeException("Programming error", e);
}
}
diff --git a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java
index 949e1f026f7..df721a4309e 100644
--- a/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java
+++ b/searchlib/src/main/java/com/yahoo/searchlib/rankingexpression/evaluation/gbdtoptimization/GBDTNode.java
@@ -70,13 +70,13 @@ public final class GBDTNode extends ExpressionNode {
int offset = (int)nextValue - MAX_LEAF_VALUE;
boolean comparisonIsTrue = false;
if (offset < MAX_VARIABLES) {
- comparisonIsTrue = context.getDouble(offset)<values[pc++];
+ comparisonIsTrue = context.getDouble(offset) < values[pc++];
}
- else if (offset < MAX_VARIABLES*2) {
- comparisonIsTrue = context.getDouble(offset-MAX_VARIABLES)==values[pc++];
+ else if (offset < MAX_VARIABLES * 2) {
+ comparisonIsTrue = context.getDouble(offset - MAX_VARIABLES) == values[pc++];
}
- else if (offset<MAX_VARIABLES*3) {
- double testValue = context.getDouble(offset-MAX_VARIABLES*2);
+ else if (offset < MAX_VARIABLES * 3) {
+ double testValue = context.getDouble(offset - MAX_VARIABLES * 2);
int setValuesLeft = (int)values[pc++];
while (setValuesLeft > 0) { // test each value in the set
setValuesLeft--;
@@ -88,13 +88,13 @@ public final class GBDTNode extends ExpressionNode {
pc += setValuesLeft; // jump to after the set
}
else { // offset<MAX_VARIABLES*4
- comparisonIsTrue = ! (context.getDouble(offset-MAX_VARIABLES*3)>=values[pc++]);
+ comparisonIsTrue = ! (context.getDouble(offset - MAX_VARIABLES * 3) >= values[pc++]);
}
if (comparisonIsTrue)
pc++; // true branch - skip the jump value
else
- pc += values[pc]; // false branch - jump
+ pc += (int)values[pc]; // false branch - jump
}
else { // a leaf
return nextValue;
diff --git a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java
index d846d322720..8a33f320bb0 100644
--- a/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java
+++ b/searchlib/src/test/java/com/yahoo/searchlib/gbdt/GbdtConverterTestCase.java
@@ -3,14 +3,12 @@ package com.yahoo.searchlib.gbdt;
import com.yahoo.searchlib.rankingexpression.RankingExpression;
import com.yahoo.searchlib.rankingexpression.parser.ParseException;
-import org.junit.After;
-import org.junit.Before;
import org.junit.Test;
import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
-import java.security.Permission;
+import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertEquals;
@@ -21,36 +19,6 @@ import static org.junit.Assert.fail;
*/
public class GbdtConverterTestCase {
- @Before
- @SuppressWarnings("removal")
- public void enableSecurityManager() {
- System.setSecurityManager(new NoExitSecurityManager());
- }
-
- @After
- @SuppressWarnings("removal")
- public void disableSecurityManager() {
- System.setSecurityManager(null);
- }
-
- @Test
- public void testOnlyOneArgumentIsAccepted() throws UnsupportedEncodingException {
- assertError("Usage: GbdtConverter <filename>\n", new String[0]);
- assertError("Usage: GbdtConverter <filename>\n", new String[] { "foo", "bar" });
- }
-
- @Test
- public void testFileIsFound() throws UnsupportedEncodingException {
- assertError("Could not find file 'not.found'.\n", new String[] { "not.found" });
- }
-
- @Test
- public void testFileParsingExceptionIsCaught() throws UnsupportedEncodingException {
- assertError("An error occurred while parsing the content of file 'src/test/files/gbdt_err.xml': " +
- "Node 'Unknown' has no 'DecisionTree' children.\n",
- new String[] { "src/test/files/gbdt_err.xml" });
- }
-
@Test
public void testEmptyTreesAreIgnored() throws Exception {
assertConvert("src/test/files/gbdt_empty_tree.xml",
@@ -125,7 +93,7 @@ public class GbdtConverterTestCase {
ByteArrayOutputStream out = new ByteArrayOutputStream();
System.setOut(new PrintStream(out));
GbdtConverter.main(new String[] { gbdtModelFile });
- String actualExpression = out.toString("UTF-8");
+ String actualExpression = out.toString(StandardCharsets.UTF_8);
assertEquals(expectedExpression, actualExpression);
assertNotNull(new RankingExpression(actualExpression));
}
@@ -138,26 +106,7 @@ public class GbdtConverterTestCase {
fail();
} catch (ExitException e) {
assertEquals(1, e.status);
- assertEquals(expected, err.toString("UTF-8"));
- }
- }
-
- @SuppressWarnings("removal")
- private static class NoExitSecurityManager extends SecurityManager {
-
- @Override
- public void checkPermission(Permission perm) {
- // allow anything
- }
-
- @Override
- public void checkPermission(Permission perm, Object context) {
- // allow anything
- }
-
- @Override
- public void checkExit(int status) {
- throw new ExitException(status);
+ assertEquals(expected, err.toString(StandardCharsets.UTF_8));
}
}
@@ -169,4 +118,5 @@ public class GbdtConverterTestCase {
this.status = status;
}
}
+
}
diff --git a/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp b/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
index 16a04a746f3..2a0b973fea6 100644
--- a/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
+++ b/searchlib/src/tests/attribute/attribute_header/attribute_header_test.cpp
@@ -73,6 +73,7 @@ TEST(AttributeHeaderTest, can_be_added_to_and_extracted_from_generic_header)
verify_roundtrip_serialization(HnswIPO({16, 100, DistanceMetric::Angular}));
verify_roundtrip_serialization(HnswIPO({16, 100, DistanceMetric::GeoDegrees}));
verify_roundtrip_serialization(HnswIPO({16, 100, DistanceMetric::InnerProduct}));
+ verify_roundtrip_serialization(HnswIPO({16, 100, DistanceMetric::PrenormalizedAngular}));
verify_roundtrip_serialization(HnswIPO({16, 100, DistanceMetric::Hamming}));
verify_roundtrip_serialization(HnswIPO());
}
diff --git a/searchlib/src/tests/attribute/attribute_test.cpp b/searchlib/src/tests/attribute/attribute_test.cpp
index 7eb43faac18..3a1a5b457ef 100644
--- a/searchlib/src/tests/attribute/attribute_test.cpp
+++ b/searchlib/src/tests/attribute/attribute_test.cpp
@@ -912,8 +912,8 @@ AttributeTest::testSingle()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("sv-post-int32", cfg);
ptr->updateStat(true);
- EXPECT_EQ(339020u, ptr->getStatus().getAllocated());
- EXPECT_EQ(101852u, ptr->getStatus().getUsed());
+ EXPECT_EQ(338972u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(101632u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testSingle<IntegerAttribute, AttributeVector::largeint_t, int32_t>(ptr, values);
}
@@ -934,8 +934,8 @@ AttributeTest::testSingle()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("sv-post-float", cfg);
ptr->updateStat(true);
- EXPECT_EQ(339020, ptr->getStatus().getAllocated());
- EXPECT_EQ(101852u, ptr->getStatus().getUsed());
+ EXPECT_EQ(338972u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(101632u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testSingle<FloatingPointAttribute, double, float>(ptr, values);
}
@@ -947,8 +947,8 @@ AttributeTest::testSingle()
{
AttributePtr ptr = createAttribute("sv-string", Config(BasicType::STRING, CollectionType::SINGLE));
ptr->updateStat(true);
- EXPECT_EQ(117256u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
- EXPECT_EQ(53240u + sizeof_large_string_entry, ptr->getStatus().getUsed());
+ EXPECT_EQ(116528u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
+ EXPECT_EQ(52844u + sizeof_large_string_entry, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testSingle<StringAttribute, string, string>(ptr, values);
}
@@ -957,8 +957,8 @@ AttributeTest::testSingle()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("sv-fs-string", cfg);
ptr->updateStat(true);
- EXPECT_EQ(345624u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
- EXPECT_EQ(105176u + sizeof_large_string_entry, ptr->getStatus().getUsed());
+ EXPECT_EQ(344848u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
+ EXPECT_EQ(104556u + sizeof_large_string_entry, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testSingle<StringAttribute, string, string>(ptr, values);
}
@@ -1089,8 +1089,8 @@ AttributeTest::testArray()
{
AttributePtr ptr = createAttribute("a-int32", Config(BasicType::INT32, CollectionType::ARRAY));
ptr->updateStat(true);
- EXPECT_EQ(512056u, ptr->getStatus().getAllocated());
- EXPECT_EQ(504392u, ptr->getStatus().getUsed());
+ EXPECT_EQ(487480u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(479720u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<IntegerAttribute, AttributeVector::largeint_t>(ptr, values);
}
@@ -1099,8 +1099,8 @@ AttributeTest::testArray()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("flags", cfg);
ptr->updateStat(true);
- EXPECT_EQ(512056u, ptr->getStatus().getAllocated());
- EXPECT_EQ(504392u, ptr->getStatus().getUsed());
+ EXPECT_EQ(487480u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(479720u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<IntegerAttribute, AttributeVector::largeint_t>(ptr, values);
}
@@ -1109,8 +1109,8 @@ AttributeTest::testArray()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("a-fs-int32", cfg);
ptr->updateStat(true);
- EXPECT_EQ(868788u, ptr->getStatus().getAllocated());
- EXPECT_EQ(606264u, ptr->getStatus().getUsed());
+ EXPECT_EQ(844116u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(581372u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<IntegerAttribute, AttributeVector::largeint_t>(ptr, values);
}
@@ -1128,8 +1128,8 @@ AttributeTest::testArray()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("a-fs-float", cfg);
ptr->updateStat(true);
- EXPECT_EQ(868788u, ptr->getStatus().getAllocated());
- EXPECT_EQ(606264u, ptr->getStatus().getUsed());
+ EXPECT_EQ(844116u, ptr->getStatus().getAllocated());
+ EXPECT_EQ(581372u, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<FloatingPointAttribute, double>(ptr, values);
}
@@ -1140,8 +1140,8 @@ AttributeTest::testArray()
{
AttributePtr ptr = createAttribute("a-string", Config(BasicType::STRING, CollectionType::ARRAY));
ptr->updateStat(true);
- EXPECT_EQ(625088u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
- EXPECT_EQ(557632u + sizeof_large_string_entry, ptr->getStatus().getUsed());
+ EXPECT_EQ(599784u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
+ EXPECT_EQ(532564u + sizeof_large_string_entry, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<StringAttribute, string>(ptr, values);
}
@@ -1150,8 +1150,8 @@ AttributeTest::testArray()
cfg.setFastSearch(true);
AttributePtr ptr = createAttribute("afs-string", cfg);
ptr->updateStat(true);
- EXPECT_EQ(875392u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
- EXPECT_EQ(609588u + sizeof_large_string_entry, ptr->getStatus().getUsed());
+ EXPECT_EQ(849992u + sizeof_large_string_entry, ptr->getStatus().getAllocated());
+ EXPECT_EQ(584296u + sizeof_large_string_entry, ptr->getStatus().getUsed());
addDocs(ptr, numDocs);
testArray<StringAttribute, string>(ptr, values);
}
@@ -1718,7 +1718,7 @@ AttributeTest::testStatus()
ptr->commit(true);
EXPECT_EQ(ptr->getStatus().getNumDocs(), 100u);
EXPECT_EQ(ptr->getStatus().getNumValues(), 100u);
- EXPECT_EQ(ptr->getStatus().getNumUniqueValues(), 1u);
+ EXPECT_EQ(ptr->getStatus().getNumUniqueValues(), 2u);
size_t expUsed = 0;
expUsed += 1 * InternalNodeSize + 1 * LeafNodeSize; // enum store tree
expUsed += 1 * 32; // enum store (uniquevalues * bytes per entry)
@@ -1741,7 +1741,7 @@ AttributeTest::testStatus()
ptr->commit(true);
EXPECT_EQ(ptr->getStatus().getNumDocs(), numDocs);
EXPECT_EQ(ptr->getStatus().getNumValues(), numDocs*numValuesPerDoc);
- EXPECT_EQ(ptr->getStatus().getNumUniqueValues(), numUniq);
+ EXPECT_EQ(ptr->getStatus().getNumUniqueValues(), numUniq + 1);
size_t expUsed = 0;
expUsed += 1 * InternalNodeSize + 1 * LeafNodeSize; // Approximate enum store tree
expUsed += 272; // TODO Approximate... enum store (16 unique values, 17 bytes per entry)
@@ -2145,12 +2145,12 @@ AttributeTest::test_default_value_ref_count_is_updated_after_shrink_lid_space()
const auto & iattr = dynamic_cast<const search::IntegerAttributeTemplate<int32_t> &>(*attr);
attr->addReservedDoc();
attr->addDocs(10);
- EXPECT_EQ(11u, get_default_value_ref_count(*attr, iattr.defaultValue()));
+ EXPECT_EQ(12u, get_default_value_ref_count(*attr, iattr.defaultValue()));
attr->compactLidSpace(6);
- EXPECT_EQ(11u, get_default_value_ref_count(*attr, iattr.defaultValue()));
+ EXPECT_EQ(12u, get_default_value_ref_count(*attr, iattr.defaultValue()));
attr->shrinkLidSpace();
EXPECT_EQ(6u, attr->getNumDocs());
- EXPECT_EQ(6u, get_default_value_ref_count(*attr, iattr.defaultValue()));
+ EXPECT_EQ(7u, get_default_value_ref_count(*attr, iattr.defaultValue()));
}
template <typename AttributeType>
@@ -2170,7 +2170,7 @@ AttributeTest::requireThatAddressSpaceUsageIsReported(const Config &config, bool
AddressSpaceUsage after = attrPtr->getAddressSpaceUsage();
if (attrPtr->hasEnum()) {
LOG(info, "requireThatAddressSpaceUsageIsReported(%s): Has enum", attrName.c_str());
- EXPECT_EQ(before.enum_store_usage().used(), 1u);
+ EXPECT_EQ(before.enum_store_usage().used(), 2u);
EXPECT_EQ(before.enum_store_usage().dead(), 1u);
EXPECT_GT(after.enum_store_usage().used(), before.enum_store_usage().used());
EXPECT_GE(after.enum_store_usage().limit(), before.enum_store_usage().limit());
@@ -2334,6 +2334,10 @@ AttributeTest::test_paged_attribute(const vespalib::string& name, const vespalib
size_t rounded_size = vespalib::round_up_to_page_size(1);
size_t lid_mapping_size = 1200;
size_t sv_maxlid = 1200;
+ if (rounded_size == 16_Ki) {
+ lid_mapping_size = 4200;
+ sv_maxlid = 1300;
+ }
if (rounded_size == 64_Ki) {
lid_mapping_size = 17000;
sv_maxlid = 1500;
diff --git a/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp b/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
index 820f39089d1..5501c99652b 100644
--- a/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
+++ b/searchlib/src/tests/attribute/enumeratedsave/enumeratedsave_test.cpp
@@ -183,7 +183,7 @@ MemAttr::bufEqual(const Buffer &lhs, const Buffer &rhs) const
return false;
if (lhs.get() == NULL)
return true;
- if (!EXPECT_TRUE(lhs->getDataLen() == rhs->getDataLen()))
+ if (!EXPECT_EQUAL(lhs->getDataLen(), rhs->getDataLen()))
return false;
if (!EXPECT_TRUE(vespalib::memcmp_safe(lhs->getData(), rhs->getData(),
lhs->getDataLen()) == 0))
@@ -243,8 +243,9 @@ EnumeratedSaveTest::populate(IntegerAttribute &v, unsigned seed,
int weight = 1;
for(size_t i(0), m(v.getNumDocs()); i < m; i++) {
v.clearDoc(i);
- if (i == 9)
+ if (i == 9) {
continue;
+ }
if (i == 7) {
if (v.hasMultiValue()) {
v.append(i, -42, 27);
@@ -270,7 +271,7 @@ EnumeratedSaveTest::populate(IntegerAttribute &v, unsigned seed,
i + 1);
}
} else {
- EXPECT_TRUE( v.update(i, lrand48() & mask) );
+ EXPECT_TRUE( v.update(i, rnd.lrand48() & mask) );
}
}
v.commit();
@@ -288,8 +289,9 @@ EnumeratedSaveTest::populate(FloatingPointAttribute &v, unsigned seed,
int weight = 1;
for(size_t i(0), m(v.getNumDocs()); i < m; i++) {
v.clearDoc(i);
- if (i == 9)
+ if (i == 9) {
continue;
+ }
if (i == 7) {
if (v.hasMultiValue()) {
v.append(i, -42.0, 27);
@@ -315,7 +317,7 @@ EnumeratedSaveTest::populate(FloatingPointAttribute &v, unsigned seed,
i + 1);
}
} else {
- EXPECT_TRUE( v.update(i, lrand48()) );
+ EXPECT_TRUE( v.update(i, rnd.lrand48()) );
}
}
v.commit();
@@ -332,8 +334,9 @@ EnumeratedSaveTest::populate(StringAttribute &v, unsigned seed,
int weight = 1;
for(size_t i(0), m(v.getNumDocs()); i < m; i++) {
v.clearDoc(i);
- if (i == 9)
+ if (i == 9) {
continue;
+ }
if (i == 7) {
if (v.hasMultiValue()) {
v.append(i, "foo", 27);
@@ -712,9 +715,9 @@ EnumeratedSaveTest::test(BasicType bt, CollectionType ct,
Config check_cfg(cfg);
check_cfg.setFastSearch(true);
- checkLoad<VectorType, BufferType>(check_cfg, pref + "0_ee", v0);
- checkLoad<VectorType, BufferType>(check_cfg, pref + "1_ee", v1);
- checkLoad<VectorType, BufferType>(check_cfg, pref + "2_ee", v2);
+ TEST_DO((checkLoad<VectorType, BufferType>(check_cfg, pref + "0_ee", v0)));
+ TEST_DO((checkLoad<VectorType, BufferType>(check_cfg, pref + "1_ee", v1)));
+ TEST_DO((checkLoad<VectorType, BufferType>(check_cfg, pref + "2_ee", v2)));
TEST_DO((testReload<VectorType, BufferType>(v0, v1, v2,
mv0, mv1, mv2,
diff --git a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
index b3c7516777c..2b01c266e80 100644
--- a/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
+++ b/searchlib/src/tests/attribute/enumstore/enumstore_test.cpp
@@ -180,15 +180,35 @@ TYPED_TEST(FloatEnumStoreTest, numbers_can_be_inserted_and_retrieved)
}
}
+TEST(EnumStoreTest, default_value_is_present)
+{
+ StringEnumStore ses(false, DictionaryConfig::Type::BTREE);
+ using EntryType = StringEnumStore::EntryType;
+ EntryType undefined = attribute::getUndefined<EntryType>();
+ EnumIndex idx;
+ EXPECT_TRUE(ses.find_index(undefined, idx));
+ EXPECT_TRUE(idx.valid());
+ EXPECT_EQ(ses.get_default_value_ref().load_relaxed(), idx);
+ ses.clear_default_value_ref();
+ EXPECT_FALSE(ses.find_index(undefined, idx));
+ EXPECT_FALSE(ses.get_default_value_ref().load_relaxed().valid());
+ ses.setup_default_value_ref();
+ idx = EnumIndex();
+ EXPECT_TRUE(ses.find_index(undefined, idx));
+ EXPECT_TRUE(idx.valid());
+ EXPECT_EQ(ses.get_default_value_ref().load_relaxed(), idx);
+}
+
TEST(EnumStoreTest, test_find_folded_on_string_enum_store)
{
StringEnumStore ses(false, DictionaryConfig::Type::BTREE);
+ using EntryType = StringEnumStore::EntryType;
std::vector<EnumIndex> indices;
std::vector<std::string> unique({"", "one", "two", "TWO", "Two", "three"});
for (std::string &str : unique) {
EnumIndex idx = ses.insert(str.c_str());
indices.push_back(idx);
- EXPECT_EQ(1u, ses.get_ref_count(idx));
+ EXPECT_EQ((str == attribute::getUndefined<EntryType>()) ? 2u : 1u, ses.get_ref_count(idx));
}
ses.freeze_dictionary();
for (uint32_t i = 0; i < indices.size(); ++i) {
@@ -233,13 +253,14 @@ void
StringEnumStoreTest::testInsert(bool hasPostings)
{
StringEnumStore ses(hasPostings, DictionaryConfig::Type::BTREE);
+ using EntryType = StringEnumStore::EntryType;
std::vector<EnumIndex> indices;
std::vector<std::string> unique = {"", "add", "enumstore", "unique"};
for (const auto & i : unique) {
EnumIndex idx = ses.insert(i.c_str());
- EXPECT_EQ(1u, ses.get_ref_count(idx));
+ EXPECT_EQ((i == attribute::getUndefined<EntryType>()) ? 2u : 1u, ses.get_ref_count(idx));
indices.push_back(idx);
EXPECT_TRUE(ses.find_index(i.c_str(), idx));
}
@@ -253,7 +274,7 @@ StringEnumStoreTest::testInsert(bool hasPostings)
EnumIndex idx;
EXPECT_TRUE(ses.find_index(unique[i].c_str(), idx));
EXPECT_TRUE(idx == indices[i]);
- EXPECT_EQ(1u, ses.get_ref_count(indices[i]));
+ EXPECT_EQ((i == 0) ? 2u : 1u, ses.get_ref_count(indices[i]));
const char* value = nullptr;
EXPECT_TRUE(ses.get_value(indices[i], value));
EXPECT_TRUE(strcmp(unique[i].c_str(), value) == 0);
@@ -354,22 +375,22 @@ TEST(EnumStoreTest, address_space_usage_is_reported)
NumericEnumStore store(false, DictionaryConfig::Type::BTREE);
using vespalib::AddressSpace;
- EXPECT_EQ(AddressSpace(1, 1, ADDRESS_LIMIT), store.get_values_address_space_usage());
- EnumIndex idx1 = store.insert(10);
EXPECT_EQ(AddressSpace(2, 1, ADDRESS_LIMIT), store.get_values_address_space_usage());
- EnumIndex idx2 = store.insert(20);
+ EnumIndex idx1 = store.insert(10);
// Address limit increases because buffer is re-sized.
EXPECT_EQ(AddressSpace(3, 1, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
+ EnumIndex idx2 = store.insert(20);
+ EXPECT_EQ(AddressSpace(4, 1, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
dec_ref_count(store, idx1);
- EXPECT_EQ(AddressSpace(3, 2, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
+ EXPECT_EQ(AddressSpace(4, 2, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
dec_ref_count(store, idx2);
- EXPECT_EQ(AddressSpace(3, 3, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
+ EXPECT_EQ(AddressSpace(4, 3, ADDRESS_LIMIT + 2), store.get_values_address_space_usage());
}
TEST(EnumStoreTest, provided_memory_allocator_is_used)
{
AllocStats stats;
- NumericEnumStore ses(false, DictionaryConfig::Type::BTREE, std::make_unique<MemoryAllocatorObserver>(stats));
+ NumericEnumStore ses(false, DictionaryConfig::Type::BTREE, std::make_unique<MemoryAllocatorObserver>(stats), attribute::getUndefined<NumericEnumStore::EntryType>());
EXPECT_EQ(AllocStats(1, 0), stats);
}
@@ -539,6 +560,7 @@ TYPED_TEST_SUITE(LoaderTest, LoaderTestTypes);
TYPED_TEST(LoaderTest, store_is_instantiated_with_enumerated_loader)
{
+ this->store.clear_default_value_ref();
auto loader = this->store.make_enumerated_loader();
this->load_values(loader);
loader.allocate_enums_histogram();
@@ -554,6 +576,7 @@ TYPED_TEST(LoaderTest, store_is_instantiated_with_enumerated_loader)
TYPED_TEST(LoaderTest, store_is_instantiated_with_enumerated_postings_loader)
{
+ this->store.clear_default_value_ref();
auto loader = this->store.make_enumerated_postings_loader();
this->load_values(loader);
this->set_ref_count(0, 1, loader);
@@ -568,6 +591,7 @@ TYPED_TEST(LoaderTest, store_is_instantiated_with_enumerated_postings_loader)
TYPED_TEST(LoaderTest, store_is_instantiated_with_non_enumerated_loader)
{
+ this->store.clear_default_value_ref();
auto loader = this->store.make_non_enumerated_loader();
using MyValues = LoaderTestValues<typename TypeParam::EnumStoreType>;
loader.insert(MyValues::values[0], 100);
@@ -610,6 +634,7 @@ public:
void test_normalize_posting_lists(bool use_filter, bool one_filter);
void test_foreach_posting_list(bool one_filter);
static EntryRef fake_pidx() { return EntryRef(42); }
+ EnumIndex check_default_value_ref() const noexcept;
};
template <typename EnumStoreTypeAndDictionaryType>
@@ -775,6 +800,16 @@ EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::test_foreach_posting_li
clear_sample_values(large_population);
}
+template <typename EnumStoreTypeAndDictionaryType>
+EnumIndex
+EnumStoreDictionaryTest<EnumStoreTypeAndDictionaryType>::check_default_value_ref() const noexcept
+{
+ EnumIndex default_value_ref = store.get_default_value_ref().load_relaxed();
+ EXPECT_TRUE(default_value_ref.valid());
+ EXPECT_EQ(attribute::getUndefined<EntryType>(), store.get_value(default_value_ref));
+ return default_value_ref;
+}
+
using EnumStoreDictionaryTestTypes = ::testing::Types<BTreeNumericEnumStore, HybridNumericEnumStore, HashNumericEnumStore>;
TYPED_TEST_SUITE(EnumStoreDictionaryTest, EnumStoreDictionaryTestTypes);
@@ -875,6 +910,7 @@ TYPED_TEST(EnumStoreDictionaryTest, compact_worst_works)
updater.commit();
generation_t gen = 3;
inc_generation(gen, this->store);
+ // Compact dictionary
auto& dict = this->store.get_dictionary();
if (dict.get_has_btree_dictionary()) {
EXPECT_LT(CompactionStrategy::DEAD_BYTES_SLACK, dict.get_btree_memory_usage().deadBytes());
@@ -902,8 +938,31 @@ TYPED_TEST(EnumStoreDictionaryTest, compact_worst_works)
if (dict.get_has_hash_dictionary()) {
EXPECT_GT(CompactionStrategy::DEAD_BYTES_SLACK, dict.get_hash_memory_usage().deadBytes());
}
+ auto old_default_value_ref = this->check_default_value_ref();
+ // Compact values
+ EXPECT_LT(CompactionStrategy::DEAD_BYTES_SLACK, this->store.get_values_memory_usage().deadBytes());
+ compaction_strategy = CompactionStrategy::make_compact_all_active_buffers_strategy();
+ int compact_values_count = 0;
+ for (uint32_t i = 0; i < 2; ++i) {
+ this->store.update_stat(compaction_strategy);
+ auto remapper = this->store.consider_compact_values(compaction_strategy);
+ if (remapper) {
+ remapper->done();
+ ++compact_values_count;
+ } else {
+ break;
+ }
+ EXPECT_FALSE(this->store.consider_compact_values(compaction_strategy));
+ inc_generation(gen, this->store);
+ }
+ EXPECT_EQ(1, compact_values_count);
+ auto new_default_value_ref = this->check_default_value_ref();
+ EXPECT_NE(old_default_value_ref, new_default_value_ref);
+ EXPECT_GT(CompactionStrategy::DEAD_BYTES_SLACK, this->store.get_values_memory_usage().deadBytes());
+
std::vector<int32_t> exp_values;
std::vector<int32_t> values;
+ exp_values.push_back(std::numeric_limits<int32_t>::min());
for (int32_t i = 0; i < 20; ++i) {
exp_values.push_back(i);
}
diff --git a/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp b/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
index 9e65dfcfc07..e55344aded0 100644
--- a/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
+++ b/searchlib/src/tests/attribute/imported_attribute_vector/imported_attribute_vector_test.cpp
@@ -240,6 +240,24 @@ TEST_F("original lid range is used by read guard", Fixture)
EXPECT_EQUAL(getUndefined<int>(), first_guard->getInt(DocId(10)));
}
+TEST_F("Original target lid range is used by read guard", Fixture)
+{
+ reset_with_single_value_reference_mappings<IntegerAttribute, int32_t>(
+ f, BasicType::INT32,
+ {});
+ EXPECT_EQUAL(11u, f.target_attr->getNumDocs());
+ auto first_guard = f.get_imported_attr();
+ add_n_docs_with_undefined_values(*f.target_attr, 1);
+ EXPECT_EQUAL(12u, f.target_attr->getNumDocs());
+ auto typed_target_attr = f.template target_attr_as<IntegerAttribute>();
+ ASSERT_TRUE(typed_target_attr->update(11, 2345));
+ f.target_attr->commit();
+ f.map_reference(DocId(8), dummy_gid(11), DocId(11));
+ auto second_guard = f.get_imported_attr();
+ EXPECT_EQUAL(2345, second_guard->getInt(DocId(8)));
+ EXPECT_NOT_EQUAL(2345, first_guard->getInt(DocId(8)));
+}
+
struct SingleStringAttrFixture : Fixture {
SingleStringAttrFixture() : Fixture() {
setup();
diff --git a/searchlib/src/tests/attribute/imported_search_context/imported_search_context_test.cpp b/searchlib/src/tests/attribute/imported_search_context/imported_search_context_test.cpp
index 847a992d241..19327245083 100644
--- a/searchlib/src/tests/attribute/imported_search_context/imported_search_context_test.cpp
+++ b/searchlib/src/tests/attribute/imported_search_context/imported_search_context_test.cpp
@@ -429,6 +429,21 @@ TEST_F("original lid range is used by search context", SingleValueFixture)
EXPECT_TRUE(second_ctx->matches(DocId(10)));
}
+TEST_F("Original target lid range is used by search context", SingleValueFixture)
+{
+ EXPECT_EQUAL(11u, f.target_attr->getNumDocs());
+ auto first_ctx = f.create_context(word_term("2345"));
+ add_n_docs_with_undefined_values(*f.target_attr, 1);
+ EXPECT_EQUAL(12u, f.target_attr->getNumDocs());
+ auto typed_target_attr = f.template target_attr_as<IntegerAttribute>();
+ ASSERT_TRUE(typed_target_attr->update(11, 2345));
+ f.target_attr->commit();
+ f.map_reference(DocId(8), dummy_gid(11), DocId(11));
+ auto second_ctx = f.create_context(word_term("2345"));
+ EXPECT_FALSE(first_ctx->matches(DocId(8)));
+ EXPECT_TRUE(second_ctx->matches(DocId(8)));
+}
+
// Note: this uses an underlying string attribute, as queryTerm() does not seem to
// implemented at all for (single) numeric attributes. Intentional?
TEST_F("queryTerm() returns term context was created with", WsetValueFixture) {
diff --git a/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp b/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
index df2a5ea2ac9..17e852c8b92 100644
--- a/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
+++ b/searchlib/src/tests/attribute/multi_value_mapping/multi_value_mapping_test.cpp
@@ -22,11 +22,11 @@ using vespalib::datastore::CompactionStrategy;
using vespalib::alloc::test::MemoryAllocatorObserver;
using AllocStats = MemoryAllocatorObserver::Stats;
-template <typename EntryT>
+template <typename ElemT>
void
-assertArray(const std::vector<EntryT> &exp, vespalib::ConstArrayRef<EntryT> values)
+assertArray(const std::vector<ElemT> &exp, vespalib::ConstArrayRef<ElemT> values)
{
- EXPECT_EQ(exp, std::vector<EntryT>(values.cbegin(), values.cend()));
+ EXPECT_EQ(exp, std::vector<ElemT>(values.cbegin(), values.cend()));
}
template <class MvMapping>
@@ -69,10 +69,10 @@ public:
constexpr float ALLOC_GROW_FACTOR = 0.2;
-template <typename EntryT>
+template <typename ElemT>
class MappingTestBase : public ::testing::Test {
protected:
- using MvMapping = search::attribute::MultiValueMapping<EntryT>;
+ using MvMapping = search::attribute::MultiValueMapping<ElemT>;
using AttributeType = MyAttribute<MvMapping>;
AllocStats _stats;
std::unique_ptr<MvMapping> _mvMapping;
@@ -82,8 +82,8 @@ protected:
using generation_t = vespalib::GenerationHandler::generation_t;
public:
- using ArrayRef = vespalib::ArrayRef<EntryT>;
- using ConstArrayRef = vespalib::ConstArrayRef<EntryT>;
+ using ArrayRef = vespalib::ArrayRef<ElemT>;
+ using ConstArrayRef = vespalib::ConstArrayRef<ElemT>;
MappingTestBase()
: _stats(),
_mvMapping(),
@@ -99,9 +99,9 @@ public:
_attr = std::make_unique<AttributeType>(*_mvMapping);
_maxSmallArraySize = maxSmallArraySize;
}
- void setup(uint32_t maxSmallArraySize, size_t minArrays, size_t maxArrays, size_t numArraysForNewBuffer, bool enable_free_lists = true) {
+ void setup(uint32_t maxSmallArraySize, size_t min_entries, size_t max_entries, size_t num_entries_for_new_buffer, bool enable_free_lists = true) {
ArrayStoreConfig config(maxSmallArraySize,
- ArrayStoreConfig::AllocSpec(minArrays, maxArrays, numArraysForNewBuffer, ALLOC_GROW_FACTOR));
+ ArrayStoreConfig::AllocSpec(min_entries, max_entries, num_entries_for_new_buffer, ALLOC_GROW_FACTOR));
config.enable_free_lists(enable_free_lists);
_mvMapping = std::make_unique<MvMapping>(config, vespalib::GrowStrategy(), std::make_unique<MemoryAllocatorObserver>(_stats));
_attr = std::make_unique<AttributeType>(*_mvMapping);
@@ -109,12 +109,12 @@ public:
}
~MappingTestBase() { }
- void set(uint32_t docId, const std::vector<EntryT> &values) { _mvMapping->set(docId, values); }
+ void set(uint32_t docId, const std::vector<ElemT> &values) { _mvMapping->set(docId, values); }
ConstArrayRef get(uint32_t docId) { return _mvMapping->get(docId); }
ArrayRef get_writable(uint32_t docId) { return _mvMapping->get_writable(docId); }
- void assertGet(uint32_t docId, const std::vector<EntryT> &exp) {
+ void assertGet(uint32_t docId, const std::vector<ElemT> &exp) {
ConstArrayRef act = get(docId);
- EXPECT_EQ(exp, std::vector<EntryT>(act.cbegin(), act.cend()));
+ EXPECT_EQ(exp, std::vector<ElemT>(act.cbegin(), act.cend()));
}
void assign_generation(generation_t current_gen) { _mvMapping->assign_generation(current_gen); }
void reclaim_memory(generation_t oldest_used_gen) { _mvMapping->reclaim_memory(oldest_used_gen); }
diff --git a/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
index a19b093ef4d..5ba90d2b077 100644
--- a/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
+++ b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
@@ -8,7 +8,6 @@
#include <vespa/searchlib/attribute/enumstore.hpp>
#include <vespa/searchlib/attribute/single_string_enum_search_context.h>
-#include <vespa/searchlib/attribute/singlestringpostattribute.h>
#include <vespa/searchlib/attribute/multistringpostattribute.hpp>
#include <vespa/log/log.h>
@@ -391,7 +390,7 @@ TEST("testSingleValue")
{
EXPECT_EQUAL(24u, sizeof(SearchContext));
EXPECT_EQUAL(32u, sizeof(StringSearchHelper));
- EXPECT_EQUAL(80u, sizeof(attribute::SingleStringEnumSearchContext));
+ EXPECT_EQUAL(88u, sizeof(attribute::SingleStringEnumSearchContext));
{
Config cfg(BasicType::STRING, CollectionType::SINGLE);
SingleValueStringAttribute svsa("svsa", cfg);
diff --git a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
index 28c50891225..e3c9e05073e 100644
--- a/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
+++ b/searchlib/src/tests/attribute/tensorattribute/tensorattribute_test.cpp
@@ -294,30 +294,33 @@ public:
std::unique_ptr<NearestNeighborIndexLoader> make_loader(FastOS_FileInterface& file) override {
return std::make_unique<MockIndexLoader>(_index_value, file);
}
- std::vector<Neighbor> find_top_k(uint32_t k, vespalib::eval::TypedCells vector, uint32_t explore_k,
+ std::vector<Neighbor> find_top_k(uint32_t k,
+ const search::tensor::BoundDistanceFunction &df,
+ uint32_t explore_k,
double distance_threshold) const override
{
(void) k;
- (void) vector;
+ (void) df;
(void) explore_k;
(void) distance_threshold;
return std::vector<Neighbor>();
}
- std::vector<Neighbor> find_top_k_with_filter(uint32_t k, vespalib::eval::TypedCells vector,
+ std::vector<Neighbor> find_top_k_with_filter(uint32_t k,
+ const search::tensor::BoundDistanceFunction &df,
const GlobalFilter& filter, uint32_t explore_k,
double distance_threshold) const override
{
(void) k;
- (void) vector;
+ (void) df;
(void) explore_k;
(void) filter;
(void) distance_threshold;
return std::vector<Neighbor>();
}
- const search::tensor::DistanceFunction *distance_function() const override {
- static search::tensor::SquaredEuclideanDistance my_dist_fun(vespalib::eval::CellType::DOUBLE);
- return &my_dist_fun;
+ search::tensor::DistanceFunctionFactory &distance_function_factory() const override {
+ static search::tensor::DistanceFunctionFactory::UP my_dist_fun = search::tensor::make_distance_function_factory(search::attribute::DistanceMetric::Euclidean, vespalib::eval::CellType::DOUBLE);
+ return *my_dist_fun;
}
};
diff --git a/searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp b/searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp
index 834cdbef50d..73a81be9f90 100644
--- a/searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp
+++ b/searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp
@@ -2,9 +2,9 @@
#include <vespa/log/log.h>
LOG_SETUP("summaryfeatures_test");
#include <vespa/vespalib/testkit/testapp.h>
-#include <vespa/searchlib/common/featureset.h>
+#include <vespa/vespalib/util/featureset.h>
-using namespace search;
+using vespalib::FeatureSet;
using vespalib::Memory;
TEST_SETUP(Test);
diff --git a/searchlib/src/tests/expression/attributenode/attribute_node_test.cpp b/searchlib/src/tests/expression/attributenode/attribute_node_test.cpp
index 896fd27f90e..757f74bdaff 100644
--- a/searchlib/src/tests/expression/attributenode/attribute_node_test.cpp
+++ b/searchlib/src/tests/expression/attributenode/attribute_node_test.cpp
@@ -54,8 +54,7 @@ namespace {
vespalib::string stringValue(const ResultNode &result, const IAttributeVector &attr) {
if (result.inherits(EnumResultNode::classId)) {
auto enumHandle = result.getEnum();
- auto &stringAttr = dynamic_cast<const StringAttribute &>(attr);
- return vespalib::string(stringAttr.getFromEnum(enumHandle));
+ return vespalib::string(attr.getStringFromEnum(enumHandle));
}
char buf[100];
BufferRef bref(&buf[0], sizeof(buf));
@@ -199,8 +198,9 @@ AttributeManagerFixture::buildIntegerArrayAttribute(const vespalib::string &name
}
-struct Fixture
+class Fixture
{
+public:
AttributeManagerFixture attrs;
AttributeContext context;
Fixture();
@@ -208,11 +208,13 @@ struct Fixture
std::unique_ptr<AttributeNode> makeNode(const vespalib::string &attributeName, bool useEnumOptimiation = false, bool preserveAccurateTypes = false);
void assertInts(std::vector<IAttributeVector::largeint_t> expVals, const vespalib::string &attributteName, bool preserveAccurateTypes = false);
void assertBools(std::vector<bool> expVals, const vespalib::string &attributteName, bool preserveAccurateTypes = false);
- void assertStrings(std::vector<vespalib::string> expVals, const vespalib::string &attributteName, bool useEnumOptimization = false);
+ void assertStrings(std::vector<vespalib::string> expVals, const vespalib::string &attributteName);
void assertFloats(std::vector<double> expVals, const vespalib::string &attributteName);
void assertIntArrays(std::vector<std::vector<IAttributeVector::largeint_t>> expVals, const vespalib::string &attributteName, bool preserveAccurateTypes = false);
void assertStringArrays(std::vector<std::vector<vespalib::string>> expVals, const vespalib::string &attributteName, bool useEnumOptimization = false);
void assertFloatArrays(std::vector<std::vector<double>> expVals, const vespalib::string &attributteName);
+private:
+ void assertStrings(std::vector<vespalib::string> expVals, const vespalib::string &attributteName, bool useEnumOptimization);
};
Fixture::Fixture()
@@ -280,6 +282,11 @@ Fixture::assertBools(std::vector<bool> expVals, const vespalib::string &attribut
}
}
+void
+Fixture::assertStrings(std::vector<vespalib::string> expVals, const vespalib::string &attributeName) {
+ assertStrings(expVals, attributeName, false);
+ assertStrings(expVals, attributeName, true);
+}
void
Fixture::assertStrings(std::vector<vespalib::string> expVals, const vespalib::string &attributeName, bool useEnumOptimization)
@@ -293,6 +300,9 @@ Fixture::assertStrings(std::vector<vespalib::string> expVals, const vespalib::st
const auto &result = *node->getResult();
if (useEnumOptimization) {
ASSERT_TRUE(result.inherits(EnumResultNode::classId));
+ search::enumstore::EnumHandle enumVal(0);
+ ASSERT_TRUE(node->getAttribute()->findEnum(expDocVal.c_str(), enumVal));
+ EXPECT_EQUAL(result.getEnum(), enumVal);
} else {
ASSERT_TRUE(result.inherits(StringResultNode::classId));
}
@@ -404,7 +414,6 @@ TEST_F("test single values", Fixture)
TEST_DO(f.assertInts({ 10, getUndefined<int8_t>()}, "ifield"));
TEST_DO(f.assertInts({ 10, getUndefined<int8_t>()}, "ifield", true));
TEST_DO(f.assertStrings({ "n1", "" }, "sfield"));
- TEST_DO(f.assertStrings({ "n1", "" }, "sfield", true));
TEST_DO(f.assertFloats({ 110.0, getUndefined<double>() }, "ffield"));
}
diff --git a/searchlib/src/tests/features/prod_features.cpp b/searchlib/src/tests/features/prod_features.cpp
index 4ebc94ccb8b..dc64c3328e4 100644
--- a/searchlib/src/tests/features/prod_features.cpp
+++ b/searchlib/src/tests/features/prod_features.cpp
@@ -1252,7 +1252,7 @@ Test::testDotProduct()
{ // test funky syntax
dotproduct::wset::EnumVector out(sv);
WeightedSetParser::parse("( a: 1, b:2 ,c: , :3)", out);
- EXPECT_EQUAL(out.getVector().size(), 3u);
+ EXPECT_EQUAL(out.getVector().size(), 4u);
EXPECT_TRUE(sv->findEnum("a", e));
EXPECT_EQUAL(out.getVector()[0].first, e);
EXPECT_EQUAL(out.getVector()[0].second, 1);
@@ -1262,6 +1262,9 @@ Test::testDotProduct()
EXPECT_TRUE(sv->findEnum("c", e));
EXPECT_EQUAL(out.getVector()[2].first, e);
EXPECT_EQUAL(out.getVector()[2].second, 0);
+ EXPECT_TRUE(sv->findEnum("", e));
+ EXPECT_EQUAL(out.getVector()[3].first, e);
+ EXPECT_EQUAL(out.getVector()[3].second, 3);
}
{ // strings not in attribute vector
dotproduct::wset::EnumVector out(sv);
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 b57a2f42ea7..69478c09a25 100644
--- a/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
+++ b/searchlib/src/tests/memoryindex/field_index/field_index_test.cpp
@@ -1032,14 +1032,14 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
auto beforeStats = getFeatureStoreMemStats(_fic);
LOG(info,
- "Before feature compaction: allocElems=%zu, usedElems=%zu"
- ", deadElems=%zu, holdElems=%zu"
+ "Before feature compaction: alloc_entries=%zu, used_entries=%zu"
+ ", dead_entries=%zu, hold_entries=%zu"
", freeBuffers=%u, activeBuffers=%u"
", holdBuffers=%u",
- beforeStats._allocElems,
- beforeStats._usedElems,
- beforeStats._deadElems,
- beforeStats._holdElems,
+ beforeStats._alloc_entries,
+ beforeStats._used_entries,
+ beforeStats._dead_entries,
+ beforeStats._hold_entries,
beforeStats._freeBuffers,
beforeStats._activeBuffers,
beforeStats._holdBuffers);
@@ -1052,14 +1052,14 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
myCommit(_fic, *_pushThreads);
auto duringStats = getFeatureStoreMemStats(_fic);
LOG(info,
- "During feature compaction: allocElems=%zu, usedElems=%zu"
- ", deadElems=%zu, holdElems=%zu"
+ "During feature compaction: alloc_entries=%zu, used_entries=%zu"
+ ", dead_entries=%zu, hold_entries=%zu"
", freeBuffers=%u, activeBuffers=%u"
", holdBuffers=%u",
- duringStats._allocElems,
- duringStats._usedElems,
- duringStats._deadElems,
- duringStats._holdElems,
+ duringStats._alloc_entries,
+ duringStats._used_entries,
+ duringStats._dead_entries,
+ duringStats._hold_entries,
duringStats._freeBuffers,
duringStats._activeBuffers,
duringStats._holdBuffers);
@@ -1067,14 +1067,14 @@ TEST_F(BasicInverterTest, require_that_inversion_is_working)
myCommit(_fic, *_pushThreads);
auto afterStats = getFeatureStoreMemStats(_fic);
LOG(info,
- "After feature compaction: allocElems=%zu, usedElems=%zu"
- ", deadElems=%zu, holdElems=%zu"
+ "After feature compaction: alloc_entries=%zu, used_entries=%zu"
+ ", dead_entries=%zu, hold_entries=%zu"
", freeBuffers=%u, activeBuffers=%u"
", holdBuffers=%u",
- afterStats._allocElems,
- afterStats._usedElems,
- afterStats._deadElems,
- afterStats._holdElems,
+ afterStats._alloc_entries,
+ afterStats._used_entries,
+ afterStats._dead_entries,
+ afterStats._hold_entries,
afterStats._freeBuffers,
afterStats._activeBuffers,
afterStats._holdBuffers);
diff --git a/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp b/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
index 8073fb8d232..e3b9cf9702d 100644
--- a/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
+++ b/searchlib/src/tests/memoryindex/memory_index/memory_index_test.cpp
@@ -462,8 +462,8 @@ TEST(MemoryIndexTest, require_that_num_docs_and_doc_id_limit_is_returned)
TEST(MemoryIndexTest, require_that_we_understand_the_memory_footprint)
{
- constexpr size_t BASE_ALLOCATED = 361032u;
- constexpr size_t BASE_USED = 151188u;
+ constexpr size_t BASE_ALLOCATED = 360936u;
+ constexpr size_t BASE_USED = 150804u;
{
MySetup setup;
Index index(setup);
diff --git a/searchlib/src/tests/predicate/document_features_store_test.cpp b/searchlib/src/tests/predicate/document_features_store_test.cpp
index 4ac4bdc32f0..d30df9dba6e 100644
--- a/searchlib/src/tests/predicate/document_features_store_test.cpp
+++ b/searchlib/src/tests/predicate/document_features_store_test.cpp
@@ -165,17 +165,17 @@ TEST("require that both features and ranges are removed by 'remove'") {
TEST("require that both features and ranges counts towards memory usage") {
DocumentFeaturesStore features_store(10);
- EXPECT_EQUAL(50136u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50064u, features_store.getMemoryUsage().usedBytes());
PredicateTreeAnnotations annotations;
annotations.features.push_back(PredicateHash::hash64("foo=100-199"));
features_store.insert(annotations, doc_id);
- EXPECT_EQUAL(50144u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50072u, features_store.getMemoryUsage().usedBytes());
annotations.features.clear();
annotations.range_features.push_back({"foo", 100, 199});
features_store.insert(annotations, doc_id + 1);
- EXPECT_EQUAL(50240u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50168u, features_store.getMemoryUsage().usedBytes());
}
TEST("require that DocumentFeaturesStore can be serialized") {
@@ -205,17 +205,17 @@ TEST("require that serialization cleans up wordstore") {
PredicateTreeAnnotations annotations;
annotations.range_features.push_back({"foo", 100, 199});
features_store.insert(annotations, doc_id);
- EXPECT_EQUAL(50232u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50160u, features_store.getMemoryUsage().usedBytes());
annotations.range_features.push_back({"bar", 100, 199});
features_store.insert(annotations, doc_id + 1);
- EXPECT_EQUAL(50620u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50548u, features_store.getMemoryUsage().usedBytes());
features_store.remove(doc_id + 1);
- EXPECT_EQUAL(50572u, features_store.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50500u, features_store.getMemoryUsage().usedBytes());
vespalib::DataBuffer buffer;
features_store.serialize(buffer);
DocumentFeaturesStore features_store2(buffer);
- EXPECT_EQUAL(50232u, features_store2.getMemoryUsage().usedBytes());
+ EXPECT_EQUAL(50160u, features_store2.getMemoryUsage().usedBytes());
}
diff --git a/searchlib/src/tests/query/streaming_query_test.cpp b/searchlib/src/tests/query/streaming_query_test.cpp
index f354f635def..210f32af15e 100644
--- a/searchlib/src/tests/query/streaming_query_test.cpp
+++ b/searchlib/src/tests/query/streaming_query_test.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include <vespa/searchlib/query/streaming/query.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
#include <vespa/searchlib/query/tree/querybuilder.h>
#include <vespa/searchlib/query/tree/simplequery.h>
#include <vespa/searchlib/query/tree/stackdumpcreator.h>
@@ -804,6 +805,42 @@ TEST("testSameElementEvaluate") {
EXPECT_TRUE(sameElem->evaluate());
}
+TEST("test_nearest_neighbor_query_node")
+{
+ QueryBuilder<SimpleQueryNodeTypes> builder;
+ constexpr double distance_threshold = 35.5;
+ constexpr int32_t id = 42;
+ constexpr int32_t weight = 1;
+ constexpr uint32_t target_num_hits = 100;
+ constexpr bool allow_approximate = false;
+ constexpr uint32_t explore_additional_hits = 800;
+ constexpr double distance = 0.5;
+ builder.add_nearest_neighbor_term("qtensor", "field", id, Weight(weight), target_num_hits, allow_approximate, explore_additional_hits, distance_threshold);
+ auto build_node = builder.build();
+ auto stack_dump = StackDumpCreator::create(*build_node);
+ QueryNodeResultFactory empty;
+ Query q(empty, stack_dump);
+ auto* qterm = dynamic_cast<QueryTerm *>(&q.getRoot());
+ EXPECT_TRUE(qterm != nullptr);
+ auto* node = dynamic_cast<NearestNeighborQueryNode *>(&q.getRoot());
+ EXPECT_TRUE(node != nullptr);
+ EXPECT_EQUAL(node, qterm->as_nearest_neighbor_query_node());
+ EXPECT_EQUAL("qtensor", node->get_query_tensor_name());
+ EXPECT_EQUAL("field", node->getIndex());
+ EXPECT_EQUAL(id, static_cast<int32_t>(node->uniqueId()));
+ EXPECT_EQUAL(weight, node->weight().percent());
+ EXPECT_EQUAL(distance_threshold, node->get_distance_threshold());
+ EXPECT_FALSE(node->get_distance().has_value());
+ EXPECT_FALSE(node->evaluate());
+ node->set_distance(distance);
+ EXPECT_TRUE(node->get_distance().has_value());
+ EXPECT_EQUAL(distance, node->get_distance().value());
+ EXPECT_TRUE(node->evaluate());
+ node->reset();
+ EXPECT_FALSE(node->get_distance().has_value());
+ EXPECT_FALSE(node->evaluate());
+}
+
TEST("Control the size of query terms") {
EXPECT_EQUAL(112u, sizeof(QueryTermSimple));
EXPECT_EQUAL(128u, sizeof(QueryTermUCS4));
diff --git a/searchlib/src/tests/queryeval/nearest_neighbor/nearest_neighbor_test.cpp b/searchlib/src/tests/queryeval/nearest_neighbor/nearest_neighbor_test.cpp
index 2801bf90080..fd07529795a 100644
--- a/searchlib/src/tests/queryeval/nearest_neighbor/nearest_neighbor_test.cpp
+++ b/searchlib/src/tests/queryeval/nearest_neighbor/nearest_neighbor_test.cpp
@@ -134,7 +134,9 @@ SimpleResult find_matches(Fixture &env, const Value &qtv, double threshold = std
auto md = MatchData::makeTestInstance(2, 2);
auto &tfmd = *(md->resolveTermField(0));
auto &attr = *(env._attr);
- DistanceCalculator dist_calc(attr, qtv, env.dist_fun());
+
+ auto dff = search::tensor::make_distance_function_factory(DistanceMetric::Euclidean, qtv.cells().type);
+ DistanceCalculator dist_calc(attr, dff->for_query_vector(qtv.cells()));
NearestNeighborDistanceHeap dh(2);
dh.set_distance_threshold(env.dist_fun().convert_threshold(threshold));
const GlobalFilter &filter = *env._global_filter;
@@ -260,7 +262,8 @@ std::vector<feature_t> get_rawscores(Fixture &env, const Value &qtv) {
auto md = MatchData::makeTestInstance(2, 2);
auto &tfmd = *(md->resolveTermField(0));
auto &attr = *(env._attr);
- DistanceCalculator dist_calc(attr, qtv, env.dist_fun());
+ auto dff = search::tensor::make_distance_function_factory(DistanceMetric::Euclidean, qtv.cells().type);
+ DistanceCalculator dist_calc(attr, dff->for_query_vector(qtv.cells()));
NearestNeighborDistanceHeap dh(2);
auto dummy_filter = GlobalFilter::create();
auto search = NearestNeighborIterator::create(strict, tfmd, dist_calc, dh, *dummy_filter);
@@ -333,7 +336,10 @@ TEST(NnsIndexIteratorTest, require_that_iterator_works_as_expected) {
std::vector<NnsIndexIterator::Hit> hits{{2,4.0}, {3,9.0}, {5,1.0}, {8,16.0}, {9,36.0}};
auto md = MatchData::makeTestInstance(2, 2);
auto &tfmd = *(md->resolveTermField(0));
- auto search = NnsIndexIterator::create(tfmd, hits, *euclid_d);
+ auto dff = search::tensor::make_distance_function_factory(DistanceMetric::Euclidean, CellType::DOUBLE);
+ vespalib::eval::TypedCells dummy;
+ auto df = dff->for_query_vector(dummy);
+ auto search = NnsIndexIterator::create(tfmd, hits, *df);
search->initFullRange();
expect_not_match(*search, 1, 2);
expect_match(*search, 2);
diff --git a/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp b/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp
index f4faabde559..9b8ad0d26ce 100644
--- a/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp
+++ b/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp
@@ -18,14 +18,17 @@ using search::attribute::DistanceMetric;
template <typename T>
TypedCells t(const std::vector<T> &v) { return TypedCells(v); }
-void verify_geo_miles(const DistanceFunction *dist_fun,
- const std::vector<double> &p1,
+void verify_geo_miles(const std::vector<double> &p1,
const std::vector<double> &p2,
double exp_miles)
{
+ static GeoDistanceFunctionFactory dff;
TypedCells t1(p1);
TypedCells t2(p2);
- double abstract_distance = dist_fun->calc(t1, t2);
+ auto dist_fun = dff.for_query_vector(t1);
+ double abstract_distance = dist_fun->calc(t2);
+ EXPECT_EQ(dff.for_insertion_vector(t1)->calc(t2), abstract_distance);
+ EXPECT_FLOAT_EQ(dff.for_query_vector(t2)->calc(t1), abstract_distance);
double raw_score = dist_fun->to_rawscore(abstract_distance);
double km = ((1.0/raw_score)-1.0);
double d_miles = km / 1.609344;
@@ -44,6 +47,32 @@ void verify_geo_miles(const DistanceFunction *dist_fun,
}
}
+double computeEuclideanChecked(TypedCells a, TypedCells b) {
+ static EuclideanDistanceFunctionFactory<Int8Float> i8f_dff;
+ static EuclideanDistanceFunctionFactory<float> flt_dff;
+ static EuclideanDistanceFunctionFactory<double> dbl_dff;
+ auto d_n = dbl_dff.for_query_vector(a);
+ auto d_f = flt_dff.for_query_vector(a);
+ auto d_r = dbl_dff.for_query_vector(b);
+ auto d_i = dbl_dff.for_insertion_vector(a);
+ // normal:
+ double result = d_n->calc(b);
+ // insert is exactly same:
+ EXPECT_EQ(d_i->calc(b), result);
+ // reverse:
+ EXPECT_DOUBLE_EQ(d_r->calc(a), result);
+ // float factory:
+ EXPECT_FLOAT_EQ(d_f->calc(b), result);
+ if (a.type == vespalib::eval::CellType::INT8 ||
+ b.type == vespalib::eval::CellType::INT8)
+ {
+ auto d_8 = i8f_dff.for_query_vector(a);
+ EXPECT_DOUBLE_EQ(d_8->calc(b), result);
+ }
+ return result;
+}
+
+namespace { const double sq_root_half = std::sqrt(0.5); }
TEST(DistanceFunctionsTest, euclidean_gives_expected_score)
{
@@ -55,19 +84,60 @@ TEST(DistanceFunctionsTest, euclidean_gives_expected_score)
std::vector<double> p1{1.0, 0.0, 0.0};
std::vector<double> p2{0.0, 1.0, 0.0};
std::vector<double> p3{0.0, 0.0, 1.0};
- std::vector<double> p4{0.5, 0.5, 0.707107};
+ std::vector<double> p4{0.5, 0.5, sq_root_half};
std::vector<double> p5{0.0,-1.0, 0.0};
std::vector<double> p6{1.0, 2.0, 2.0};
- double n4 = euclid->calc(t(p0), t(p4));
+ double n4 = computeEuclideanChecked(t(p0), t(p4));
EXPECT_FLOAT_EQ(n4, 1.0);
- double d12 = euclid->calc(t(p1), t(p2));
+ double d12 = computeEuclideanChecked(t(p1), t(p2));
EXPECT_EQ(d12, 2.0);
EXPECT_DOUBLE_EQ(euclid->to_rawscore(d12), 1.0/(1.0 + sqrt(2.0)));
double threshold = euclid->convert_threshold(8.0);
EXPECT_EQ(threshold, 64.0);
threshold = euclid->convert_threshold(0.5);
EXPECT_EQ(threshold, 0.25);
+
+ // simple hand-checked distances:
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p0)), 0.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p1)), 1.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p2)), 1.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p3)), 1.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p5)), 1.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p0), t(p6)), 9.0);
+
+ EXPECT_EQ(computeEuclideanChecked(t(p1), t(p1)), 0.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p1), t(p2)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p1), t(p3)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p1), t(p5)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p1), t(p6)), 8.0);
+
+ EXPECT_EQ(computeEuclideanChecked(t(p2), t(p2)), 0.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p2), t(p3)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p2), t(p5)), 4.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p2), t(p6)), 6.0);
+
+ EXPECT_EQ(computeEuclideanChecked(t(p3), t(p3)), 0.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p3), t(p5)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p3), t(p6)), 6.0);
+
+ EXPECT_EQ(computeEuclideanChecked(t(p5), t(p5)), 0.0);
+ EXPECT_EQ(computeEuclideanChecked(t(p5), t(p6)), 14.0);
+
+ EXPECT_EQ(computeEuclideanChecked(t(p6), t(p6)), 0.0);
+
+ // smoke test for bfloat16:
+ std::vector<vespalib::BFloat16> bf16v;
+ bf16v.emplace_back(1.0);
+ bf16v.emplace_back(1.0);
+ bf16v.emplace_back(1.0);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p0)), 3.0);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p1)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p2)), 2.0);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p3)), 2.0);
+ EXPECT_FLOAT_EQ(computeEuclideanChecked(t(bf16v), t(p4)), 0.5857863);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p5)), 6.0);
+ EXPECT_EQ(computeEuclideanChecked(t(bf16v), t(p6)), 2.0);
}
TEST(DistanceFunctionsTest, euclidean_int8_smoketest)
@@ -81,34 +151,50 @@ TEST(DistanceFunctionsTest, euclidean_int8_smoketest)
std::vector<Int8Float> p5{0.0,-1.0, 0.0};
std::vector<Int8Float> p7{-1.0, 2.0, -2.0};
- EXPECT_DOUBLE_EQ(1.0, euclid->calc(t(p0), t(p1)));
- EXPECT_DOUBLE_EQ(1.0, euclid->calc(t(p0), t(p5)));
- EXPECT_DOUBLE_EQ(9.0, euclid->calc(t(p0), t(p7)));
+ EXPECT_DOUBLE_EQ(1.0, computeEuclideanChecked(t(p0), t(p1)));
+ EXPECT_DOUBLE_EQ(1.0, computeEuclideanChecked(t(p0), t(p5)));
+ EXPECT_DOUBLE_EQ(9.0, computeEuclideanChecked(t(p0), t(p7)));
- EXPECT_DOUBLE_EQ(2.0, euclid->calc(t(p1), t(p5)));
- EXPECT_DOUBLE_EQ(12.0, euclid->calc(t(p1), t(p7)));
- EXPECT_DOUBLE_EQ(14.0, euclid->calc(t(p5), t(p7)));
+ EXPECT_DOUBLE_EQ(2.0, computeEuclideanChecked(t(p1), t(p5)));
+ EXPECT_DOUBLE_EQ(12.0, computeEuclideanChecked(t(p1), t(p7)));
+ EXPECT_DOUBLE_EQ(14.0, computeEuclideanChecked(t(p5), t(p7)));
+}
+double computeAngularChecked(TypedCells a, TypedCells b) {
+ static AngularDistanceFunctionFactory<float> flt_dff;
+ static AngularDistanceFunctionFactory<double> dbl_dff;
+ auto d_n = dbl_dff.for_query_vector(a);
+ auto d_f = flt_dff.for_query_vector(a);
+ auto d_r = dbl_dff.for_query_vector(b);
+ auto d_i = dbl_dff.for_insertion_vector(a);
+ // normal:
+ double result = d_n->calc(b);
+ // insert is exactly same:
+ EXPECT_EQ(d_i->calc(b), result);
+ // reverse:
+ EXPECT_DOUBLE_EQ(d_r->calc(a), result);
+ // float factory:
+ EXPECT_FLOAT_EQ(d_f->calc(b), result);
+ return result;
}
TEST(DistanceFunctionsTest, angular_gives_expected_score)
{
- auto ct = vespalib::eval::CellType::DOUBLE;
-
- auto angular = make_distance_function(DistanceMetric::Angular, ct);
-
std::vector<double> p0{0.0, 0.0, 0.0};
std::vector<double> p1{1.0, 0.0, 0.0};
std::vector<double> p2{0.0, 1.0, 0.0};
std::vector<double> p3{0.0, 0.0, 1.0};
- std::vector<double> p4{0.5, 0.5, 0.707107};
+ std::vector<double> p4{0.5, 0.5, sq_root_half};
std::vector<double> p5{0.0,-1.0, 0.0};
std::vector<double> p6{1.0, 2.0, 2.0};
+ AngularDistanceFunctionFactory<double> dff;
+ auto angular = dff.for_query_vector(t(p0));
+
constexpr double pi = 3.14159265358979323846;
- double a12 = angular->calc(t(p1), t(p2));
- double a13 = angular->calc(t(p1), t(p3));
- double a23 = angular->calc(t(p2), t(p3));
+ double a12 = computeAngularChecked(t(p1), t(p2));
+ double a13 = computeAngularChecked(t(p1), t(p3));
+ double a23 = computeAngularChecked(t(p2), t(p3));
EXPECT_DOUBLE_EQ(a12, 1.0);
EXPECT_DOUBLE_EQ(a13, 1.0);
EXPECT_DOUBLE_EQ(a23, 1.0);
@@ -117,44 +203,146 @@ TEST(DistanceFunctionsTest, angular_gives_expected_score)
double threshold = angular->convert_threshold(pi/2);
EXPECT_DOUBLE_EQ(threshold, 1.0);
- double a14 = angular->calc(t(p1), t(p4));
- double a24 = angular->calc(t(p2), t(p4));
+ double a14 = computeAngularChecked(t(p1), t(p4));
+ double a24 = computeAngularChecked(t(p2), t(p4));
EXPECT_FLOAT_EQ(a14, 0.5);
EXPECT_FLOAT_EQ(a24, 0.5);
EXPECT_FLOAT_EQ(angular->to_rawscore(a14), 1.0/(1.0 + pi/3));
threshold = angular->convert_threshold(pi/3);
EXPECT_DOUBLE_EQ(threshold, 0.5);
- double a34 = angular->calc(t(p3), t(p4));
- EXPECT_FLOAT_EQ(a34, (1.0 - 0.707107));
+ double a34 = computeAngularChecked(t(p3), t(p4));
+ EXPECT_FLOAT_EQ(a34, (1.0 - sq_root_half));
EXPECT_FLOAT_EQ(angular->to_rawscore(a34), 1.0/(1.0 + pi/4));
threshold = angular->convert_threshold(pi/4);
EXPECT_FLOAT_EQ(threshold, a34);
- double a25 = angular->calc(t(p2), t(p5));
+ double a25 = computeAngularChecked(t(p2), t(p5));
EXPECT_DOUBLE_EQ(a25, 2.0);
EXPECT_FLOAT_EQ(angular->to_rawscore(a25), 1.0/(1.0 + pi));
threshold = angular->convert_threshold(pi);
EXPECT_FLOAT_EQ(threshold, 2.0);
- double a44 = angular->calc(t(p4), t(p4));
+ double a44 = computeAngularChecked(t(p4), t(p4));
EXPECT_GE(a44, 0.0);
EXPECT_LT(a44, 0.000001);
EXPECT_FLOAT_EQ(angular->to_rawscore(a44), 1.0);
- double a66 = angular->calc(t(p6), t(p6));
+ double a66 = computeAngularChecked(t(p6), t(p6));
EXPECT_GE(a66, 0.0);
EXPECT_LT(a66, 0.000001);
EXPECT_FLOAT_EQ(angular->to_rawscore(a66), 1.0);
threshold = angular->convert_threshold(0.0);
EXPECT_FLOAT_EQ(threshold, 0.0);
- double a16 = angular->calc(t(p1), t(p6));
- double a26 = angular->calc(t(p2), t(p6));
- double a36 = angular->calc(t(p3), t(p6));
+ double a16 = computeAngularChecked(t(p1), t(p6));
+ double a26 = computeAngularChecked(t(p2), t(p6));
+ double a36 = computeAngularChecked(t(p3), t(p6));
EXPECT_FLOAT_EQ(a16, 1.0 - (1.0/3.0));
EXPECT_FLOAT_EQ(a26, 1.0 - (2.0/3.0));
EXPECT_FLOAT_EQ(a36, 1.0 - (2.0/3.0));
+
+ // check also that cell type conversion works:
+ std::vector<Int8Float> iv0{0.0, 0.0, 0.0};
+ std::vector<Int8Float> iv1{1.0, 0.0, 0.0};
+ std::vector<Int8Float> iv2{0.0, 1.0, 0.0};
+ std::vector<Int8Float> iv3{0.0, 0.0, 1.0};
+ std::vector<Int8Float> iv5{0.0,-1.0, 0.0};
+ std::vector<Int8Float> iv6{1.0, 2.0, 2.0};
+
+ EXPECT_DOUBLE_EQ(a12, computeAngularChecked(t(iv1), t(iv2)));
+ EXPECT_DOUBLE_EQ(a13, computeAngularChecked(t(iv1), t(iv3)));
+ EXPECT_DOUBLE_EQ(a14, computeAngularChecked(t(iv1), t(p4)));
+ EXPECT_DOUBLE_EQ(a24, computeAngularChecked(t(iv2), t(p4)));
+ EXPECT_DOUBLE_EQ(a34, computeAngularChecked(t(iv3), t(p4)));
+ EXPECT_DOUBLE_EQ(a25, computeAngularChecked(t(iv2), t(iv5)));
+ EXPECT_DOUBLE_EQ(a16, computeAngularChecked(t(iv1), t(iv6)));
+ EXPECT_DOUBLE_EQ(a26, computeAngularChecked(t(iv2), t(iv6)));
+ EXPECT_DOUBLE_EQ(a36, computeAngularChecked(t(iv3), t(iv6)));
+ EXPECT_DOUBLE_EQ(a66, computeAngularChecked(t(iv6), t(iv6)));
+}
+
+double computePrenormalizedAngularChecked(TypedCells a, TypedCells b) {
+ static PrenormalizedAngularDistanceFunctionFactory<float> flt_dff;
+ static PrenormalizedAngularDistanceFunctionFactory<double> dbl_dff;
+ auto d_n = dbl_dff.for_query_vector(a);
+ auto d_f = flt_dff.for_query_vector(a);
+ auto d_r = dbl_dff.for_query_vector(b);
+ auto d_i = dbl_dff.for_insertion_vector(a);
+ // normal:
+ double result = d_n->calc(b);
+ // insert is exactly same:
+ EXPECT_EQ(d_i->calc(b), result);
+ // note: for this distance, reverse is not necessarily equal,
+ // since we normalize based on length of LHS only
+ EXPECT_FLOAT_EQ(d_r->calc(a), result);
+ // float factory:
+ EXPECT_FLOAT_EQ(d_f->calc(b), result);
+ double closeness_n = d_n->to_rawscore(result);
+ double closeness_f = d_f->to_rawscore(result);
+ double closeness_r = d_r->to_rawscore(result);
+ double closeness_i = d_i->to_rawscore(result);
+ EXPECT_DOUBLE_EQ(closeness_n, closeness_f);
+ EXPECT_DOUBLE_EQ(closeness_n, closeness_r);
+ EXPECT_DOUBLE_EQ(closeness_n, closeness_i);
+ EXPECT_GT(closeness_n, 0.0);
+ EXPECT_LE(closeness_n, 1.0);
+ return result;
+}
+
+TEST(DistanceFunctionsTest, prenormalized_angular_gives_expected_score)
+{
+ std::vector<double> p0{0.0, 0.0, 0.0};
+ std::vector<double> p1{1.0, 0.0, 0.0};
+ std::vector<double> p2{0.0, 1.0, 0.0};
+ std::vector<double> p3{0.0, 0.0, 1.0};
+ std::vector<double> p4{0.5, 0.5, sq_root_half};
+ std::vector<double> p5{0.0,-1.0, 0.0};
+ std::vector<double> p6{1.0, 2.0, 2.0};
+ std::vector<double> p7{2.0, -1.0, -2.0};
+ std::vector<double> p8{3.0, 0.0, 0.0};
+
+ PrenormalizedAngularDistanceFunctionFactory<double> dff;
+ auto pnad = dff.for_query_vector(t(p0));
+
+ double i12 = computePrenormalizedAngularChecked(t(p1), t(p2));
+ double i13 = computePrenormalizedAngularChecked(t(p1), t(p3));
+ double i23 = computePrenormalizedAngularChecked(t(p2), t(p3));
+ EXPECT_DOUBLE_EQ(i12, 1.0);
+ EXPECT_DOUBLE_EQ(i13, 1.0);
+ EXPECT_DOUBLE_EQ(i23, 1.0);
+
+ double i14 = computePrenormalizedAngularChecked(t(p1), t(p4));
+ double i24 = computePrenormalizedAngularChecked(t(p2), t(p4));
+ EXPECT_DOUBLE_EQ(i14, 0.5);
+ EXPECT_DOUBLE_EQ(i24, 0.5);
+ double i34 = computePrenormalizedAngularChecked(t(p3), t(p4));
+ EXPECT_FLOAT_EQ(i34, 1.0 - sq_root_half);
+
+ double i25 = computePrenormalizedAngularChecked(t(p2), t(p5));
+ EXPECT_DOUBLE_EQ(i25, 2.0);
+
+ double i44 = computePrenormalizedAngularChecked(t(p4), t(p4));
+ EXPECT_GE(i44, 0.0);
+ EXPECT_LT(i44, 0.000001);
+
+ double i66 = computePrenormalizedAngularChecked(t(p6), t(p6));
+ EXPECT_GE(i66, 0.0);
+ EXPECT_LT(i66, 0.000001);
+
+ double i67 = computePrenormalizedAngularChecked(t(p6), t(p7));
+ EXPECT_DOUBLE_EQ(i67, 13.0);
+ double i68 = computePrenormalizedAngularChecked(t(p6), t(p8));
+ EXPECT_DOUBLE_EQ(i68, 6.0);
+ double i78 = computePrenormalizedAngularChecked(t(p7), t(p8));
+ EXPECT_DOUBLE_EQ(i78, 3.0);
+
+ double threshold = pnad->convert_threshold(0.25);
+ EXPECT_DOUBLE_EQ(threshold, 0.25);
+ threshold = pnad->convert_threshold(0.5);
+ EXPECT_DOUBLE_EQ(threshold, 0.5);
+ threshold = pnad->convert_threshold(1.0);
+ EXPECT_DOUBLE_EQ(threshold, 1.0);
}
TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
@@ -167,7 +355,7 @@ TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
std::vector<double> p1{1.0, 0.0, 0.0};
std::vector<double> p2{0.0, 1.0, 0.0};
std::vector<double> p3{0.0, 0.0, 1.0};
- std::vector<double> p4{0.5, 0.5, 0.707107};
+ std::vector<double> p4{0.5, 0.5, sq_root_half};
std::vector<double> p5{0.0,-1.0, 0.0};
std::vector<double> p6{1.0, 2.0, 2.0};
@@ -177,13 +365,13 @@ TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
EXPECT_DOUBLE_EQ(i12, 1.0);
EXPECT_DOUBLE_EQ(i13, 1.0);
EXPECT_DOUBLE_EQ(i23, 1.0);
-
+
double i14 = innerproduct->calc(t(p1), t(p4));
double i24 = innerproduct->calc(t(p2), t(p4));
EXPECT_DOUBLE_EQ(i14, 0.5);
EXPECT_DOUBLE_EQ(i24, 0.5);
double i34 = innerproduct->calc(t(p3), t(p4));
- EXPECT_FLOAT_EQ(i34, 1.0 - 0.707107);
+ EXPECT_FLOAT_EQ(i34, 1.0 - sq_root_half);
double i25 = innerproduct->calc(t(p2), t(p5));
EXPECT_DOUBLE_EQ(i25, 2.0);
@@ -192,6 +380,10 @@ TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
EXPECT_GE(i44, 0.0);
EXPECT_LT(i44, 0.000001);
+ double i66 = innerproduct->calc(t(p6), t(p6));
+ EXPECT_GE(i66, 0.0);
+ EXPECT_LT(i66, 0.000001);
+
double threshold = innerproduct->convert_threshold(0.25);
EXPECT_DOUBLE_EQ(threshold, 0.25);
threshold = innerproduct->convert_threshold(0.5);
@@ -202,6 +394,7 @@ TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
TEST(DistanceFunctionsTest, hamming_gives_expected_score)
{
+ static HammingDistanceFunctionFactory<Int8Float> dff;
auto ct = vespalib::eval::CellType::DOUBLE;
auto hamming = make_distance_function(DistanceMetric::Hamming, ct);
@@ -218,6 +411,9 @@ TEST(DistanceFunctionsTest, hamming_gives_expected_score)
double h0 = hamming->calc(t(p), t(p));
EXPECT_EQ(h0, 0.0);
EXPECT_EQ(hamming->to_rawscore(h0), 1.0);
+ auto dist_fun = dff.for_query_vector(t(p));
+ EXPECT_EQ(dist_fun->calc(t(p)), 0.0);
+ EXPECT_EQ(dist_fun->to_rawscore(h0), 1.0);
}
double d12 = hamming->calc(t(points[1]), t(points[2]));
EXPECT_EQ(d12, 3.0);
@@ -238,7 +434,7 @@ TEST(DistanceFunctionsTest, hamming_gives_expected_score)
double d25 = hamming->calc(t(points[2]), t(points[5]));
EXPECT_EQ(d25, 1.0);
EXPECT_DOUBLE_EQ(hamming->to_rawscore(d25), 1.0/(1.0 + 1.0));
-
+
double threshold = hamming->convert_threshold(0.25);
EXPECT_DOUBLE_EQ(threshold, 0.25);
threshold = hamming->convert_threshold(0.5);
@@ -250,13 +446,12 @@ TEST(DistanceFunctionsTest, hamming_gives_expected_score)
std::vector<Int8Float> bytes_b = { 1, 2, 2, 4, 8, 16, 32, 65, -128, 0, 1, 0, 4, 8, 16, 32, 64, -128, 0, 1, -1 };
// expect diff: 1 2 1 1 7
EXPECT_EQ(hamming->calc(TypedCells(bytes_a), TypedCells(bytes_b)), 12.0);
+ auto dist_fun = dff.for_query_vector(TypedCells(bytes_a));
+ EXPECT_EQ(dist_fun->calc(TypedCells(bytes_b)), 12.0);
}
TEST(GeoDegreesTest, gives_expected_score)
{
- auto ct = vespalib::eval::CellType::DOUBLE;
- auto geodeg = make_distance_function(DistanceMetric::GeoDegrees, ct);
-
std::vector<double> g1_sfo{37.61, -122.38};
std::vector<double> g2_lhr{51.47, -0.46};
std::vector<double> g3_osl{60.20, 11.08};
@@ -267,7 +462,8 @@ TEST(GeoDegreesTest, gives_expected_score)
std::vector<double> g8_lax{33.94, -118.41};
std::vector<double> g9_jfk{40.64, -73.78};
- double g63_a = geodeg->calc(t(g6_trd), t(g3_osl));
+ auto geodeg = GeoDistanceFunctionFactory().for_query_vector(t(g6_trd));
+ double g63_a = geodeg->calc(t(g3_osl));
double g63_r = geodeg->to_rawscore(g63_a);
double g63_km = ((1.0/g63_r)-1.0);
EXPECT_GT(g63_km, 350);
@@ -277,96 +473,95 @@ TEST(GeoDegreesTest, gives_expected_score)
// Great Circle Mapper for airports using
// a more accurate formula - we should agree
// with < 1.0% deviation
- verify_geo_miles(geodeg.get(), g1_sfo, g1_sfo, 0);
- verify_geo_miles(geodeg.get(), g1_sfo, g2_lhr, 5367);
- verify_geo_miles(geodeg.get(), g1_sfo, g3_osl, 5196);
- verify_geo_miles(geodeg.get(), g1_sfo, g4_gig, 6604);
- verify_geo_miles(geodeg.get(), g1_sfo, g5_hkg, 6927);
- verify_geo_miles(geodeg.get(), g1_sfo, g6_trd, 5012);
- verify_geo_miles(geodeg.get(), g1_sfo, g7_syd, 7417);
- verify_geo_miles(geodeg.get(), g1_sfo, g8_lax, 337);
- verify_geo_miles(geodeg.get(), g1_sfo, g9_jfk, 2586);
-
- verify_geo_miles(geodeg.get(), g2_lhr, g1_sfo, 5367);
- verify_geo_miles(geodeg.get(), g2_lhr, g2_lhr, 0);
- verify_geo_miles(geodeg.get(), g2_lhr, g3_osl, 750);
- verify_geo_miles(geodeg.get(), g2_lhr, g4_gig, 5734);
- verify_geo_miles(geodeg.get(), g2_lhr, g5_hkg, 5994);
- verify_geo_miles(geodeg.get(), g2_lhr, g6_trd, 928);
- verify_geo_miles(geodeg.get(), g2_lhr, g7_syd, 10573);
- verify_geo_miles(geodeg.get(), g2_lhr, g8_lax, 5456);
- verify_geo_miles(geodeg.get(), g2_lhr, g9_jfk, 3451);
-
- verify_geo_miles(geodeg.get(), g3_osl, g1_sfo, 5196);
- verify_geo_miles(geodeg.get(), g3_osl, g2_lhr, 750);
- verify_geo_miles(geodeg.get(), g3_osl, g3_osl, 0);
- verify_geo_miles(geodeg.get(), g3_osl, g4_gig, 6479);
- verify_geo_miles(geodeg.get(), g3_osl, g5_hkg, 5319);
- verify_geo_miles(geodeg.get(), g3_osl, g6_trd, 226);
- verify_geo_miles(geodeg.get(), g3_osl, g7_syd, 9888);
- verify_geo_miles(geodeg.get(), g3_osl, g8_lax, 5345);
- verify_geo_miles(geodeg.get(), g3_osl, g9_jfk, 3687);
-
- verify_geo_miles(geodeg.get(), g4_gig, g1_sfo, 6604);
- verify_geo_miles(geodeg.get(), g4_gig, g2_lhr, 5734);
- verify_geo_miles(geodeg.get(), g4_gig, g3_osl, 6479);
- verify_geo_miles(geodeg.get(), g4_gig, g4_gig, 0);
- verify_geo_miles(geodeg.get(), g4_gig, g5_hkg, 10989);
- verify_geo_miles(geodeg.get(), g4_gig, g6_trd, 6623);
- verify_geo_miles(geodeg.get(), g4_gig, g7_syd, 8414);
- verify_geo_miles(geodeg.get(), g4_gig, g8_lax, 6294);
- verify_geo_miles(geodeg.get(), g4_gig, g9_jfk, 4786);
-
- verify_geo_miles(geodeg.get(), g5_hkg, g1_sfo, 6927);
- verify_geo_miles(geodeg.get(), g5_hkg, g2_lhr, 5994);
- verify_geo_miles(geodeg.get(), g5_hkg, g3_osl, 5319);
- verify_geo_miles(geodeg.get(), g5_hkg, g4_gig, 10989);
- verify_geo_miles(geodeg.get(), g5_hkg, g5_hkg, 0);
- verify_geo_miles(geodeg.get(), g5_hkg, g6_trd, 5240);
- verify_geo_miles(geodeg.get(), g5_hkg, g7_syd, 4581);
- verify_geo_miles(geodeg.get(), g5_hkg, g8_lax, 7260);
- verify_geo_miles(geodeg.get(), g5_hkg, g9_jfk, 8072);
-
- verify_geo_miles(geodeg.get(), g6_trd, g1_sfo, 5012);
- verify_geo_miles(geodeg.get(), g6_trd, g2_lhr, 928);
- verify_geo_miles(geodeg.get(), g6_trd, g3_osl, 226);
- verify_geo_miles(geodeg.get(), g6_trd, g4_gig, 6623);
- verify_geo_miles(geodeg.get(), g6_trd, g5_hkg, 5240);
- verify_geo_miles(geodeg.get(), g6_trd, g6_trd, 0);
- verify_geo_miles(geodeg.get(), g6_trd, g7_syd, 9782);
- verify_geo_miles(geodeg.get(), g6_trd, g8_lax, 5171);
- verify_geo_miles(geodeg.get(), g6_trd, g9_jfk, 3611);
-
- verify_geo_miles(geodeg.get(), g7_syd, g1_sfo, 7417);
- verify_geo_miles(geodeg.get(), g7_syd, g2_lhr, 10573);
- verify_geo_miles(geodeg.get(), g7_syd, g3_osl, 9888);
- verify_geo_miles(geodeg.get(), g7_syd, g4_gig, 8414);
- verify_geo_miles(geodeg.get(), g7_syd, g5_hkg, 4581);
- verify_geo_miles(geodeg.get(), g7_syd, g6_trd, 9782);
- verify_geo_miles(geodeg.get(), g7_syd, g7_syd, 0);
- verify_geo_miles(geodeg.get(), g7_syd, g8_lax, 7488);
- verify_geo_miles(geodeg.get(), g7_syd, g9_jfk, 9950);
-
- verify_geo_miles(geodeg.get(), g8_lax, g1_sfo, 337);
- verify_geo_miles(geodeg.get(), g8_lax, g2_lhr, 5456);
- verify_geo_miles(geodeg.get(), g8_lax, g3_osl, 5345);
- verify_geo_miles(geodeg.get(), g8_lax, g4_gig, 6294);
- verify_geo_miles(geodeg.get(), g8_lax, g5_hkg, 7260);
- verify_geo_miles(geodeg.get(), g8_lax, g6_trd, 5171);
- verify_geo_miles(geodeg.get(), g8_lax, g7_syd, 7488);
- verify_geo_miles(geodeg.get(), g8_lax, g8_lax, 0);
- verify_geo_miles(geodeg.get(), g8_lax, g9_jfk, 2475);
-
- verify_geo_miles(geodeg.get(), g9_jfk, g1_sfo, 2586);
- verify_geo_miles(geodeg.get(), g9_jfk, g2_lhr, 3451);
- verify_geo_miles(geodeg.get(), g9_jfk, g3_osl, 3687);
- verify_geo_miles(geodeg.get(), g9_jfk, g4_gig, 4786);
- verify_geo_miles(geodeg.get(), g9_jfk, g5_hkg, 8072);
- verify_geo_miles(geodeg.get(), g9_jfk, g6_trd, 3611);
- verify_geo_miles(geodeg.get(), g9_jfk, g7_syd, 9950);
- verify_geo_miles(geodeg.get(), g9_jfk, g8_lax, 2475);
- verify_geo_miles(geodeg.get(), g9_jfk, g9_jfk, 0);
-
+ verify_geo_miles(g1_sfo, g1_sfo, 0);
+ verify_geo_miles(g1_sfo, g2_lhr, 5367);
+ verify_geo_miles(g1_sfo, g3_osl, 5196);
+ verify_geo_miles(g1_sfo, g4_gig, 6604);
+ verify_geo_miles(g1_sfo, g5_hkg, 6927);
+ verify_geo_miles(g1_sfo, g6_trd, 5012);
+ verify_geo_miles(g1_sfo, g7_syd, 7417);
+ verify_geo_miles(g1_sfo, g8_lax, 337);
+ verify_geo_miles(g1_sfo, g9_jfk, 2586);
+
+ verify_geo_miles(g2_lhr, g1_sfo, 5367);
+ verify_geo_miles(g2_lhr, g2_lhr, 0);
+ verify_geo_miles(g2_lhr, g3_osl, 750);
+ verify_geo_miles(g2_lhr, g4_gig, 5734);
+ verify_geo_miles(g2_lhr, g5_hkg, 5994);
+ verify_geo_miles(g2_lhr, g6_trd, 928);
+ verify_geo_miles(g2_lhr, g7_syd, 10573);
+ verify_geo_miles(g2_lhr, g8_lax, 5456);
+ verify_geo_miles(g2_lhr, g9_jfk, 3451);
+
+ verify_geo_miles(g3_osl, g1_sfo, 5196);
+ verify_geo_miles(g3_osl, g2_lhr, 750);
+ verify_geo_miles(g3_osl, g3_osl, 0);
+ verify_geo_miles(g3_osl, g4_gig, 6479);
+ verify_geo_miles(g3_osl, g5_hkg, 5319);
+ verify_geo_miles(g3_osl, g6_trd, 226);
+ verify_geo_miles(g3_osl, g7_syd, 9888);
+ verify_geo_miles(g3_osl, g8_lax, 5345);
+ verify_geo_miles(g3_osl, g9_jfk, 3687);
+
+ verify_geo_miles(g4_gig, g1_sfo, 6604);
+ verify_geo_miles(g4_gig, g2_lhr, 5734);
+ verify_geo_miles(g4_gig, g3_osl, 6479);
+ verify_geo_miles(g4_gig, g4_gig, 0);
+ verify_geo_miles(g4_gig, g5_hkg, 10989);
+ verify_geo_miles(g4_gig, g6_trd, 6623);
+ verify_geo_miles(g4_gig, g7_syd, 8414);
+ verify_geo_miles(g4_gig, g8_lax, 6294);
+ verify_geo_miles(g4_gig, g9_jfk, 4786);
+
+ verify_geo_miles(g5_hkg, g1_sfo, 6927);
+ verify_geo_miles(g5_hkg, g2_lhr, 5994);
+ verify_geo_miles(g5_hkg, g3_osl, 5319);
+ verify_geo_miles(g5_hkg, g4_gig, 10989);
+ verify_geo_miles(g5_hkg, g5_hkg, 0);
+ verify_geo_miles(g5_hkg, g6_trd, 5240);
+ verify_geo_miles(g5_hkg, g7_syd, 4581);
+ verify_geo_miles(g5_hkg, g8_lax, 7260);
+ verify_geo_miles(g5_hkg, g9_jfk, 8072);
+
+ verify_geo_miles(g6_trd, g1_sfo, 5012);
+ verify_geo_miles(g6_trd, g2_lhr, 928);
+ verify_geo_miles(g6_trd, g3_osl, 226);
+ verify_geo_miles(g6_trd, g4_gig, 6623);
+ verify_geo_miles(g6_trd, g5_hkg, 5240);
+ verify_geo_miles(g6_trd, g6_trd, 0);
+ verify_geo_miles(g6_trd, g7_syd, 9782);
+ verify_geo_miles(g6_trd, g8_lax, 5171);
+ verify_geo_miles(g6_trd, g9_jfk, 3611);
+
+ verify_geo_miles(g7_syd, g1_sfo, 7417);
+ verify_geo_miles(g7_syd, g2_lhr, 10573);
+ verify_geo_miles(g7_syd, g3_osl, 9888);
+ verify_geo_miles(g7_syd, g4_gig, 8414);
+ verify_geo_miles(g7_syd, g5_hkg, 4581);
+ verify_geo_miles(g7_syd, g6_trd, 9782);
+ verify_geo_miles(g7_syd, g7_syd, 0);
+ verify_geo_miles(g7_syd, g8_lax, 7488);
+ verify_geo_miles(g7_syd, g9_jfk, 9950);
+
+ verify_geo_miles(g8_lax, g1_sfo, 337);
+ verify_geo_miles(g8_lax, g2_lhr, 5456);
+ verify_geo_miles(g8_lax, g3_osl, 5345);
+ verify_geo_miles(g8_lax, g4_gig, 6294);
+ verify_geo_miles(g8_lax, g5_hkg, 7260);
+ verify_geo_miles(g8_lax, g6_trd, 5171);
+ verify_geo_miles(g8_lax, g7_syd, 7488);
+ verify_geo_miles(g8_lax, g8_lax, 0);
+ verify_geo_miles(g8_lax, g9_jfk, 2475);
+
+ verify_geo_miles(g9_jfk, g1_sfo, 2586);
+ verify_geo_miles(g9_jfk, g2_lhr, 3451);
+ verify_geo_miles(g9_jfk, g3_osl, 3687);
+ verify_geo_miles(g9_jfk, g4_gig, 4786);
+ verify_geo_miles(g9_jfk, g5_hkg, 8072);
+ verify_geo_miles(g9_jfk, g6_trd, 3611);
+ verify_geo_miles(g9_jfk, g7_syd, 9950);
+ verify_geo_miles(g9_jfk, g8_lax, 2475);
+ verify_geo_miles(g9_jfk, g9_jfk, 0);
}
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp b/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
index d9230849699..9f6216f5867 100644
--- a/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
+++ b/searchlib/src/tests/tensor/hnsw_index/hnsw_index_test.cpp
@@ -105,10 +105,16 @@ public:
~HnswIndexTest() {}
+ auto dff() {
+ return search::tensor::make_distance_function_factory(
+ search::attribute::DistanceMetric::Euclidean,
+ vespalib::eval::CellType::FLOAT);
+ }
+
void init(bool heuristic_select_neighbors) {
auto generator = std::make_unique<LevelGenerator>();
level_generator = generator.get();
- index = std::make_unique<IndexType>(vectors, std::make_unique<SquaredEuclideanDistance>(vespalib::eval::CellType::FLOAT),
+ index = std::make_unique<IndexType>(vectors, dff(),
std::move(generator),
HnswIndexConfig(5, 2, 10, 0, heuristic_select_neighbors));
}
@@ -165,9 +171,10 @@ public:
uint32_t explore_k = 100;
vespalib::ArrayRef qv_ref(qv);
vespalib::eval::TypedCells qv_cells(qv_ref);
+ auto df = index->distance_function_factory().for_query_vector(qv_cells);
auto got_by_docid = (global_filter->is_active()) ?
- index->find_top_k_with_filter(k, qv_cells, *global_filter, explore_k, 10000.0) :
- index->find_top_k(k, qv_cells, explore_k, 10000.0);
+ index->find_top_k_with_filter(k, *df, *global_filter, explore_k, 10000.0) :
+ index->find_top_k(k, *df, explore_k, 10000.0);
std::vector<uint32_t> act;
act.reserve(got_by_docid.size());
for (auto& hit : got_by_docid) {
@@ -178,7 +185,8 @@ public:
void expect_top_3(uint32_t docid, std::vector<uint32_t> exp_hits) {
uint32_t k = 3;
auto qv = vectors.get_vector(docid, 0);
- auto rv = index->top_k_candidates(qv, k, global_filter->ptr_if_active()).peek();
+ auto df = index->distance_function_factory().for_query_vector(qv);
+ auto rv = index->top_k_candidates(*df, k, global_filter->ptr_if_active()).peek();
std::sort(rv.begin(), rv.end(), LesserDistance());
size_t idx = 0;
for (const auto & hit : rv) {
@@ -189,7 +197,7 @@ public:
if (exp_hits.size() == k) {
std::vector<uint32_t> expected_by_docid = exp_hits;
std::sort(expected_by_docid.begin(), expected_by_docid.end());
- auto got_by_docid = index->find_top_k(k, qv, k, 100100.25);
+ auto got_by_docid = index->find_top_k(k, *df, k, 100100.25);
for (idx = 0; idx < k; ++idx) {
EXPECT_EQ(expected_by_docid[idx], got_by_docid[idx].docid);
}
@@ -198,15 +206,16 @@ public:
}
void check_with_distance_threshold(uint32_t docid) {
auto qv = vectors.get_vector(docid, 0);
+ auto df = index->distance_function_factory().for_query_vector(qv);
uint32_t k = 3;
- auto rv = index->top_k_candidates(qv, k, global_filter->ptr_if_active()).peek();
+ auto rv = index->top_k_candidates(*df, k, global_filter->ptr_if_active()).peek();
std::sort(rv.begin(), rv.end(), LesserDistance());
EXPECT_EQ(rv.size(), 3);
EXPECT_LE(rv[0].distance, rv[1].distance);
double thr = (rv[0].distance + rv[1].distance) * 0.5;
auto got_by_docid = (global_filter->is_active())
- ? index->find_top_k_with_filter(k, qv, *global_filter, k, thr)
- : index->find_top_k(k, qv, k, thr);
+ ? index->find_top_k_with_filter(k, *df, *global_filter, k, thr)
+ : index->find_top_k(k, *df, k, thr);
EXPECT_EQ(got_by_docid.size(), 1);
EXPECT_EQ(got_by_docid[0].docid, index->get_docid(rv[0].nodeid));
for (const auto & hit : got_by_docid) {
diff --git a/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp b/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
index ecf310798af..0dcd77ec392 100644
--- a/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
+++ b/searchlib/src/tests/tensor/hnsw_index/stress_hnsw_mt.cpp
@@ -261,9 +261,15 @@ public:
~Stressor() {}
+ auto dff() {
+ return search::tensor::make_distance_function_factory(
+ search::attribute::DistanceMetric::Euclidean,
+ vespalib::eval::CellType::FLOAT);
+ }
+
void init() {
uint32_t m = 16;
- index = std::make_unique<IndexType>(vectors, std::make_unique<FloatSqEuclideanDistance>(),
+ index = std::make_unique<IndexType>(vectors, dff(),
std::make_unique<InvLogLevelGenerator>(m),
HnswIndexConfig(2*m, m, 200, 10, true));
}
diff --git a/searchlib/src/vespa/searchcommon/attribute/distance_metric.h b/searchlib/src/vespa/searchcommon/attribute/distance_metric.h
index 26efa30bba4..5b747d2016d 100644
--- a/searchlib/src/vespa/searchcommon/attribute/distance_metric.h
+++ b/searchlib/src/vespa/searchcommon/attribute/distance_metric.h
@@ -4,6 +4,6 @@
namespace search::attribute {
-enum class DistanceMetric { Euclidean, Angular, GeoDegrees, InnerProduct, Hamming };
+enum class DistanceMetric { Euclidean, Angular, GeoDegrees, InnerProduct, Hamming, PrenormalizedAngular };
}
diff --git a/searchlib/src/vespa/searchcommon/attribute/i_search_context.h b/searchlib/src/vespa/searchcommon/attribute/i_search_context.h
index 8867d1b87e4..4657d41a4a0 100644
--- a/searchlib/src/vespa/searchcommon/attribute/i_search_context.h
+++ b/searchlib/src/vespa/searchcommon/attribute/i_search_context.h
@@ -70,6 +70,11 @@ public:
bool matches(DocId docId, int32_t &weight) const { return matches(*this, docId, weight); }
bool matches(DocId doc) const { return find(doc, 0) >= 0; }
+ /*
+ * Committed docid limit on attribute vector when search context was
+ * created.
+ */
+ virtual uint32_t get_committed_docid_limit() const noexcept = 0;
};
}
diff --git a/searchlib/src/vespa/searchcommon/common/undefinedvalues.h b/searchlib/src/vespa/searchcommon/common/undefinedvalues.h
index bbe3198a8dc..a080648c054 100644
--- a/searchlib/src/vespa/searchcommon/common/undefinedvalues.h
+++ b/searchlib/src/vespa/searchcommon/common/undefinedvalues.h
@@ -24,6 +24,10 @@ inline constexpr double getUndefined<double>() {
return -std::numeric_limits<double>::quiet_NaN();
}
+template <>
+inline constexpr const char* getUndefined<const char*>() {
+ return "";
+}
// for all signed integers
template <typename T>
diff --git a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
index e40717e6375..b9b3c63a0c3 100644
--- a/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attribute_header.cpp
@@ -28,6 +28,7 @@ const vespalib::string euclidean = "euclidean";
const vespalib::string angular = "angular";
const vespalib::string geodegrees = "geodegrees";
const vespalib::string innerproduct = "innerproduct";
+const vespalib::string prenormalized_angular = "prenormalized_angular";
const vespalib::string hamming = "hamming";
const vespalib::string doc_id_limit_tag = "docIdLimit";
const vespalib::string enumerated_tag = "enumerated";
@@ -101,6 +102,7 @@ to_string(DistanceMetric metric)
case DistanceMetric::GeoDegrees: return geodegrees;
case DistanceMetric::InnerProduct: return innerproduct;
case DistanceMetric::Hamming: return hamming;
+ case DistanceMetric::PrenormalizedAngular: return prenormalized_angular;
}
throw vespalib::IllegalArgumentException("Unknown distance metric " + std::to_string(static_cast<int>(metric)));
}
@@ -116,6 +118,8 @@ to_distance_metric(const vespalib::string& metric)
return DistanceMetric::GeoDegrees;
} else if (metric == innerproduct) {
return DistanceMetric::InnerProduct;
+ } else if (metric == prenormalized_angular) {
+ return DistanceMetric::PrenormalizedAngular;
} else if (metric == hamming) {
return DistanceMetric::Hamming;
} else {
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
index e6b584d29b2..f4ab447ed51 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.cpp
@@ -353,6 +353,8 @@ AttributeVector::load(vespalib::Executor * executor) {
bool loaded = onLoad(executor);
if (loaded) {
commit();
+ incGeneration();
+ updateStat(true);
}
_loaded = loaded;
return _loaded;
@@ -440,21 +442,18 @@ AttributeVector::addReservedDoc()
addDoc(docId); // Reserved
assert(docId == 0u);
assert(docId < getNumDocs());
+ set_reserved_doc_values();
+}
+
+void
+AttributeVector::set_reserved_doc_values()
+{
+ uint32_t docId = 0;
+ if (docId >= getNumDocs()) {
+ return;
+ }
clearDoc(docId);
commit();
- FloatingPointAttribute * vec = dynamic_cast<FloatingPointAttribute *>(this);
- if (vec) {
- if (hasMultiValue()) {
- bool appendedUndefined = vec->append(0, attribute::getUndefined<double>(), 1);
- assert(appendedUndefined);
- (void) appendedUndefined;
- } else {
- bool updatedUndefined = vec->update(0, attribute::getUndefined<double>());
- assert(updatedUndefined);
- (void) updatedUndefined;
- }
- commit();
- }
}
attribute::IPostingListAttributeBase *AttributeVector::getIPostingListAttributeBase() { return nullptr; }
diff --git a/searchlib/src/vespa/searchlib/attribute/attributevector.h b/searchlib/src/vespa/searchlib/attribute/attributevector.h
index e40785911ea..b58060a05bf 100644
--- a/searchlib/src/vespa/searchlib/attribute/attributevector.h
+++ b/searchlib/src/vespa/searchlib/attribute/attributevector.h
@@ -478,6 +478,10 @@ public:
* Add reserved initial document with docId 0 and undefined value.
*/
void addReservedDoc();
+ /**
+ * set undefined values for reserved document 0.
+ */
+ void set_reserved_doc_values();
bool getEnumeratedSave() const { return _hasEnum; }
virtual attribute::IPostingListAttributeBase * getIPostingListAttributeBase();
diff --git a/searchlib/src/vespa/searchlib/attribute/configconverter.cpp b/searchlib/src/vespa/searchlib/attribute/configconverter.cpp
index a799abb34c7..31a0b2f2733 100644
--- a/searchlib/src/vespa/searchlib/attribute/configconverter.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/configconverter.cpp
@@ -134,6 +134,9 @@ ConfigConverter::convert(const AttributesConfig::Attribute & cfg)
case CfgDm::HAMMING:
dm = DistanceMetric::Hamming;
break;
+ case CfgDm::PRENORMALIZED_ANGULAR:
+ dm = DistanceMetric::PrenormalizedAngular;
+ break;
}
retval.set_distance_metric(dm);
if (cfg.index.hnsw.enabled) {
diff --git a/searchlib/src/vespa/searchlib/attribute/empty_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/empty_search_context.cpp
index 7a5d82cd9ba..91bdb45ff19 100644
--- a/searchlib/src/vespa/searchlib/attribute/empty_search_context.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/empty_search_context.cpp
@@ -30,6 +30,12 @@ EmptySearchContext::approximateHits() const
return 0u;
}
+uint32_t
+EmptySearchContext::get_committed_docid_limit() const noexcept
+{
+ return 0u;
+}
+
std::unique_ptr<queryeval::SearchIterator>
EmptySearchContext::createIterator(fef::TermFieldMatchData*, bool)
{
diff --git a/searchlib/src/vespa/searchlib/attribute/empty_search_context.h b/searchlib/src/vespa/searchlib/attribute/empty_search_context.h
index ae6f6d76edf..133e540d87f 100644
--- a/searchlib/src/vespa/searchlib/attribute/empty_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/empty_search_context.h
@@ -19,6 +19,7 @@ class EmptySearchContext : public SearchContext
public:
EmptySearchContext(const AttributeVector& attr) noexcept;
~EmptySearchContext();
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.cpp b/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.cpp
index eeaa3e9539f..c1345b4f770 100644
--- a/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.cpp
@@ -93,6 +93,7 @@ EnumeratedLoader::build_dictionary()
{
_store.get_dictionary().build(_indexes);
release_enum_indexes();
+ _store.setup_default_value_ref();
}
EnumeratedPostingsLoader::EnumeratedPostingsLoader(IEnumStore& store)
@@ -131,6 +132,13 @@ EnumeratedPostingsLoader::build_dictionary()
_store.get_dictionary().build_with_payload(_indexes, _posting_indexes);
release_enum_indexes();
EntryRefVector().swap(_posting_indexes);
+ _store.setup_default_value_ref();
+}
+
+void
+EnumeratedPostingsLoader::build_empty_dictionary()
+{
+ _store.setup_default_value_ref();
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.h b/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.h
index 2a72fcac628..937ceb91628 100644
--- a/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.h
+++ b/searchlib/src/vespa/searchlib/attribute/enum_store_loaders.h
@@ -85,6 +85,7 @@ public:
void set_ref_count(Index idx, uint32_t ref_count);
vespalib::ArrayRef<EntryRef> initialize_empty_posting_indexes();
void build_dictionary();
+ void build_empty_dictionary();
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/enumattribute.h b/searchlib/src/vespa/searchlib/attribute/enumattribute.h
index f0ff23a06b4..4753dbe65f9 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/enumattribute.h
@@ -50,13 +50,12 @@ protected:
/*
* Iterate through the change vector and find new unique values.
- * Perform compaction if necessary and insert the new unique values into the EnumStore.
+ * Insert the new unique values into the EnumStore.
*/
void insertNewUniqueValues(EnumStoreBatchUpdater& updater);
virtual void considerAttributeChange(const Change & c, EnumStoreBatchUpdater & inserter) = 0;
vespalib::MemoryUsage getEnumStoreValuesMemoryUsage() const override;
void populate_address_space_usage(AddressSpaceUsage& usage) const override;
- void cache_change_data_entry_ref(const Change& c) const;
public:
EnumAttribute(const vespalib::string & baseFileName, const AttributeVector::Config & cfg);
~EnumAttribute();
diff --git a/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp
index c5188b89129..66d555df3cb 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/enumattribute.hpp
@@ -15,7 +15,7 @@ EnumAttribute<B>::
EnumAttribute(const vespalib::string &baseFileName,
const AttributeVector::Config &cfg)
: B(baseFileName, cfg),
- _enumStore(cfg.fastSearch(), cfg.get_dictionary_config(), this->get_memory_allocator())
+ _enumStore(cfg.fastSearch(), cfg.get_dictionary_config(), this->get_memory_allocator(), this->_defaultValue._data.raw())
{
this->setEnum(true);
}
@@ -50,6 +50,7 @@ void EnumAttribute<B>::load_enum_store(LoadedVector& loaded)
loader.set_ref_count_for_last_value(prevRefCount);
}
loader.build_dictionary();
+ _enumStore.setup_default_value_ref();
}
}
@@ -85,15 +86,4 @@ EnumAttribute<B>::populate_address_space_usage(AddressSpaceUsage& usage) const
usage.set(AddressSpaceComponents::enum_store, _enumStore.get_values_address_space_usage());
}
-template <typename B>
-void
-EnumAttribute<B>::cache_change_data_entry_ref(const Change& c) const
-{
- EnumIndex new_idx;
- _enumStore.find_index(c._data.raw(), new_idx);
- c.set_entry_ref(new_idx.ref());
-}
-
} // namespace search
-
-
diff --git a/searchlib/src/vespa/searchlib/attribute/enumhintsearchcontext.h b/searchlib/src/vespa/searchlib/attribute/enumhintsearchcontext.h
index 0342976ffd6..86ffa1c8ab0 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumhintsearchcontext.h
+++ b/searchlib/src/vespa/searchlib/attribute/enumhintsearchcontext.h
@@ -41,6 +41,7 @@ protected:
void fetchPostings(const queryeval::ExecuteInfo & execInfo) override;
unsigned int approximateHits() const override;
+ uint32_t get_committed_docid_limit() const noexcept { return _docIdLimit; }
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/enumstore.h b/searchlib/src/vespa/searchlib/attribute/enumstore.h
index 266437fafa1..f6467194d74 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumstore.h
+++ b/searchlib/src/vespa/searchlib/attribute/enumstore.h
@@ -28,6 +28,9 @@ namespace search {
* It uses an instance of vespalib::datastore::UniqueStore to store the actual values.
* It also exposes the dictionary used for fast lookups into the set of unique values.
*
+ * The default value is always present except for a short time window
+ * during attribute vector load.
+ *
* @tparam EntryType The type of the entries/values stored.
* It has special handling of type 'const char *' for strings.
*/
@@ -55,6 +58,8 @@ private:
ComparatorType _comparator;
ComparatorType _foldedComparator;
enumstore::EnumStoreCompactionSpec _compaction_spec;
+ EntryType _default_value;
+ AtomicIndex _default_value_ref;
EnumStoreT(const EnumStoreT & rhs) = delete;
EnumStoreT & operator=(const EnumStoreT & rhs) = delete;
@@ -75,7 +80,7 @@ private:
std::unique_ptr<EntryComparator> allocate_optionally_folded_comparator(bool folded) const;
ComparatorType make_optionally_folded_comparator(bool folded) const;
public:
- EnumStoreT(bool has_postings, const search::DictionaryConfig& dict_cfg, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator);
+ EnumStoreT(bool has_postings, const search::DictionaryConfig& dict_cfg, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator, EntryType default_value);
EnumStoreT(bool has_postings, const search::DictionaryConfig & dict_cfg);
~EnumStoreT() override;
@@ -201,6 +206,9 @@ public:
bool find_index(EntryType value, Index& idx) const;
void free_unused_values() override;
void free_unused_values(IndexList to_remove);
+ void clear_default_value_ref() override;
+ void setup_default_value_ref() override;
+ const AtomicIndex& get_default_value_ref() const noexcept { return _default_value_ref; }
vespalib::MemoryUsage update_stat(const CompactionStrategy& compaction_strategy) override;
std::unique_ptr<EnumIndexRemapper> consider_compact_values(const CompactionStrategy& compaction_strategy) override;
std::unique_ptr<EnumIndexRemapper> compact_worst_values(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/enumstore.hpp b/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
index bc767a296eb..c0eebee8e94 100644
--- a/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/enumstore.hpp
@@ -17,6 +17,7 @@
#include <vespa/vespalib/datastore/unique_store.hpp>
#include <vespa/vespalib/datastore/unique_store_string_allocator.hpp>
#include <vespa/vespalib/util/array.hpp>
+#include <vespa/searchcommon/common/undefinedvalues.h>
#include <vespa/searchlib/util/bufferwriter.h>
#include <vespa/vespalib/datastore/compaction_strategy.h>
@@ -72,23 +73,26 @@ EnumStoreT<EntryT>::load_unique_value(const void* src, size_t available, Index&
}
template <typename EntryT>
-EnumStoreT<EntryT>::EnumStoreT(bool has_postings, const DictionaryConfig& dict_cfg, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator)
+EnumStoreT<EntryT>::EnumStoreT(bool has_postings, const DictionaryConfig& dict_cfg, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator, EntryType default_value)
: _store(std::move(memory_allocator)),
_dict(),
_is_folded(dict_cfg.getMatch() == DictionaryConfig::Match::UNCASED),
_comparator(_store.get_data_store()),
_foldedComparator(make_optionally_folded_comparator(is_folded())),
- _compaction_spec()
+ _compaction_spec(),
+ _default_value(default_value),
+ _default_value_ref()
{
_store.set_dictionary(make_enum_store_dictionary(*this, has_postings, dict_cfg,
allocate_comparator(),
allocate_optionally_folded_comparator(is_folded())));
_dict = static_cast<IEnumStoreDictionary*>(&_store.get_dictionary());
+ setup_default_value_ref();
}
template <typename EntryT>
EnumStoreT<EntryT>::EnumStoreT(bool has_postings, const DictionaryConfig& dict_cfg)
- : EnumStoreT<EntryT>(has_postings, dict_cfg, {})
+ : EnumStoreT<EntryT>(has_postings, dict_cfg, {}, attribute::getUndefined<EntryType>())
{
}
@@ -215,6 +219,33 @@ EnumStoreT<EntryT>::insert(EntryType value)
return _store.add(value).ref();
}
+
+template <typename EntryT>
+void
+EnumStoreT<EntryT>::clear_default_value_ref()
+{
+ auto ref = _default_value_ref.load_relaxed();
+ if (ref.valid()) {
+ auto updater = make_batch_updater();
+ updater.dec_ref_count(ref);
+ _default_value_ref.store_relaxed(Index());
+ updater.commit();
+ }
+}
+
+template <typename EntryT>
+void
+EnumStoreT<EntryT>::setup_default_value_ref()
+{
+ if (!_default_value_ref.load_relaxed().valid()) {
+ auto updater = make_batch_updater();
+ auto ref = updater.insert(_default_value);
+ updater.inc_ref_count(ref);
+ _default_value_ref.store_relaxed(ref);
+ updater.commit();
+ }
+}
+
template <typename EntryT>
vespalib::MemoryUsage
EnumStoreT<EntryT>::update_stat(const CompactionStrategy& compaction_strategy)
@@ -236,7 +267,14 @@ template <typename EntryT>
std::unique_ptr<IEnumStore::EnumIndexRemapper>
EnumStoreT<EntryT>::compact_worst_values(CompactionSpec compaction_spec, const CompactionStrategy& compaction_strategy)
{
- return _store.compact_worst(compaction_spec, compaction_strategy);
+ auto remapper = _store.compact_worst(compaction_spec, compaction_strategy);
+ if (remapper) {
+ auto ref = _default_value_ref.load_relaxed();
+ if (ref.valid() && remapper->get_entry_ref_filter().has(ref)) {
+ _default_value_ref.store_release(remapper->remap(ref));
+ }
+ }
+ return remapper;
}
template <typename EntryT>
diff --git a/searchlib/src/vespa/searchlib/attribute/extendableattributes.h b/searchlib/src/vespa/searchlib/attribute/extendableattributes.h
index cc2cb216bbb..bc884f9020e 100644
--- a/searchlib/src/vespa/searchlib/attribute/extendableattributes.h
+++ b/searchlib/src/vespa/searchlib/attribute/extendableattributes.h
@@ -2,7 +2,7 @@
/**
* @class search::SearchVisitor
*
- * @brief Visitor that applies a search query to visitor data and converts them to a SearchResultCommand
+ * @brief Visitor that applies a search query to visitor data and converts them to a QueryResultCommand
*/
#pragma once
diff --git a/searchlib/src/vespa/searchlib/attribute/extendableattributes.hpp b/searchlib/src/vespa/searchlib/attribute/extendableattributes.hpp
index c515a1bec7f..0b1d1221df2 100644
--- a/searchlib/src/vespa/searchlib/attribute/extendableattributes.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/extendableattributes.hpp
@@ -2,7 +2,7 @@
/**
* @class search::SearchVisitor
*
- * @brief Visitor that applies a search query to visitor data and converts them to a SearchResultCommand
+ * @brief Visitor that applies a search query to visitor data and converts them to a QueryResultCommand
*/
#pragma once
diff --git a/searchlib/src/vespa/searchlib/attribute/i_enum_store.h b/searchlib/src/vespa/searchlib/attribute/i_enum_store.h
index 2157db3e5ed..aa9fd549b60 100644
--- a/searchlib/src/vespa/searchlib/attribute/i_enum_store.h
+++ b/searchlib/src/vespa/searchlib/attribute/i_enum_store.h
@@ -74,6 +74,8 @@ public:
virtual std::unique_ptr<Enumerator> make_enumerator() = 0;
virtual std::unique_ptr<vespalib::datastore::EntryComparator> allocate_comparator() const = 0;
+ virtual void clear_default_value_ref() = 0;
+ virtual void setup_default_value_ref() = 0;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp
index a1a5e9f7894..b50a3720ff8 100644
--- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.cpp
@@ -17,12 +17,14 @@ ImportedAttributeVectorReadGuard::ImportedAttributeVectorReadGuard(std::shared_p
_target_document_meta_store_read_guard(std::move(targetMetaStoreReadGuard)),
_imported_attribute(imported_attribute),
_targetLids(),
+ _target_docid_limit(0u),
_reference_attribute_guard(imported_attribute.getReferenceAttribute()),
_target_attribute_guard(imported_attribute.getTargetAttribute()->makeReadGuard(stableEnumGuard)),
_reference_attribute(*imported_attribute.getReferenceAttribute()),
_target_attribute(*_target_attribute_guard->attribute())
{
_targetLids = _reference_attribute.getTargetLids();
+ _target_docid_limit = _target_attribute.getCommittedDocIdLimit();
}
ImportedAttributeVectorReadGuard::~ImportedAttributeVectorReadGuard() = default;
diff --git a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h
index cb48399f688..1297acad9b8 100644
--- a/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h
+++ b/searchlib/src/vespa/searchlib/attribute/imported_attribute_vector_read_guard.h
@@ -95,6 +95,7 @@ private:
std::shared_ptr<MetaStoreReadGuard> _target_document_meta_store_read_guard;
const ImportedAttributeVector &_imported_attribute;
TargetLids _targetLids;
+ uint32_t _target_docid_limit;
AttributeGuard _reference_attribute_guard;
std::unique_ptr<attribute::AttributeReadGuard> _target_attribute_guard;
const ReferenceAttribute &_reference_attribute;
@@ -103,7 +104,9 @@ protected:
uint32_t getTargetLid(uint32_t lid) const {
// Check range to avoid reading memory beyond end of mapping array
- return lid < _targetLids.size() ? _targetLids[lid].load_acquire() : 0u;
+ uint32_t target_lid = lid < _targetLids.size() ? _targetLids[lid].load_acquire() : 0u;
+ // Check target range
+ return target_lid < _target_docid_limit ? target_lid : 0u;
}
long onSerializeForAscendingSort(DocId doc, void * serTo, long available,
diff --git a/searchlib/src/vespa/searchlib/attribute/imported_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/imported_search_context.cpp
index 1e8adc3922e..3d308b82b04 100644
--- a/searchlib/src/vespa/searchlib/attribute/imported_search_context.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/imported_search_context.cpp
@@ -43,6 +43,7 @@ ImportedSearchContext::ImportedSearchContext(
_target_attribute(target_attribute),
_target_search_context(_target_attribute.createSearchContext(std::move(term), params)),
_targetLids(_reference_attribute.getTargetLids()),
+ _target_docid_limit(_target_search_context->get_committed_docid_limit()),
_merger(_reference_attribute.getCommittedDocIdLimit()),
_params(params),
_zero_hits(false)
@@ -327,4 +328,10 @@ const vespalib::string& ImportedSearchContext::attributeName() const {
return _imported_attribute.getName();
}
+uint32_t
+ImportedSearchContext::get_committed_docid_limit() const noexcept
+{
+ return _targetLids.size();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/imported_search_context.h b/searchlib/src/vespa/searchlib/attribute/imported_search_context.h
index d9c09d8c645..d6b6d09e8fc 100644
--- a/searchlib/src/vespa/searchlib/attribute/imported_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/imported_search_context.h
@@ -39,6 +39,7 @@ class ImportedSearchContext : public ISearchContext {
const IAttributeVector &_target_attribute;
std::unique_ptr<ISearchContext> _target_search_context;
TargetLids _targetLids;
+ uint32_t _target_docid_limit;
PostingListMerger<int32_t> _merger;
SearchContextParams _params;
mutable std::atomic<bool> _zero_hits;
@@ -47,7 +48,9 @@ class ImportedSearchContext : public ISearchContext {
uint32_t getTargetLid(uint32_t lid) const {
// Check range to avoid reading memory beyond end of mapping array
- return lid < _targetLids.size() ? _targetLids[lid].load_acquire() : 0u;
+ uint32_t target_lid = lid < _targetLids.size() ? _targetLids[lid].load_acquire() : 0u;
+ // Check target range
+ return target_lid < _target_docid_limit ? target_lid : 0u;
}
void makeMergedPostings(bool isFilter);
@@ -90,6 +93,7 @@ public:
const ISearchContext &target_search_context() const noexcept {
return *_target_search_context;
}
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
index 5b393d8bdb2..161c6799787 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.h
@@ -59,6 +59,7 @@ public:
std::unique_ptr<queryeval::SearchIterator>
createFilterIterator(fef::TermFieldMatchData* matchData, bool strict) override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
index e7901199e50..15abcf6f0d9 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_enum_search_context.hpp
@@ -33,4 +33,11 @@ MultiEnumSearchContext<T, BaseSC, M>::createFilterIterator(fef::TermFieldMatchDa
: std::make_unique<AttributeIteratorT<MultiEnumSearchContext>>(*this, matchData);
}
+template <typename T, typename BaseSC, typename M>
+uint32_t
+MultiEnumSearchContext<T, BaseSC, M>::get_committed_docid_limit() const noexcept
+{
+ return _mv_mapping_read_view.get_committed_docid_limit();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
index b2c76a120f9..23e56e23af9 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.h
@@ -54,6 +54,7 @@ public:
std::unique_ptr<queryeval::SearchIterator>
createFilterIterator(fef::TermFieldMatchData* matchData, bool strict) override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
index 15b851215f8..7e1fd1aeb5a 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_numeric_search_context.hpp
@@ -33,4 +33,11 @@ MultiNumericSearchContext<T, M>::createFilterIterator(fef::TermFieldMatchData* m
: std::make_unique<AttributeIteratorT<MultiNumericSearchContext<T, M>>>(*this, matchData);
}
+template <typename T, typename M>
+uint32_t
+MultiNumericSearchContext<T, M>::get_committed_docid_limit() const noexcept
+{
+ return _mv_mapping_read_view.get_committed_docid_limit();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
index bd624e9f388..4fce64aa762 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.h
@@ -12,18 +12,18 @@ namespace search::attribute {
/**
* Class for mapping from document id to an array of values.
*/
-template <typename EntryT, typename RefT = vespalib::datastore::EntryRefT<19> >
+template <typename ElemT, typename RefT = vespalib::datastore::EntryRefT<19> >
class MultiValueMapping : public MultiValueMappingBase
{
public:
- using MultiValueType = EntryT;
+ using MultiValueType = ElemT;
using RefType = RefT;
- using ReadView = MultiValueMappingReadView<EntryT, RefT>;
+ using ReadView = MultiValueMappingReadView<ElemT, RefT>;
private:
- using ArrayRef = vespalib::ArrayRef<EntryT>;
- using ArrayStore = vespalib::datastore::ArrayStore<EntryT, RefT>;
+ using ArrayRef = vespalib::ArrayRef<ElemT>;
+ using ArrayStore = vespalib::datastore::ArrayStore<ElemT, RefT>;
using generation_t = vespalib::GenerationHandler::generation_t;
- using ConstArrayRef = vespalib::ConstArrayRef<EntryT>;
+ using ConstArrayRef = vespalib::ConstArrayRef<ElemT>;
ArrayStore _store;
public:
@@ -73,7 +73,7 @@ public:
static vespalib::datastore::ArrayStoreConfig optimizedConfigForHugePage(size_t maxSmallArraySize,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor,
bool enable_free_lists);
};
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp
index b486fa60265..ab68bea58cc 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping.hpp
@@ -7,8 +7,8 @@
namespace search::attribute {
-template <typename EntryT, typename RefT>
-MultiValueMapping<EntryT,RefT>::MultiValueMapping(const vespalib::datastore::ArrayStoreConfig &storeCfg,
+template <typename ElemT, typename RefT>
+MultiValueMapping<ElemT,RefT>::MultiValueMapping(const vespalib::datastore::ArrayStoreConfig &storeCfg,
const vespalib::GrowStrategy &gs,
std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator)
: MultiValueMappingBase(gs, ArrayStore::getGenerationHolderLocation(_store), memory_allocator),
@@ -16,12 +16,12 @@ MultiValueMapping<EntryT,RefT>::MultiValueMapping(const vespalib::datastore::Arr
{
}
-template <typename EntryT, typename RefT>
-MultiValueMapping<EntryT,RefT>::~MultiValueMapping() = default;
+template <typename ElemT, typename RefT>
+MultiValueMapping<ElemT,RefT>::~MultiValueMapping() = default;
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
void
-MultiValueMapping<EntryT,RefT>::set(uint32_t docId, ConstArrayRef values)
+MultiValueMapping<ElemT,RefT>::set(uint32_t docId, ConstArrayRef values)
{
_indices.ensure_size(docId + 1);
EntryRef oldRef(_indices[docId].load_relaxed());
@@ -31,18 +31,18 @@ MultiValueMapping<EntryT,RefT>::set(uint32_t docId, ConstArrayRef values)
_store.remove(oldRef);
}
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
vespalib::MemoryUsage
-MultiValueMapping<EntryT,RefT>::update_stat(const CompactionStrategy& compaction_strategy)
+MultiValueMapping<ElemT,RefT>::update_stat(const CompactionStrategy& compaction_strategy)
{
auto retval = _store.update_stat(compaction_strategy);
retval.merge(_indices.getMemoryUsage());
return retval;
}
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
void
-MultiValueMapping<EntryT,RefT>::compact_worst(const CompactionStrategy& compaction_strategy)
+MultiValueMapping<ElemT,RefT>::compact_worst(const CompactionStrategy& compaction_strategy)
{
vespalib::datastore::ICompactionContext::UP compactionContext(_store.compact_worst(compaction_strategy));
if (compactionContext) {
@@ -50,29 +50,29 @@ MultiValueMapping<EntryT,RefT>::compact_worst(const CompactionStrategy& compacti
}
}
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
vespalib::MemoryUsage
-MultiValueMapping<EntryT,RefT>::getArrayStoreMemoryUsage() const
+MultiValueMapping<ElemT,RefT>::getArrayStoreMemoryUsage() const
{
return _store.getMemoryUsage();
}
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
vespalib::AddressSpace
-MultiValueMapping<EntryT, RefT>::getAddressSpaceUsage() const {
+MultiValueMapping<ElemT, RefT>::getAddressSpaceUsage() const {
return _store.addressSpaceUsage();
}
-template <typename EntryT, typename RefT>
+template <typename ElemT, typename RefT>
vespalib::datastore::ArrayStoreConfig
-MultiValueMapping<EntryT, RefT>::optimizedConfigForHugePage(size_t maxSmallArraySize,
+MultiValueMapping<ElemT, RefT>::optimizedConfigForHugePage(size_t maxSmallArraySize,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor,
bool enable_free_lists)
{
- auto result = ArrayStore::optimizedConfigForHugePage(maxSmallArraySize, hugePageSize, smallPageSize, minNumArraysForNewBuffer, allocGrowFactor);
+ auto result = ArrayStore::optimizedConfigForHugePage(maxSmallArraySize, hugePageSize, smallPageSize, min_num_entries_for_new_buffer, allocGrowFactor);
result.enable_free_lists(enable_free_lists);
return result;
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
index 116e069e8b4..609989208c3 100644
--- a/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
+++ b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
@@ -11,12 +11,12 @@ namespace search::attribute {
/**
* Class for mapping from document id to an array of values as reader.
*/
-template <typename EntryT, typename RefT = vespalib::datastore::EntryRefT<19> >
+template <typename ElemT, typename RefT = vespalib::datastore::EntryRefT<19> >
class MultiValueMappingReadView
{
using AtomicEntryRef = vespalib::datastore::AtomicEntryRef;
using Indices = vespalib::ConstArrayRef<AtomicEntryRef>;
- using ArrayStore = vespalib::datastore::ArrayStore<EntryT, RefT>;
+ using ArrayStore = vespalib::datastore::ArrayStore<ElemT, RefT>;
Indices _indices;
const ArrayStore* _store;
@@ -31,8 +31,9 @@ public:
_store(store)
{
}
- vespalib::ConstArrayRef<EntryT> get(uint32_t doc_id) const { return _store->get(_indices[doc_id].load_acquire()); }
+ vespalib::ConstArrayRef<ElemT> get(uint32_t doc_id) const { return _store->get(_indices[doc_id].load_acquire()); }
bool valid() const noexcept { return _store != nullptr; }
+ uint32_t get_committed_docid_limit() const noexcept { return _indices.size(); }
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
index edfea23f48d..59c1216829d 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericenumattribute.hpp
@@ -97,6 +97,10 @@ MultiValueNumericEnumAttribute<B, M>::onLoad(vespalib::Executor *)
return false;
}
+ this->_enumStore.clear_default_value_ref();
+ this->commit();
+ this->incGeneration();
+
this->setCreateSerialNum(attrReader.getCreateSerialNum());
if (attrReader.getEnumerated()) {
diff --git a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
index ea449300aef..1009fa2fb5f 100644
--- a/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multinumericpostattribute.hpp
@@ -51,7 +51,7 @@ template <typename B, typename M>
MultiValueNumericPostingAttribute<B, M>::~MultiValueNumericPostingAttribute()
{
this->disableFreeLists();
- this->disableElemHoldList();
+ this->disable_entry_hold_list();
clearAllPostings();
}
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
index a63862126fa..7b11fcd59f4 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringattribute.hpp
@@ -42,7 +42,6 @@ MultiValueStringAttributeT<B, M>::freezeEnumDictionary()
this->getEnumStore().freeze_dictionary();
}
-
template <typename B, typename M>
std::unique_ptr<attribute::SearchContext>
MultiValueStringAttributeT<B, M>::getSearch(QueryTermSimpleUP qTerm,
diff --git a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
index cd46bbb5a8a..19840b5a474 100644
--- a/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/multistringpostattribute.hpp
@@ -27,7 +27,7 @@ template <typename B, typename T>
MultiValueStringPostingAttributeT<B, T>::~MultiValueStringPostingAttributeT()
{
this->disableFreeLists();
- this->disableElemHoldList();
+ this->disable_entry_hold_list();
clearAllPostings();
}
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
index 6ef3b575c3e..01e68949f92 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.cpp
@@ -49,6 +49,7 @@ PostingListAttributeBase<P>::handle_load_posting_lists_and_update_enum_store(enu
PostingChange<P> postings;
const auto& loaded_enums = loader.get_loaded_enums();
if (loaded_enums.empty()) {
+ loader.build_empty_dictionary();
return;
}
uint32_t preve = 0;
diff --git a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
index 29440b6ce43..ecf7a46f21e 100644
--- a/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/postinglistattribute.h
@@ -58,7 +58,7 @@ protected:
void updatePostings(PostingMap &changePost, const vespalib::datastore::EntryComparator &cmp);
void clearAllPostings();
void disableFreeLists() { _postingList.disableFreeLists(); }
- void disableElemHoldList() { _postingList.disableElemHoldList(); }
+ void disable_entry_hold_list() { _postingList.disable_entry_hold_list(); }
void handle_load_posting_lists_and_update_enum_store(enumstore::EnumeratedPostingsLoader& loader);
bool forwardedOnAddDoc(DocId doc, size_t wantSize, size_t wantCapacity);
diff --git a/searchlib/src/vespa/searchlib/attribute/postingstore.cpp b/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
index 94720212faf..2703201b292 100644
--- a/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/postingstore.cpp
@@ -231,7 +231,7 @@ PostingStore<DataT>::dropBitVector(EntryRef &ref)
(void) tree;
(void) docFreq;
_bvs.erase(ref.ref());
- _store.holdElem(iRef, 1);
+ _store.hold_entry(iRef);
_status.decBitVectors();
_bvExtraBytes -= bv->writer().extraByteSize();
ref = ref2;
@@ -267,7 +267,7 @@ PostingStore<DataT>::makeBitVector(EntryRef &ref)
if (_enableOnlyBitVector) {
BTreeType *tree = getWTreeEntry(iRef);
tree->clear(_allocator);
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
} else {
bve->_tree = ref;
}
@@ -590,19 +590,19 @@ PostingStore<DataT>::clear(const EntryRef ref)
assert(isBTree(iRef2));
BTreeType *tree = getWTreeEntry(iRef2);
tree->clear(_allocator);
- _store.holdElem(iRef2, 1);
+ _store.hold_entry(iRef2);
}
_bvs.erase(ref.ref());
_status.decBitVectors();
_bvExtraBytes -= bve->_bv->writer().extraByteSize();
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
} else {
BTreeType *tree = getWTreeEntry(iRef);
tree->clear(_allocator);
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
}
} else {
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
index b701a6fd08f..9343dafe917 100644
--- a/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/reference_attribute.cpp
@@ -454,12 +454,14 @@ class ReferenceSearchContext : public attribute::SearchContext {
private:
const ReferenceAttribute& _ref_attr;
GlobalId _term;
+ uint32_t _docid_limit;
public:
ReferenceSearchContext(const ReferenceAttribute& ref_attr, const GlobalId& term)
: attribute::SearchContext(ref_attr),
_ref_attr(ref_attr),
- _term(term)
+ _term(term),
+ _docid_limit(ref_attr.getCommittedDocIdLimit())
{
}
bool valid() const override {
@@ -480,8 +482,15 @@ public:
int32_t weight;
return onFind(docId, elementId, weight);
}
+ uint32_t get_committed_docid_limit() const noexcept override;
};
+uint32_t
+ReferenceSearchContext::get_committed_docid_limit() const noexcept
+{
+ return _docid_limit;
+}
+
}
std::unique_ptr<attribute::SearchContext>
diff --git a/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.h
index 83d6c696117..f6a2f94dedb 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.h
@@ -17,7 +17,9 @@ class SingleEnumSearchContext : public BaseSC
{
protected:
using DocId = ISearchContext::DocId;
- const vespalib::datastore::AtomicEntryRef* _enum_indices;
+ using AtomicEntryRef = vespalib::datastore::AtomicEntryRef;
+ using EnumIndices = vespalib::ConstArrayRef<AtomicEntryRef>;
+ EnumIndices _enum_indices;
const EnumStoreT<T>& _enum_store;
int32_t onFind(DocId docId, int32_t elemId, int32_t & weight) const final {
@@ -29,7 +31,7 @@ protected:
}
public:
- SingleEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<T>& enum_store);
+ SingleEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<T>& enum_store);
int32_t find(DocId docId, int32_t elemId, int32_t & weight) const {
if ( elemId != 0) return -1;
@@ -46,6 +48,7 @@ public:
std::unique_ptr<queryeval::SearchIterator>
createFilterIterator(fef::TermFieldMatchData* matchData, bool strict) override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.hpp
index a415c301f9c..6b6cf480d6a 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_enum_search_context.hpp
@@ -9,7 +9,7 @@
namespace search::attribute {
template <typename T, typename BaseSC>
-SingleEnumSearchContext<T, BaseSC>::SingleEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<T>& enum_store)
+SingleEnumSearchContext<T, BaseSC>::SingleEnumSearchContext(typename BaseSC::MatcherType&& matcher, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<T>& enum_store)
: BaseSC(toBeSearched, std::move(matcher)),
_enum_indices(enum_indices),
_enum_store(enum_store)
@@ -33,4 +33,11 @@ SingleEnumSearchContext<T, BaseSC>::createFilterIterator(fef::TermFieldMatchData
: std::make_unique<AttributeIteratorT<SingleEnumSearchContext>>(*this, matchData);
}
+template <typename T, typename BaseSC>
+uint32_t
+SingleEnumSearchContext<T, BaseSC>::get_committed_docid_limit() const noexcept
+{
+ return _enum_indices.size();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.h
index 86283f59283..fd3f4c03a8a 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.h
@@ -16,7 +16,9 @@ template <typename T>
class SingleNumericEnumSearchContext : public SingleEnumSearchContext<T, NumericSearchContext<NumericRangeMatcher<T>>>
{
public:
- SingleNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<T>& enum_store);
+ using AtomicEntryRef = vespalib::datastore::AtomicEntryRef;
+ using EnumIndices = vespalib::ConstArrayRef<AtomicEntryRef>;
+ SingleNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<T>& enum_store);
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.hpp
index f4e049cb6f1..c0818d4d18a 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_numeric_enum_search_context.hpp
@@ -8,7 +8,7 @@
namespace search::attribute {
template <typename T>
-SingleNumericEnumSearchContext<T>::SingleNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<T>& enum_store)
+SingleNumericEnumSearchContext<T>::SingleNumericEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<T>& enum_store)
: SingleEnumSearchContext<T, NumericSearchContext<NumericRangeMatcher<T>>>(NumericRangeMatcher<T>(*qTerm, true), toBeSearched, enum_indices, enum_store)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.h
index 5f6925f7f4d..6362c69cdac 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.h
@@ -3,6 +3,7 @@
#pragma once
#include "numeric_search_context.h"
+#include <vespa/vespalib/util/arrayref.h>
#include <vespa/vespalib/util/atomic.h>
namespace search::attribute {
@@ -16,7 +17,7 @@ class SingleNumericSearchContext final : public NumericSearchContext<M>
{
private:
using DocId = ISearchContext::DocId;
- const T* _data;
+ vespalib::ConstArrayRef<T> _data;
int32_t onFind(DocId docId, int32_t elemId, int32_t& weight) const override {
return find(docId, elemId, weight);
@@ -27,7 +28,7 @@ private:
}
public:
- SingleNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const T* data);
+ SingleNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, vespalib::ConstArrayRef<T> data);
int32_t find(DocId docId, int32_t elemId, int32_t& weight) const {
if ( elemId != 0) return -1;
const T v = vespalib::atomic::load_ref_relaxed(_data[docId]);
@@ -43,6 +44,7 @@ public:
std::unique_ptr<queryeval::SearchIterator>
createFilterIterator(fef::TermFieldMatchData* matchData, bool strict) override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.hpp b/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.hpp
index 75d3da9de7f..b40b1336e6f 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_numeric_search_context.hpp
@@ -9,7 +9,7 @@
namespace search::attribute {
template <typename T, typename M>
-SingleNumericSearchContext<T, M>::SingleNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const T* data)
+SingleNumericSearchContext<T, M>::SingleNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, vespalib::ConstArrayRef<T> data)
: NumericSearchContext<M>(toBeSearched, *qTerm, true),
_data(data)
{
@@ -32,4 +32,11 @@ SingleNumericSearchContext<T, M>::createFilterIterator(fef::TermFieldMatchData*
: std::make_unique<AttributeIteratorT<SingleNumericSearchContext<T, M>>>(*this, matchData);
}
+template <typename T, typename M>
+uint32_t
+SingleNumericSearchContext<T, M>::get_committed_docid_limit() const noexcept
+{
+ return _data.size();
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.cpp
index 5eeef7cd61a..074435809cc 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.cpp
@@ -6,13 +6,14 @@
namespace search::attribute {
-SingleSmallNumericSearchContext::SingleSmallNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const Word* word_data, Word value_mask, uint32_t value_shift_shift, uint32_t value_shift_mask, uint32_t word_shift)
+SingleSmallNumericSearchContext::SingleSmallNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const Word* word_data, Word value_mask, uint32_t value_shift_shift, uint32_t value_shift_mask, uint32_t word_shift, uint32_t docid_limit)
: NumericSearchContext<NumericRangeMatcher<T>>(toBeSearched, *qTerm, false),
_wordData(word_data),
_valueMask(value_mask),
_valueShiftShift(value_shift_shift),
_valueShiftMask(value_shift_mask),
- _wordShift(word_shift)
+ _wordShift(word_shift),
+ _docid_limit(docid_limit)
{
}
@@ -32,4 +33,10 @@ SingleSmallNumericSearchContext::createFilterIterator(fef::TermFieldMatchData* m
: std::make_unique<AttributeIteratorT<SingleSmallNumericSearchContext>>(*this, matchData);
}
+uint32_t
+SingleSmallNumericSearchContext::get_committed_docid_limit() const noexcept
+{
+ return _docid_limit;
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.h
index 46ed02b3eca..a42c8b9b29c 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_small_numeric_search_context.h
@@ -22,6 +22,7 @@ private:
uint32_t _valueShiftShift;
uint32_t _valueShiftMask;
uint32_t _wordShift;
+ uint32_t _docid_limit;
int32_t onFind(DocId docId, int32_t elementId, int32_t & weight) const override {
return find(docId, elementId, weight);
@@ -32,7 +33,7 @@ private:
}
public:
- SingleSmallNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const Word* word_data, Word value_mask, uint32_t value_shift_shift, uint32_t value_shift_mask, uint32_t word_shift);
+ SingleSmallNumericSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const AttributeVector& toBeSearched, const Word* word_data, Word value_mask, uint32_t value_shift_shift, uint32_t value_shift_mask, uint32_t word_shift, uint32_t docid_limit);
int32_t find(DocId docId, int32_t elemId, int32_t & weight) const {
if ( elemId != 0) return -1;
@@ -53,6 +54,7 @@ public:
std::unique_ptr<queryeval::SearchIterator>
createFilterIterator(fef::TermFieldMatchData* matchData, bool strict) override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.cpp
index 70023b27802..2d1748cefa5 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.cpp
@@ -5,10 +5,10 @@
namespace search::attribute {
-SingleStringEnumHintSearchContext::SingleStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values)
+SingleStringEnumHintSearchContext::SingleStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<const char*>& enum_store, uint64_t num_values)
: SingleStringEnumSearchContext(std::move(qTerm), cased, toBeSearched, enum_indices, enum_store),
EnumHintSearchContext(enum_store.get_dictionary(),
- doc_id_limit, num_values)
+ enum_indices.size(), num_values)
{
setup_enum_hint_sc(enum_store, *this);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.h
index f9d44454cd0..f157bf17a71 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_string_enum_hint_search_context.h
@@ -16,7 +16,7 @@ class SingleStringEnumHintSearchContext : public SingleStringEnumSearchContext,
public EnumHintSearchContext
{
public:
- SingleStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<const char*>& enum_store, uint32_t doc_id_limit, uint64_t num_values);
+ SingleStringEnumHintSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<const char*>& enum_store, uint64_t num_values);
~SingleStringEnumHintSearchContext() override;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.cpp b/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.cpp
index cba1d207501..8d23eaf7af0 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.cpp
@@ -6,7 +6,7 @@
namespace search::attribute {
-SingleStringEnumSearchContext::SingleStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<const char*>& enum_store)
+SingleStringEnumSearchContext::SingleStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<const char*>& enum_store)
: SingleEnumSearchContext<const char*, StringSearchContext>(StringMatcher(std::move(qTerm), cased), toBeSearched, enum_indices, enum_store)
{
}
diff --git a/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.h b/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.h
index 6a9ed38b4ea..b8014b1b0e3 100644
--- a/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.h
+++ b/searchlib/src/vespa/searchlib/attribute/single_string_enum_search_context.h
@@ -14,7 +14,7 @@ namespace search::attribute {
class SingleStringEnumSearchContext : public SingleEnumSearchContext<const char*, StringSearchContext>
{
public:
- SingleStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, const vespalib::datastore::AtomicEntryRef* enum_indices, const EnumStoreT<const char*>& enum_store);
+ SingleStringEnumSearchContext(std::unique_ptr<QueryTermSimple> qTerm, bool cased, const AttributeVector& toBeSearched, EnumIndices enum_indices, const EnumStoreT<const char*>& enum_store);
SingleStringEnumSearchContext(SingleStringEnumSearchContext&&) noexcept;
~SingleStringEnumSearchContext() override;
};
diff --git a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
index 15fc819300c..87b7049b9b7 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/singleboolattribute.cpp
@@ -132,6 +132,7 @@ public:
void fetchPostings(const queryeval::ExecuteInfo &execInfo) override;
std::unique_ptr<queryeval::SearchIterator> createPostingIterator(fef::TermFieldMatchData *matchData, bool strict) override;
unsigned int approximateHits() const override;
+ uint32_t get_committed_docid_limit() const noexcept override;
};
BitVectorSearchContext::BitVectorSearchContext(std::unique_ptr<QueryTermSimple> qTerm, const SingleBoolAttribute & attr)
@@ -177,6 +178,12 @@ BitVectorSearchContext::approximateHits() const {
: 0;
}
+uint32_t
+BitVectorSearchContext::get_committed_docid_limit() const noexcept
+{
+ return _doc_id_limit;
+}
+
}
std::unique_ptr<attribute::SearchContext>
diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
index aac9a7b5416..7f36238ec6a 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.h
@@ -67,14 +67,14 @@ protected:
void considerAttributeChange(const Change & c, EnumStoreBatchUpdater & inserter) override;
// implemented by single value numeric enum attribute.
- virtual void considerUpdateAttributeChange(const Change & c) { (void) c; }
+ virtual void considerUpdateAttributeChange(DocId, const Change&) { }
virtual void considerArithmeticAttributeChange(const Change & c, EnumStoreBatchUpdater & inserter) { (void) c; (void) inserter; }
virtual void applyValueChanges(EnumStoreBatchUpdater& updater) ;
virtual void applyArithmeticValueChange(const Change& c, EnumStoreBatchUpdater& updater) {
(void) c; (void) updater;
}
- void updateEnumRefCounts(const Change& c, EnumIndex newIdx, EnumIndex oldIdx, EnumStoreBatchUpdater& updater);
+ void updateEnumRefCounts(DocId doc, EnumIndex newIdx, EnumIndex oldIdx, EnumStoreBatchUpdater& updater);
virtual void freezeEnumDictionary() {
this->getEnumStore().freeze_dictionary();
diff --git a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
index f4f2b777abd..95976609940 100644
--- a/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singleenumattribute.hpp
@@ -146,7 +146,7 @@ SingleValueEnumAttribute<B>::considerUpdateAttributeChange(const Change & c, Enu
} else {
c.set_entry_ref(idx.ref());
}
- considerUpdateAttributeChange(c); // for numeric
+ considerUpdateAttributeChange(c._doc, c); // for numeric
}
template <typename B>
@@ -158,9 +158,7 @@ SingleValueEnumAttribute<B>::considerAttributeChange(const Change & c, EnumStore
} else if (c._type >= ChangeBase::ADD && c._type <= ChangeBase::DIV) {
considerArithmeticAttributeChange(c, inserter); // for numeric
} else if (c._type == ChangeBase::CLEARDOC) {
- Change clearDoc(this->_defaultValue);
- clearDoc._doc = c._doc;
- considerUpdateAttributeChange(clearDoc, inserter);
+ considerUpdateAttributeChange(c._doc, this->_defaultValue);
}
}
@@ -175,7 +173,7 @@ SingleValueEnumAttribute<B>::applyUpdateValueChange(const Change& c, EnumStoreBa
} else {
this->_enumStore.find_index(c._data.raw(), newIdx);
}
- updateEnumRefCounts(c, newIdx, oldIdx, updater);
+ updateEnumRefCounts(c._doc, newIdx, oldIdx, updater);
}
template <typename B>
@@ -183,30 +181,26 @@ void
SingleValueEnumAttribute<B>::applyValueChanges(EnumStoreBatchUpdater& updater)
{
ValueModifier valueGuard(this->getValueModifier());
- // This avoids searching for the defaultValue in the enum store for each CLEARDOC in the change vector.
- this->cache_change_data_entry_ref(this->_defaultValue);
for (const auto& change : this->_changes.getInsertOrder()) {
if (change._type == ChangeBase::UPDATE) {
applyUpdateValueChange(change, updater);
} else if (change._type >= ChangeBase::ADD && change._type <= ChangeBase::DIV) {
applyArithmeticValueChange(change, updater);
} else if (change._type == ChangeBase::CLEARDOC) {
- Change clearDoc(this->_defaultValue);
- clearDoc._doc = change._doc;
- applyUpdateValueChange(clearDoc, updater);
+ EnumIndex oldIdx = _enumIndices[change._doc].load_relaxed();
+ EnumIndex newIdx = this->_enumStore.get_default_value_ref().load_relaxed();
+ updateEnumRefCounts(change._doc, newIdx, oldIdx, updater);
}
}
- // We must clear the cached entry ref as the defaultValue might be located in another data buffer on later invocations.
- this->_defaultValue.clear_entry_ref();
}
template <typename B>
void
-SingleValueEnumAttribute<B>::updateEnumRefCounts(const Change& c, EnumIndex newIdx, EnumIndex oldIdx,
+SingleValueEnumAttribute<B>::updateEnumRefCounts(DocId doc, EnumIndex newIdx, EnumIndex oldIdx,
EnumStoreBatchUpdater& updater)
{
updater.inc_ref_count(newIdx);
- _enumIndices[c._doc].store_release(newIdx);
+ _enumIndices[doc].store_release(newIdx);
if (oldIdx.valid()) {
updater.dec_ref_count(oldIdx);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
index a105d980986..606c7a92ef5 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
@@ -134,8 +134,9 @@ SingleValueNumericAttribute<B>::onLoad(vespalib::Executor *)
PrimitiveReader<T> attrReader(*this);
bool ok(attrReader.getHasLoadData());
- if (!ok)
+ if (!ok) {
return false;
+ }
this->setCreateSerialNum(attrReader.getCreateSerialNum());
@@ -163,7 +164,7 @@ SingleValueNumericAttribute<B>::getSearch(QueryTermSimple::UP qTerm,
{
(void) params;
QueryTermSimple::RangeResult<T> res = qTerm->getRange<T>();
- const T* data = &_data.acquire_elem_ref(0);
+ auto data = _data.make_read_view(this->getCommittedDocIdLimit());
if (res.isEqual()) {
return std::make_unique<attribute::SingleNumericSearchContext<T, attribute::NumericMatcher<T>>>(std::move(qTerm), *this, data);
} else {
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.h b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.h
index 5b0e1c6131e..4eeb6ceda57 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.h
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.h
@@ -43,7 +43,7 @@ private:
protected:
// from SingleValueEnumAttribute
- void considerUpdateAttributeChange(const Change & c) override;
+ void considerUpdateAttributeChange(DocId doc, const Change & c) override;
void considerArithmeticAttributeChange(const Change & c, EnumStoreBatchUpdater & inserter) override;
void applyArithmeticValueChange(const Change& c, EnumStoreBatchUpdater& updater) override;
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
index 52ea0a53533..e459d3d9c9c 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
@@ -15,9 +15,9 @@ namespace search {
template <typename B>
void
-SingleValueNumericEnumAttribute<B>::considerUpdateAttributeChange(const Change & c)
+SingleValueNumericEnumAttribute<B>::considerUpdateAttributeChange(DocId doc, const Change & c)
{
- _currDocValues[c._doc] = c._data.get();
+ _currDocValues[doc] = c._data.get();
}
template <typename B>
@@ -53,7 +53,7 @@ SingleValueNumericEnumAttribute<B>::applyArithmeticValueChange(const Change& c,
T newValue = this->template applyArithmetic<T, typename Change::DataType>(get(c._doc), c._data.getArithOperand(), c._type);
this->_enumStore.find_index(newValue, newIdx);
- this->updateEnumRefCounts(c, newIdx, oldIdx, updater);
+ this->updateEnumRefCounts(c._doc, newIdx, oldIdx, updater);
}
template <typename B>
@@ -117,6 +117,10 @@ SingleValueNumericEnumAttribute<B>::onLoad(vespalib::Executor *)
return false;
}
+ this->_enumStore.clear_default_value_ref();
+ this->commit();
+ this->incGeneration();
+
this->setCreateSerialNum(attrReader.getCreateSerialNum());
if (attrReader.getEnumerated()) {
@@ -156,7 +160,8 @@ SingleValueNumericEnumAttribute<B>::getSearch(QueryTermSimple::UP qTerm,
const attribute::SearchContextParams & params) const
{
(void) params;
- return std::make_unique<attribute::SingleNumericEnumSearchContext<T>>(std::move(qTerm), *this, &this->_enumIndices.acquire_elem_ref(0), this->_enumStore);
+ auto docid_limit = this->getCommittedDocIdLimit();
+ return std::make_unique<attribute::SingleNumericEnumSearchContext<T>>(std::move(qTerm), *this, this->_enumIndices.make_read_view(docid_limit), this->_enumStore);
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
index 1775774171d..a4b9abb084a 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
@@ -13,7 +13,7 @@ template <typename B>
SingleValueNumericPostingAttribute<B>::~SingleValueNumericPostingAttribute()
{
this->disableFreeLists();
- this->disableElemHoldList();
+ this->disable_entry_hold_list();
clearAllPostings();
}
@@ -89,8 +89,6 @@ SingleValueNumericPostingAttribute<B>::applyValueChanges(EnumStoreBatchUpdater&
// used to make sure several arithmetic operations on the same document in a single commit works
std::map<DocId, EnumIndex> currEnumIndices;
- // This avoids searching for the defaultValue in the enum store for each CLEARDOC in the change vector.
- this->cache_change_data_entry_ref(this->_defaultValue);
for (const auto& change : this->_changes.getInsertOrder()) {
auto enumIter = currEnumIndices.find(change._doc);
EnumIndex oldIdx;
@@ -111,13 +109,9 @@ SingleValueNumericPostingAttribute<B>::applyValueChanges(EnumStoreBatchUpdater&
currEnumIndices[change._doc] = newIdx;
}
} else if(change._type == ChangeBase::CLEARDOC) {
- Change clearDoc(this->_defaultValue);
- clearDoc._doc = change._doc;
- applyUpdateValueChange(clearDoc, enumStore, currEnumIndices);
+ currEnumIndices[change._doc] = enumStore.get_default_value_ref().load_relaxed();
}
}
- // We must clear the cached entry ref as the defaultValue might be located in another data buffer on later invocations.
- this->_defaultValue.clear_entry_ref();
makePostingChange(enumStore.get_comparator(), currEnumIndices, changePost);
@@ -149,7 +143,8 @@ SingleValueNumericPostingAttribute<B>::getSearch(QueryTermSimple::UP qTerm,
{
using BaseSC = attribute::SingleNumericEnumSearchContext<T>;
using SC = attribute::NumericPostingSearchContext<BaseSC, SelfType, vespalib::btree::BTreeNoLeafData>;
- BaseSC base_sc(std::move(qTerm), *this, &this->_enumIndices.acquire_elem_ref(0), this->_enumStore);
+ auto docid_limit = this->getCommittedDocIdLimit();
+ BaseSC base_sc(std::move(qTerm), *this, this->_enumIndices.make_read_view(docid_limit), this->_enumStore);
return std::make_unique<SC>(std::move(base_sc), params, *this);
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
index 13bf2f932e8..3c1621ac244 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp
@@ -170,7 +170,8 @@ std::unique_ptr<attribute::SearchContext>
SingleValueSmallNumericAttribute::getSearch(std::unique_ptr<QueryTermSimple> qTerm,
const attribute::SearchContextParams &) const
{
- return std::make_unique<attribute::SingleSmallNumericSearchContext>(std::move(qTerm), *this, &_wordData.acquire_elem_ref(0), _valueMask, _valueShiftShift, _valueShiftMask, _wordShift);
+ auto docid_limit = getCommittedDocIdLimit();
+ return std::make_unique<attribute::SingleSmallNumericSearchContext>(std::move(qTerm), *this, &_wordData.acquire_elem_ref(0), _valueMask, _valueShiftShift, _valueShiftMask, _wordShift, docid_limit);
}
void
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp
index 82a4393fc91..c3f5c295260 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp
@@ -40,14 +40,14 @@ SingleValueStringAttributeT<B>::freezeEnumDictionary()
this->getEnumStore().freeze_dictionary();
}
-
template <typename B>
std::unique_ptr<attribute::SearchContext>
SingleValueStringAttributeT<B>::getSearch(QueryTermSimpleUP qTerm,
const attribute::SearchContextParams &) const
{
bool cased = this->get_match_is_cased();
- return std::make_unique<attribute::SingleStringEnumHintSearchContext>(std::move(qTerm), cased, *this, &this->_enumIndices.acquire_elem_ref(0), this->_enumStore, this->getCommittedDocIdLimit(), this->getStatus().getNumValues());
+ auto docid_limit = this->getCommittedDocIdLimit();
+ return std::make_unique<attribute::SingleStringEnumHintSearchContext>(std::move(qTerm), cased, *this, this->_enumIndices.make_read_view(docid_limit), this->_enumStore, this->getStatus().getNumValues());
}
}
diff --git a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
index eef72984e79..60847636baa 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
@@ -27,7 +27,7 @@ template <typename B>
SingleValueStringPostingAttributeT<B>::~SingleValueStringPostingAttributeT()
{
this->disableFreeLists();
- this->disableElemHoldList();
+ this->disable_entry_hold_list();
clearAllPostings();
}
@@ -98,8 +98,6 @@ SingleValueStringPostingAttributeT<B>::applyValueChanges(EnumStoreBatchUpdater&
// used to make sure several arithmetic operations on the same document in a single commit works
std::map<DocId, EnumIndex> currEnumIndices;
- // This avoids searching for the defaultValue in the enum store for each CLEARDOC in the change vector.
- this->cache_change_data_entry_ref(this->_defaultValue);
for (const auto& change : this->_changes.getInsertOrder()) {
auto enumIter = currEnumIndices.find(change._doc);
EnumIndex oldIdx;
@@ -111,12 +109,9 @@ SingleValueStringPostingAttributeT<B>::applyValueChanges(EnumStoreBatchUpdater&
if (change._type == ChangeBase::UPDATE) {
applyUpdateValueChange(change, enumStore, currEnumIndices);
} else if (change._type == ChangeBase::CLEARDOC) {
- this->_defaultValue._doc = change._doc;
- applyUpdateValueChange(this->_defaultValue, enumStore, currEnumIndices);
+ currEnumIndices[change._doc] = enumStore.get_default_value_ref().load_relaxed();
}
}
- // We must clear the cached entry ref as the defaultValue might be located in another data buffer on later invocations.
- this->_defaultValue.clear_entry_ref();
makePostingChange(enumStore.get_folded_comparator(), dictionary, currEnumIndices, changePost);
@@ -150,7 +145,8 @@ SingleValueStringPostingAttributeT<B>::getSearch(QueryTermSimpleUP qTerm,
using BaseSC = attribute::SingleStringEnumSearchContext;
using SC = attribute::StringPostingSearchContext<BaseSC, SelfType, vespalib::btree::BTreeNoLeafData>;
bool cased = this->get_match_is_cased();
- BaseSC base_sc(std::move(qTerm), cased, *this, &this->_enumIndices.acquire_elem_ref(0), this->_enumStore);
+ auto docid_limit = this->getCommittedDocIdLimit();
+ BaseSC base_sc(std::move(qTerm), cased, *this, this->_enumIndices.make_read_view(docid_limit), this->_enumStore);
return std::make_unique<SC>(std::move(base_sc),
params.useBitVector(),
*this);
diff --git a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
index 80967affaa7..b37318d470e 100644
--- a/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
+++ b/searchlib/src/vespa/searchlib/attribute/stringbase.cpp
@@ -223,6 +223,10 @@ StringAttribute::onLoad(vespalib::Executor *)
return false;
}
+ getEnumStoreBase()->clear_default_value_ref();
+ commit();
+ incGeneration();
+
setCreateSerialNum(attrReader.getCreateSerialNum());
assert(attrReader.getEnumerated());
diff --git a/searchlib/src/vespa/searchlib/attribute/stringbase.h b/searchlib/src/vespa/searchlib/attribute/stringbase.h
index ed8777d6bab..98a3316947b 100644
--- a/searchlib/src/vespa/searchlib/attribute/stringbase.h
+++ b/searchlib/src/vespa/searchlib/attribute/stringbase.h
@@ -53,16 +53,16 @@ public:
largeint_t getInt(DocId doc) const override { return strtoll(get(doc), nullptr, 0); }
double getFloat(DocId doc) const override;
vespalib::ConstArrayRef<char> get_raw(DocId) const override;
+ static const char * defaultValue() { return ""; }
protected:
StringAttribute(const vespalib::string & name);
StringAttribute(const vespalib::string & name, const Config & c);
~StringAttribute() override;
- static const char * defaultValue() { return ""; }
using Change = ChangeTemplate<StringChangeData>;
using ChangeVector = ChangeVectorT<Change>;
using EnumEntryType = const char*;
ChangeVector _changes;
- Change _defaultValue;
+ const Change _defaultValue;
bool onLoad(vespalib::Executor *executor) override;
bool onLoadEnumerated(ReaderBase &attrReader);
diff --git a/searchlib/src/vespa/searchlib/common/CMakeLists.txt b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
index a7c8d56f11d..089151455f3 100644
--- a/searchlib/src/vespa/searchlib/common/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/common/CMakeLists.txt
@@ -9,7 +9,6 @@ vespa_add_library(searchlib_common OBJECT
condensedbitvectors.cpp
documentlocations.cpp
documentsummary.cpp
- featureset.cpp
fileheadercontext.cpp
flush_token.cpp
geo_gcd.cpp
diff --git a/searchlib/src/vespa/searchlib/engine/searchreply.h b/searchlib/src/vespa/searchlib/engine/searchreply.h
index 8f862d8dcf7..6b0edca3086 100644
--- a/searchlib/src/vespa/searchlib/engine/searchreply.h
+++ b/searchlib/src/vespa/searchlib/engine/searchreply.h
@@ -6,8 +6,8 @@
#include <vespa/document/base/globalid.h>
#include <vespa/searchlib/common/hitrank.h>
#include <vespa/searchlib/common/unique_issues.h>
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/vespalib/util/array.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vector>
namespace search::engine {
@@ -15,6 +15,7 @@ namespace search::engine {
class SearchReply
{
public:
+ using FeatureValues = vespalib::FeatureValues;
using UP = std::unique_ptr<SearchReply>;
class Hit
diff --git a/searchlib/src/vespa/searchlib/expression/attribute_map_lookup_node.cpp b/searchlib/src/vespa/searchlib/expression/attribute_map_lookup_node.cpp
index b3cc126efdb..52ee467fac8 100644
--- a/searchlib/src/vespa/searchlib/expression/attribute_map_lookup_node.cpp
+++ b/searchlib/src/vespa/searchlib/expression/attribute_map_lookup_node.cpp
@@ -1,11 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "attribute_map_lookup_node.h"
-#include <vespa/vespalib/stllike/asciistream.h>
-#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/searchlib/attribute/stringbase.h>
#include <vespa/searchcommon/attribute/attributecontent.h>
#include <vespa/searchcommon/attribute/iattributecontext.h>
-#include <vespa/searchcommon/common/undefinedvalues.h>
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vespalib/util/exceptions.h>
using search::attribute::AttributeContent;
using search::attribute::IAttributeVector;
@@ -36,8 +36,6 @@ public:
namespace {
-vespalib::string indirectKeyMarker("attribute(");
-
class BadKeyHandler : public AttributeMapLookupNode::KeyHandler
{
public:
@@ -220,7 +218,6 @@ IAttributeVector::largeint_t getUndefinedValue(BasicType::Type basicType)
return getUndefined<int32_t>();
case BasicType::INT64:
return getUndefined<int64_t>();
- break;
default:
return 0;
}
@@ -330,7 +327,6 @@ AttributeMapLookupNode::onPrepare(bool preserveAccurateTypes)
break;
default:
throw std::runtime_error("This is no valid integer attribute " + attribute->getName());
- break;
}
} else {
prepareIntValues<Int64ResultNode>(std::move(keyHandler), *attribute, undefinedValue);
@@ -342,7 +338,11 @@ AttributeMapLookupNode::onPrepare(bool preserveAccurateTypes)
} else if (attribute->isStringType()) {
if (_useEnumOptimization) {
auto resultNode = std::make_unique<EnumResultNode>();
- _handler = std::make_unique<EnumValueHandler>(std::move(keyHandler), *attribute, *resultNode, EnumHandle());
+ const StringAttribute & sattr = dynamic_cast<const StringAttribute &>(*attribute);
+ EnumHandle undefined(0);
+ bool found = attribute->findEnum(sattr.defaultValue(), undefined);
+ assert(found);
+ _handler = std::make_unique<EnumValueHandler>(std::move(keyHandler), *attribute, *resultNode, undefined);
setResultType(std::move(resultNode));
} else {
auto resultNode = std::make_unique<StringResultNode>();
diff --git a/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp b/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
index 72f4a7ae579..4bc7f5b1144 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
+++ b/searchlib/src/vespa/searchlib/memoryindex/feature_store.cpp
@@ -36,7 +36,7 @@ EntryRef
FeatureStore::addFeatures(const uint8_t *src, uint64_t byteLen)
{
uint32_t pad = Aligner::pad(byteLen);
- auto result = _store.rawAllocator<uint8_t>(_typeId).alloc(byteLen + pad, DECODE_SAFETY);
+ auto result = _store.rawAllocator<uint8_t>(_typeId).alloc((byteLen + pad) / buffer_array_size, DECODE_SAFETY_ENTRIES);
uint8_t *dst = result.data;
memcpy(dst, src, byteLen);
dst += byteLen;
@@ -113,7 +113,7 @@ FeatureStore::add_features_guard_bytes()
{
uint32_t len = DECODE_SAFETY;
uint32_t pad = Aligner::pad(len);
- auto result = _store.rawAllocator<uint8_t>(_typeId).alloc(len + pad);
+ auto result = _store.rawAllocator<uint8_t>(_typeId).alloc((len + pad) / buffer_array_size);
memset(result.data, 0, len + pad);
}
diff --git a/searchlib/src/vespa/searchlib/memoryindex/feature_store.h b/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
index 53588fa2894..1e48189987e 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
+++ b/searchlib/src/vespa/searchlib/memoryindex/feature_store.h
@@ -29,6 +29,7 @@ private:
using PosOccFieldsParams = bitcompression::PosOccFieldsParams;
static constexpr uint32_t DECODE_SAFETY = 16;
+ static constexpr uint32_t DECODE_SAFETY_ENTRIES = 16 / buffer_array_size;
DataStoreType _store;
@@ -117,7 +118,7 @@ public:
* overrun beyond the compressed data either goes into other features
* already written or into the guard area.
*
- * If buffer type is changed to have a nonzero numArraysForNewBuffer then
+ * If buffer type is changed to have a nonzero num_entries_for_new_buffer then
* extra logic to add guard bytes is needed when switching primary buffer
* to avoid issues if the buffer is resumed as primary buffer later on.
*/
diff --git a/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp b/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
index 4be3031303e..8dd76a90b14 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
+++ b/searchlib/src/vespa/searchlib/memoryindex/field_index.cpp
@@ -55,9 +55,9 @@ template <bool interleaved_features>
FieldIndex<interleaved_features>::~FieldIndex()
{
_postingListStore.disableFreeLists();
- _postingListStore.disableElemHoldList();
+ _postingListStore.disable_entry_hold_list();
_dict.disableFreeLists();
- _dict.disableElemHoldList();
+ _dict.disable_entry_hold_list();
// XXX: Kludge
for (DictionaryTree::Iterator it = _dict.begin();
it.valid(); ++it) {
diff --git a/searchlib/src/vespa/searchlib/memoryindex/word_store.cpp b/searchlib/src/vespa/searchlib/memoryindex/word_store.cpp
index 3e4c38ceb0e..e330dc83055 100644
--- a/searchlib/src/vespa/searchlib/memoryindex/word_store.cpp
+++ b/searchlib/src/vespa/searchlib/memoryindex/word_store.cpp
@@ -27,7 +27,7 @@ WordStore::addWord(const vespalib::stringref word)
{
size_t wordSize = word.size() + 1;
size_t bufferSize = wordSize + Aligner::pad(wordSize);
- auto result = _store.rawAllocator<char>(_typeId).alloc(bufferSize);
+ auto result = _store.rawAllocator<char>(_typeId).alloc(bufferSize / buffer_array_size);
char *be = result.data;
for (size_t i = 0; i < word.size(); ++i) {
*be++ = word[i];
diff --git a/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.h b/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.h
index 46b89fdfeb4..9bef389a278 100644
--- a/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.h
+++ b/searchlib/src/vespa/searchlib/parsequery/stackdumpiterator.h
@@ -109,7 +109,7 @@ public:
uint32_t getArity() const { return _currArity; }
uint32_t getNearDistance() const { return _extraIntArg1; }
- uint32_t getTargetNumHits() const { return _extraIntArg1; }
+ uint32_t getTargetHits() const { return _extraIntArg1; }
double getDistanceThreshold() const { return _extraDoubleArg4; }
double getScoreThreshold() const { return _extraDoubleArg4; }
double getThresholdBoostFactor() const { return _extraDoubleArg5; }
diff --git a/searchlib/src/vespa/searchlib/predicate/document_features_store.cpp b/searchlib/src/vespa/searchlib/predicate/document_features_store.cpp
index 604a467a6e6..a6a82ec09f8 100644
--- a/searchlib/src/vespa/searchlib/predicate/document_features_store.cpp
+++ b/searchlib/src/vespa/searchlib/predicate/document_features_store.cpp
@@ -102,7 +102,7 @@ DocumentFeaturesStore::DocumentFeaturesStore(DataBuffer &buffer)
DocumentFeaturesStore::~DocumentFeaturesStore() {
_word_index.disableFreeLists();
- _word_index.disableElemHoldList();
+ _word_index.disable_entry_hold_list();
_word_index.getAllocator().freeze();
_word_index.clear();
}
diff --git a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
index af809b2fa69..af5aae6e519 100644
--- a/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
+++ b/searchlib/src/vespa/searchlib/predicate/predicate_interval_store.cpp
@@ -95,7 +95,7 @@ PredicateIntervalStore::remove(EntryRef ref) {
// BufferState &state = _store.getBufferState(buffer_id);
// uint32_t type_id = state.getTypeId();
// uint32_t size = type_id <= MAX_ARRAY_SIZE ? type_id : 1;
- // _store.holdElem(ref, size);
+ // _store.hold_entries(ref, size);
}
}
diff --git a/searchlib/src/vespa/searchlib/predicate/simple_index.hpp b/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
index c6f640d72ed..9320488f88e 100644
--- a/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
+++ b/searchlib/src/vespa/searchlib/predicate/simple_index.hpp
@@ -41,7 +41,7 @@ SimpleIndex<Posting, Key, DocId>::insertIntoVectorPosting(vespalib::datastore::E
template <typename Posting, typename Key, typename DocId>
SimpleIndex<Posting, Key, DocId>::~SimpleIndex() {
_btree_posting_lists.disableFreeLists();
- _btree_posting_lists.disableElemHoldList();
+ _btree_posting_lists.disable_entry_hold_list();
for (auto it = _dictionary.begin(); it.valid(); ++it) {
vespalib::datastore::EntryRef ref(it.getData());
@@ -51,13 +51,13 @@ SimpleIndex<Posting, Key, DocId>::~SimpleIndex() {
}
_vector_posting_lists.disableFreeLists();
- _vector_posting_lists.disableElemHoldList();
+ _vector_posting_lists.disable_entry_hold_list();
_vector_posting_lists.clear();
_vector_posting_lists.getAllocator().freeze();
_vector_posting_lists.getAllocator().reclaim_all_memory();
_dictionary.disableFreeLists();
- _dictionary.disableElemHoldList();
+ _dictionary.disable_entry_hold_list();
_dictionary.clear();
_dictionary.getAllocator().freeze();
_dictionary.getAllocator().reclaim_all_memory();
diff --git a/searchlib/src/vespa/searchlib/query/query_term_simple.h b/searchlib/src/vespa/searchlib/query/query_term_simple.h
index 74728ab1f2e..a79e33dba32 100644
--- a/searchlib/src/vespa/searchlib/query/query_term_simple.h
+++ b/searchlib/src/vespa/searchlib/query/query_term_simple.h
@@ -23,7 +23,8 @@ public:
SUFFIXTERM = 4,
REGEXP = 5,
GEO_LOCATION = 6,
- FUZZYTERM = 7
+ FUZZYTERM = 7,
+ NEAREST_NEIGHBOR = 8
};
template <typename N>
@@ -65,6 +66,7 @@ public:
bool isRegex() const { return (_type == Type::REGEXP); }
bool isGeoLoc() const { return (_type == Type::GEO_LOCATION); }
bool isFuzzy() const { return (_type == Type::FUZZYTERM); }
+ bool is_nearest_neighbor() const noexcept { return (_type == Type::NEAREST_NEIGHBOR); }
bool empty() const { return _term.empty(); }
virtual void visitMembers(vespalib::ObjectVisitor &visitor) const;
vespalib::string getClassName() const;
diff --git a/searchlib/src/vespa/searchlib/query/streaming/CMakeLists.txt b/searchlib/src/vespa/searchlib/query/streaming/CMakeLists.txt
index 27f9870dc18..c71b838fb37 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/query/streaming/CMakeLists.txt
@@ -1,6 +1,7 @@
# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
vespa_add_library(searchlib_query_streaming OBJECT
SOURCES
+ nearest_neighbor_query_node.cpp
query.cpp
querynode.cpp
querynoderesultbase.cpp
diff --git a/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp
new file mode 100644
index 00000000000..b2d8a0ee4be
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp
@@ -0,0 +1,52 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "nearest_neighbor_query_node.h"
+#include <cassert>
+
+namespace search::streaming {
+
+NearestNeighborQueryNode::NearestNeighborQueryNode(std::unique_ptr<QueryNodeResultBase> resultBase,
+ const string& query_tensor_name, const string& field_name,
+ uint32_t target_hits, double distance_threshold,
+ int32_t unique_id, search::query::Weight weight)
+ : QueryTerm(std::move(resultBase), query_tensor_name, field_name, Type::NEAREST_NEIGHBOR),
+ _target_hits(target_hits),
+ _distance_threshold(distance_threshold),
+ _distance(),
+ _calc()
+{
+ setUniqueId(unique_id);
+ setWeight(weight);
+}
+
+NearestNeighborQueryNode::~NearestNeighborQueryNode() = default;
+
+bool
+NearestNeighborQueryNode::evaluate() const
+{
+ return _distance.has_value();
+}
+
+void
+NearestNeighborQueryNode::reset()
+{
+ _distance.reset();
+}
+
+NearestNeighborQueryNode*
+NearestNeighborQueryNode::as_nearest_neighbor_query_node() noexcept
+{
+ return this;
+}
+
+std::optional<double>
+NearestNeighborQueryNode::get_raw_score() const
+{
+ if (_distance.has_value()) {
+ assert(_calc != nullptr);
+ return _calc->to_raw_score(_distance.value());
+ }
+ return std::nullopt;
+}
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h
new file mode 100644
index 00000000000..c66364b0c52
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "queryterm.h"
+#include <optional>
+
+namespace search::streaming {
+
+/*
+ * Nearest neighbor query node for streaming search.
+ */
+class NearestNeighborQueryNode: public QueryTerm {
+public:
+ class RawScoreCalculator {
+ public:
+ virtual ~RawScoreCalculator() = default;
+ /**
+ * Convert the given distance to a raw score.
+ *
+ * This is used during unpacking, and also signals that the entire document was a match.
+ */
+ virtual double to_raw_score(double distance) = 0;
+ };
+
+private:
+ uint32_t _target_hits;
+ double _distance_threshold;
+ // When this value is set it also indicates a match for this query node.
+ std::optional<double> _distance;
+ RawScoreCalculator* _calc;
+
+
+public:
+ NearestNeighborQueryNode(std::unique_ptr<QueryNodeResultBase> resultBase,
+ const string& query_tensor_name, const string& field_name,
+ uint32_t target_hits, double distance_threshold,
+ int32_t unique_id, search::query::Weight weight);
+ NearestNeighborQueryNode(const NearestNeighborQueryNode &) = delete;
+ NearestNeighborQueryNode & operator = (const NearestNeighborQueryNode &) = delete;
+ NearestNeighborQueryNode(NearestNeighborQueryNode &&) = delete;
+ NearestNeighborQueryNode & operator = (NearestNeighborQueryNode &&) = delete;
+ ~NearestNeighborQueryNode() override;
+ bool evaluate() const override;
+ void reset() override;
+ NearestNeighborQueryNode* as_nearest_neighbor_query_node() noexcept override;
+ const vespalib::string& get_query_tensor_name() const { return getTermString(); }
+ uint32_t get_target_hits() const { return _target_hits; }
+ double get_distance_threshold() const { return _distance_threshold; }
+ void set_raw_score_calc(RawScoreCalculator* calc_in) { _calc = calc_in; }
+ void set_distance(double value) { _distance = value; }
+ const std::optional<double>& get_distance() const { return _distance; }
+ // This is used during unpacking, and also signals to the RawScoreCalculator that the entire document was a match.
+ std::optional<double> get_raw_score() const;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp b/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
index 6d59886a4f5..84344831cbc 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
+++ b/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "query.h"
+#include "nearest_neighbor_query_node.h"
#include <vespa/searchlib/parsequery/stackdumpiterator.h>
#include <charconv>
#include <vespa/log/log.h>
@@ -77,6 +78,9 @@ QueryNode::Build(const QueryNode * parent, const QueryNodeResultFactory & factor
queryRep.getIndexName(),
QueryTerm::Type::GEO_LOCATION);
break;
+ case ParseItem::ITEM_NEAREST_NEIGHBOR:
+ qn = build_nearest_neighbor_query_node(factory, queryRep);
+ break;
case ParseItem::ITEM_NUMTERM:
case ParseItem::ITEM_TERM:
case ParseItem::ITEM_PREFIXTERM:
@@ -191,4 +195,22 @@ const HitList & QueryNode::evaluateHits(HitList & hl) const
return hl;
}
+std::unique_ptr<QueryNode>
+QueryNode::build_nearest_neighbor_query_node(const QueryNodeResultFactory& factory, SimpleQueryStackDumpIterator& query_rep)
+{
+ vespalib::stringref query_tensor_name = query_rep.getTerm();
+ vespalib::stringref field_name = query_rep.getIndexName();
+ int32_t unique_id = query_rep.getUniqueId();
+ auto weight = query_rep.GetWeight();
+ uint32_t target_hits = query_rep.getTargetHits();
+ double distance_threshold = query_rep.getDistanceThreshold();
+ return std::make_unique<NearestNeighborQueryNode>(factory.create(),
+ query_tensor_name,
+ field_name,
+ target_hits,
+ distance_threshold,
+ unique_id,
+ weight);
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/query/streaming/querynode.h b/searchlib/src/vespa/searchlib/query/streaming/querynode.h
index 574a3c16ca3..c3fa2b63f69 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/querynode.h
+++ b/searchlib/src/vespa/searchlib/query/streaming/querynode.h
@@ -28,6 +28,7 @@ using ConstQueryTermList = std::vector<const QueryTerm *>;
*/
class QueryNode
{
+ static std::unique_ptr<QueryNode> build_nearest_neighbor_query_node(const QueryNodeResultFactory& factory, SimpleQueryStackDumpIterator& queryRep);
public:
using UP = std::unique_ptr<QueryNode>;
@@ -54,7 +55,7 @@ class QueryNode
virtual size_t depth() const { return 1; }
/// Return the width of this tree.
virtual size_t width() const { return 1; }
- static UP Build(const QueryNode * parent, const QueryNodeResultFactory & org, SimpleQueryStackDumpIterator & queryRep, bool allowRewrite);
+ static UP Build(const QueryNode * parent, const QueryNodeResultFactory& factory, SimpleQueryStackDumpIterator & queryRep, bool allowRewrite);
};
/// A list conating the QuerNode objects. With copy/assignment.
diff --git a/searchlib/src/vespa/searchlib/query/streaming/queryterm.cpp b/searchlib/src/vespa/searchlib/query/streaming/queryterm.cpp
index 83f4410a520..11557bf1dcc 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/queryterm.cpp
+++ b/searchlib/src/vespa/searchlib/query/streaming/queryterm.cpp
@@ -92,4 +92,10 @@ void QueryTerm::add(unsigned pos, unsigned context, uint32_t elemId, int32_t wei
_hitList.emplace_back(pos, context, elemId, weight_);
}
+NearestNeighborQueryNode*
+QueryTerm::as_nearest_neighbor_query_node() noexcept
+{
+ return nullptr;
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/query/streaming/queryterm.h b/searchlib/src/vespa/searchlib/query/streaming/queryterm.h
index dd9f56b11e1..51987225692 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/queryterm.h
+++ b/searchlib/src/vespa/searchlib/query/streaming/queryterm.h
@@ -12,6 +12,8 @@
namespace search::streaming {
+class NearestNeighborQueryNode;
+
/**
This is a leaf in the Query tree. All terms are leafs.
A QueryTerm has the index for where to find the term. The term is a string,
@@ -57,7 +59,7 @@ public:
QueryTerm & operator = (const QueryTerm &) = delete;
QueryTerm(QueryTerm &&) = delete;
QueryTerm & operator = (QueryTerm &&) = delete;
- ~QueryTerm();
+ ~QueryTerm() override;
bool evaluate() const override;
const HitList & evaluateHits(HitList & hl) const override;
void reset() override;
@@ -87,6 +89,7 @@ public:
const string & getIndex() const override { return _index; }
void setFuzzyMaxEditDistance(uint32_t fuzzyMaxEditDistance) { _fuzzyMaxEditDistance = fuzzyMaxEditDistance; }
void setFuzzyPrefixLength(uint32_t fuzzyPrefixLength) { _fuzzyPrefixLength = fuzzyPrefixLength; }
+ virtual NearestNeighborQueryNode* as_nearest_neighbor_query_node() noexcept;
protected:
using QueryNodeResultBaseContainer = std::unique_ptr<QueryNodeResultBase>;
string _index;
diff --git a/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h b/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
index 90bd87979c7..a552a650704 100644
--- a/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
+++ b/searchlib/src/vespa/searchlib/query/tree/stackdumpquerycreator.h
@@ -89,7 +89,7 @@ private:
pureTermView = view;
} else if (type == ParseItem::ITEM_WEAK_AND) {
vespalib::stringref view = queryStack.getIndexName();
- uint32_t targetNumHits = queryStack.getTargetNumHits();
+ uint32_t targetNumHits = queryStack.getTargetHits();
builder.addWeakAnd(arity, targetNumHits, view);
pureTermView = view;
} else if (type == ParseItem::ITEM_EQUIV) {
@@ -134,7 +134,7 @@ private:
vespalib::stringref view = queryStack.getIndexName();
int32_t id = queryStack.getUniqueId();
Weight weight = queryStack.GetWeight();
- uint32_t targetNumHits = queryStack.getTargetNumHits();
+ uint32_t targetNumHits = queryStack.getTargetHits();
double scoreThreshold = queryStack.getScoreThreshold();
double thresholdBoostFactor = queryStack.getThresholdBoostFactor();
auto & wand = builder.addWandTerm(arity, view, id, weight, targetNumHits, scoreThreshold, thresholdBoostFactor);
@@ -146,7 +146,7 @@ private:
} else if (type == ParseItem::ITEM_NEAREST_NEIGHBOR) {
vespalib::stringref query_tensor_name = queryStack.getTerm();
vespalib::stringref field_name = queryStack.getIndexName();
- uint32_t target_num_hits = queryStack.getTargetNumHits();
+ uint32_t target_num_hits = queryStack.getTargetHits();
int32_t id = queryStack.getUniqueId();
Weight weight = queryStack.GetWeight();
bool allow_approximate = queryStack.getAllowApproximate();
diff --git a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp
index c50c6ec49f5..86f520c8711 100644
--- a/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/leaf_blueprints.cpp
@@ -129,8 +129,16 @@ struct FakeContext : attribute::ISearchContext {
DoubleRange getAsDoubleTerm() const override { abort(); }
const QueryTermUCS4 * queryTerm() const override { abort(); }
const vespalib::string &attributeName() const override { return name; }
+ uint32_t get_committed_docid_limit() const noexcept override;
};
+uint32_t
+FakeContext::get_committed_docid_limit() const noexcept
+{
+ auto& documents = result.inspect();
+ return documents.empty() ? 0 : (documents.back().docId + 1);
+}
+
}
SearchIterator::UP
diff --git a/searchlib/src/vespa/searchlib/queryeval/nearest_neighbor_blueprint.cpp b/searchlib/src/vespa/searchlib/queryeval/nearest_neighbor_blueprint.cpp
index 7fdf5230325..7c307a1e35f 100644
--- a/searchlib/src/vespa/searchlib/queryeval/nearest_neighbor_blueprint.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/nearest_neighbor_blueprint.cpp
@@ -106,13 +106,13 @@ NearestNeighborBlueprint::set_global_filter(const GlobalFilter &global_filter, d
void
NearestNeighborBlueprint::perform_top_k(const search::tensor::NearestNeighborIndex* nns_index)
{
- auto lhs = _query_tensor.cells();
uint32_t k = _adjusted_target_hits;
+ const auto &df = _distance_calc->function();
if (_global_filter->is_active()) {
- _found_hits = nns_index->find_top_k_with_filter(k, lhs, *_global_filter, k + _explore_additional_hits, _distance_threshold);
+ _found_hits = nns_index->find_top_k_with_filter(k, df, *_global_filter, k + _explore_additional_hits, _distance_threshold);
_algorithm = Algorithm::INDEX_TOP_K_WITH_FILTER;
} else {
- _found_hits = nns_index->find_top_k(k, lhs, k + _explore_additional_hits, _distance_threshold);
+ _found_hits = nns_index->find_top_k(k, df, k + _explore_additional_hits, _distance_threshold);
_algorithm = Algorithm::INDEX_TOP_K;
}
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.cpp b/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.cpp
index 95264a79431..5ec4357ca24 100644
--- a/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.cpp
@@ -18,13 +18,13 @@ class NeighborVectorIterator : public NnsIndexIterator
private:
fef::TermFieldMatchData &_tfmd;
const std::vector<Neighbor> &_hits;
- const search::tensor::DistanceFunction &_dist_fun;
+ const search::tensor::BoundDistanceFunction &_dist_fun;
uint32_t _idx;
double _last_abstract_dist;
public:
NeighborVectorIterator(fef::TermFieldMatchData &tfmd,
const std::vector<Neighbor> &hits,
- const search::tensor::DistanceFunction &dist_fun)
+ const search::tensor::BoundDistanceFunction &dist_fun)
: _tfmd(tfmd),
_hits(hits),
_dist_fun(dist_fun),
@@ -65,7 +65,7 @@ std::unique_ptr<NnsIndexIterator>
NnsIndexIterator::create(
fef::TermFieldMatchData &tfmd,
const std::vector<Neighbor> &hits,
- const search::tensor::DistanceFunction &dist_fun)
+ const search::tensor::BoundDistanceFunction &dist_fun)
{
return std::make_unique<NeighborVectorIterator>(tfmd, hits, dist_fun);
}
diff --git a/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.h b/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.h
index 031a603de49..84ff0f04813 100644
--- a/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.h
+++ b/searchlib/src/vespa/searchlib/queryeval/nns_index_iterator.h
@@ -16,7 +16,7 @@ public:
static std::unique_ptr<NnsIndexIterator> create(
fef::TermFieldMatchData &tfmd,
const std::vector<Hit> &hits,
- const search::tensor::DistanceFunction &dist_fun);
+ const search::tensor::BoundDistanceFunction &dist_fun);
};
} // namespace
diff --git a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_search.cpp b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_search.cpp
index 292605127fb..1a7e91b2d1a 100644
--- a/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_search.cpp
+++ b/searchlib/src/vespa/searchlib/queryeval/wand/parallel_weak_and_search.cpp
@@ -136,27 +136,25 @@ createWand(const wand::Terms &terms,
using WandType = ParallelWeakAndSearchImpl<VectorizedIteratorTerms, FutureHeap, PastHeap, IS_STRICT>;
if (should_monitor_wand()) {
wand::Terms termsWithMonitoring = insertMonitoringSearchIterator(terms);
- MonitoringSearchIterator::UP monitoringIterator =
- MonitoringSearchIterator::UP(new MonitoringSearchIterator
- (make_string("PWAND(%u,%" PRId64 "),strict=%u",
- matchParams.scores.getScoresToTrack(),
- matchParams.scoreThreshold,
- IS_STRICT),
- SearchIterator::UP(new WandType(rankParams.rootMatchData,
- VectorizedIteratorTerms(termsWithMonitoring,
- DotProductScorer(),
- matchParams.docIdLimit,
- std::move(rankParams.childrenMatchData)),
- matchParams)),
- false));
+ auto monitoringIterator = std::make_unique<MonitoringSearchIterator>(
+ make_string("PWAND(%u,%" PRId64 "),strict=%u",
+ matchParams.scores.getScoresToTrack(),
+ matchParams.scoreThreshold, IS_STRICT),
+ std::make_unique<WandType>(rankParams.rootMatchData,
+ VectorizedIteratorTerms(termsWithMonitoring,
+ DotProductScorer(),
+ matchParams.docIdLimit,
+ std::move(rankParams.childrenMatchData)),
+ matchParams),
+ false);
return std::make_unique<MonitoringDumpIterator>(std::move(monitoringIterator));
}
return std::make_unique<WandType>(rankParams.rootMatchData,
- VectorizedIteratorTerms(terms,
- DotProductScorer(),
- matchParams.docIdLimit,
- std::move(rankParams.childrenMatchData)),
- matchParams);
+ VectorizedIteratorTerms(terms,
+ DotProductScorer(),
+ matchParams.docIdLimit,
+ std::move(rankParams.childrenMatchData)),
+ matchParams);
}
} // namespace search::queryeval::wand::<unnamed>
diff --git a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
index 313863d8dcb..2e874ffa4ae 100644
--- a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
@@ -3,6 +3,7 @@ vespa_add_library(searchlib_tensor OBJECT
SOURCES
angular_distance.cpp
bitvector_visited_tracker.cpp
+ bound_distance_function.cpp
default_nearest_neighbor_index_factory.cpp
dense_tensor_attribute.cpp
dense_tensor_store.cpp
@@ -29,10 +30,12 @@ vespa_add_library(searchlib_tensor OBJECT
large_subspaces_buffer_type.cpp
nearest_neighbor_index.cpp
nearest_neighbor_index_saver.cpp
+ prenormalized_angular_distance.cpp
serialized_fast_value_attribute.cpp
serialized_tensor_ref.cpp
small_subspaces_buffer_type.cpp
subspace_type.cpp
+ temporary_vector_store.cpp
tensor_attribute.cpp
tensor_attribute_loader.cpp
tensor_attribute_saver.cpp
diff --git a/searchlib/src/vespa/searchlib/tensor/angular_distance.cpp b/searchlib/src/vespa/searchlib/tensor/angular_distance.cpp
index 263bf91877a..a7ae02bb9f4 100644
--- a/searchlib/src/vespa/searchlib/tensor/angular_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/angular_distance.cpp
@@ -1,9 +1,11 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "angular_distance.h"
+#include "temporary_vector_store.h"
using vespalib::typify_invoke;
using vespalib::eval::TypifyCellType;
+using vespalib::eval::TypedCells;
namespace search::tensor {
@@ -49,4 +51,73 @@ AngularDistance::calc(const vespalib::eval::TypedCells& lhs,
template class AngularDistanceHW<float>;
template class AngularDistanceHW<double>;
+
+template<typename FloatType>
+class BoundAngularDistance : public BoundDistanceFunction {
+private:
+ const vespalib::hwaccelrated::IAccelrated & _computer;
+ mutable TemporaryVectorStore<FloatType> _tmpSpace;
+ const vespalib::ConstArrayRef<FloatType> _lhs;
+ double _lhs_norm_sq;
+public:
+ BoundAngularDistance(const vespalib::eval::TypedCells& lhs)
+ : _computer(vespalib::hwaccelrated::IAccelrated::getAccelerator()),
+ _tmpSpace(lhs.size),
+ _lhs(_tmpSpace.storeLhs(lhs))
+ {
+ auto a = _lhs.data();
+ _lhs_norm_sq = _computer.dotProduct(a, a, lhs.size);
+ }
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ size_t sz = _lhs.size();
+ vespalib::ConstArrayRef<FloatType> rhs_vector = _tmpSpace.convertRhs(rhs);
+ assert(sz == rhs_vector.size());
+ auto a = _lhs.data();
+ auto b = rhs_vector.data();
+ double b_norm_sq = _computer.dotProduct(b, b, sz);
+ double squared_norms = _lhs_norm_sq * b_norm_sq;
+ double dot_product = _computer.dotProduct(a, b, sz);
+ double div = (squared_norms > 0) ? sqrt(squared_norms) : 1.0;
+ double cosine_similarity = dot_product / div;
+ double distance = 1.0 - cosine_similarity; // in range [0,2]
+ return distance;
+ }
+ double convert_threshold(double threshold) const override {
+ double cosine_similarity = cos(threshold);
+ return 1.0 - cosine_similarity;
+ }
+ double to_rawscore(double distance) const override {
+ double cosine_similarity = 1.0 - distance;
+ // should be in the range [-1,1] but roundoff may cause problems:
+ cosine_similarity = std::min(1.0, cosine_similarity);
+ cosine_similarity = std::max(-1.0, cosine_similarity);
+ double angle_distance = acos(cosine_similarity); // in range [0,pi]
+ double score = 1.0 / (1.0 + angle_distance);
+ return score;
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double) const override {
+ return calc(rhs);
+ }
+};
+
+template class BoundAngularDistance<float>;
+template class BoundAngularDistance<double>;
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+AngularDistanceFunctionFactory<FloatType>::for_query_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundAngularDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+AngularDistanceFunctionFactory<FloatType>::for_insertion_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundAngularDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template class AngularDistanceFunctionFactory<float>;
+template class AngularDistanceFunctionFactory<double>;
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/angular_distance.h b/searchlib/src/vespa/searchlib/tensor/angular_distance.h
index e3b21f9546e..bba83576153 100644
--- a/searchlib/src/vespa/searchlib/tensor/angular_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/angular_distance.h
@@ -3,6 +3,8 @@
#pragma once
#include "distance_function.h"
+#include "bound_distance_function.h"
+#include "distance_function_factory.h"
#include <vespa/eval/eval/typed_cells.h>
#include <vespa/vespalib/hwaccelrated/iaccelrated.h>
#include <cmath>
@@ -22,7 +24,7 @@ public:
}
double to_rawscore(double distance) const override {
double cosine_similarity = 1.0 - distance;
- // should be in in range [-1,1] but roundoff may cause problems:
+ // should be in the range [-1,1] but roundoff may cause problems:
cosine_similarity = std::min(1.0, cosine_similarity);
cosine_similarity = std::max(-1.0, cosine_similarity);
double angle_distance = acos(cosine_similarity); // in range [0,pi]
@@ -58,8 +60,8 @@ public:
auto rhs_vector = rhs.typify<FloatType>();
size_t sz = lhs_vector.size();
assert(sz == rhs_vector.size());
- auto a = &lhs_vector[0];
- auto b = &rhs_vector[0];
+ auto a = lhs_vector.data();
+ auto b = rhs_vector.data();
double a_norm_sq = _computer.dotProduct(a, a, sz);
double b_norm_sq = _computer.dotProduct(b, b, sz);
double squared_norms = a_norm_sq * b_norm_sq;
@@ -73,4 +75,15 @@ private:
const vespalib::hwaccelrated::IAccelrated & _computer;
};
+template <typename FloatType>
+class AngularDistanceFunctionFactory : public DistanceFunctionFactory {
+public:
+ AngularDistanceFunctionFactory()
+ : DistanceFunctionFactory(vespalib::eval::get_cell_type<FloatType>())
+ {}
+
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override;
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override;
+};
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp
new file mode 100644
index 00000000000..33b94e5218c
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp
@@ -0,0 +1,3 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "bound_distance_function.h"
diff --git a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h
new file mode 100644
index 00000000000..c072d6de8e5
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h
@@ -0,0 +1,38 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vespa/eval/eval/cell_type.h>
+#include <vespa/eval/eval/typed_cells.h>
+#include <vespa/vespalib/util/arrayref.h>
+#include "distance_function.h"
+
+namespace vespalib::eval { struct TypedCells; }
+
+namespace search::tensor {
+
+/**
+ * Interface used to calculate the distance from a prebound n-dimensional vector.
+ *
+ * Use from a single thread only - not required to be thread safe.
+ * The actual implementation may keep state about the prebound vector and
+ * mutable temporary storage.
+ */
+class BoundDistanceFunction : public DistanceConverter {
+public:
+ using UP = std::unique_ptr<BoundDistanceFunction>;
+
+ BoundDistanceFunction() = default;
+
+ virtual ~BoundDistanceFunction() = default;
+
+ // calculate internal distance (comparable)
+ virtual double calc(const vespalib::eval::TypedCells& rhs) const = 0;
+
+ // calculate internal distance, early return allowed if > limit
+ virtual double calc_with_limit(const vespalib::eval::TypedCells& rhs,
+ double limit) const = 0;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/default_nearest_neighbor_index_factory.cpp b/searchlib/src/vespa/searchlib/tensor/default_nearest_neighbor_index_factory.cpp
index 4f6f8ac5c87..77c912dc690 100644
--- a/searchlib/src/vespa/searchlib/tensor/default_nearest_neighbor_index_factory.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/default_nearest_neighbor_index_factory.cpp
@@ -41,12 +41,12 @@ DefaultNearestNeighborIndexFactory::make(const DocVectorAccess& vectors,
true);
if (multi_vector_index) {
return std::make_unique<HnswIndex<HnswIndexType::MULTI>>(vectors,
- make_distance_function(params.distance_metric(), cell_type),
+ make_distance_function_factory(params.distance_metric(), cell_type),
make_random_level_generator(m),
cfg);
} else {
return std::make_unique<HnswIndex<HnswIndexType::SINGLE>>(vectors,
- make_distance_function(params.distance_metric(), cell_type),
+ make_distance_function_factory(params.distance_metric(), cell_type),
make_random_level_generator(m),
cfg);
}
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
index c373f6bdcd0..c51d0ec7fd3 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.cpp
@@ -62,10 +62,10 @@ DenseTensorStore::BufferType::BufferType(const TensorSizeCalc &tensorSizeCalc, s
DenseTensorStore::BufferType::~BufferType() = default;
void
-DenseTensorStore::BufferType::cleanHold(void *buffer, size_t offset,
- ElemCount numElems, CleanContext)
+DenseTensorStore::BufferType::clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext)
{
- memset(static_cast<char *>(buffer) + offset, 0, numElems);
+ auto num_elems = num_entries * getArraySize();
+ memset(static_cast<char *>(buffer) + offset * getArraySize(), 0, num_elems);
}
const vespalib::alloc::MemoryAllocator*
@@ -107,7 +107,7 @@ DenseTensorStore::allocRawBuffer()
{
size_t bufSize = getBufSize();
size_t alignedBufSize = _tensorSizeCalc.alignedSize();
- auto result = _concreteStore.freeListRawAllocator<char>(0u).alloc(alignedBufSize);
+ auto result = _concreteStore.freeListRawAllocator<char>(0u).alloc(1);
clearPadAreaAfterBuffer(result.data, bufSize, alignedBufSize);
return result;
}
@@ -118,7 +118,7 @@ DenseTensorStore::holdTensor(EntryRef ref)
if (!ref.valid()) {
return;
}
- _concreteStore.holdElem(ref, _tensorSizeCalc.alignedSize());
+ _concreteStore.hold_entry(ref);
}
TensorStore::EntryRef
diff --git a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
index 9e326e0ab1e..0dd483e7f08 100644
--- a/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/dense_tensor_store.h
@@ -44,7 +44,7 @@ public:
public:
BufferType(const TensorSizeCalc &tensorSizeCalc, std::shared_ptr<vespalib::alloc::MemoryAllocator> allocator);
~BufferType() override;
- void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
+ void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
private:
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
index fa13ab6303c..8526138fd31 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.cpp
@@ -30,11 +30,11 @@ DirectTensorStore::TensorBufferType::TensorBufferType()
}
void
-DirectTensorStore::TensorBufferType::cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx)
+DirectTensorStore::TensorBufferType::clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext clean_ctx)
{
TensorSP* elem = static_cast<TensorSP*>(buffer) + offset;
const auto& empty = empty_entry();
- for (size_t i = 0; i < num_elems; ++i) {
+ for (size_t i = 0; i < num_entries; ++i) {
clean_ctx.extraBytesCleaned((*elem)->get_memory_usage().allocatedBytes());
*elem = empty;
++elem;
@@ -69,7 +69,7 @@ DirectTensorStore::holdTensor(EntryRef ref)
}
const auto& tensor = _tensor_store.getEntry(ref);
assert(tensor);
- _tensor_store.holdElem(ref, 1, tensor->get_memory_usage().allocatedBytes());
+ _tensor_store.hold_entry(ref, tensor->get_memory_usage().allocatedBytes());
}
EntryRef
diff --git a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
index 01084e89776..1230494fe41 100644
--- a/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
+++ b/searchlib/src/vespa/searchlib/tensor/direct_tensor_store.h
@@ -20,7 +20,7 @@ namespace search::tensor {
*/
class DirectTensorStore : public TensorStore {
private:
- // Note: Must use SP (instead of UP) because of fallbackCopy() and initializeReservedElements() in BufferType,
+ // Note: Must use SP (instead of UP) because of fallback_copy() and initialize_reserved_entries() in BufferType,
// and implementation of move().
using TensorSP = std::shared_ptr<vespalib::eval::Value>;
using TensorStoreType = vespalib::datastore::DataStore<TensorSP>;
@@ -32,7 +32,7 @@ private:
using CleanContext = typename ParentType::CleanContext;
public:
TensorBufferType();
- void cleanHold(void* buffer, size_t offset, ElemCount num_elems, CleanContext clean_ctx) override;
+ void clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext clean_ctx) override;
};
TensorStoreType _tensor_store;
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_calculator.cpp b/searchlib/src/vespa/searchlib/tensor/distance_calculator.cpp
index b669b5ffea6..8da777d97eb 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_calculator.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/distance_calculator.cpp
@@ -47,8 +47,6 @@ struct ConvertCellsSelector
}
};
-
-
}
namespace search::tensor {
@@ -58,36 +56,27 @@ DistanceCalculator::DistanceCalculator(const tensor::ITensorAttribute& attr_tens
: _attr_tensor(attr_tensor),
_query_tensor_uptr(),
_query_tensor(&query_tensor_in),
- _query_tensor_cells(),
- _dist_fun_uptr(make_distance_function(_attr_tensor.distance_metric(),
- _attr_tensor.getTensorType().cell_type())),
- _dist_fun(_dist_fun_uptr.get())
+ _dist_fun()
{
- assert(_dist_fun);
- auto nns_index = _attr_tensor.nearest_neighbor_index();
- if (nns_index) {
- _dist_fun = nns_index->distance_function();
- assert(_dist_fun);
- }
+ auto * nns_index = _attr_tensor.nearest_neighbor_index();
+ auto & dff = nns_index ? nns_index->distance_function_factory() : attr_tensor.distance_function_factory();
auto query_ct = _query_tensor->cells().type;
- CellType required_ct = _dist_fun->expected_cell_type();
+ CellType required_ct = dff.expected_cell_type;
if (query_ct != required_ct) {
ConvertCellsSelector converter;
_query_tensor_uptr = converter(query_ct, required_ct, *_query_tensor);
_query_tensor = _query_tensor_uptr.get();
}
- _query_tensor_cells = _query_tensor->cells();
+ _dist_fun = dff.for_query_vector(_query_tensor->cells());
+ assert(_dist_fun);
}
DistanceCalculator::DistanceCalculator(const tensor::ITensorAttribute& attr_tensor,
- const vespalib::eval::Value& query_tensor_in,
- const DistanceFunction& function_in)
+ BoundDistanceFunction::UP function_in)
: _attr_tensor(attr_tensor),
_query_tensor_uptr(),
- _query_tensor(&query_tensor_in),
- _query_tensor_cells(_query_tensor->cells()),
- _dist_fun_uptr(),
- _dist_fun(&function_in)
+ _query_tensor(nullptr),
+ _dist_fun(std::move(function_in))
{
}
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_calculator.h b/searchlib/src/vespa/searchlib/tensor/distance_calculator.h
index 6b4cf142264..a3ca771e30c 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_calculator.h
+++ b/searchlib/src/vespa/searchlib/tensor/distance_calculator.h
@@ -2,6 +2,7 @@
#pragma once
#include "distance_function.h"
+#include "distance_function_factory.h"
#include "i_tensor_attribute.h"
#include "vector_bundle.h"
#include <optional>
@@ -23,9 +24,7 @@ private:
const tensor::ITensorAttribute& _attr_tensor;
std::unique_ptr<vespalib::eval::Value> _query_tensor_uptr;
const vespalib::eval::Value* _query_tensor;
- vespalib::eval::TypedCells _query_tensor_cells;
- std::unique_ptr<DistanceFunction> _dist_fun_uptr;
- const DistanceFunction* _dist_fun;
+ std::unique_ptr<BoundDistanceFunction> _dist_fun;
public:
DistanceCalculator(const tensor::ITensorAttribute& attr_tensor,
@@ -35,20 +34,22 @@ public:
* Only used by unit tests where ownership of query tensor and distance function is handled outside.
*/
DistanceCalculator(const tensor::ITensorAttribute& attr_tensor,
- const vespalib::eval::Value& query_tensor_in,
- const DistanceFunction& function_in);
+ BoundDistanceFunction::UP function_in);
~DistanceCalculator();
const tensor::ITensorAttribute& attribute_tensor() const { return _attr_tensor; }
- const vespalib::eval::Value& query_tensor() const { return *_query_tensor; }
- const DistanceFunction& function() const { return *_dist_fun; }
+ const vespalib::eval::Value& query_tensor() const {
+ assert(_query_tensor != nullptr);
+ return *_query_tensor;
+ }
+ const BoundDistanceFunction& function() const { return *_dist_fun; }
double calc_raw_score(uint32_t docid) const {
auto vectors = _attr_tensor.get_vectors(docid);
double result = 0.0;
for (uint32_t i = 0; i < vectors.subspaces(); ++i) {
- double distance = _dist_fun->calc(_query_tensor_cells, vectors.cells(i));
+ double distance = _dist_fun->calc(vectors.cells(i));
double score = _dist_fun->to_rawscore(distance);
result = std::max(result, score);
}
@@ -59,7 +60,7 @@ public:
auto vectors = _attr_tensor.get_vectors(docid);
double result = std::numeric_limits<double>::max();
for (uint32_t i = 0; i < vectors.subspaces(); ++i) {
- double distance = _dist_fun->calc_with_limit(_query_tensor_cells, vectors.cells(i), limit);
+ double distance = _dist_fun->calc_with_limit(vectors.cells(i), limit);
result = std::min(result, distance);
}
return result;
@@ -67,7 +68,7 @@ public:
void calc_closest_subspace(VectorBundle vectors, std::optional<uint32_t>& closest_subspace, double& best_distance) {
for (uint32_t i = 0; i < vectors.subspaces(); ++i) {
- double distance = _dist_fun->calc(_query_tensor_cells, vectors.cells(i));
+ double distance = _dist_fun->calc(vectors.cells(i));
if (!closest_subspace.has_value() || distance < best_distance) {
best_distance = distance;
closest_subspace = i;
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_function.h b/searchlib/src/vespa/searchlib/tensor/distance_function.h
index d5ebf656189..443191a272c 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_function.h
+++ b/searchlib/src/vespa/searchlib/tensor/distance_function.h
@@ -9,13 +9,24 @@ namespace vespalib::eval { struct TypedCells; }
namespace search::tensor {
+class DistanceConverter {
+public:
+ virtual ~DistanceConverter() = default;
+
+ // convert threshold (external distance units) to internal units
+ virtual double convert_threshold(double threshold) const = 0;
+
+ // convert internal distance to rawscore (1.0 / (1.0 + d))
+ virtual double to_rawscore(double distance) const = 0;
+};
+
/**
* Interface used to calculate the distance between two n-dimensional vectors.
*
* The vectors must be of same size and same cell type (float or double).
* The actual implementation must know which type the vectors are.
*/
-class DistanceFunction {
+class DistanceFunction : public DistanceConverter {
private:
vespalib::eval::CellType _expect_cell_type;
public:
@@ -33,12 +44,6 @@ public:
// calculate internal distance (comparable)
virtual double calc(const vespalib::eval::TypedCells& lhs, const vespalib::eval::TypedCells& rhs) const = 0;
- // convert threshold (external distance units) to internal units
- virtual double convert_threshold(double threshold) const = 0;
-
- // convert internal distance to rawscore (1.0 / (1.0 + d))
- virtual double to_rawscore(double distance) const = 0;
-
// calculate internal distance, early return allowed if > limit
virtual double calc_with_limit(const vespalib::eval::TypedCells& lhs,
const vespalib::eval::TypedCells& rhs,
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
index 96dfc580d87..c088d498f0f 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
@@ -3,6 +3,8 @@
#include "distance_function_factory.h"
#include "distance_functions.h"
#include <vespa/vespalib/util/typify.h>
+#include <vespa/vespalib/util/array.h>
+#include <vespa/vespalib/util/arrayref.h>
#include <vespa/log/log.h>
LOG_SETUP(".searchlib.tensor.distance_function_factory");
@@ -21,9 +23,9 @@ make_distance_function(DistanceMetric variant, CellType cell_type)
switch (cell_type) {
case CellType::FLOAT: return std::make_unique<SquaredEuclideanDistanceHW<float>>();
case CellType::DOUBLE: return std::make_unique<SquaredEuclideanDistanceHW<double>>();
- case CellType::INT8: return std::make_unique<SquaredEuclideanDistanceHW<vespalib::eval::Int8Float>>();
+ case CellType::INT8: return std::make_unique<SquaredEuclideanDistanceHW<vespalib::eval::Int8Float>>();
default: return std::make_unique<SquaredEuclideanDistance>(CellType::FLOAT);
- }
+ }
case DistanceMetric::Angular:
switch (cell_type) {
case CellType::FLOAT: return std::make_unique<AngularDistanceHW<float>>();
@@ -32,6 +34,7 @@ make_distance_function(DistanceMetric variant, CellType cell_type)
}
case DistanceMetric::GeoDegrees:
return std::make_unique<GeoDegreesDistance>(CellType::DOUBLE);
+ case DistanceMetric::PrenormalizedAngular:
case DistanceMetric::InnerProduct:
switch (cell_type) {
case CellType::FLOAT: return std::make_unique<InnerProductDistanceHW<float>>();
@@ -45,4 +48,80 @@ make_distance_function(DistanceMetric variant, CellType cell_type)
return DistanceFunction::UP();
}
+
+class SimpleBoundDistanceFunction : public BoundDistanceFunction {
+ const vespalib::eval::TypedCells _lhs;
+ const DistanceFunction &_df;
+public:
+ SimpleBoundDistanceFunction(const vespalib::eval::TypedCells& lhs,
+ const DistanceFunction &df)
+ : _lhs(lhs),
+ _df(df)
+ {}
+
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ return _df.calc(_lhs, rhs);
+ }
+ double convert_threshold(double threshold) const override {
+ return _df.convert_threshold(threshold);
+ }
+ double to_rawscore(double distance) const override {
+ return _df.to_rawscore(distance);
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double limit) const override {
+ return _df.calc_with_limit(_lhs, rhs, limit);
+ }
+};
+
+class SimpleDistanceFunctionFactory : public DistanceFunctionFactory {
+ DistanceFunction::UP _df;
+public:
+ SimpleDistanceFunctionFactory(DistanceFunction::UP df)
+ : DistanceFunctionFactory(df->expected_cell_type()),
+ _df(std::move(df))
+ {}
+
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override {
+ return std::make_unique<SimpleBoundDistanceFunction>(lhs, *_df);
+ }
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override {
+ return std::make_unique<SimpleBoundDistanceFunction>(lhs, *_df);
+ }
+};
+
+std::unique_ptr<DistanceFunctionFactory>
+make_distance_function_factory(search::attribute::DistanceMetric variant,
+ vespalib::eval::CellType cell_type)
+{
+ switch (variant) {
+ case DistanceMetric::Angular:
+ switch (cell_type) {
+ case CellType::DOUBLE: return std::make_unique<AngularDistanceFunctionFactory<double>>();
+ default: return std::make_unique<AngularDistanceFunctionFactory<float>>();
+ }
+ case DistanceMetric::Euclidean:
+ switch (cell_type) {
+ case CellType::DOUBLE: return std::make_unique<EuclideanDistanceFunctionFactory<double>>();
+ case CellType::INT8: return std::make_unique<EuclideanDistanceFunctionFactory<vespalib::eval::Int8Float>>();
+ default: return std::make_unique<EuclideanDistanceFunctionFactory<float>>();
+ }
+ case DistanceMetric::InnerProduct:
+ case DistanceMetric::PrenormalizedAngular:
+ switch (cell_type) {
+ case CellType::DOUBLE: return std::make_unique<PrenormalizedAngularDistanceFunctionFactory<double>>();
+ default: return std::make_unique<PrenormalizedAngularDistanceFunctionFactory<float>>();
+ }
+ case DistanceMetric::GeoDegrees:
+ return std::make_unique<GeoDistanceFunctionFactory>();
+ case DistanceMetric::Hamming:
+ switch (cell_type) {
+ case CellType::DOUBLE: return std::make_unique<HammingDistanceFunctionFactory<double>>();
+ case CellType::INT8: return std::make_unique<HammingDistanceFunctionFactory<vespalib::eval::Int8Float>>();
+ default: return std::make_unique<HammingDistanceFunctionFactory<float>>();
+ }
+ }
+ // not reached:
+ return {};
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.h b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.h
index 2d7eb4e73c1..1edb94bd7aa 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.h
+++ b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.h
@@ -3,12 +3,27 @@
#pragma once
#include "distance_function.h"
+#include "bound_distance_function.h"
#include <vespa/eval/eval/value_type.h>
#include <vespa/searchcommon/attribute/distance_metric.h>
namespace search::tensor {
/**
+ * API for binding the LHS of a distance calculation
+ * This allows keeping global state in the factory itself, and state
+ * for one particular vector in the distance function object.
+ */
+struct DistanceFunctionFactory {
+ const vespalib::eval::CellType expected_cell_type;
+ DistanceFunctionFactory(vespalib::eval::CellType ct) : expected_cell_type(ct) {}
+ virtual ~DistanceFunctionFactory() {}
+ virtual BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) = 0;
+ virtual BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) = 0;
+ using UP = std::unique_ptr<DistanceFunctionFactory>;
+};
+
+/**
* Create a distance function object customized for the given metric
* variant and cell type.
**/
@@ -16,4 +31,8 @@ DistanceFunction::UP
make_distance_function(search::attribute::DistanceMetric variant,
vespalib::eval::CellType cell_type);
+DistanceFunctionFactory::UP
+make_distance_function_factory(search::attribute::DistanceMetric variant,
+ vespalib::eval::CellType cell_type);
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_functions.h b/searchlib/src/vespa/searchlib/tensor/distance_functions.h
index b28cc2bda46..2300dba2db1 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_functions.h
+++ b/searchlib/src/vespa/searchlib/tensor/distance_functions.h
@@ -8,3 +8,4 @@
#include "geo_degrees_distance.h"
#include "hamming_distance.h"
#include "inner_product_distance.h"
+#include "prenormalized_angular_distance.h"
diff --git a/searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp b/searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp
index c83f1821321..7995c87d055 100644
--- a/searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "euclidean_distance.h"
+#include "temporary_vector_store.h"
using vespalib::typify_invoke;
using vespalib::eval::TypifyCellType;
@@ -48,4 +49,72 @@ SquaredEuclideanDistance::calc_with_limit(const vespalib::eval::TypedCells& lhs,
template class SquaredEuclideanDistanceHW<float>;
template class SquaredEuclideanDistanceHW<double>;
+using vespalib::eval::Int8Float;
+
+template<typename FloatType>
+class BoundEuclideanDistance : public BoundDistanceFunction {
+private:
+ const vespalib::hwaccelrated::IAccelrated & _computer;
+ mutable TemporaryVectorStore<FloatType> _tmpSpace;
+ const vespalib::ConstArrayRef<FloatType> _lhs_vector;
+ static const double *cast(const double * p) { return p; }
+ static const float *cast(const float * p) { return p; }
+ static const int8_t *cast(const Int8Float * p) { return reinterpret_cast<const int8_t *>(p); }
+public:
+ BoundEuclideanDistance(const vespalib::eval::TypedCells& lhs)
+ : _computer(vespalib::hwaccelrated::IAccelrated::getAccelerator()),
+ _tmpSpace(lhs.size),
+ _lhs_vector(_tmpSpace.storeLhs(lhs))
+ {}
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ size_t sz = _lhs_vector.size();
+ vespalib::ConstArrayRef<FloatType> rhs_vector = _tmpSpace.convertRhs(rhs);
+ assert(sz == rhs_vector.size());
+ auto a = _lhs_vector.data();
+ auto b = rhs_vector.data();
+ return _computer.squaredEuclideanDistance(cast(a), cast(b), sz);
+ }
+ double convert_threshold(double threshold) const override {
+ return threshold*threshold;
+ }
+ double to_rawscore(double distance) const override {
+ double d = sqrt(distance);
+ double score = 1.0 / (1.0 + d);
+ return score;
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double limit) const override {
+ vespalib::ConstArrayRef<FloatType> rhs_vector = _tmpSpace.convertRhs(rhs);
+ double sum = 0.0;
+ size_t sz = _lhs_vector.size();
+ assert(sz == rhs_vector.size());
+ for (size_t i = 0; i < sz && sum <= limit; ++i) {
+ double diff = _lhs_vector[i] - rhs_vector[i];
+ sum += diff*diff;
+ }
+ return sum;
+ }
+};
+
+template class BoundEuclideanDistance<Int8Float>;
+template class BoundEuclideanDistance<float>;
+template class BoundEuclideanDistance<double>;
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+EuclideanDistanceFunctionFactory<FloatType>::for_query_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundEuclideanDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+EuclideanDistanceFunctionFactory<FloatType>::for_insertion_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundEuclideanDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template class EuclideanDistanceFunctionFactory<Int8Float>;
+template class EuclideanDistanceFunctionFactory<float>;
+template class EuclideanDistanceFunctionFactory<double>;
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/euclidean_distance.h b/searchlib/src/vespa/searchlib/tensor/euclidean_distance.h
index 6505ea119ea..b406f0d3d1a 100644
--- a/searchlib/src/vespa/searchlib/tensor/euclidean_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/euclidean_distance.h
@@ -3,6 +3,7 @@
#pragma once
#include "distance_function.h"
+#include "distance_function_factory.h"
#include <vespa/eval/eval/typed_cells.h>
#include <vespa/vespalib/hwaccelrated/iaccelrated.h>
#include <cmath>
@@ -78,4 +79,15 @@ private:
const vespalib::hwaccelrated::IAccelrated & _computer;
};
+
+template <typename FloatType>
+class EuclideanDistanceFunctionFactory : public DistanceFunctionFactory {
+public:
+ EuclideanDistanceFunctionFactory()
+ : DistanceFunctionFactory(vespalib::eval::get_cell_type<FloatType>())
+ {}
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override;
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override;
+};
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.cpp b/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.cpp
index bcce75da3ab..38ba8205c90 100644
--- a/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "geo_degrees_distance.h"
+#include "temporary_vector_store.h"
using vespalib::typify_invoke;
using vespalib::eval::TypifyCellType;
@@ -27,11 +28,11 @@ struct CalcGeoDegrees {
double lat_diff = lat_A - lat_B;
double lon_diff = lon_A - lon_B;
-
+
// haversines of differences:
double hav_lat = GeoDegreesDistance::hav(lat_diff);
double hav_lon = GeoDegreesDistance::hav(lon_diff);
-
+
// haversine of central angle between the two points:
double hav_central_angle = hav_lat + cos(lat_A)*cos(lat_B)*hav_lon;
return hav_central_angle;
@@ -42,9 +43,63 @@ struct CalcGeoDegrees {
double
GeoDegreesDistance::calc(const vespalib::eval::TypedCells& lhs,
- const vespalib::eval::TypedCells& rhs) const
+ const vespalib::eval::TypedCells& rhs) const
{
return typify_invoke<2,TypifyCellType,CalcGeoDegrees>(lhs.type, rhs.type, lhs, rhs);
}
+using vespalib::eval::TypedCells;
+
+class BoundGeoDistance : public BoundDistanceFunction {
+private:
+ mutable TemporaryVectorStore<double> _tmpSpace;
+ const vespalib::ConstArrayRef<double> _lh_vector;
+ static GeoDegreesDistance _g_d_helper;
+public:
+ BoundGeoDistance(const vespalib::eval::TypedCells& lhs)
+ : _tmpSpace(lhs.size),
+ _lh_vector(_tmpSpace.storeLhs(lhs))
+ {}
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ vespalib::ConstArrayRef<double> rhs_vector = _tmpSpace.convertRhs(rhs);
+ assert(2 == _lh_vector.size());
+ assert(2 == rhs_vector.size());
+ // convert to radians:
+ double lat_A = _lh_vector[0] * GeoDegreesDistance::degrees_to_radians;
+ double lat_B = rhs_vector[0] * GeoDegreesDistance::degrees_to_radians;
+ double lon_A = _lh_vector[1] * GeoDegreesDistance::degrees_to_radians;
+ double lon_B = rhs_vector[1] * GeoDegreesDistance::degrees_to_radians;
+
+ double lat_diff = lat_A - lat_B;
+ double lon_diff = lon_A - lon_B;
+
+ // haversines of differences:
+ double hav_lat = GeoDegreesDistance::hav(lat_diff);
+ double hav_lon = GeoDegreesDistance::hav(lon_diff);
+
+ // haversine of central angle between the two points:
+ double hav_central_angle = hav_lat + cos(lat_A)*cos(lat_B)*hav_lon;
+ return hav_central_angle;
+ }
+ double convert_threshold(double threshold) const override {
+ return _g_d_helper.convert_threshold(threshold);
+ }
+ double to_rawscore(double distance) const override {
+ return _g_d_helper.to_rawscore(distance);
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double) const override {
+ return calc(rhs);
+ }
+};
+
+BoundDistanceFunction::UP
+GeoDistanceFunctionFactory::for_query_vector(const vespalib::eval::TypedCells& lhs) {
+ return std::make_unique<BoundGeoDistance>(lhs);
+}
+
+BoundDistanceFunction::UP
+GeoDistanceFunctionFactory::for_insertion_vector(const vespalib::eval::TypedCells& lhs) {
+ return std::make_unique<BoundGeoDistance>(lhs);
+}
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.h b/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.h
index 46feee19119..4522bc03c9e 100644
--- a/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/geo_degrees_distance.h
@@ -3,6 +3,7 @@
#pragma once
#include "distance_function.h"
+#include "distance_function_factory.h"
#include <vespa/eval/eval/typed_cells.h>
#include <vespa/vespalib/hwaccelrated/iaccelrated.h>
#include <vespa/vespalib/util/typify.h>
@@ -50,4 +51,11 @@ public:
}
};
+class GeoDistanceFunctionFactory : public DistanceFunctionFactory {
+public:
+ GeoDistanceFunctionFactory() : DistanceFunctionFactory(vespalib::eval::CellType::DOUBLE) {}
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override;
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override;
+};
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp b/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
index 43596478a6f..f4f6842715f 100644
--- a/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/hamming_distance.cpp
@@ -1,6 +1,7 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "hamming_distance.h"
+#include "temporary_vector_store.h"
#include <vespa/vespalib/util/binary_hamming_distance.h>
using vespalib::typify_invoke;
@@ -52,4 +53,63 @@ HammingDistance::calc_with_limit(const vespalib::eval::TypedCells& lhs,
return calc(lhs, rhs);
}
+using vespalib::eval::Int8Float;
+
+template<typename FloatType>
+class BoundHammingDistance : public BoundDistanceFunction {
+private:
+ mutable TemporaryVectorStore<FloatType> _tmpSpace;
+ const vespalib::ConstArrayRef<FloatType> _lhs_vector;
+public:
+ BoundHammingDistance(const vespalib::eval::TypedCells& lhs)
+ : _tmpSpace(lhs.size),
+ _lhs_vector(_tmpSpace.storeLhs(lhs))
+ {}
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ size_t sz = _lhs_vector.size();
+ vespalib::ConstArrayRef<FloatType> rhs_vector = _tmpSpace.convertRhs(rhs);
+ assert(sz == rhs_vector.size());
+ auto a = _lhs_vector.data();
+ auto b = rhs_vector.data();
+ if constexpr (std::is_same<Int8Float, FloatType>::value) {
+ return (double) vespalib::binary_hamming_distance(a, b, sz);
+ } else {
+ size_t sum = 0;
+ for (size_t i = 0; i < sz; ++i) {
+ sum += (_lhs_vector[i] == rhs_vector[i]) ? 0 : 1;
+ }
+ return (double)sum;
+ }
+ }
+ double convert_threshold(double threshold) const override {
+ return threshold;
+ }
+ double to_rawscore(double distance) const override {
+ double score = 1.0 / (1.0 + distance);
+ return score;
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double) const override {
+ // consider optimizing:
+ return calc(rhs);
+ }
+};
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+HammingDistanceFunctionFactory<FloatType>::for_query_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundHammingDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+HammingDistanceFunctionFactory<FloatType>::for_insertion_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundHammingDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template class HammingDistanceFunctionFactory<Int8Float>;
+template class HammingDistanceFunctionFactory<float>;
+template class HammingDistanceFunctionFactory<double>;
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hamming_distance.h b/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
index c64fc5b532d..23c855eb137 100644
--- a/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/hamming_distance.h
@@ -3,6 +3,7 @@
#pragma once
#include "distance_function.h"
+#include "distance_function_factory.h"
#include <vespa/eval/eval/typed_cells.h>
#include <vespa/vespalib/util/typify.h>
#include <cmath>
@@ -29,4 +30,14 @@ public:
double calc_with_limit(const vespalib::eval::TypedCells& lhs, const vespalib::eval::TypedCells& rhs, double) const override;
};
+template <typename FloatType>
+class HammingDistanceFunctionFactory : public DistanceFunctionFactory {
+public:
+ HammingDistanceFunctionFactory()
+ : DistanceFunctionFactory(vespalib::eval::get_cell_type<FloatType>())
+ {}
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override;
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override;
+};
+
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
index af332189b61..fa7f150fd89 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.cpp
@@ -278,23 +278,25 @@ double
HnswIndex<type>::calc_distance(uint32_t lhs_nodeid, uint32_t rhs_nodeid) const
{
auto lhs = get_vector(lhs_nodeid);
- return calc_distance(lhs, rhs_nodeid);
+ auto df = _distance_ff->for_insertion_vector(lhs);
+ auto rhs = get_vector(rhs_nodeid);
+ return df->calc(rhs);
}
template <HnswIndexType type>
double
-HnswIndex<type>::calc_distance(const TypedCells& lhs, uint32_t rhs_nodeid) const
+HnswIndex<type>::calc_distance(const BoundDistanceFunction &df, uint32_t rhs_nodeid) const
{
auto rhs = get_vector(rhs_nodeid);
- return _distance_func->calc(lhs, rhs);
+ return df.calc(rhs);
}
template <HnswIndexType type>
double
-HnswIndex<type>::calc_distance(const TypedCells& lhs, uint32_t rhs_docid, uint32_t rhs_subspace) const
+HnswIndex<type>::calc_distance(const BoundDistanceFunction &df, uint32_t rhs_docid, uint32_t rhs_subspace) const
{
auto rhs = get_vector(rhs_docid, rhs_subspace);
- return _distance_func->calc(lhs, rhs);
+ return df.calc(rhs);
}
template <HnswIndexType type>
@@ -323,7 +325,9 @@ HnswIndex<type>::estimate_visited_nodes(uint32_t level, uint32_t nodeid_limit, u
template <HnswIndexType type>
HnswCandidate
-HnswIndex<type>::find_nearest_in_layer(const TypedCells& input, const HnswCandidate& entry_point, uint32_t level) const
+HnswIndex<type>::find_nearest_in_layer(
+ const BoundDistanceFunction &df,
+ const HnswCandidate& entry_point, uint32_t level) const
{
HnswCandidate nearest = entry_point;
bool keep_searching = true;
@@ -334,7 +338,7 @@ HnswIndex<type>::find_nearest_in_layer(const TypedCells& input, const HnswCandid
auto neighbor_ref = neighbor_node.levels_ref().load_acquire();
uint32_t neighbor_docid = acquire_docid(neighbor_node, neighbor_nodeid);
uint32_t neighbor_subspace = neighbor_node.acquire_subspace();
- double dist = calc_distance(input, neighbor_docid, neighbor_subspace);
+ double dist = calc_distance(df, neighbor_docid, neighbor_subspace);
if (_graph.still_valid(neighbor_nodeid, neighbor_ref)
&& dist < nearest.distance)
{
@@ -349,9 +353,11 @@ HnswIndex<type>::find_nearest_in_layer(const TypedCells& input, const HnswCandid
template <HnswIndexType type>
template <class VisitedTracker, class BestNeighbors>
void
-HnswIndex<type>::search_layer_helper(const TypedCells& input, uint32_t neighbors_to_find,
- BestNeighbors& best_neighbors, uint32_t level, const GlobalFilter *filter,
- uint32_t nodeid_limit, uint32_t estimated_visited_nodes) const
+HnswIndex<type>::search_layer_helper(
+ const BoundDistanceFunction &df,
+ uint32_t neighbors_to_find,
+ BestNeighbors& best_neighbors, uint32_t level, const GlobalFilter *filter,
+ uint32_t nodeid_limit, uint32_t estimated_visited_nodes) const
{
NearestPriQ candidates;
GlobalFilterWrapper<type> filter_wrapper(filter);
@@ -389,7 +395,7 @@ HnswIndex<type>::search_layer_helper(const TypedCells& input, uint32_t neighbors
}
uint32_t neighbor_docid = acquire_docid(neighbor_node, neighbor_nodeid);
uint32_t neighbor_subspace = neighbor_node.acquire_subspace();
- double dist_to_input = calc_distance(input, neighbor_docid, neighbor_subspace);
+ double dist_to_input = calc_distance(df, neighbor_docid, neighbor_subspace);
if (dist_to_input < limit_dist) {
candidates.emplace(neighbor_nodeid, neighbor_ref, dist_to_input);
if (filter_wrapper.check(neighbor_docid)) {
@@ -407,29 +413,31 @@ HnswIndex<type>::search_layer_helper(const TypedCells& input, uint32_t neighbors
template <HnswIndexType type>
template <class BestNeighbors>
void
-HnswIndex<type>::search_layer(const TypedCells& input, uint32_t neighbors_to_find,
- BestNeighbors& best_neighbors, uint32_t level, const GlobalFilter *filter) const
+HnswIndex<type>::search_layer(
+ const BoundDistanceFunction &df,
+ uint32_t neighbors_to_find,
+ BestNeighbors& best_neighbors, uint32_t level, const GlobalFilter *filter) const
{
uint32_t nodeid_limit = _graph.nodes_size.load(std::memory_order_acquire);
uint32_t estimated_visited_nodes = estimate_visited_nodes(level, nodeid_limit, neighbors_to_find, filter);
if (estimated_visited_nodes >= nodeid_limit / 128) {
- search_layer_helper<BitVectorVisitedTracker>(input, neighbors_to_find, best_neighbors, level, filter, nodeid_limit, estimated_visited_nodes);
+ search_layer_helper<BitVectorVisitedTracker>(df, neighbors_to_find, best_neighbors, level, filter, nodeid_limit, estimated_visited_nodes);
} else {
- search_layer_helper<HashSetVisitedTracker>(input, neighbors_to_find, best_neighbors, level, filter, nodeid_limit, estimated_visited_nodes);
+ search_layer_helper<HashSetVisitedTracker>(df, neighbors_to_find, best_neighbors, level, filter, nodeid_limit, estimated_visited_nodes);
}
}
template <HnswIndexType type>
-HnswIndex<type>::HnswIndex(const DocVectorAccess& vectors, DistanceFunction::UP distance_func,
+HnswIndex<type>::HnswIndex(const DocVectorAccess& vectors, DistanceFunctionFactory::UP distance_ff,
RandomLevelGenerator::UP level_generator, const HnswIndexConfig& cfg)
: _graph(),
_vectors(vectors),
- _distance_func(std::move(distance_func)),
+ _distance_ff(std::move(distance_ff)),
_level_generator(std::move(level_generator)),
_id_mapping(),
_cfg(cfg)
{
- assert(_distance_func);
+ assert(_distance_ff);
}
template <HnswIndexType type>
@@ -483,12 +491,13 @@ HnswIndex<type>::internal_prepare_add_node(PreparedAddDoc& op, TypedCells input_
return;
}
int search_level = entry.level;
- double entry_dist = calc_distance(input_vector, entry.nodeid);
+ auto df = _distance_ff->for_insertion_vector(input_vector);
+ double entry_dist = calc_distance(*df, entry.nodeid);
uint32_t entry_docid = get_docid(entry.nodeid);
// TODO: check if entry nodeid/levels_ref is still valid here
HnswCandidate entry_point(entry.nodeid, entry_docid, entry.levels_ref, entry_dist);
while (search_level > node_max_level) {
- entry_point = find_nearest_in_layer(input_vector, entry_point, search_level);
+ entry_point = find_nearest_in_layer(*df, entry_point, search_level);
--search_level;
}
@@ -497,7 +506,7 @@ HnswIndex<type>::internal_prepare_add_node(PreparedAddDoc& op, TypedCells input_
search_level = std::min(node_max_level, search_level);
// Find neighbors of the added document in each level it should exist in.
while (search_level >= 0) {
- search_layer(input_vector, _cfg.neighbors_to_explore_at_construction(), best_neighbors, search_level);
+ search_layer(*df, _cfg.neighbors_to_explore_at_construction(), best_neighbors, search_level);
auto neighbors = select_neighbors(best_neighbors.peek(), _cfg.max_links_on_inserts());
auto& links = connections[search_level];
links.reserve(neighbors.used.size());
@@ -850,11 +859,13 @@ struct NeighborsByDocId {
template <HnswIndexType type>
std::vector<NearestNeighborIndex::Neighbor>
-HnswIndex<type>::top_k_by_docid(uint32_t k, TypedCells vector,
- const GlobalFilter *filter, uint32_t explore_k,
- double distance_threshold) const
+HnswIndex<type>::top_k_by_docid(
+ uint32_t k,
+ const BoundDistanceFunction &df,
+ const GlobalFilter *filter, uint32_t explore_k,
+ double distance_threshold) const
{
- SearchBestNeighbors candidates = top_k_candidates(vector, std::max(k, explore_k), filter);
+ SearchBestNeighbors candidates = top_k_candidates(df, std::max(k, explore_k), filter);
auto result = candidates.get_neighbors(k, distance_threshold);
std::sort(result.begin(), result.end(), NeighborsByDocId());
return result;
@@ -862,24 +873,31 @@ HnswIndex<type>::top_k_by_docid(uint32_t k, TypedCells vector,
template <HnswIndexType type>
std::vector<NearestNeighborIndex::Neighbor>
-HnswIndex<type>::find_top_k(uint32_t k, TypedCells vector, uint32_t explore_k,
- double distance_threshold) const
+HnswIndex<type>::find_top_k(
+ uint32_t k,
+ const BoundDistanceFunction &df,
+ uint32_t explore_k,
+ double distance_threshold) const
{
- return top_k_by_docid(k, vector, nullptr, explore_k, distance_threshold);
+ return top_k_by_docid(k, df, nullptr, explore_k, distance_threshold);
}
template <HnswIndexType type>
std::vector<NearestNeighborIndex::Neighbor>
-HnswIndex<type>::find_top_k_with_filter(uint32_t k, TypedCells vector,
- const GlobalFilter &filter, uint32_t explore_k,
- double distance_threshold) const
+HnswIndex<type>::find_top_k_with_filter(
+ uint32_t k,
+ const BoundDistanceFunction &df,
+ const GlobalFilter &filter, uint32_t explore_k,
+ double distance_threshold) const
{
- return top_k_by_docid(k, vector, &filter, explore_k, distance_threshold);
+ return top_k_by_docid(k, df, &filter, explore_k, distance_threshold);
}
template <HnswIndexType type>
typename HnswIndex<type>::SearchBestNeighbors
-HnswIndex<type>::top_k_candidates(const TypedCells &vector, uint32_t k, const GlobalFilter *filter) const
+HnswIndex<type>::top_k_candidates(
+ const BoundDistanceFunction &df,
+ uint32_t k, const GlobalFilter *filter) const
{
SearchBestNeighbors best_neighbors;
auto entry = _graph.get_entry_node();
@@ -888,16 +906,16 @@ HnswIndex<type>::top_k_candidates(const TypedCells &vector, uint32_t k, const Gl
return best_neighbors;
}
int search_level = entry.level;
- double entry_dist = calc_distance(vector, entry.nodeid);
+ double entry_dist = calc_distance(df, entry.nodeid);
uint32_t entry_docid = get_docid(entry.nodeid);
// TODO: check if entry docid/levels_ref is still valid here
HnswCandidate entry_point(entry.nodeid, entry_docid, entry.levels_ref, entry_dist);
while (search_level > 0) {
- entry_point = find_nearest_in_layer(vector, entry_point, search_level);
+ entry_point = find_nearest_in_layer(df, entry_point, search_level);
--search_level;
}
best_neighbors.push(entry_point);
- search_layer(vector, k, best_neighbors, 0, filter);
+ search_layer(df, k, best_neighbors, 0, filter);
return best_neighbors;
}
diff --git a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
index 984acc6c9a1..0809dcf4fe3 100644
--- a/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/hnsw_index.h
@@ -4,6 +4,7 @@
#include "hnsw_index_config.h"
#include "distance_function.h"
+#include "distance_function_factory.h"
#include "doc_vector_access.h"
#include "hnsw_identity_mapping.h"
#include "hnsw_index_utils.h"
@@ -104,7 +105,7 @@ protected:
GraphType _graph;
const DocVectorAccess& _vectors;
- DistanceFunction::UP _distance_func;
+ std::unique_ptr<DistanceFunctionFactory> _distance_ff;
RandomLevelGenerator::UP _level_generator;
IdMapping _id_mapping; // mapping from docid to nodeid vector
HnswIndexConfig _cfg;
@@ -158,23 +159,23 @@ protected:
}
double calc_distance(uint32_t lhs_nodeid, uint32_t rhs_nodeid) const;
- double calc_distance(const TypedCells& lhs, uint32_t rhs_nodeid) const;
- double calc_distance(const TypedCells& lhs, uint32_t rhs_docid, uint32_t rhs_subspace) const;
+ double calc_distance(const BoundDistanceFunction &df, uint32_t rhs_nodeid) const;
+ double calc_distance(const BoundDistanceFunction &df, uint32_t rhs_docid, uint32_t rhs_subspace) const;
uint32_t estimate_visited_nodes(uint32_t level, uint32_t nodeid_limit, uint32_t neighbors_to_find, const GlobalFilter* filter) const;
/**
* Performs a greedy search in the given layer to find the candidate that is nearest the input vector.
*/
- HnswCandidate find_nearest_in_layer(const TypedCells& input, const HnswCandidate& entry_point, uint32_t level) const;
+ HnswCandidate find_nearest_in_layer(const BoundDistanceFunction &df, const HnswCandidate& entry_point, uint32_t level) const;
template <class VisitedTracker, class BestNeighbors>
- void search_layer_helper(const TypedCells& input, uint32_t neighbors_to_find, BestNeighbors& best_neighbors,
+ void search_layer_helper(const BoundDistanceFunction &df, uint32_t neighbors_to_find, BestNeighbors& best_neighbors,
uint32_t level, const GlobalFilter *filter,
uint32_t nodeid_limit,
uint32_t estimated_visited_nodes) const;
template <class BestNeighbors>
- void search_layer(const TypedCells& input, uint32_t neighbors_to_find, BestNeighbors& best_neighbors,
+ void search_layer(const BoundDistanceFunction &df, uint32_t neighbors_to_find, BestNeighbors& best_neighbors,
uint32_t level, const GlobalFilter *filter = nullptr) const;
- std::vector<Neighbor> top_k_by_docid(uint32_t k, TypedCells vector,
+ std::vector<Neighbor> top_k_by_docid(uint32_t k, const BoundDistanceFunction &df,
const GlobalFilter *filter, uint32_t explore_k,
double distance_threshold) const;
@@ -185,7 +186,7 @@ protected:
void internal_complete_add(uint32_t docid, internal::PreparedAddDoc &op);
void internal_complete_add_node(uint32_t nodeid, uint32_t docid, uint32_t subspace, internal::PreparedAddNode &prepared_node);
public:
- HnswIndex(const DocVectorAccess& vectors, DistanceFunction::UP distance_func,
+ HnswIndex(const DocVectorAccess& vectors, DistanceFunctionFactory::UP distance_ff,
RandomLevelGenerator::UP level_generator, const HnswIndexConfig& cfg);
~HnswIndex() override;
@@ -213,14 +214,23 @@ public:
std::unique_ptr<NearestNeighborIndexSaver> make_saver() const override;
std::unique_ptr<NearestNeighborIndexLoader> make_loader(FastOS_FileInterface& file) override;
- std::vector<Neighbor> find_top_k(uint32_t k, TypedCells vector, uint32_t explore_k,
- double distance_threshold) const override;
- std::vector<Neighbor> find_top_k_with_filter(uint32_t k, TypedCells vector,
- const GlobalFilter &filter, uint32_t explore_k,
- double distance_threshold) const override;
- const DistanceFunction *distance_function() const override { return _distance_func.get(); }
+ std::vector<Neighbor> find_top_k(
+ uint32_t k,
+ const BoundDistanceFunction &df,
+ uint32_t explore_k,
+ double distance_threshold) const override;
- SearchBestNeighbors top_k_candidates(const TypedCells &vector, uint32_t k, const GlobalFilter *filter) const;
+ std::vector<Neighbor> find_top_k_with_filter(
+ uint32_t k,
+ const BoundDistanceFunction &df,
+ const GlobalFilter &filter, uint32_t explore_k,
+ double distance_threshold) const override;
+
+ DistanceFunctionFactory &distance_function_factory() const override { return *_distance_ff; }
+
+ SearchBestNeighbors top_k_candidates(
+ const BoundDistanceFunction &df,
+ uint32_t k, const GlobalFilter *filter) const;
uint32_t get_entry_nodeid() const { return _graph.get_entry_node().nodeid; }
int32_t get_entry_level() const { return _graph.get_entry_node().level; }
diff --git a/searchlib/src/vespa/searchlib/tensor/i_tensor_attribute.h b/searchlib/src/vespa/searchlib/tensor/i_tensor_attribute.h
index ec6774c9517..b734663a6f4 100644
--- a/searchlib/src/vespa/searchlib/tensor/i_tensor_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/i_tensor_attribute.h
@@ -12,6 +12,7 @@ namespace vespalib::slime { struct Inserter; }
namespace search::tensor {
+struct DistanceFunctionFactory;
class NearestNeighborIndex;
class SerializedTensorRef;
@@ -32,6 +33,7 @@ public:
virtual const vespalib::eval::ValueType & getTensorType() const = 0;
+ virtual DistanceFunctionFactory& distance_function_factory() const = 0;
virtual const NearestNeighborIndex* nearest_neighbor_index() const { return nullptr; }
using DistanceMetric = search::attribute::DistanceMetric;
virtual DistanceMetric distance_metric() const = 0;
diff --git a/searchlib/src/vespa/searchlib/tensor/imported_tensor_attribute_vector_read_guard.h b/searchlib/src/vespa/searchlib/tensor/imported_tensor_attribute_vector_read_guard.h
index 4e1cc9efd96..0fb0fd1bf78 100644
--- a/searchlib/src/vespa/searchlib/tensor/imported_tensor_attribute_vector_read_guard.h
+++ b/searchlib/src/vespa/searchlib/tensor/imported_tensor_attribute_vector_read_guard.h
@@ -38,6 +38,9 @@ public:
SerializedTensorRef get_serialized_tensor_ref(uint32_t docid) const override;
bool supports_extract_cells_ref() const override { return _target_tensor_attribute.supports_extract_cells_ref(); }
bool supports_get_tensor_ref() const override { return _target_tensor_attribute.supports_get_tensor_ref(); }
+ DistanceFunctionFactory& distance_function_factory() const override {
+ return _target_tensor_attribute.distance_function_factory();
+ }
DistanceMetric distance_metric() const override { return _target_tensor_attribute.distance_metric(); }
bool supports_get_serialized_tensor_ref() const override;
uint32_t get_num_docs() const override { return getNumDocs(); }
diff --git a/searchlib/src/vespa/searchlib/tensor/inner_product_distance.h b/searchlib/src/vespa/searchlib/tensor/inner_product_distance.h
index 8ba14580885..135bb186fd4 100644
--- a/searchlib/src/vespa/searchlib/tensor/inner_product_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/inner_product_distance.h
@@ -54,7 +54,7 @@ public:
auto rhs_vector = rhs.typify<FloatType>();
size_t sz = lhs_vector.size();
assert(sz == rhs_vector.size());
- double score = 1.0 - _computer.dotProduct(&lhs_vector[0], &rhs_vector[0], sz);
+ double score = 1.0 - _computer.dotProduct(lhs_vector.data(), rhs_vector.data(), sz);
return std::max(0.0, score);
}
private:
diff --git a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
index cb074348f08..5d3b2206703 100644
--- a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.cpp
@@ -12,7 +12,7 @@ using vespalib::alloc::MemoryAllocator;
namespace search::tensor {
LargeSubspacesBufferType::LargeSubspacesBufferType(const AllocSpec& spec, std::shared_ptr<MemoryAllocator> memory_allocator, TensorBufferTypeMapper& type_mapper) noexcept
- : ParentType(1u, spec.minArraysInBuffer, spec.maxArraysInBuffer, spec.numArraysForNewBuffer, spec.allocGrowFactor),
+ : ParentType(1u, spec.min_entries_in_buffer, spec.max_entries_in_buffer, spec.num_entries_for_new_buffer, spec.allocGrowFactor),
_memory_allocator(std::move(memory_allocator)),
_ops(type_mapper.get_tensor_buffer_operations())
{
@@ -21,10 +21,10 @@ LargeSubspacesBufferType::LargeSubspacesBufferType(const AllocSpec& spec, std::s
LargeSubspacesBufferType::~LargeSubspacesBufferType() = default;
void
-LargeSubspacesBufferType::cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx)
+LargeSubspacesBufferType::clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx)
{
auto elem = static_cast<ArrayType*>(buffer) + offset;
- for (size_t i = 0; i < numElems; ++i) {
+ for (size_t i = 0; i < num_entries; ++i) {
if (!elem->empty()) {
cleanCtx.extraBytesCleaned(elem->size());
_ops.reclaim_labels({elem->data(), elem->size()});
@@ -35,10 +35,10 @@ LargeSubspacesBufferType::cleanHold(void* buffer, size_t offset, ElemCount numEl
}
void
-LargeSubspacesBufferType::destroyElements(void *buffer, ElemCount numElems)
+LargeSubspacesBufferType::destroy_entries(void *buffer, EntryCount num_entries)
{
auto elem = static_cast<ArrayType*>(buffer);
- for (size_t i = 0; i < numElems; ++i) {
+ for (size_t i = 0; i < num_entries; ++i) {
if (!elem->empty()) {
_ops.reclaim_labels({elem->data(), elem->size()});
ArrayType().swap(*elem);
@@ -48,11 +48,11 @@ LargeSubspacesBufferType::destroyElements(void *buffer, ElemCount numElems)
}
void
-LargeSubspacesBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems)
+LargeSubspacesBufferType::fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries)
{
auto old_elems = static_cast<const ArrayType*>(oldBuffer);
auto new_elems = static_cast<ArrayType*>(newBuffer);
- for (size_t i = 0; i < numElems; ++i) {
+ for (size_t i = 0; i < num_entries; ++i) {
auto& old_elem = old_elems[i];
new (new_elems + i) ArrayType(old_elem);
if (!old_elem.empty()) {
@@ -62,12 +62,12 @@ LargeSubspacesBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, E
}
void
-LargeSubspacesBufferType::initializeReservedElements(void *buffer, ElemCount reservedElements)
+LargeSubspacesBufferType::initialize_reserved_entries(void *buffer, EntryCount reserved_entries)
{
- auto new_elems = static_cast<ArrayType*>(buffer);
+ auto new_entries = static_cast<ArrayType*>(buffer);
const auto& empty = empty_entry();
- for (size_t i = 0; i < reservedElements; ++i) {
- new (new_elems + i) ArrayType(empty);
+ for (size_t i = 0; i < reserved_entries; ++i) {
+ new (new_entries + i) ArrayType(empty);
}
}
diff --git a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.h b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.h
index cfab8ef20af..8cce08e9d81 100644
--- a/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.h
+++ b/searchlib/src/vespa/searchlib/tensor/large_subspaces_buffer_type.h
@@ -30,10 +30,10 @@ class LargeSubspacesBufferType : public vespalib::datastore::BufferType<vespalib
public:
LargeSubspacesBufferType(const AllocSpec& spec, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator, TensorBufferTypeMapper& type_mapper) noexcept;
~LargeSubspacesBufferType() override;
- void cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
- void destroyElements(void *buffer, ElemCount numElems) override;
- void fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems) override;
- void initializeReservedElements(void *buffer, ElemCount reservedElements) override;
+ void clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
+ void destroy_entries(void *buffer, EntryCount num_entries) override;
+ void fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries) override;
+ void initialize_reserved_entries(void *buffer, EntryCount reserved_entries) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
diff --git a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
index 3f6c9b82a65..4b7b934fee0 100644
--- a/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
+++ b/searchlib/src/vespa/searchlib/tensor/nearest_neighbor_index.h
@@ -3,6 +3,7 @@
#pragma once
#include "distance_function.h"
+#include "distance_function_factory.h"
#include "prepare_result.h"
#include "vector_bundle.h"
#include <vespa/vespalib/util/generationhandler.h>
@@ -97,18 +98,18 @@ public:
virtual std::unique_ptr<NearestNeighborIndexLoader> make_loader(FastOS_FileInterface& file) = 0;
virtual std::vector<Neighbor> find_top_k(uint32_t k,
- vespalib::eval::TypedCells vector,
+ const BoundDistanceFunction &df,
uint32_t explore_k,
double distance_threshold) const = 0;
// only return neighbors where the corresponding filter bit is set
virtual std::vector<Neighbor> find_top_k_with_filter(uint32_t k,
- vespalib::eval::TypedCells vector,
+ const BoundDistanceFunction &df,
const GlobalFilter &filter,
uint32_t explore_k,
double distance_threshold) const = 0;
- virtual const DistanceFunction *distance_function() const = 0;
+ virtual DistanceFunctionFactory &distance_function_factory() const = 0;
};
}
diff --git a/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp
new file mode 100644
index 00000000000..292edc1259d
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp
@@ -0,0 +1,81 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "prenormalized_angular_distance.h"
+#include "temporary_vector_store.h"
+
+using vespalib::typify_invoke;
+using vespalib::eval::TypifyCellType;
+
+namespace search::tensor {
+
+template<typename FloatType>
+class BoundPrenormalizedAngularDistance : public BoundDistanceFunction {
+private:
+ const vespalib::hwaccelrated::IAccelrated & _computer;
+ mutable TemporaryVectorStore<FloatType> _tmpSpace;
+ const vespalib::ConstArrayRef<FloatType> _lhs;
+ double _lhs_norm_sq;
+public:
+ BoundPrenormalizedAngularDistance(const vespalib::eval::TypedCells& lhs)
+ : _computer(vespalib::hwaccelrated::IAccelrated::getAccelerator()),
+ _tmpSpace(lhs.size),
+ _lhs(_tmpSpace.storeLhs(lhs))
+ {
+ auto a = _lhs.data();
+ _lhs_norm_sq = _computer.dotProduct(a, a, lhs.size);
+ if (_lhs_norm_sq <= 0.0) {
+ _lhs_norm_sq = 1.0;
+ }
+ }
+ double calc(const vespalib::eval::TypedCells& rhs) const override {
+ size_t sz = _lhs.size();
+ vespalib::ConstArrayRef<FloatType> rhs_vector = _tmpSpace.convertRhs(rhs);
+ assert(sz == rhs_vector.size());
+ auto a = _lhs.data();
+ auto b = rhs_vector.data();
+ double dot_product = _computer.dotProduct(a, b, sz);
+ double distance = _lhs_norm_sq - dot_product;
+ return distance;
+ }
+ double convert_threshold(double threshold) const override {
+ double cosine_similarity = 1.0 - threshold;
+ double dot_product = cosine_similarity * _lhs_norm_sq;
+ double distance = _lhs_norm_sq - dot_product;
+ return distance;
+ }
+ double to_rawscore(double distance) const override {
+ double dot_product = _lhs_norm_sq - distance;
+ double cosine_similarity = dot_product / _lhs_norm_sq;
+ // should be in in range [-1,1] but roundoff may cause problems:
+ cosine_similarity = std::min(1.0, cosine_similarity);
+ cosine_similarity = std::max(-1.0, cosine_similarity);
+ double cosine_distance = 1.0 - cosine_similarity; // in range [0,2]
+ double score = 1.0 / (1.0 + cosine_distance);
+ return score;
+ }
+ double calc_with_limit(const vespalib::eval::TypedCells& rhs, double) const override {
+ return calc(rhs);
+ }
+};
+
+template class BoundPrenormalizedAngularDistance<float>;
+template class BoundPrenormalizedAngularDistance<double>;
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+PrenormalizedAngularDistanceFunctionFactory<FloatType>::for_query_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundPrenormalizedAngularDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template <typename FloatType>
+BoundDistanceFunction::UP
+PrenormalizedAngularDistanceFunctionFactory<FloatType>::for_insertion_vector(const vespalib::eval::TypedCells& lhs) {
+ using DFT = BoundPrenormalizedAngularDistance<FloatType>;
+ return std::make_unique<DFT>(lhs);
+}
+
+template class PrenormalizedAngularDistanceFunctionFactory<float>;
+template class PrenormalizedAngularDistanceFunctionFactory<double>;
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.h b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.h
new file mode 100644
index 00000000000..88953a236e7
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.h
@@ -0,0 +1,27 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "distance_function.h"
+#include "bound_distance_function.h"
+#include "distance_function_factory.h"
+#include <vespa/eval/eval/typed_cells.h>
+#include <vespa/vespalib/hwaccelrated/iaccelrated.h>
+
+namespace search::tensor {
+
+/**
+ * Calculates inner-product "distance" between vectors with assumed norm 1.
+ * Should give same ordering as Angular distance, but is less expensive.
+ */
+template <typename FloatType>
+class PrenormalizedAngularDistanceFunctionFactory : public DistanceFunctionFactory {
+public:
+ PrenormalizedAngularDistanceFunctionFactory()
+ : DistanceFunctionFactory(vespalib::eval::get_cell_type<FloatType>())
+ {}
+ BoundDistanceFunction::UP for_query_vector(const vespalib::eval::TypedCells& lhs) override;
+ BoundDistanceFunction::UP for_insertion_vector(const vespalib::eval::TypedCells& lhs) override;
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
index 6a71388a3b9..7b54182f062 100644
--- a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.cpp
@@ -10,7 +10,7 @@ using vespalib::alloc::MemoryAllocator;
namespace search::tensor {
SmallSubspacesBufferType::SmallSubspacesBufferType(uint32_t array_size, const AllocSpec& spec, std::shared_ptr<MemoryAllocator> memory_allocator, TensorBufferTypeMapper& type_mapper) noexcept
- : ParentType(array_size, spec.minArraysInBuffer, spec.maxArraysInBuffer, spec.numArraysForNewBuffer, spec.allocGrowFactor),
+ : ParentType(array_size, spec.min_entries_in_buffer, spec.max_entries_in_buffer, spec.num_entries_for_new_buffer, spec.allocGrowFactor),
_memory_allocator(std::move(memory_allocator)),
_ops(type_mapper.get_tensor_buffer_operations())
{
@@ -19,45 +19,45 @@ SmallSubspacesBufferType::SmallSubspacesBufferType(uint32_t array_size, const Al
SmallSubspacesBufferType::~SmallSubspacesBufferType() = default;
void
-SmallSubspacesBufferType::cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext)
+SmallSubspacesBufferType::clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext)
{
- char* elem = static_cast<char *>(buffer) + offset;
- while (numElems >= getArraySize()) {
+ char* elem = static_cast<char *>(buffer) + offset * getArraySize();
+ while (num_entries >= 1) {
_ops.reclaim_labels(vespalib::ArrayRef<char>(elem, getArraySize()));
elem += getArraySize();
- numElems -= getArraySize();
+ --num_entries;
}
}
void
-SmallSubspacesBufferType::destroyElements(void *buffer, ElemCount numElems)
+SmallSubspacesBufferType::destroy_entries(void *buffer, EntryCount num_entries)
{
char* elem = static_cast<char *>(buffer);
- while (numElems >= getArraySize()) {
+ while (num_entries >= 1) {
_ops.reclaim_labels(vespalib::ArrayRef<char>(elem, getArraySize()));
elem += getArraySize();
- numElems -= getArraySize();
+ --num_entries;
}
}
void
-SmallSubspacesBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems)
+SmallSubspacesBufferType::fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries)
{
- if (numElems > 0) {
- memcpy(newBuffer, oldBuffer, numElems);
+ if (num_entries > 0) {
+ memcpy(newBuffer, oldBuffer, num_entries * getArraySize());
}
const char *elem = static_cast<const char *>(oldBuffer);
- while (numElems >= getArraySize()) {
+ while (num_entries >= 1) {
_ops.copied_labels(unconstify(vespalib::ConstArrayRef<char>(elem, getArraySize())));
elem += getArraySize();
- numElems -= getArraySize();
+ --num_entries;
}
}
void
-SmallSubspacesBufferType::initializeReservedElements(void *buffer, ElemCount reservedElements)
+SmallSubspacesBufferType::initialize_reserved_entries(void *buffer, EntryCount reserved_entries)
{
- memset(buffer, 0, reservedElements);
+ memset(buffer, 0, reserved_entries * getArraySize());
}
const vespalib::alloc::MemoryAllocator*
diff --git a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.h b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.h
index 5622e9970b8..2f287ef1f3d 100644
--- a/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.h
+++ b/searchlib/src/vespa/searchlib/tensor/small_subspaces_buffer_type.h
@@ -30,10 +30,10 @@ public:
SmallSubspacesBufferType& operator=(SmallSubspacesBufferType&&) noexcept = delete;
SmallSubspacesBufferType(uint32_t array_size, const AllocSpec& spec, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator, TensorBufferTypeMapper& type_mapper) noexcept;
~SmallSubspacesBufferType() override;
- void cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
- void destroyElements(void *buffer, ElemCount numElems) override;
- void fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems) override;
- void initializeReservedElements(void *buffer, ElemCount reservedElements) override;
+ void clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
+ void destroy_entries(void *buffer, EntryCount num_entries) override;
+ void fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries) override;
+ void initialize_reserved_entries(void *buffer, EntryCount reserved_entries) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
diff --git a/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.cpp b/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.cpp
new file mode 100644
index 00000000000..cc45f857d9f
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.cpp
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "temporary_vector_store.h"
+
+#include <vespa/log/log.h>
+
+LOG_SETUP(".searchlib.tensor.temporary_vector_store");
+
+using vespalib::ConstArrayRef;
+using vespalib::ArrayRef;
+using vespalib::eval::CellType;
+using vespalib::eval::TypedCells;
+
+namespace search::tensor {
+
+namespace {
+
+template<typename FromType, typename ToType>
+ConstArrayRef<ToType>
+convert_cells(ArrayRef<ToType> space, TypedCells cells)
+{
+ assert(cells.size == space.size());
+ auto old_cells = cells.typify<FromType>();
+ ToType *p = space.data();
+ for (FromType value : old_cells) {
+ ToType conv(value);
+ *p++ = conv;
+ }
+ return space;
+}
+
+template <typename ToType>
+struct ConvertCellsSelector
+{
+ template <typename FromType> static auto invoke(ArrayRef<ToType> dst, TypedCells src) {
+ return convert_cells<FromType, ToType>(dst, src);
+ }
+};
+
+} // namespace
+
+template <typename FloatType>
+ConstArrayRef<FloatType>
+TemporaryVectorStore<FloatType>::internal_convert(TypedCells cells, size_t offset) {
+ LOG_ASSERT(cells.size * 2 == _tmpSpace.size());
+ ArrayRef<FloatType> where(_tmpSpace.data() + offset, cells.size);
+ using MyTypify = vespalib::eval::TypifyCellType;
+ using MySelector = ConvertCellsSelector<FloatType>;
+ ConstArrayRef<FloatType> result = vespalib::typify_invoke<1,MyTypify,MySelector>(cells.type, where, cells);
+ return result;
+}
+
+template class TemporaryVectorStore<vespalib::eval::Int8Float>;
+template class TemporaryVectorStore<float>;
+template class TemporaryVectorStore<double>;
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.h b/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.h
new file mode 100644
index 00000000000..cd816621f91
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/temporary_vector_store.h
@@ -0,0 +1,32 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include <memory>
+#include <vespa/eval/eval/cell_type.h>
+#include <vespa/eval/eval/typed_cells.h>
+#include <vespa/vespalib/util/arrayref.h>
+
+namespace search::tensor {
+
+/** helper class - temporary storage of possibly-converted vector cells */
+template <typename FloatType>
+class TemporaryVectorStore {
+private:
+ std::vector<FloatType> _tmpSpace;
+ vespalib::ConstArrayRef<FloatType> internal_convert(vespalib::eval::TypedCells cells, size_t offset);
+public:
+ TemporaryVectorStore(size_t vectorSize) : _tmpSpace(vectorSize * 2) {}
+ vespalib::ConstArrayRef<FloatType> storeLhs(vespalib::eval::TypedCells cells) {
+ return internal_convert(cells, 0);
+ }
+ vespalib::ConstArrayRef<FloatType> convertRhs(vespalib::eval::TypedCells cells) {
+ if (vespalib::eval::get_cell_type<FloatType>() == cells.type) [[likely]] {
+ return cells.unsafe_typify<FloatType>();
+ } else {
+ return internal_convert(cells, cells.size);
+ }
+ }
+};
+
+}
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
index 1e388199ef8..5e554f76779 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.cpp
@@ -58,6 +58,7 @@ TensorAttribute::TensorAttribute(vespalib::stringref name, const Config &cfg, Te
: NotImplementedAttribute(name, cfg),
_refVector(cfg.getGrowStrategy(), getGenerationHolder()),
_tensorStore(tensorStore),
+ _distance_function_factory(make_distance_function_factory(cfg.distance_metric(), cfg.tensorType().cell_type())),
_index(),
_is_dense(cfg.tensorType().is_dense()),
_emptyTensor(createEmptyTensor(cfg.tensorType())),
@@ -280,6 +281,13 @@ TensorAttribute::getTensorType() const
return getConfig().tensorType();
}
+DistanceFunctionFactory&
+TensorAttribute::distance_function_factory() const
+{
+ return *_distance_function_factory;
+
+}
+
const NearestNeighborIndex*
TensorAttribute::nearest_neighbor_index() const
{
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
index 4cb903c6c67..f629562a34d 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_attribute.h
@@ -28,6 +28,7 @@ protected:
RefVector _refVector; // docId -> ref in data store for serialized tensor
TensorStore &_tensorStore; // data store for serialized tensors
+ std::unique_ptr<DistanceFunctionFactory> _distance_function_factory;
std::unique_ptr<NearestNeighborIndex> _index;
bool _is_dense;
std::unique_ptr<vespalib::eval::Value> _emptyTensor;
@@ -67,6 +68,7 @@ public:
bool supports_get_tensor_ref() const override { return false; }
bool supports_get_serialized_tensor_ref() const override;
const vespalib::eval::ValueType & getTensorType() const override;
+ DistanceFunctionFactory& distance_function_factory() const override;
const NearestNeighborIndex* nearest_neighbor_index() const override;
void get_state(const vespalib::slime::Inserter& inserter) const override;
void clearDocs(DocId lidLow, DocId lidLimit, bool in_shrink_lid_space) override;
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.cpp b/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.cpp
index 19c8cf6053b..f474d65a19d 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.cpp
@@ -36,6 +36,7 @@ TensorExtAttribute::TensorExtAttribute(const vespalib::string& name, const Confi
: NotImplementedAttribute(name, cfg),
ITensorAttribute(),
IExtendAttribute(),
+ _distance_function_factory(make_distance_function_factory(cfg.distance_metric(), cfg.tensorType().cell_type())),
_subspace_type(cfg.tensorType()),
_empty(_subspace_type),
_empty_tensor(create_empty_tensor(cfg.tensorType()))
diff --git a/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.h b/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.h
index a58426cd146..93d7a94c257 100644
--- a/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.h
+++ b/searchlib/src/vespa/searchlib/tensor/tensor_ext_attribute.h
@@ -5,6 +5,7 @@
#include "i_tensor_attribute.h"
#include "empty_subspace.h"
#include "subspace_type.h"
+#include "distance_function_factory.h"
#include <vespa/searchlib/attribute/not_implemented_attribute.h>
#include <vespa/vespalib/stllike/allocator.h>
@@ -20,9 +21,12 @@ class TensorExtAttribute : public NotImplementedAttribute,
public IExtendAttribute
{
std::vector<const vespalib::eval::Value*> _data;
+ // XXX this should probably be longer-lived:
+ std::unique_ptr<DistanceFunctionFactory> _distance_function_factory;
SubspaceType _subspace_type;
EmptySubspace _empty;
std::unique_ptr<vespalib::eval::Value> _empty_tensor;
+
public:
TensorExtAttribute(const vespalib::string& name, const Config& cfg);
~TensorExtAttribute() override;
@@ -46,6 +50,9 @@ public:
bool supports_get_tensor_ref() const override;
bool supports_get_serialized_tensor_ref() const override;
const vespalib::eval::ValueType & getTensorType() const override;
+ DistanceFunctionFactory& distance_function_factory() const override {
+ return *_distance_function_factory;
+ }
search::attribute::DistanceMetric distance_metric() const override;
uint32_t get_num_docs() const override;
void get_state(const vespalib::slime::Inserter& inserter) const override;
diff --git a/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.cpp b/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.cpp
index a030dcb0640..2edd00ad5a5 100644
--- a/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.cpp
+++ b/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.cpp
@@ -13,7 +13,7 @@ vespalib::string indirectKeyMarker("attribute(");
}
std::unique_ptr<AttributeNode>
-makeAttributeMapLookupNode(const vespalib::string attributeName)
+makeAttributeMapLookupNode(vespalib::stringref attributeName)
{
vespalib::asciistream keyName;
vespalib::asciistream valueName;
diff --git a/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.h b/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.h
index f56cea962d3..baa074b4004 100644
--- a/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.h
+++ b/searchlib/src/vespa/searchlib/test/make_attribute_map_lookup_node.h
@@ -9,6 +9,6 @@ namespace search::expression { class AttributeNode; }
namespace search::expression::test {
std::unique_ptr<AttributeNode>
-makeAttributeMapLookupNode(const vespalib::string attributeName);
+makeAttributeMapLookupNode(vespalib::stringref attributeName);
}
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
index 2c644a243c8..a765208cb9e 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
+++ b/searchsummary/src/vespa/searchsummary/docsummary/docsumstate.h
@@ -3,9 +3,9 @@
#pragma once
#include "getdocsumargs.h"
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/common/geo_location_spec.h>
#include <vespa/vespalib/stllike/hash_map.h>
+#include <vespa/vespalib/util/featureset.h>
#include <vespa/vespalib/util/stash.h>
namespace juniper {
@@ -48,6 +48,7 @@ protected:
class GetDocsumsState
{
public:
+ using FeatureSet = vespalib::FeatureSet;
const search::attribute::IAttributeVector * getAttribute(size_t index) const { return _attributes[index]; }
GetDocsumArgs _args; // from getdocsums request
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
index bad1ad5a6f3..c5e823bf9f4 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/rankfeaturesdfw.cpp
@@ -5,6 +5,8 @@
#include <vespa/vespalib/data/slime/cursor.h>
#include <vespa/vespalib/data/slime/inserter.h>
+using vespalib::FeatureSet;
+
namespace search::docsummary {
RankFeaturesDFW::RankFeaturesDFW() = default;
diff --git a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
index a680b01d887..a1b2d6b3af6 100644
--- a/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
+++ b/searchsummary/src/vespa/searchsummary/docsummary/summaryfeaturesdfw.cpp
@@ -5,8 +5,7 @@
#include <vespa/vespalib/data/slime/cursor.h>
#include <vespa/vespalib/data/slime/inserter.h>
-#include <vespa/log/log.h>
-LOG_SETUP(".searchlib.docsummary.summaryfeaturesdfw");
+using vespalib::FeatureSet;
namespace search::docsummary {
diff --git a/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java b/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java
index 1f160d94c6a..bd085f6f624 100644
--- a/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java
+++ b/security-utils/src/main/java/com/yahoo/security/SideChannelSafe.java
@@ -46,7 +46,7 @@ public class SideChannelSafe {
// differed in any byte compared between the two arrays.
byte accu = 0;
for (int i = 0; i < lhs.length; ++i) {
- accu |= (lhs[i] ^ rhs[i]);
+ accu |= (byte)(lhs[i] ^ rhs[i]);
}
return (accu == 0);
}
diff --git a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
index 26627e9a5fa..90b8beb461f 100644
--- a/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
+++ b/security-utils/src/test/java/com/yahoo/security/SharedKeyTest.java
@@ -285,12 +285,12 @@ public class SharedKeyTest {
String plaintext = "...hello world?";
byte[] encrypted = streamEncryptString(plaintext, myShared);
// Corrupt MAC tag in ciphertext
- encrypted[encrypted.length - 1] ^= 0x80;
+ encrypted[encrypted.length - 1] ^= (byte)0x80;
// We don't necessarily know _which_ exception is thrown, but one _should_ be thrown!
assertThrows(Exception.class, () -> doOutputStreamCipherDecrypt(myShared, encrypted));
// Also try with corrupted ciphertext (pre MAC tag)
- encrypted[encrypted.length - 1] ^= 0x80; // Flip MAC bit back to correct state
- encrypted[encrypted.length - 17] ^= 0x80; // Pre 128-bit MAC tag
+ encrypted[encrypted.length - 1] ^= (byte)0x80; // Flip MAC bit back to correct state
+ encrypted[encrypted.length - 17] ^= (byte)0x80; // Pre 128-bit MAC tag
assertThrows(Exception.class, () -> doOutputStreamCipherDecrypt(myShared, encrypted));
}
diff --git a/storage/src/tests/distributor/CMakeLists.txt b/storage/src/tests/distributor/CMakeLists.txt
index c59b56eb68f..11bbf2c241a 100644
--- a/storage/src/tests/distributor/CMakeLists.txt
+++ b/storage/src/tests/distributor/CMakeLists.txt
@@ -19,7 +19,6 @@ vespa_add_executable(storage_distributor_gtest_runner_app TEST
externaloperationhandlertest.cpp
garbagecollectiontest.cpp
getoperationtest.cpp
- gtest_runner.cpp
idealstatemanagertest.cpp
joinbuckettest.cpp
maintenancemocks.cpp
@@ -27,6 +26,7 @@ vespa_add_executable(storage_distributor_gtest_runner_app TEST
mergelimitertest.cpp
mergeoperationtest.cpp
multi_thread_stripe_access_guard_test.cpp
+ newest_replica_test.cpp
node_supported_features_repo_test.cpp
nodeinfotest.cpp
nodemaintenancestatstrackertest.cpp
@@ -57,7 +57,7 @@ vespa_add_executable(storage_distributor_gtest_runner_app TEST
storage_testcommon
storage_testhostreporter
storage
- GTest::GTest
+ GTest::gmock_main
)
vespa_add_test(
diff --git a/storage/src/tests/distributor/getoperationtest.cpp b/storage/src/tests/distributor/getoperationtest.cpp
index 36a1495579f..6601fcabbeb 100644
--- a/storage/src/tests/distributor/getoperationtest.cpp
+++ b/storage/src/tests/distributor/getoperationtest.cpp
@@ -19,11 +19,11 @@
#include <vespa/vespalib/gtest/gtest.h>
#include <iomanip>
-using std::shared_ptr;
using config::ConfigGetter;
using config::FileSpec;
using document::test::makeDocumentBucket;
using document::BucketId;
+using documentapi::TestAndSetCondition;
using namespace ::testing;
namespace storage::distributor {
@@ -54,23 +54,28 @@ struct GetOperationTest : Test, DistributorStripeTestUtil {
op.reset();
}
- void sendGet(api::InternalReadConsistency consistency = api::InternalReadConsistency::Strong) {
- auto msg = std::make_shared<api::GetCommand>(makeDocumentBucket(BucketId(0)), docId, document::AllFields::NAME);
+ void start_operation(std::shared_ptr<api::GetCommand> cmd, api::InternalReadConsistency consistency) {
op = std::make_unique<GetOperation>(
node_context(), getDistributorBucketSpace(),
getDistributorBucketSpace().getBucketDatabase().acquire_read_guard(),
- msg, metrics().gets,
+ std::move(cmd), metrics().gets,
consistency);
op->start(_sender);
}
+ void sendGet(api::InternalReadConsistency consistency = api::InternalReadConsistency::Strong) {
+ auto msg = std::make_shared<api::GetCommand>(makeDocumentBucket(BucketId(0)), docId, document::AllFields::NAME);
+ start_operation(std::move(msg), consistency);
+ }
+
static constexpr uint32_t LastCommand = UINT32_MAX;
void sendReply(uint32_t idx,
api::ReturnCode::Result result,
std::string authorVal,
uint32_t timestamp,
- bool is_tombstone = false)
+ bool is_tombstone = false,
+ bool condition_matched = false)
{
if (idx == LastCommand) {
idx = _sender.commands().size() - 1;
@@ -91,7 +96,7 @@ struct GetOperationTest : Test, DistributorStripeTestUtil {
document::StringFieldValue(authorVal));
}
- auto reply = std::make_shared<api::GetReply>(*tmp, doc, timestamp, false, is_tombstone);
+ auto reply = std::make_shared<api::GetReply>(*tmp, doc, timestamp, false, is_tombstone, condition_matched);
reply->setResult(result);
op->receive(_sender, reply);
@@ -101,6 +106,10 @@ struct GetOperationTest : Test, DistributorStripeTestUtil {
sendReply(idx, api::ReturnCode::OK, "", tombstone_ts, true);
}
+ void reply_with_condition_match(uint32_t idx, uint32_t timestamp, bool condition_match) {
+ sendReply(idx, api::ReturnCode::OK, "", timestamp, false, condition_match);
+ }
+
void replyWithFailure() {
sendReply(LastCommand, api::ReturnCode::IO_FAILURE, "", 0);
}
@@ -147,6 +156,7 @@ struct GetOperationTest : Test, DistributorStripeTestUtil {
}
void do_test_read_consistency_is_propagated(api::InternalReadConsistency consistency);
+ void set_up_condition_match_get_operation();
};
GetOperationTest::GetOperationTest() = default;
@@ -154,8 +164,10 @@ GetOperationTest::~GetOperationTest() = default;
namespace {
-NewestReplica replica_of(api::Timestamp ts, const document::BucketId& bucket_id, uint16_t node, bool is_tombstone) {
- return NewestReplica::of(ts, bucket_id, node, is_tombstone);
+NewestReplica replica_of(api::Timestamp ts, const document::BucketId& bucket_id, uint16_t node,
+ bool is_tombstone, bool condition_matched)
+{
+ return NewestReplica::of(ts, bucket_id, node, is_tombstone, condition_matched);
}
}
@@ -177,7 +189,7 @@ TEST_F(GetOperationTest, simple) {
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_TRUE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 0, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 0, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, ask_all_checksum_groups_if_inconsistent_even_if_trusted_replica_available) {
@@ -198,7 +210,7 @@ TEST_F(GetOperationTest, ask_all_checksum_groups_if_inconsistent_even_if_trusted
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(2), bucketId, 0, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(2), bucketId, 0, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, ask_all_nodes_if_bucket_is_inconsistent) {
@@ -221,7 +233,7 @@ TEST_F(GetOperationTest, ask_all_nodes_if_bucket_is_inconsistent) {
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(2), bucketId, 1, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(2), bucketId, 1, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, send_to_all_invalid_copies) {
@@ -289,7 +301,7 @@ TEST_F(GetOperationTest, inconsistent_split) {
EXPECT_FALSE(last_reply_had_consistent_replicas());
// Bucket with highest timestamp should be returned. In this case it's the one on node 0.
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(2), BucketId(16, 0x0593), 0, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(2), BucketId(16, 0x0593), 0, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, multi_inconsistent_bucket_not_found) {
@@ -333,7 +345,7 @@ TEST_F(GetOperationTest, multi_inconsistent_bucket_not_found_deleted) {
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(3), bucketId, 1, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(3), bucketId, 1, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, multi_inconsistent_bucket) {
@@ -383,7 +395,7 @@ TEST_F(GetOperationTest, multi_inconsistent_bucket_fail) {
EXPECT_FALSE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
// First send to node 2 fails, second is to node 3 which returned the highest timestamp
- EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 3, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 3, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, return_not_found_when_bucket_not_in_db) {
@@ -423,7 +435,7 @@ TEST_F(GetOperationTest, not_found) {
// the caller may want to perform special logic if all replicas are in sync
// but are missing the document.
// FIXME make sure all callers are aware of this!
- EXPECT_EQ(replica_of(api::Timestamp(0), bucketId, 0, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(0), bucketId, 0, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, not_found_on_subset_of_replicas_marks_get_as_inconsistent) {
@@ -468,7 +480,7 @@ TEST_F(GetOperationTest, resend_on_storage_failure) {
// Replica had read failure, but they're still in sync. An immutable Get won't change that fact.
EXPECT_TRUE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 2, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 2, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, storage_failure_of_out_of_sync_replica_is_tracked_as_inconsistent) {
@@ -485,7 +497,7 @@ TEST_F(GetOperationTest, storage_failure_of_out_of_sync_replica_is_tracked_as_in
EXPECT_TRUE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(3), bucketId, 2, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(3), bucketId, 2, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, resend_on_storage_failure_all_fail) {
@@ -534,7 +546,7 @@ TEST_F(GetOperationTest, send_to_ideal_copy_if_bucket_in_sync) {
_sender.getLastReply());
EXPECT_TRUE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 1, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 1, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, multiple_copies_with_failure_on_local_node) {
@@ -565,7 +577,7 @@ TEST_F(GetOperationTest, multiple_copies_with_failure_on_local_node) {
EXPECT_TRUE(op->any_replicas_failed());
EXPECT_TRUE(last_reply_had_consistent_replicas());
ASSERT_TRUE(op->newest_replica().has_value());
- EXPECT_EQ(replica_of(api::Timestamp(3), BucketId(16, 0x0593), 2, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(3), BucketId(16, 0x0593), 2, false, false), *op->newest_replica());
}
TEST_F(GetOperationTest, can_get_documents_when_all_replica_nodes_retired) {
@@ -610,7 +622,7 @@ TEST_F(GetOperationTest, replicas_considered_consistent_if_all_equal_tombstone_t
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_TRUE(last_reply_had_consistent_replicas());
EXPECT_FALSE(last_reply_has_document());
- EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 0, true), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(100), bucketId, 0, true, false), *op->newest_replica());
}
TEST_F(GetOperationTest, newer_tombstone_hides_older_document) {
@@ -628,7 +640,7 @@ TEST_F(GetOperationTest, newer_tombstone_hides_older_document) {
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
EXPECT_FALSE(last_reply_has_document());
- EXPECT_EQ(replica_of(api::Timestamp(200), bucketId, 1, true), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(200), bucketId, 1, true, false), *op->newest_replica());
}
TEST_F(GetOperationTest, older_tombstone_does_not_hide_newer_document) {
@@ -646,7 +658,88 @@ TEST_F(GetOperationTest, older_tombstone_does_not_hide_newer_document) {
EXPECT_FALSE(op->any_replicas_failed());
EXPECT_FALSE(last_reply_had_consistent_replicas());
EXPECT_TRUE(last_reply_has_document());
- EXPECT_EQ(replica_of(api::Timestamp(200), bucketId, 0, false), *op->newest_replica());
+ EXPECT_EQ(replica_of(api::Timestamp(200), bucketId, 0, false, false), *op->newest_replica());
+}
+
+TEST_F(GetOperationTest, provided_condition_is_propagated_to_sent_gets) {
+ setClusterState("distributor:1 storage:1");
+ addNodesToBucketDB(bucketId, "0=123");
+
+ TestAndSetCondition my_cond("my_cool_condition");
+ auto msg = std::make_shared<api::GetCommand>(makeDocumentBucket(BucketId(0)), docId, document::NoFields::NAME);
+ msg->set_condition(my_cond);
+
+ start_operation(std::move(msg), api::InternalReadConsistency::Strong);
+ ASSERT_EQ("Get => 0", _sender.getCommands(true));
+ auto& cmd = dynamic_cast<const api::GetCommand&>(*_sender.command(0));
+ EXPECT_EQ(cmd.condition().getSelection(), my_cond.getSelection());
+}
+
+void GetOperationTest::set_up_condition_match_get_operation() {
+ setClusterState("distributor:1 storage:3");
+ addNodesToBucketDB(bucketId, "0=100,2=200,1=300");
+
+ TestAndSetCondition my_cond("my_cool_condition");
+ auto msg = std::make_shared<api::GetCommand>(makeDocumentBucket(BucketId(0)), docId, document::NoFields::NAME);
+ msg->set_condition(my_cond);
+ start_operation(std::move(msg), api::InternalReadConsistency::Strong);
+
+ ASSERT_EQ("Get => 0,Get => 2,Get => 1", _sender.getCommands(true));
+}
+
+TEST_F(GetOperationTest, condition_match_result_is_aggregated_for_newest_replica_mismatch_case) {
+ ASSERT_NO_FATAL_FAILURE(set_up_condition_match_get_operation());
+ // node 0 (send index 0) has an old doc without a match
+ // node 2 (send index 1) has an old tombstone without match
+ // node 1 (send index 2) has a new doc without a match
+ // Newest replica should reflect node 1's results
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(0, 200, false));
+ ASSERT_NO_FATAL_FAILURE(reply_with_tombstone(1, 100));
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(2, 300, false));
+
+ ASSERT_EQ("GetReply(BucketId(0x0000000000000000), id:ns:text/html::uri, timestamp 300) ReturnCode(NONE)",
+ _sender.getLastReply());
+ EXPECT_FALSE(op->any_replicas_failed());
+ EXPECT_FALSE(last_reply_had_consistent_replicas());
+ EXPECT_FALSE(last_reply_has_document());
+ EXPECT_EQ(replica_of(api::Timestamp(300), bucketId, 1, false, false), *op->newest_replica());
+}
+
+TEST_F(GetOperationTest, condition_match_result_is_aggregated_for_newest_replica_match_case) {
+ ASSERT_NO_FATAL_FAILURE(set_up_condition_match_get_operation());
+ // node 0 (send index 0) has a new doc with a match
+ // node 2 (send index 1) has an old tombstone without match
+ // node 1 (send index 2) has an old doc without a match
+ // Newest replica should reflect node 0's results
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(0, 400, true));
+ ASSERT_NO_FATAL_FAILURE(reply_with_tombstone(1, 300));
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(2, 200, false));
+
+ ASSERT_EQ("GetReply(BucketId(0x0000000000000000), id:ns:text/html::uri, timestamp 400) ReturnCode(NONE)",
+ _sender.getLastReply());
+ EXPECT_FALSE(op->any_replicas_failed());
+ EXPECT_FALSE(last_reply_had_consistent_replicas());
+ EXPECT_FALSE(last_reply_has_document());
+ EXPECT_EQ(replica_of(api::Timestamp(400), bucketId, 0, false, true), *op->newest_replica());
+}
+
+TEST_F(GetOperationTest, condition_match_result_is_aggregated_for_newest_replica_tombstone_case) {
+ ASSERT_NO_FATAL_FAILURE(set_up_condition_match_get_operation());
+ // node 0 (send index 0) has an old doc with a match
+ // node 2 (send index 1) has a new tombstone without match
+ // node 1 (send index 2) has an old doc without a match
+ // Newest replica should reflect node 2's results
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(0, 400, true));
+ ASSERT_NO_FATAL_FAILURE(reply_with_tombstone(1, 500));
+ ASSERT_NO_FATAL_FAILURE(reply_with_condition_match(2, 300, false));
+
+ // Timestamp 0 in reply signals "not found" to clients
+ ASSERT_EQ("GetReply(BucketId(0x0000000000000000), id:ns:text/html::uri, timestamp 0) ReturnCode(NONE)",
+ _sender.getLastReply());
+ EXPECT_FALSE(op->any_replicas_failed());
+ EXPECT_FALSE(last_reply_had_consistent_replicas());
+ EXPECT_FALSE(last_reply_has_document());
+ EXPECT_EQ(replica_of(api::Timestamp(500), bucketId, 2, true, false), *op->newest_replica());
}
}
diff --git a/storage/src/tests/distributor/gtest_runner.cpp b/storage/src/tests/distributor/gtest_runner.cpp
deleted file mode 100644
index 7c20f681093..00000000000
--- a/storage/src/tests/distributor/gtest_runner.cpp
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include <vespa/vespalib/gtest/gtest.h>
-
-#include <vespa/log/log.h>
-LOG_SETUP("storage_distributor_gtest_runner");
-
-GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/storage/src/tests/distributor/newest_replica_test.cpp b/storage/src/tests/distributor/newest_replica_test.cpp
new file mode 100644
index 00000000000..9267c6e37a2
--- /dev/null
+++ b/storage/src/tests/distributor/newest_replica_test.cpp
@@ -0,0 +1,24 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <vespa/storage/distributor/operations/external/newest_replica.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vespalib/gtest/matchers/elements_are_distinct.h>
+
+using namespace ::testing;
+using storage::api::Timestamp;
+using document::BucketId;
+
+namespace storage::distributor {
+
+TEST(NewestReplicaTest, equality_predicate_considers_all_fields) {
+ std::vector elems = {
+ NewestReplica::of(Timestamp(1000), BucketId(16, 1), 0, false, false),
+ NewestReplica::of(Timestamp(1001), BucketId(16, 1), 0, false, false),
+ NewestReplica::of(Timestamp(1000), BucketId(16, 2), 0, false, false),
+ NewestReplica::of(Timestamp(1000), BucketId(16, 1), 1, false, false),
+ NewestReplica::of(Timestamp(1000), BucketId(16, 1), 0, true, false),
+ NewestReplica::of(Timestamp(1000), BucketId(16, 1), 0, false, true)
+ };
+ EXPECT_THAT(elems, ElementsAreDistinct());
+}
+
+}
diff --git a/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp b/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp
index d5d33a178fe..567e0a947da 100644
--- a/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp
+++ b/storage/src/tests/distributor/top_level_bucket_db_updater_test.cpp
@@ -2513,6 +2513,7 @@ TEST_F(TopLevelBucketDBUpdaterTest, node_feature_sets_are_aggregated_from_nodes_
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(i).unordered_merge_chaining);
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(i).two_phase_remove_location);
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(i).no_implicit_indexing_of_active_buckets);
+ EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(i).document_condition_probe);
}
}
@@ -2526,6 +2527,7 @@ TEST_F(TopLevelBucketDBUpdaterTest, node_feature_sets_are_aggregated_from_nodes_
reply.supported_node_features().unordered_merge_chaining = true;
reply.supported_node_features().two_phase_remove_location = true;
reply.supported_node_features().no_implicit_indexing_of_active_buckets = true;
+ reply.supported_node_features().document_condition_probe = true;
}
}));
}
@@ -2535,14 +2537,17 @@ TEST_F(TopLevelBucketDBUpdaterTest, node_feature_sets_are_aggregated_from_nodes_
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(0).unordered_merge_chaining);
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(0).two_phase_remove_location);
EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(0).no_implicit_indexing_of_active_buckets);
+ EXPECT_FALSE(s->node_supported_features_repo().node_supported_features(0).document_condition_probe);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(1).unordered_merge_chaining);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(1).two_phase_remove_location);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(1).no_implicit_indexing_of_active_buckets);
+ EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(1).document_condition_probe);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(2).unordered_merge_chaining);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(2).two_phase_remove_location);
EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(2).no_implicit_indexing_of_active_buckets);
+ EXPECT_TRUE(s->node_supported_features_repo().node_supported_features(2).document_condition_probe);
}
}
diff --git a/storage/src/tests/persistence/persistencetestutils.h b/storage/src/tests/persistence/persistencetestutils.h
index 94ae7b9fb53..e60260f3ee8 100644
--- a/storage/src/tests/persistence/persistencetestutils.h
+++ b/storage/src/tests/persistence/persistencetestutils.h
@@ -150,6 +150,18 @@ public:
_replySender, MockBucketLock::make(bucket, _mock_bucket_locks), std::move(cmd));
}
+ template <typename T>
+ requires std::is_base_of_v<api::StorageReply, T>
+ [[nodiscard]] std::shared_ptr<T>
+ fetch_single_reply(MessageTracker::UP tracker) {
+ if (tracker && tracker->hasReply()) {
+ tracker->sendReply(); // Forward to queue so we can fetch it below
+ }
+ std::shared_ptr<api::StorageMessage> msg;
+ _replySender.queue.getNext(msg, 60s);
+ return std::dynamic_pointer_cast<T>(msg);
+ }
+
api::ReturnCode
fetchResult(const MessageTracker::UP & tracker) {
if (tracker) {
diff --git a/storage/src/tests/persistence/testandsettest.cpp b/storage/src/tests/persistence/testandsettest.cpp
index 5be1c7cd92a..1aa359de634 100644
--- a/storage/src/tests/persistence/testandsettest.cpp
+++ b/storage/src/tests/persistence/testandsettest.cpp
@@ -1,16 +1,16 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
// @author Vegard Sjonfjell
-#include <vespa/storage/persistence/persistencehandler.h>
#include <tests/persistence/persistencetestutils.h>
#include <vespa/document/test/make_document_bucket.h>
-#include <vespa/documentapi/messagebus/messages/testandsetcondition.h>
#include <vespa/document/fieldvalue/fieldvalues.h>
#include <vespa/document/update/documentupdate.h>
#include <vespa/document/update/assignvalueupdate.h>
#include <vespa/document/fieldset/fieldsets.h>
+#include <vespa/documentapi/messagebus/messages/testandsetcondition.h>
#include <vespa/persistence/spi/test.h>
#include <vespa/persistence/spi/persistenceprovider.h>
#include <vespa/persistence/spi/docentry.h>
+#include <vespa/storage/persistence/persistencehandler.h>
#include <functional>
using std::unique_ptr;
@@ -19,6 +19,7 @@ using std::shared_ptr;
using storage::spi::test::makeSpiBucket;
using document::test::makeDocumentBucket;
using document::StringFieldValue;
+using documentapi::TestAndSetCondition;
using namespace ::testing;
namespace storage {
@@ -34,15 +35,18 @@ struct TestAndSetTest : PersistenceTestUtils {
const StringFieldValue OLD_CONTENT{"Some old content"};
const StringFieldValue NEW_CONTENT{"Freshly pressed and squeezed content"};
const document::Bucket BUCKET = makeDocumentBucket(BUCKET_ID);
+ const TestAndSetCondition MATCHING_CONDITION{"testdoctype1.hstringval=\"*woofy dog*\""};
unique_ptr<PersistenceHandler> persistenceHandler;
const AsyncHandler * asyncHandler;
+ const SimpleMessageHandler* simple_handler;
shared_ptr<document::Document> testDoc;
document::DocumentId testDocId;
TestAndSetTest()
: persistenceHandler(),
- asyncHandler(nullptr)
+ asyncHandler(nullptr),
+ simple_handler(nullptr)
{}
void SetUp() override {
@@ -54,6 +58,7 @@ struct TestAndSetTest : PersistenceTestUtils {
testDoc = createTestDocument();
testDocId = testDoc->getId();
asyncHandler = &_persistenceHandler->asyncHandler();
+ simple_handler = &_persistenceHandler->simpleMessageHandler();
}
void TearDown() override {
@@ -68,6 +73,8 @@ struct TestAndSetTest : PersistenceTestUtils {
document::Document::SP retrieveTestDocument();
void setTestCondition(api::TestAndSetCommand & command);
void putTestDocument(bool matchingHeader, api::Timestamp timestamp);
+ std::shared_ptr<api::GetReply> invoke_conditional_get();
+ void feed_remove_entry_with_timestamp(api::Timestamp timestamp);
void assertTestDocumentFoundAndMatchesContent(const document::FieldValue & value);
static std::string expectedDocEntryString(
@@ -247,6 +254,59 @@ TEST_F(TestAndSetTest, conditional_put_to_non_existing_document_should_fail) {
EXPECT_EQ("", dumpBucket(BUCKET_ID));
}
+TEST_F(TestAndSetTest, conditional_get_returns_doc_metadata_on_match) {
+ const api::Timestamp timestamp = 12345;
+ putTestDocument(true, timestamp);
+ auto reply = invoke_conditional_get();
+
+ ASSERT_EQ(reply->getResult(), api::ReturnCode());
+ EXPECT_EQ(reply->getLastModifiedTimestamp(), timestamp);
+ EXPECT_TRUE(reply->condition_matched());
+ EXPECT_FALSE(reply->is_tombstone());
+ // Checking reply->wasFound() is tempting but doesn't make sense here, as that checks for
+ // the presence of a document object, which metadata-only gets by definition do not return.
+}
+
+TEST_F(TestAndSetTest, conditional_get_returns_doc_metadata_on_mismatch) {
+ const api::Timestamp timestamp = 12345;
+ putTestDocument(false, timestamp);
+ auto reply = invoke_conditional_get();
+
+ ASSERT_EQ(reply->getResult(), api::ReturnCode());
+ EXPECT_EQ(reply->getLastModifiedTimestamp(), timestamp);
+ EXPECT_FALSE(reply->condition_matched());
+ EXPECT_FALSE(reply->is_tombstone());
+}
+
+TEST_F(TestAndSetTest, conditional_get_for_non_existing_document_returns_zero_timestamp) {
+ auto reply = invoke_conditional_get();
+
+ ASSERT_EQ(reply->getResult(), api::ReturnCode());
+ EXPECT_EQ(reply->getLastModifiedTimestamp(), 0);
+ EXPECT_FALSE(reply->condition_matched());
+ EXPECT_FALSE(reply->is_tombstone());
+}
+
+TEST_F(TestAndSetTest, conditional_get_for_non_existing_document_with_explicit_tombstone_returns_tombstone_timestamp) {
+ api::Timestamp timestamp = 56789;
+ feed_remove_entry_with_timestamp(timestamp);
+ auto reply = invoke_conditional_get();
+
+ ASSERT_EQ(reply->getResult(), api::ReturnCode());
+ EXPECT_EQ(reply->getLastModifiedTimestamp(), timestamp);
+ EXPECT_FALSE(reply->condition_matched());
+ EXPECT_TRUE(reply->is_tombstone());
+}
+
+TEST_F(TestAndSetTest, conditional_get_requires_metadata_only_fieldset) {
+ auto get = std::make_shared<api::GetCommand>(BUCKET, testDocId, document::AllFields::NAME);
+ get->set_condition(MATCHING_CONDITION);
+ // Note: uses fetchResult instead of fetch_single_reply due to implicit failure signalling via tracker instance.
+ auto result = fetchResult(simple_handler->handleGet(*get, createTracker(get, BUCKET)));
+ ASSERT_EQ(result, api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS,
+ "Conditional Get operations must be metadata-only"));
+}
+
document::Document::SP
TestAndSetTest::createTestDocument()
{
@@ -270,7 +330,7 @@ TestAndSetTest::retrieveTestDocument()
auto tracker = _persistenceHandler->simpleMessageHandler().handleGet(*get, createTracker(get, BUCKET));
assert(tracker->getResult() == api::ReturnCode::Result::OK);
- auto & reply = static_cast<api::GetReply &>(tracker->getReply());
+ auto& reply = dynamic_cast<api::GetReply&>(tracker->getReply());
assert(reply.wasFound());
return reply.getDocument();
@@ -278,7 +338,7 @@ TestAndSetTest::retrieveTestDocument()
void TestAndSetTest::setTestCondition(api::TestAndSetCommand & command)
{
- command.setCondition(documentapi::TestAndSetCondition("testdoctype1.hstringval=\"*woofy dog*\""));
+ command.setCondition(MATCHING_CONDITION);
}
void TestAndSetTest::putTestDocument(bool matchingHeader, api::Timestamp timestamp) {
@@ -290,6 +350,17 @@ void TestAndSetTest::putTestDocument(bool matchingHeader, api::Timestamp timesta
fetchResult(asyncHandler->handlePut(*put, createTracker(put, BUCKET)));
}
+std::shared_ptr<api::GetReply> TestAndSetTest::invoke_conditional_get() {
+ auto get = std::make_shared<api::GetCommand>(BUCKET, testDocId, document::NoFields::NAME);
+ get->set_condition(MATCHING_CONDITION);
+ return fetch_single_reply<api::GetReply>(simple_handler->handleGet(*get, createTracker(get, BUCKET)));
+}
+
+void TestAndSetTest::feed_remove_entry_with_timestamp(api::Timestamp timestamp) {
+ auto remove = std::make_shared<api::RemoveCommand>(BUCKET, testDocId, timestamp);
+ (void)fetchResult(asyncHandler->handleRemove(*remove, createTracker(remove, BUCKET)));
+}
+
void TestAndSetTest::assertTestDocumentFoundAndMatchesContent(const document::FieldValue & value)
{
auto doc = retrieveTestDocument();
diff --git a/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp b/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp
index d3036a2fad3..3a3a3a6b016 100644
--- a/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp
+++ b/storage/src/tests/storageapi/mbusprot/storageprotocoltest.cpp
@@ -289,6 +289,7 @@ TEST_P(StorageProtocolTest, get) {
EXPECT_EQ(Timestamp(123), reply2->getBeforeTimestamp());
EXPECT_EQ(Timestamp(100), reply2->getLastModifiedTimestamp());
EXPECT_FALSE(reply2->is_tombstone());
+ EXPECT_FALSE(reply2->condition_matched());
EXPECT_NO_FATAL_FAILURE(assert_bucket_info_reply_fields_propagated(*reply2));
}
@@ -310,6 +311,13 @@ TEST_P(StorageProtocolTest, can_set_internal_read_consistency_on_get_commands) {
EXPECT_EQ(cmd2->internal_read_consistency(), InternalReadConsistency::Strong);
}
+TEST_P(StorageProtocolTest, get_command_with_condition) {
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar,vekterli", 123);
+ cmd->set_condition(TestAndSetCondition(CONDITION_STRING));
+ auto cmd2 = copyCommand(cmd);
+ EXPECT_EQ(cmd->condition().getSelection(), cmd2->condition().getSelection());
+}
+
TEST_P(StorageProtocolTest, tombstones_propagated_for_gets) {
auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar", 123);
auto reply = std::make_shared<GetReply>(*cmd, std::shared_ptr<Document>(), 100, false, true);
@@ -323,6 +331,14 @@ TEST_P(StorageProtocolTest, tombstones_propagated_for_gets) {
EXPECT_TRUE(reply2->is_tombstone());
}
+TEST_P(StorageProtocolTest, condition_matched_propagated_for_get_result) {
+ auto cmd = std::make_shared<GetCommand>(_bucket, _testDocId, "foo,bar", 123);
+ auto reply = std::make_shared<GetReply>(*cmd, std::shared_ptr<Document>(), 100, false, false, true);
+ set_dummy_bucket_info_reply_fields(*reply);
+ auto reply2 = copyReply(reply);
+ EXPECT_TRUE(reply2->condition_matched());
+}
+
TEST_P(StorageProtocolTest, remove) {
auto cmd = std::make_shared<RemoveCommand>(_bucket, _testDocId, 159);
auto cmd2 = copyCommand(cmd);
@@ -390,6 +406,7 @@ TEST_P(StorageProtocolTest, request_bucket_info) {
EXPECT_TRUE(reply2->supported_node_features().unordered_merge_chaining);
EXPECT_TRUE(reply2->supported_node_features().two_phase_remove_location);
EXPECT_TRUE(reply2->supported_node_features().no_implicit_indexing_of_active_buckets);
+ EXPECT_TRUE(reply2->supported_node_features().document_condition_probe);
}
}
@@ -848,7 +865,7 @@ TEST_P(StorageProtocolTest, track_memory_footprint_for_some_messages) {
EXPECT_EQ(144u + sizeof(vespalib::string), sizeof(PutCommand));
EXPECT_EQ(144u + sizeof(vespalib::string), sizeof(UpdateCommand));
EXPECT_EQ(224u + sizeof(vespalib::string), sizeof(RemoveCommand));
- EXPECT_EQ(296u, sizeof(GetCommand));
+ EXPECT_EQ(296u + sizeof(documentapi::TestAndSetCondition), sizeof(GetCommand));
}
} // storage::api
diff --git a/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp b/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp
index 1c0f9ec7a59..e24e97ff2a4 100644
--- a/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp
+++ b/storage/src/vespa/storage/bucketdb/btree_lockable_map.hpp
@@ -49,7 +49,7 @@ struct BTreeLockableMap<T>::ValueTraits {
return store.addEntry(value).ref();
}
static void remove_by_wrapped_value(DataStoreType& store, uint64_t value) noexcept {
- store.holdElem(entry_ref_from_value(value), 1);
+ store.hold_entry(entry_ref_from_value(value));
}
static ValueType unwrap_from_key_value(const DataStoreType& store, [[maybe_unused]] uint64_t key, uint64_t value) {
return store.getEntry(entry_ref_from_value(value));
diff --git a/storage/src/vespa/storage/distributor/node_supported_features.h b/storage/src/vespa/storage/distributor/node_supported_features.h
index bbd17403a6d..f4c9553775b 100644
--- a/storage/src/vespa/storage/distributor/node_supported_features.h
+++ b/storage/src/vespa/storage/distributor/node_supported_features.h
@@ -14,6 +14,7 @@ struct NodeSupportedFeatures {
bool unordered_merge_chaining = false;
bool two_phase_remove_location = false;
bool no_implicit_indexing_of_active_buckets = false;
+ bool document_condition_probe = false;
bool operator==(const NodeSupportedFeatures& rhs) const noexcept = default;
};
diff --git a/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp
index 41260115297..44148bdbf25 100644
--- a/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp
+++ b/storage/src/vespa/storage/distributor/operations/external/getoperation.cpp
@@ -112,6 +112,9 @@ GetOperation::sendForChecksum(DistributorStripeMessageSender& sender, const docu
_msg->getFieldSet(), _msg->getBeforeTimestamp());
copyMessageSettings(*_msg, *command);
command->set_internal_read_consistency(_desired_read_consistency);
+ if (_msg->has_condition()) {
+ command->set_condition(_msg->condition());
+ }
LOG(spam, "Sending %s to node %d", command->toString(true).c_str(), res[best].copy.getNode());
@@ -175,9 +178,9 @@ GetOperation::onReceive(DistributorStripeMessageSender& sender, const std::share
if (!_newest_replica.has_value() || getreply->getLastModifiedTimestamp() > _newest_replica->timestamp) {
_returnCode = getreply->getResult();
assert(response.second[i].to_node != UINT16_MAX);
- _newest_replica = NewestReplica::of(getreply->getLastModifiedTimestamp(), bucket_id,
- send_state.to_node, getreply->is_tombstone());
- _doc = getreply->getDocument(); // May be empty (tombstones).
+ _newest_replica = NewestReplica::of(getreply->getLastModifiedTimestamp(), bucket_id, send_state.to_node,
+ getreply->is_tombstone(), getreply->condition_matched());
+ _doc = getreply->getDocument(); // May be empty (tombstones or metadata-only).
}
} else {
_any_replicas_failed = true;
diff --git a/storage/src/vespa/storage/distributor/operations/external/newest_replica.cpp b/storage/src/vespa/storage/distributor/operations/external/newest_replica.cpp
index 4632501d59b..cfc03882ed3 100644
--- a/storage/src/vespa/storage/distributor/operations/external/newest_replica.cpp
+++ b/storage/src/vespa/storage/distributor/operations/external/newest_replica.cpp
@@ -9,6 +9,7 @@ std::ostream& operator<<(std::ostream& os, const NewestReplica& nr) {
<< ", bucket_id " << nr.bucket_id
<< ", node " << nr.node
<< ", is_tombstone " << nr.is_tombstone
+ << ", condition_matched " << nr.condition_matched
<< ')';
return os;
}
diff --git a/storage/src/vespa/storage/distributor/operations/external/newest_replica.h b/storage/src/vespa/storage/distributor/operations/external/newest_replica.h
index 5fcb719aee2..94274747f30 100644
--- a/storage/src/vespa/storage/distributor/operations/external/newest_replica.h
+++ b/storage/src/vespa/storage/distributor/operations/external/newest_replica.h
@@ -18,23 +18,26 @@ struct NewestReplica {
document::BucketId bucket_id;
uint16_t node {UINT16_MAX};
bool is_tombstone {false};
+ bool condition_matched {false}; // Only relevant if a condition was initially sent
static NewestReplica of(api::Timestamp timestamp,
const document::BucketId& bucket_id,
uint16_t node,
- bool is_tombstone) noexcept {
- return {timestamp, bucket_id, node, is_tombstone};
+ bool is_tombstone,
+ bool condition_matched) noexcept {
+ return {timestamp, bucket_id, node, is_tombstone, condition_matched};
}
static NewestReplica make_empty() {
- return {api::Timestamp(0), document::BucketId(), 0, false};
+ return {api::Timestamp(0), document::BucketId(), 0, false, false};
}
bool operator==(const NewestReplica& rhs) const noexcept {
return ((timestamp == rhs.timestamp) &&
(bucket_id == rhs.bucket_id) &&
(node == rhs.node) &&
- (is_tombstone == rhs.is_tombstone));
+ (is_tombstone == rhs.is_tombstone) &&
+ (condition_matched == rhs.condition_matched));
}
bool operator!=(const NewestReplica& rhs) const noexcept {
return !(*this == rhs);
diff --git a/storage/src/vespa/storage/distributor/operations/external/putoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/putoperation.cpp
index dea215f47dc..aa5ad217ae9 100644
--- a/storage/src/vespa/storage/distributor/operations/external/putoperation.cpp
+++ b/storage/src/vespa/storage/distributor/operations/external/putoperation.cpp
@@ -15,13 +15,12 @@
#include <algorithm>
#include <vespa/log/log.h>
-LOG_SETUP(".distributor.callback.doc.put");
+LOG_SETUP(".distributor.operations.external.put");
-
-using namespace storage::distributor;
-using namespace storage;
using document::BucketSpace;
+namespace storage::distributor {
+
PutOperation::PutOperation(const DistributorNodeContext& node_ctx,
DistributorStripeOperationContext& op_ctx,
DistributorBucketSpace& bucketSpace,
@@ -116,6 +115,20 @@ bool PutOperation::has_unavailable_targets_in_pending_state(const OperationTarge
});
}
+bool PutOperation::at_least_one_storage_node_is_available() const {
+ const lib::ClusterState& cluster_state = _bucketSpace.getClusterState();
+
+ const uint16_t storage_node_index_ubound = cluster_state.getNodeCount(lib::NodeType::STORAGE);
+ for (uint16_t i = 0; i < storage_node_index_ubound; i++) {
+ if (cluster_state.getNodeState(lib::Node(lib::NodeType::STORAGE, i))
+ .getState().oneOf(storage_node_up_states()))
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
void
PutOperation::onStart(DistributorStripeMessageSender& sender)
{
@@ -124,19 +137,7 @@ PutOperation::onStart(DistributorStripeMessageSender& sender)
LOG(debug, "Received PUT %s for bucket %s", _msg->getDocumentId().toString().c_str(), bid.toString().c_str());
- lib::ClusterState systemState = _bucketSpace.getClusterState();
-
- // Don't do anything if all nodes are down.
- bool up = false;
- for (uint16_t i = 0; i < systemState.getNodeCount(lib::NodeType::STORAGE); i++) {
- if (systemState.getNodeState(lib::Node(lib::NodeType::STORAGE, i))
- .getState().oneOf(storage_node_up_states()))
- {
- up = true;
- }
- }
-
- if (up) {
+ if (at_least_one_storage_node_is_available()) {
std::vector<document::BucketId> bucketsToCheckForSplit;
OperationTargetResolverImpl targetResolver(_bucketSpace, _bucketSpace.getBucketDatabase(),
@@ -145,8 +146,8 @@ PutOperation::onStart(DistributorStripeMessageSender& sender)
_msg->getBucket().getBucketSpace());
OperationTargetList targets(targetResolver.getTargets(OperationTargetResolver::PUT, bid));
- for (size_t i = 0; i < targets.size(); ++i) {
- if (_op_ctx.has_pending_message(targets[i].getNode().getIndex(), targets[i].getBucket(),
+ for (const auto& target : targets) {
+ if (_op_ctx.has_pending_message(target.getNode().getIndex(), target.getBucket(),
api::MessageType::DELETEBUCKET_ID))
{
_tracker.fail(sender, api::ReturnCode(api::ReturnCode::BUCKET_DELETED,
@@ -179,13 +180,12 @@ PutOperation::onStart(DistributorStripeMessageSender& sender)
std::vector<PersistenceMessageTracker::ToSend> putBatch;
// Now send PUTs
- for (uint32_t i = 0; i < targets.size(); i++) {
- const OperationTarget& target(targets[i]);
+ for (const auto& target : targets) {
sendPutToBucketOnNode(_msg->getBucket().getBucketSpace(), target.getBucketId(),
target.getNode().getIndex(), putBatch);
}
- if (putBatch.size()) {
+ if (!putBatch.empty()) {
_tracker.queueMessageBatch(putBatch);
} else {
const char* error = "Can't store document: No storage nodes available";
@@ -196,9 +196,9 @@ PutOperation::onStart(DistributorStripeMessageSender& sender)
// Check whether buckets are large enough to be split.
// TODO(vekterli): only check entries for sendToExisting?
- for (uint32_t i = 0; i < entries.size(); ++i) {
+ for (const auto& entry : entries) {
_op_ctx.send_inline_split_if_bucket_too_large(_msg->getBucket().getBucketSpace(),
- entries[i], _msg->getPriority());
+ entry, _msg->getPriority());
}
_tracker.flushQueue(sender);
@@ -235,3 +235,5 @@ PutOperation::onClose(DistributorStripeMessageSender& sender)
LOG(debug, "%s", error);
_tracker.fail(sender, api::ReturnCode(api::ReturnCode::ABORTED, error));
}
+
+}
diff --git a/storage/src/vespa/storage/distributor/operations/external/putoperation.h b/storage/src/vespa/storage/distributor/operations/external/putoperation.h
index 9801fed0c99..283395f1406 100644
--- a/storage/src/vespa/storage/distributor/operations/external/putoperation.h
+++ b/storage/src/vespa/storage/distributor/operations/external/putoperation.h
@@ -47,9 +47,10 @@ private:
void sendPutToBucketOnNode(document::BucketSpace bucketSpace, const document::BucketId& bucketId,
const uint16_t node, std::vector<PersistenceMessageTracker::ToSend>& putBatch);
- bool shouldImplicitlyActivateReplica(const OperationTargetList& targets) const;
+ [[nodiscard]] bool shouldImplicitlyActivateReplica(const OperationTargetList& targets) const;
- bool has_unavailable_targets_in_pending_state(const OperationTargetList& targets) const;
+ [[nodiscard]] bool has_unavailable_targets_in_pending_state(const OperationTargetList& targets) const;
+ [[nodiscard]] bool at_least_one_storage_node_is_available() const;
std::shared_ptr<api::PutCommand> _msg;
DistributorStripeOperationContext& _op_ctx;
diff --git a/storage/src/vespa/storage/distributor/operations/external/removeoperation.cpp b/storage/src/vespa/storage/distributor/operations/external/removeoperation.cpp
index 584ad9de2ce..b752a7fadde 100644
--- a/storage/src/vespa/storage/distributor/operations/external/removeoperation.cpp
+++ b/storage/src/vespa/storage/distributor/operations/external/removeoperation.cpp
@@ -5,13 +5,12 @@
#include <vespa/vdslib/state/clusterstate.h>
#include <vespa/log/log.h>
-LOG_SETUP(".distributor.operation.external.remove");
+LOG_SETUP(".distributor.operations.external.remove");
-
-using namespace storage::distributor;
-using namespace storage;
using document::BucketSpace;
+namespace storage::distributor {
+
RemoveOperation::RemoveOperation(const DistributorNodeContext& node_ctx,
DistributorStripeOperationContext& op_ctx,
DistributorBucketSpace& bucketSpace,
@@ -100,3 +99,5 @@ RemoveOperation::onClose(DistributorStripeMessageSender& sender)
{
_tracker.fail(sender, api::ReturnCode(api::ReturnCode::ABORTED, "Process is shutting down"));
}
+
+}
diff --git a/storage/src/vespa/storage/distributor/pendingclusterstate.cpp b/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
index 36f7af1c1e6..9d25e19deb2 100644
--- a/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
+++ b/storage/src/vespa/storage/distributor/pendingclusterstate.cpp
@@ -333,6 +333,7 @@ PendingClusterState::update_node_supported_features_from_reply(uint16_t node, co
dest_feat.unordered_merge_chaining = src_feat.unordered_merge_chaining;
dest_feat.two_phase_remove_location = src_feat.two_phase_remove_location;
dest_feat.no_implicit_indexing_of_active_buckets = src_feat.no_implicit_indexing_of_active_buckets;
+ dest_feat.document_condition_probe = src_feat.document_condition_probe;
// This will overwrite per bucket-space reply, but does not matter since it's independent of bucket space.
_node_features.insert(std::make_pair(node, dest_feat));
}
diff --git a/storage/src/vespa/storage/persistence/asynchandler.cpp b/storage/src/vespa/storage/persistence/asynchandler.cpp
index e20c0475556..60c6d507416 100644
--- a/storage/src/vespa/storage/persistence/asynchandler.cpp
+++ b/storage/src/vespa/storage/persistence/asynchandler.cpp
@@ -358,7 +358,11 @@ bool
AsyncHandler::tasConditionMatches(const api::TestAndSetCommand & cmd, MessageTracker & tracker,
spi::Context & context, bool missingDocumentImpliesMatch) const {
try {
- TestAndSetHelper helper(_env, _spi, _bucketIdFactory, cmd, missingDocumentImpliesMatch);
+ TestAndSetHelper helper(_env, _spi, _bucketIdFactory,
+ cmd.getCondition(),
+ cmd.getBucket(), cmd.getDocumentId(),
+ cmd.getDocumentType(),
+ missingDocumentImpliesMatch);
auto code = helper.retrieveAndMatch(context);
if (code.failed()) {
diff --git a/storage/src/vespa/storage/persistence/filestorage/filestorhandlerimpl.cpp b/storage/src/vespa/storage/persistence/filestorage/filestorhandlerimpl.cpp
index 92b9e832a23..1d29a8795d5 100644
--- a/storage/src/vespa/storage/persistence/filestorage/filestorhandlerimpl.cpp
+++ b/storage/src/vespa/storage/persistence/filestorage/filestorhandlerimpl.cpp
@@ -1054,7 +1054,7 @@ FileStorHandlerImpl::Stripe::waitUntilNoLocks() const
{
std::unique_lock guard(*_lock);
while (!_lockedBuckets.empty()) {
- _cond->wait(guard);
+ _cond->wait_for(guard, 100ms);
}
}
@@ -1062,7 +1062,7 @@ void
FileStorHandlerImpl::Stripe::waitInactive(const AbortBucketOperationsCommand& cmd) const {
std::unique_lock guard(*_lock);
while (hasActive(guard, cmd)) {
- _cond->wait(guard);
+ _cond->wait_for(guard, 100ms);
}
}
diff --git a/storage/src/vespa/storage/persistence/persistencehandler.cpp b/storage/src/vespa/storage/persistence/persistencehandler.cpp
index 8d71cc9308b..69f910d0910 100644
--- a/storage/src/vespa/storage/persistence/persistencehandler.cpp
+++ b/storage/src/vespa/storage/persistence/persistencehandler.cpp
@@ -24,7 +24,7 @@ PersistenceHandler::PersistenceHandler(vespalib::ISequencedTaskExecutor & sequen
cfg.commonMergeChainOptimalizationMinimumSize),
_asyncHandler(_env, provider, bucketOwnershipNotifier, sequencedExecutor, component.getBucketIdFactory()),
_splitJoinHandler(_env, provider, bucketOwnershipNotifier, cfg.enableMultibitSplitOptimalization),
- _simpleHandler(_env, provider)
+ _simpleHandler(_env, provider, component.getBucketIdFactory())
{
}
diff --git a/storage/src/vespa/storage/persistence/simplemessagehandler.cpp b/storage/src/vespa/storage/persistence/simplemessagehandler.cpp
index e83d460f47a..ea929bf8620 100644
--- a/storage/src/vespa/storage/persistence/simplemessagehandler.cpp
+++ b/storage/src/vespa/storage/persistence/simplemessagehandler.cpp
@@ -2,6 +2,7 @@
#include "simplemessagehandler.h"
#include "persistenceutil.h"
+#include "testandsethelper.h"
#include <vespa/persistence/spi/persistenceprovider.h>
#include <vespa/persistence/spi/docentry.h>
#include <vespa/storageapi/message/bucket.h>
@@ -45,21 +46,45 @@ getFieldSet(const document::FieldSetRepo & repo, vespalib::stringref name, Messa
}
}
-SimpleMessageHandler::SimpleMessageHandler(const PersistenceUtil& env, spi::PersistenceProvider& spi)
+SimpleMessageHandler::SimpleMessageHandler(const PersistenceUtil& env,
+ spi::PersistenceProvider& spi,
+ const document::BucketIdFactory& bucket_id_factory)
: _env(env),
- _spi(spi)
+ _spi(spi),
+ _bucket_id_factory(bucket_id_factory)
{
}
MessageTracker::UP
+SimpleMessageHandler::handle_conditional_get(api::GetCommand& cmd, MessageTracker::UP tracker) const
+{
+ if (cmd.getFieldSet() == document::NoFields::NAME) {
+ TestAndSetHelper tas_helper(_env, _spi, _bucket_id_factory, cmd.condition(),
+ cmd.getBucket(), cmd.getDocumentId(), nullptr);
+ auto result = tas_helper.fetch_and_match_raw(tracker->context());
+ tracker->setReply(std::make_shared<api::GetReply>(cmd, nullptr, result.timestamp, false,
+ result.is_tombstone(), result.is_match()));
+ } else {
+ tracker->fail(api::ReturnCode::ILLEGAL_PARAMETERS, "Conditional Get operations must be metadata-only");
+ }
+ return tracker;
+}
+
+MessageTracker::UP
SimpleMessageHandler::handleGet(api::GetCommand& cmd, MessageTracker::UP tracker) const
{
auto& metrics = _env._metrics.get;
tracker->setMetric(metrics);
metrics.request_size.addValue(cmd.getApproxByteSize());
+ if (cmd.has_condition()) {
+ return handle_conditional_get(cmd, std::move(tracker));
+ }
+
auto fieldSet = getFieldSet(_env.getFieldSetRepo(), cmd.getFieldSet(), *tracker);
- if ( ! fieldSet) { return tracker; }
+ if (!fieldSet) {
+ return tracker;
+ }
tracker->context().setReadConsistency(api_read_consistency_to_spi(cmd.internal_read_consistency()));
spi::GetResult result = _spi.get(_env.getBucket(cmd.getDocumentId(), cmd.getBucket()),
@@ -70,7 +95,7 @@ SimpleMessageHandler::handleGet(api::GetCommand& cmd, MessageTracker::UP tracker
metrics.notFound.inc();
}
tracker->setReply(std::make_shared<api::GetReply>(cmd, result.getDocumentPtr(), result.getTimestamp(),
- false, result.is_tombstone()));
+ false, result.is_tombstone(), false));
}
return tracker;
diff --git a/storage/src/vespa/storage/persistence/simplemessagehandler.h b/storage/src/vespa/storage/persistence/simplemessagehandler.h
index 009fd6dff52..a5a19772556 100644
--- a/storage/src/vespa/storage/persistence/simplemessagehandler.h
+++ b/storage/src/vespa/storage/persistence/simplemessagehandler.h
@@ -7,6 +7,8 @@
#include <vespa/storage/common/bucketmessages.h>
#include <vespa/storageapi/message/persistence.h>
+namespace document { class BucketIdFactory; }
+
namespace storage {
namespace spi { struct PersistenceProvider; }
@@ -19,7 +21,9 @@ class PersistenceUtil;
*/
class SimpleMessageHandler : public Types {
public:
- SimpleMessageHandler(const PersistenceUtil&, spi::PersistenceProvider&);
+ SimpleMessageHandler(const PersistenceUtil&,
+ spi::PersistenceProvider&,
+ const document::BucketIdFactory&);
MessageTrackerUP handleGet(api::GetCommand& cmd, MessageTrackerUP tracker) const;
MessageTrackerUP handleRevert(api::RevertCommand& cmd, MessageTrackerUP tracker) const;
MessageTrackerUP handleCreateIterator(CreateIteratorCommand& cmd, MessageTrackerUP tracker) const;
@@ -27,8 +31,11 @@ public:
MessageTrackerUP handleReadBucketList(ReadBucketList& cmd, MessageTrackerUP tracker) const;
MessageTrackerUP handleReadBucketInfo(ReadBucketInfo& cmd, MessageTrackerUP tracker) const;
private:
- const PersistenceUtil & _env;
- spi::PersistenceProvider & _spi;
+ MessageTrackerUP handle_conditional_get(api::GetCommand& cmd, MessageTrackerUP tracker) const;
+
+ const PersistenceUtil& _env;
+ spi::PersistenceProvider& _spi;
+ const document::BucketIdFactory& _bucket_id_factory;
};
} // storage
diff --git a/storage/src/vespa/storage/persistence/testandsethelper.cpp b/storage/src/vespa/storage/persistence/testandsethelper.cpp
index 393dac09f72..1cda9427761 100644
--- a/storage/src/vespa/storage/persistence/testandsethelper.cpp
+++ b/storage/src/vespa/storage/persistence/testandsethelper.cpp
@@ -31,69 +31,91 @@ void TestAndSetHelper::parseDocumentSelection(const document::DocumentTypeRepo &
document::select::Parser parser(documentTypeRepo, bucketIdFactory);
try {
- _docSelectionUp = parser.parse(_cmd.getCondition().getSelection());
+ _docSelectionUp = parser.parse(_condition.getSelection());
} catch (const document::select::ParsingFailedException & e) {
throw TestAndSetException(api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS, "Failed to parse test and set condition: "s + e.getMessage()));
}
}
spi::GetResult TestAndSetHelper::retrieveDocument(const document::FieldSet & fieldSet, spi::Context & context) {
- return _spi.get(_env.getBucket(_docId, _cmd.getBucket()), fieldSet, _cmd.getDocumentId(), context);
+ return _spi.get(_env.getBucket(_docId, _bucket), fieldSet, _docId, context);
}
-TestAndSetHelper::TestAndSetHelper(const PersistenceUtil & env, const spi::PersistenceProvider & spi,
- const document::BucketIdFactory & bucketFactory,
- const api::TestAndSetCommand & cmd, bool missingDocumentImpliesMatch)
+TestAndSetHelper::TestAndSetHelper(const PersistenceUtil& env,
+ const spi::PersistenceProvider& spi,
+ const document::BucketIdFactory& bucket_id_factory,
+ const documentapi::TestAndSetCondition& condition,
+ document::Bucket bucket,
+ document::DocumentId doc_id,
+ const document::DocumentType* doc_type_ptr,
+ bool missingDocumentImpliesMatch)
: _env(env),
_spi(spi),
- _cmd(cmd),
- _docId(cmd.getDocumentId()),
- _docTypePtr(_cmd.getDocumentType()),
+ _condition(condition),
+ _bucket(bucket),
+ _docId(std::move(doc_id)),
+ _docTypePtr(doc_type_ptr),
_missingDocumentImpliesMatch(missingDocumentImpliesMatch)
{
const auto & repo = _env.getDocumentTypeRepo();
resolveDocumentType(repo);
- parseDocumentSelection(repo, bucketFactory);
+ parseDocumentSelection(repo, bucket_id_factory);
}
TestAndSetHelper::~TestAndSetHelper() = default;
-api::ReturnCode
-TestAndSetHelper::retrieveAndMatch(spi::Context & context) {
- // Walk document selection tree to build a minimal field set
+TestAndSetHelper::Result
+TestAndSetHelper::fetch_and_match_raw(spi::Context& context) {
+ // Walk document selection tree to build a minimal field set
FieldVisitor fieldVisitor(*_docTypePtr);
try {
_docSelectionUp->visit(fieldVisitor);
} catch (const document::FieldNotFoundException& e) {
- return api::ReturnCode(api::ReturnCode::ILLEGAL_PARAMETERS,
- vespalib::make_string("Condition field '%s' could not be found, or is an imported field. "
- "Imported fields are not supported in conditional mutations.",
- e.getFieldName().c_str()));
+ throw TestAndSetException(api::ReturnCode(
+ api::ReturnCode::ILLEGAL_PARAMETERS,
+ vespalib::make_string("Condition field '%s' could not be found, or is an imported field. "
+ "Imported fields are not supported in conditional mutations.",
+ e.getFieldName().c_str())));
}
-
- // Retrieve document
auto result = retrieveDocument(fieldVisitor.getFieldSet(), context);
-
// If document exists, match it with selection
if (result.hasDocument()) {
auto docPtr = result.getDocumentPtr();
if (_docSelectionUp->contains(*docPtr) != document::select::Result::True) {
- return api::ReturnCode(api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
- vespalib::make_string("Condition did not match document nodeIndex=%d bucket=%" PRIx64 " %s",
- _env._nodeIndex, _cmd.getBucketId().getRawId(),
- _cmd.hasBeenRemapped() ? "remapped" : ""));
+ return {result.getTimestamp(), Result::ConditionOutcome::IsNotMatch};
}
-
// Document matches
- return api::ReturnCode();
- } else if (_missingDocumentImpliesMatch) {
- return api::ReturnCode();
+ return {result.getTimestamp(), Result::ConditionOutcome::IsMatch};
}
+ return {result.getTimestamp(), result.is_tombstone() ? Result::ConditionOutcome::IsTombstone
+ : Result::ConditionOutcome::DocNotFound};
+}
- return api::ReturnCode(api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
- vespalib::make_string("Document does not exist nodeIndex=%d bucket=%" PRIx64 " %s",
- _env._nodeIndex, _cmd.getBucketId().getRawId(),
- _cmd.hasBeenRemapped() ? "remapped" : ""));
+api::ReturnCode
+TestAndSetHelper::to_api_return_code(const Result& result) const {
+ switch (result.condition_outcome) {
+ case Result::ConditionOutcome::IsNotMatch:
+ return {api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
+ vespalib::make_string("Condition did not match document nodeIndex=%d bucket=%" PRIx64,
+ _env._nodeIndex, _bucket.getBucketId().getRawId())};
+ case Result::ConditionOutcome::IsTombstone:
+ case Result::ConditionOutcome::DocNotFound:
+ if (!_missingDocumentImpliesMatch) {
+ return {api::ReturnCode::TEST_AND_SET_CONDITION_FAILED,
+ vespalib::make_string("Document does not exist nodeIndex=%d bucket=%" PRIx64,
+ _env._nodeIndex, _bucket.getBucketId().getRawId())};
+ }
+ [[fallthrough]]; // as match
+ case Result::ConditionOutcome::IsMatch:
+ return {}; // OK
+ }
+ abort();
+}
+
+api::ReturnCode
+TestAndSetHelper::retrieveAndMatch(spi::Context & context) {
+ auto result = fetch_and_match_raw(context);
+ return to_api_return_code(result);
}
} // storage
diff --git a/storage/src/vespa/storage/persistence/testandsethelper.h b/storage/src/vespa/storage/persistence/testandsethelper.h
index 82710e523c4..31b1cc79a54 100644
--- a/storage/src/vespa/storage/persistence/testandsethelper.h
+++ b/storage/src/vespa/storage/persistence/testandsethelper.h
@@ -25,9 +25,8 @@ class PersistenceUtil;
class TestAndSetException : public std::runtime_error {
api::ReturnCode _code;
-
public:
- TestAndSetException(api::ReturnCode code)
+ explicit TestAndSetException(api::ReturnCode code)
: std::runtime_error(code.getMessage()),
_code(std::move(code))
{}
@@ -36,11 +35,12 @@ public:
};
class TestAndSetHelper {
- const PersistenceUtil &_env;
- const spi::PersistenceProvider &_spi;
- const api::TestAndSetCommand &_cmd;
+ const PersistenceUtil& _env;
+ const spi::PersistenceProvider& _spi;
+ const documentapi::TestAndSetCondition& _condition;
+ const document::Bucket _bucket;
const document::DocumentId _docId;
- const document::DocumentType * _docTypePtr;
+ const document::DocumentType* _docTypePtr;
std::unique_ptr<document::select::Node> _docSelectionUp;
bool _missingDocumentImpliesMatch;
@@ -50,10 +50,44 @@ class TestAndSetHelper {
spi::GetResult retrieveDocument(const document::FieldSet & fieldSet, spi::Context & context);
public:
- TestAndSetHelper(const PersistenceUtil & env, const spi::PersistenceProvider & _spi,
- const document::BucketIdFactory & bucketIdFactory,
- const api::TestAndSetCommand & cmd, bool missingDocumentImpliesMatch = false);
+ struct Result {
+ enum class ConditionOutcome {
+ DocNotFound,
+ IsMatch,
+ IsNotMatch,
+ IsTombstone
+ };
+
+ api::Timestamp timestamp = 0;
+ ConditionOutcome condition_outcome = ConditionOutcome::IsNotMatch;
+
+ [[nodiscard]] bool doc_not_found() const noexcept {
+ return condition_outcome == ConditionOutcome::DocNotFound;
+ }
+ [[nodiscard]] bool is_match() const noexcept {
+ return condition_outcome == ConditionOutcome::IsMatch;
+ }
+ [[nodiscard]] bool is_not_match() const noexcept {
+ return condition_outcome == ConditionOutcome::IsNotMatch;
+ }
+ [[nodiscard]] bool is_tombstone() const noexcept {
+ return condition_outcome == ConditionOutcome::IsTombstone;
+ }
+ };
+
+ TestAndSetHelper(const PersistenceUtil& env,
+ const spi::PersistenceProvider& _spi,
+ const document::BucketIdFactory& bucket_id_factory,
+ const documentapi::TestAndSetCondition& condition,
+ document::Bucket bucket,
+ document::DocumentId doc_id,
+ const document::DocumentType* doc_type_ptr,
+ bool missingDocumentImpliesMatch = false);
~TestAndSetHelper();
+
+ Result fetch_and_match_raw(spi::Context& context);
+ api::ReturnCode to_api_return_code(const Result& result) const;
+
api::ReturnCode retrieveAndMatch(spi::Context & context);
};
diff --git a/storage/src/vespa/storage/storageserver/documentapiconverter.cpp b/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
index 9ae6aaf0653..e7eb7a752fb 100644
--- a/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
+++ b/storage/src/vespa/storage/storageserver/documentapiconverter.cpp
@@ -8,11 +8,9 @@
#include <vespa/document/fieldvalue/document.h>
#include <vespa/storage/common/bucket_resolver.h>
#include <vespa/storageapi/message/datagram.h>
-#include <vespa/storageapi/message/documentsummary.h>
#include <vespa/storageapi/message/persistence.h>
#include <vespa/storageapi/message/queryresult.h>
#include <vespa/storageapi/message/removelocation.h>
-#include <vespa/storageapi/message/searchresult.h>
#include <vespa/storageapi/message/stat.h>
#include <vespa/storageapi/message/visitor.h>
#include <vespa/messagebus/error.h>
@@ -237,24 +235,12 @@ DocumentApiConverter::toDocumentAPI(api::StorageCommand& fromMsg)
toMsg = std::move(to);
break;
}
- case api::MessageType::SEARCHRESULT_ID:
- {
- auto & from(static_cast<api::SearchResultCommand&>(fromMsg));
- toMsg = std::make_unique<documentapi::SearchResultMessage>(std::move(from));
- break;
- }
case api::MessageType::QUERYRESULT_ID:
{
auto & from(static_cast<api::QueryResultCommand&>(fromMsg));
toMsg = std::make_unique<documentapi::QueryResultMessage>(std::move(from.getSearchResult()), from.getDocumentSummary());
break;
}
- case api::MessageType::DOCUMENTSUMMARY_ID:
- {
- auto & from(static_cast<api::DocumentSummaryCommand&>(fromMsg));
- toMsg = std::make_unique<documentapi::DocumentSummaryMessage>(from);
- break;
- }
case api::MessageType::MAPVISITOR_ID:
{
auto & from(static_cast<api::MapVisitorCommand&>(fromMsg));
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto
index 92c0fdc0b87..b02e1eab0aa 100644
--- a/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/feed.proto
@@ -66,6 +66,7 @@ message GetRequest {
Weak = 1;
}
InternalReadConsistency internal_read_consistency = 5;
+ TestAndSetCondition condition = 6;
}
message GetResponse {
@@ -76,6 +77,7 @@ message GetResponse {
// Note: last_modified_timestamp and tombstone_timestamp are mutually exclusive.
// Tracked separately (rather than being a flag bool) to avoid issues during rolling upgrades.
uint64 tombstone_timestamp = 5;
+ bool condition_matched = 6;
}
message RevertRequest {
diff --git a/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto b/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto
index 0c1df005a5a..74b2646463a 100644
--- a/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto
+++ b/storage/src/vespa/storageapi/mbusprot/protobuf/maintenance.proto
@@ -113,6 +113,7 @@ message SupportedNodeFeatures {
bool unordered_merge_chaining = 1;
bool two_phase_remove_location = 2;
bool no_implicit_indexing_of_active_buckets = 3;
+ bool document_condition_probe = 4;
}
message RequestBucketInfoResponse {
diff --git a/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp
index 2083dd2700d..0ede96179e8 100644
--- a/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp
+++ b/storage/src/vespa/storageapi/mbusprot/protocolserialization7.cpp
@@ -555,6 +555,9 @@ void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetCommand& msg) co
req.set_field_set(msg.getFieldSet().data(), msg.getFieldSet().size());
}
req.set_internal_read_consistency(read_consistency_to_protobuf(msg.internal_read_consistency()));
+ if (msg.has_condition()) {
+ set_tas_condition(*req.mutable_condition(), msg.condition());
+ }
});
}
@@ -574,6 +577,7 @@ void ProtocolSerialization7::onEncode(GBBuf& buf, const api::GetReply& msg) cons
// found document for older versions.
res.set_last_modified_timestamp(0);
}
+ res.set_condition_matched(msg.condition_matched());
});
}
@@ -583,6 +587,9 @@ api::StorageCommand::UP ProtocolSerialization7::onDecodeGetCommand(BBuf& buf) co
auto op = std::make_unique<api::GetCommand>(bucket, std::move(doc_id),
req.field_set(), req.before_timestamp());
op->set_internal_read_consistency(read_consistency_from_protobuf(req.internal_read_consistency()));
+ if (req.has_condition()) {
+ op->set_condition(get_tas_condition(req.condition()));
+ }
return op;
});
}
@@ -596,7 +603,7 @@ api::StorageReply::UP ProtocolSerialization7::onDecodeGetReply(const SCmd& cmd,
: res.last_modified_timestamp());
return std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd),
std::move(document), effective_timestamp,
- false, is_tombstone);
+ false, is_tombstone, res.condition_matched());
} catch (std::exception& e) {
auto reply = std::make_unique<api::GetReply>(static_cast<const api::GetCommand&>(cmd),
std::shared_ptr<document::Document>(), 0u);
@@ -1063,6 +1070,7 @@ void ProtocolSerialization7::onEncode(GBBuf& buf, const api::RequestBucketInfoRe
res.mutable_supported_node_features()->set_unordered_merge_chaining(true);
res.mutable_supported_node_features()->set_two_phase_remove_location(true);
res.mutable_supported_node_features()->set_no_implicit_indexing_of_active_buckets(true);
+ res.mutable_supported_node_features()->set_document_condition_probe(true);
}
});
}
@@ -1106,6 +1114,7 @@ api::StorageReply::UP ProtocolSerialization7::onDecodeRequestBucketInfoReply(con
dest_features.unordered_merge_chaining = src_features.unordered_merge_chaining();
dest_features.two_phase_remove_location = src_features.two_phase_remove_location();
dest_features.no_implicit_indexing_of_active_buckets = src_features.no_implicit_indexing_of_active_buckets();
+ dest_features.document_condition_probe = src_features.document_condition_probe();
}
return reply;
});
diff --git a/storage/src/vespa/storageapi/message/CMakeLists.txt b/storage/src/vespa/storageapi/message/CMakeLists.txt
index 2a761921dff..2728b5b51ad 100644
--- a/storage/src/vespa/storageapi/message/CMakeLists.txt
+++ b/storage/src/vespa/storageapi/message/CMakeLists.txt
@@ -6,9 +6,7 @@ vespa_add_library(storageapi_message OBJECT
bucket.cpp
visitor.cpp
state.cpp
- searchresult.cpp
bucketsplitting.cpp
- documentsummary.cpp
stat.cpp
removelocation.cpp
queryresult.cpp
diff --git a/storage/src/vespa/storageapi/message/bucket.h b/storage/src/vespa/storageapi/message/bucket.h
index 801c75322b3..e02b8fcd672 100644
--- a/storage/src/vespa/storageapi/message/bucket.h
+++ b/storage/src/vespa/storageapi/message/bucket.h
@@ -396,6 +396,7 @@ public:
bool unordered_merge_chaining = false;
bool two_phase_remove_location = false;
bool no_implicit_indexing_of_active_buckets = false;
+ bool document_condition_probe = false;
};
using EntryVector = std::vector<Entry, vespalib::allocator_large<Entry>>;
private:
diff --git a/storage/src/vespa/storageapi/message/documentsummary.cpp b/storage/src/vespa/storageapi/message/documentsummary.cpp
deleted file mode 100644
index 6909b4d223c..00000000000
--- a/storage/src/vespa/storageapi/message/documentsummary.cpp
+++ /dev/null
@@ -1,43 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#include "documentsummary.h"
-#include <ostream>
-
-namespace storage {
-namespace api {
-
-IMPLEMENT_COMMAND(DocumentSummaryCommand, DocumentSummaryReply)
-IMPLEMENT_REPLY(DocumentSummaryReply)
-
-DocumentSummaryCommand::DocumentSummaryCommand()
- : StorageCommand(MessageType::DOCUMENTSUMMARY),
- DocumentSummary()
-{ }
-
-void
-DocumentSummaryCommand::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "DocumentSummary(" << getSummaryCount() << " summaries)";
- if (verbose) {
- out << " : ";
- StorageCommand::print(out, verbose, indent);
- }
-}
-
-DocumentSummaryReply::DocumentSummaryReply(const DocumentSummaryCommand& cmd)
- : StorageReply(cmd)
-{ }
-
-void
-DocumentSummaryReply::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "DocumentSummaryReply()";
- if (verbose) {
- out << " : ";
- StorageReply::print(out, verbose, indent);
- }
-}
-
-} // api
-} // storage
diff --git a/storage/src/vespa/storageapi/message/documentsummary.h b/storage/src/vespa/storageapi/message/documentsummary.h
deleted file mode 100644
index 5e2c1af3cfd..00000000000
--- a/storage/src/vespa/storageapi/message/documentsummary.h
+++ /dev/null
@@ -1,39 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-
-#include "visitor.h"
-#include <vespa/vdslib/container/documentsummary.h>
-
-namespace storage {
-namespace api {
-
-/**
- * @class DocumentSummaryCommand
- * @ingroup message
- *
- * @brief The result of a searchvisitor.
- */
-class DocumentSummaryCommand : public StorageCommand,
- public vdslib::DocumentSummary
-{
-public:
- explicit DocumentSummaryCommand();
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- DECLARE_STORAGECOMMAND(DocumentSummaryCommand, onDocumentSummary)
-};
-
-/**
- * @class DocumentSummaryReply
- * @ingroup message
- *
- * @brief Response to a document summary command.
- */
-class DocumentSummaryReply : public StorageReply {
-public:
- explicit DocumentSummaryReply(const DocumentSummaryCommand& command);
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- DECLARE_STORAGEREPLY(DocumentSummaryReply, onDocumentSummaryReply)
-};
-
-} // api
-} // storage
diff --git a/storage/src/vespa/storageapi/message/persistence.cpp b/storage/src/vespa/storageapi/message/persistence.cpp
index 41a53449b67..a8fa9a0bba1 100644
--- a/storage/src/vespa/storageapi/message/persistence.cpp
+++ b/storage/src/vespa/storageapi/message/persistence.cpp
@@ -202,7 +202,11 @@ GetCommand::getSummary() const
{
vespalib::asciistream stream;
stream << "Get(BucketId(" << vespalib::hex << getBucketId().getId() << "), " << _docId.toString()
- << ", beforetimestamp " << vespalib::dec << _beforeTimestamp << ')';
+ << ", beforetimestamp " << vespalib::dec << _beforeTimestamp;
+ if (has_condition()) {
+ stream << ", condition " << condition().getSelection();
+ }
+ stream << ')';
return stream.str();
}
@@ -211,7 +215,11 @@ GetCommand::getSummary() const
void
GetCommand::print(std::ostream& out, bool verbose, const std::string& indent) const
{
- out << "Get(" << getBucketId() << ", " << _docId << ")";
+ out << "Get(" << getBucketId() << ", " << _docId;
+ if (has_condition()) {
+ out << ", condition " << condition().getSelection();
+ }
+ out << ")";
if (verbose) {
out << " : ";
BucketCommand::print(out, verbose, indent);
@@ -222,7 +230,8 @@ GetReply::GetReply(const GetCommand& cmd,
const DocumentSP& doc,
Timestamp lastModified,
bool had_consistent_replicas,
- bool is_tombstone)
+ bool is_tombstone,
+ bool condition_matched)
: BucketInfoReply(cmd),
_docId(cmd.getDocumentId()),
_fieldSet(cmd.getFieldSet()),
@@ -230,7 +239,8 @@ GetReply::GetReply(const GetCommand& cmd,
_beforeTimestamp(cmd.getBeforeTimestamp()),
_lastModifiedTime(lastModified),
_had_consistent_replicas(had_consistent_replicas),
- _is_tombstone(is_tombstone)
+ _is_tombstone(is_tombstone),
+ _condition_matched(condition_matched)
{
}
diff --git a/storage/src/vespa/storageapi/message/persistence.h b/storage/src/vespa/storageapi/message/persistence.h
index d1709c46a6e..d010c295ca7 100644
--- a/storage/src/vespa/storageapi/message/persistence.h
+++ b/storage/src/vespa/storageapi/message/persistence.h
@@ -185,9 +185,10 @@ public:
* timestamp.
*/
class GetCommand : public BucketInfoCommand {
- document::DocumentId _docId;
- Timestamp _beforeTimestamp;
- vespalib::string _fieldSet;
+ document::DocumentId _docId;
+ Timestamp _beforeTimestamp;
+ vespalib::string _fieldSet;
+ TestAndSetCondition _condition;
InternalReadConsistency _internal_read_consistency;
public:
GetCommand(const document::Bucket &bucket, const document::DocumentId&,
@@ -198,6 +199,9 @@ public:
Timestamp getBeforeTimestamp() const { return _beforeTimestamp; }
const vespalib::string& getFieldSet() const { return _fieldSet; }
void setFieldSet(vespalib::stringref fieldSet) { _fieldSet = fieldSet; }
+ [[nodiscard]] bool has_condition() const noexcept { return _condition.isPresent(); }
+ [[nodiscard]] const TestAndSetCondition& condition() const noexcept { return _condition; }
+ void set_condition(TestAndSetCondition cond) { _condition = std::move(cond); }
InternalReadConsistency internal_read_consistency() const noexcept {
return _internal_read_consistency;
}
@@ -229,12 +233,14 @@ class GetReply : public BucketInfoReply {
Timestamp _lastModifiedTime;
bool _had_consistent_replicas;
bool _is_tombstone;
+ bool _condition_matched;
public:
explicit GetReply(const GetCommand& cmd,
const DocumentSP& doc = DocumentSP(),
Timestamp lastModified = 0,
bool had_consistent_replicas = false,
- bool is_tombstone = false);
+ bool is_tombstone = false,
+ bool condition_matched = false);
~GetReply() override;
@@ -247,6 +253,7 @@ public:
[[nodiscard]] bool had_consistent_replicas() const noexcept { return _had_consistent_replicas; }
[[nodiscard]] bool is_tombstone() const noexcept { return _is_tombstone; }
+ [[nodiscard]] bool condition_matched() const noexcept { return _condition_matched; }
bool wasFound() const { return (_doc.get() != nullptr); }
void print(std::ostream& out, bool verbose, const std::string& indent) const override;
diff --git a/storage/src/vespa/storageapi/message/searchresult.cpp b/storage/src/vespa/storageapi/message/searchresult.cpp
deleted file mode 100644
index b2cf04b0410..00000000000
--- a/storage/src/vespa/storageapi/message/searchresult.cpp
+++ /dev/null
@@ -1,47 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-
-#include "searchresult.h"
-#include <ostream>
-
-using vdslib::SearchResult;
-
-namespace storage {
-namespace api {
-
-IMPLEMENT_COMMAND(SearchResultCommand, SearchResultReply)
-IMPLEMENT_REPLY(SearchResultReply)
-
-SearchResultCommand::SearchResultCommand()
- : StorageCommand(MessageType::SEARCHRESULT),
- SearchResult()
-{
-}
-
-void
-SearchResultCommand::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "SearchResultCommand(" << getHitCount() << " hits)";
- if (verbose) {
- out << " : ";
- StorageCommand::print(out, verbose, indent);
- }
-}
-
-SearchResultReply::SearchResultReply(const SearchResultCommand& cmd)
- : StorageReply(cmd)
-{ }
-
-void
-SearchResultReply::print(std::ostream& out, bool verbose,
- const std::string& indent) const
-{
- out << "SearchResultReply()";
- if (verbose) {
- out << " : ";
- StorageReply::print(out, verbose, indent);
- }
-}
-
-} // api
-} // storage
diff --git a/storage/src/vespa/storageapi/message/searchresult.h b/storage/src/vespa/storageapi/message/searchresult.h
deleted file mode 100644
index b12fa5e1613..00000000000
--- a/storage/src/vespa/storageapi/message/searchresult.h
+++ /dev/null
@@ -1,35 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-#pragma once
-
-#include "visitor.h"
-#include <vespa/vdslib/container/searchresult.h>
-
-namespace storage::api {
-
-/**
- * @class SearchResultCommand
- * @ingroup message
- *
- * @brief The result of a searchvisitor.
- */
-class SearchResultCommand : public StorageCommand, public vdslib::SearchResult {
-public:
- SearchResultCommand();
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- DECLARE_STORAGECOMMAND(SearchResultCommand, onSearchResult)
-};
-
-/**
- * @class SearchResultReply
- * @ingroup message
- *
- * @brief Response to a search result command.
- */
-class SearchResultReply : public StorageReply {
-public:
- explicit SearchResultReply(const SearchResultCommand& command);
- void print(std::ostream& out, bool verbose, const std::string& indent) const override;
- DECLARE_STORAGEREPLY(SearchResultReply, onSearchResultReply)
-};
-
-}
diff --git a/storage/src/vespa/storageapi/messageapi/messagehandler.h b/storage/src/vespa/storageapi/messageapi/messagehandler.h
index 9ba8542e9db..fa362d5380f 100644
--- a/storage/src/vespa/storageapi/messageapi/messagehandler.h
+++ b/storage/src/vespa/storageapi/messageapi/messagehandler.h
@@ -29,8 +29,6 @@ class CreateVisitorCommand; // Create a new visitor
class DestroyVisitorCommand; // Destroy a running visitor
class VisitorInfoCommand; // Sends visitor info to visitor controller
class MapVisitorCommand;
-class SearchResultCommand;
-class DocumentSummaryCommand;
class QueryResultCommand;
class InternalCommand;
@@ -67,8 +65,6 @@ class CreateVisitorReply;
class DestroyVisitorReply;
class VisitorInfoReply;
class MapVisitorReply;
-class SearchResultReply;
-class DocumentSummaryReply;
class QueryResultReply;
class InternalReply;
@@ -137,12 +133,8 @@ public:
virtual bool onVisitorInfoReply(const std::shared_ptr<api::VisitorInfoReply>&) { return false; }
virtual bool onMapVisitor(const std::shared_ptr<api::MapVisitorCommand>&) { return false; }
virtual bool onMapVisitorReply(const std::shared_ptr<api::MapVisitorReply>&) { return false; }
- virtual bool onSearchResult(const std::shared_ptr<api::SearchResultCommand>&) { return false; }
- virtual bool onSearchResultReply(const std::shared_ptr<api::SearchResultReply>&) { return false; }
virtual bool onQueryResult(const std::shared_ptr<api::QueryResultCommand>&) { return false; }
virtual bool onQueryResultReply(const std::shared_ptr<api::QueryResultReply>&) { return false; }
- virtual bool onDocumentSummary(const std::shared_ptr<api::DocumentSummaryCommand>&) { return false; }
- virtual bool onDocumentSummaryReply(const std::shared_ptr<api::DocumentSummaryReply>&) { return false; }
virtual bool onEmptyBuckets(const std::shared_ptr<api::EmptyBucketsCommand>&) { return false; }
virtual bool onEmptyBucketsReply(const std::shared_ptr<api::EmptyBucketsReply>&) { return false; }
virtual bool onInternal(const std::shared_ptr<api::InternalCommand>&) { return false; }
diff --git a/storage/src/vespa/storageapi/messageapi/storagemessage.cpp b/storage/src/vespa/storageapi/messageapi/storagemessage.cpp
index b1d68fd77e3..c72ece80476 100644
--- a/storage/src/vespa/storageapi/messageapi/storagemessage.cpp
+++ b/storage/src/vespa/storageapi/messageapi/storagemessage.cpp
@@ -76,10 +76,6 @@ const MessageType MessageType::APPLYBUCKETDIFF("ApplyBucketDiff", APPLYBUCKETDIF
const MessageType MessageType::APPLYBUCKETDIFF_REPLY("ApplyBucketDiff reply", APPLYBUCKETDIFF_REPLY_ID, &MessageType::APPLYBUCKETDIFF);
const MessageType MessageType::VISITOR_INFO("VisitorInfo", VISITOR_INFO_ID);
const MessageType MessageType::VISITOR_INFO_REPLY("VisitorInfo reply", VISITOR_INFO_REPLY_ID, &MessageType::VISITOR_INFO);
-const MessageType MessageType::SEARCHRESULT("SearchResult", SEARCHRESULT_ID);
-const MessageType MessageType::SEARCHRESULT_REPLY("SearchResult reply", SEARCHRESULT_REPLY_ID, &MessageType::SEARCHRESULT);
-const MessageType MessageType::DOCUMENTSUMMARY("DocumentSummary", DOCUMENTSUMMARY_ID);
-const MessageType MessageType::DOCUMENTSUMMARY_REPLY("DocumentSummary reply", DOCUMENTSUMMARY_REPLY_ID, &MessageType::DOCUMENTSUMMARY);
const MessageType MessageType::MAPVISITOR("Mapvisitor", MAPVISITOR_ID);
const MessageType MessageType::MAPVISITOR_REPLY("Mapvisitor reply", MAPVISITOR_REPLY_ID, &MessageType::MAPVISITOR);
const MessageType MessageType::SPLITBUCKET("SplitBucket", SPLITBUCKET_ID);
diff --git a/storage/src/vespa/storageapi/messageapi/storagemessage.h b/storage/src/vespa/storageapi/messageapi/storagemessage.h
index 282f110646d..4649781c1e5 100644
--- a/storage/src/vespa/storageapi/messageapi/storagemessage.h
+++ b/storage/src/vespa/storageapi/messageapi/storagemessage.h
@@ -122,14 +122,14 @@ public:
DOCBLOCK_REPLY_ID = 59,
VISITOR_INFO_ID = 60,
VISITOR_INFO_REPLY_ID = 61,
- SEARCHRESULT_ID = 64,
- SEARCHRESULT_REPLY_ID = 65,
+ // SEARCHRESULT_ID = 64,
+ // SEARCHRESULT_REPLY_ID = 65,
SPLITBUCKET_ID = 66,
SPLITBUCKET_REPLY_ID = 67,
JOINBUCKETS_ID = 68,
JOINBUCKETS_REPLY_ID = 69,
- DOCUMENTSUMMARY_ID = 72,
- DOCUMENTSUMMARY_REPLY_ID = 73,
+ // DOCUMENTSUMMARY_ID = 72,
+ // DOCUMENTSUMMARY_REPLY_ID = 73,
MAPVISITOR_ID = 74,
MAPVISITOR_REPLY_ID = 75,
STATBUCKET_ID = 76,
@@ -208,14 +208,10 @@ public:
static const MessageType APPLYBUCKETDIFF_REPLY;
static const MessageType VISITOR_INFO;
static const MessageType VISITOR_INFO_REPLY;
- static const MessageType SEARCHRESULT;
- static const MessageType SEARCHRESULT_REPLY;
static const MessageType SPLITBUCKET;
static const MessageType SPLITBUCKET_REPLY;
static const MessageType JOINBUCKETS;
static const MessageType JOINBUCKETS_REPLY;
- static const MessageType DOCUMENTSUMMARY;
- static const MessageType DOCUMENTSUMMARY_REPLY;
static const MessageType MAPVISITOR;
static const MessageType MAPVISITOR_REPLY;
static const MessageType STATBUCKET;
diff --git a/streamingvisitors/CMakeLists.txt b/streamingvisitors/CMakeLists.txt
index fede7087d8d..52e19b25900 100644
--- a/streamingvisitors/CMakeLists.txt
+++ b/streamingvisitors/CMakeLists.txt
@@ -18,14 +18,16 @@ vespa_define_module(
src/vespa/vsm/vsm
TESTS
- src/tests/hitcollector
- src/tests/matching_elements_filler
- src/tests/querywrapper
- src/tests/searchvisitor
src/tests/charbuffer
src/tests/docsum
src/tests/document
+ src/tests/hitcollector
+ src/tests/matching_elements_filler
+ src/tests/nearest_neighbor_field_searcher
src/tests/query_term_filter_factory
+ src/tests/querywrapper
+ src/tests/rank_processor
src/tests/searcher
+ src/tests/searchvisitor
src/tests/textutil
)
diff --git a/streamingvisitors/src/tests/hitcollector/hitcollector_test.cpp b/streamingvisitors/src/tests/hitcollector/hitcollector_test.cpp
index 6950c90f097..791ec01162f 100644
--- a/streamingvisitors/src/tests/hitcollector/hitcollector_test.cpp
+++ b/streamingvisitors/src/tests/hitcollector/hitcollector_test.cpp
@@ -285,7 +285,7 @@ HitCollectorTest::testFeatureSet()
FeatureResolver resolver(rankProgram.get_resolver());
search::StringStringMap renames;
renames["bar"] = "qux";
- search::FeatureSet::SP sf = hc.getFeatureSet(rankProgram, resolver, renames);
+ vespalib::FeatureSet::SP sf = hc.getFeatureSet(rankProgram, resolver, renames);
EXPECT_EQUAL(sf->getNames().size(), 3u);
EXPECT_EQUAL(sf->getNames()[0], "foo");
diff --git a/streamingvisitors/src/tests/matching_elements_filler/CMakeLists.txt b/streamingvisitors/src/tests/matching_elements_filler/CMakeLists.txt
index 5cc2977b3c3..efe5c0507ab 100644
--- a/streamingvisitors/src/tests/matching_elements_filler/CMakeLists.txt
+++ b/streamingvisitors/src/tests/matching_elements_filler/CMakeLists.txt
@@ -3,6 +3,7 @@ vespa_add_executable(streamingvisitors_matching_elements_filler_test_app TEST
SOURCES
matching_elements_filler_test.cpp
DEPENDS
+ searchlib_test
streamingvisitors
GTest::GTest
)
diff --git a/streamingvisitors/src/tests/matching_elements_filler/matching_elements_filler_test.cpp b/streamingvisitors/src/tests/matching_elements_filler/matching_elements_filler_test.cpp
index 458031b5f01..dbe9dde469d 100644
--- a/streamingvisitors/src/tests/matching_elements_filler/matching_elements_filler_test.cpp
+++ b/streamingvisitors/src/tests/matching_elements_filler/matching_elements_filler_test.cpp
@@ -21,6 +21,7 @@
#include <vespa/vespalib/gtest/gtest.h>
#include <vespa/vsm/searcher/fieldsearcher.h>
#include <vespa/vsm/searcher/intfieldsearcher.h>
+#include <vespa/vsm/searcher/mock_field_searcher_env.h>
#include <vespa/vsm/searcher/utf8strchrfieldsearcher.h>
#include <iostream>
@@ -273,13 +274,12 @@ MatchingElementsFields make_matching_elements_fields() {
class MatchingElementsFillerTest : public ::testing::Test {
const MyDocType _doc_type;
MatchingElementsFields _matching_elems_fields;
- vsm::SharedFieldPathMap _field_path_map;
+ vsm::test::MockFieldSearcherEnv _env;
vsm::FieldIdTSearcherMap _field_searcher_map;
vsm::DocumentTypeIndexFieldMapT _index_to_field_ids;
HitCollector _hit_collector;
SearchResult _search_result;
Query _query;
- vsm::SharedSearcherBuf _shared_searcher_buf;
std::unique_ptr<MatchingElementsFiller> _matching_elements_filler;
std::unique_ptr<MatchingElements> _matching_elements;
std::unique_ptr<StorageDocument> _sdoc;
@@ -296,19 +296,19 @@ MatchingElementsFillerTest::MatchingElementsFillerTest()
: ::testing::Test(),
_doc_type(),
_matching_elems_fields(make_matching_elements_fields()),
- _field_path_map(make_field_path_map(_doc_type)),
+ _env(),
_field_searcher_map(make_field_searcher_map()),
_index_to_field_ids(make_index_to_field_ids()),
_hit_collector(10),
_search_result(),
_query(),
- _shared_searcher_buf(std::make_shared<vsm::SearcherBuf>()),
_matching_elements_filler(),
_matching_elements(),
_sdoc()
{
+ _env.field_paths = make_field_path_map(_doc_type);
_search_result.addHit(1, "id::test::1", 0.0, nullptr, 0);
- _sdoc = std::make_unique<StorageDocument>(_doc_type.make_test_doc(), _field_path_map, _field_path_map->size());
+ _sdoc = std::make_unique<StorageDocument>(_doc_type.make_test_doc(), _env.field_paths, _env.field_paths->size());
EXPECT_TRUE(_sdoc->valid());
MatchData md(MatchData::params());
_hit_collector.addHit(_sdoc.get(), 1, md, 0.0, nullptr, 0);
@@ -322,7 +322,7 @@ MatchingElementsFillerTest::fill_matching_elements(Query &&query)
_matching_elements_filler.reset();
_matching_elements.reset();
_query = std::move(query);
- _field_searcher_map.prepare(_index_to_field_ids, _shared_searcher_buf, _query);
+ _env.prepare(_field_searcher_map, _index_to_field_ids, _query);
_matching_elements_filler = std::make_unique<MatchingElementsFiller>(_field_searcher_map, _query, _hit_collector, _search_result);
_matching_elements = _matching_elements_filler->fill_matching_elements(_matching_elems_fields);
}
diff --git a/streamingvisitors/src/tests/nearest_neighbor_field_searcher/CMakeLists.txt b/streamingvisitors/src/tests/nearest_neighbor_field_searcher/CMakeLists.txt
new file mode 100644
index 00000000000..297e939ea72
--- /dev/null
+++ b/streamingvisitors/src/tests/nearest_neighbor_field_searcher/CMakeLists.txt
@@ -0,0 +1,12 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(vsm_nearest_neighbor_field_searcher_test_app TEST
+ SOURCES
+ nearest_neighbor_field_searcher_test.cpp
+ DEPENDS
+ searchlib
+ searchlib_test
+ streamingvisitors
+ GTest::GTest
+)
+
+vespa_add_test(NAME vsm_nearest_neighbor_field_searcher_test_app COMMAND vsm_nearest_neighbor_field_searcher_test_app)
diff --git a/streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp b/streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp
new file mode 100644
index 00000000000..b64d477fd4c
--- /dev/null
+++ b/streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp
@@ -0,0 +1,185 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/document/base/fieldpath.h>
+#include <vespa/document/datatype/tensor_data_type.h>
+#include <vespa/document/fieldvalue/tensorfieldvalue.h>
+#include <vespa/eval/eval/simple_value.h>
+#include <vespa/eval/eval/tensor_spec.h>
+#include <vespa/eval/eval/value_codec.h>
+#include <vespa/eval/eval/value_type.h>
+#include <vespa/searchlib/fef/indexproperties.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
+#include <vespa/searchlib/tensor/euclidean_distance.h>
+#include <vespa/vespalib/gtest/gtest.h>
+#include <vespa/vsm/searcher/mock_field_searcher_env.h>
+#include <vespa/vsm/searcher/nearest_neighbor_field_searcher.h>
+
+using namespace search::attribute::test;
+using namespace search::attribute;
+using namespace search::fef;
+using namespace search::streaming;
+using namespace search::tensor;
+using namespace vespalib::eval;
+using namespace vsm;
+
+using document::FieldPath;
+using document::FieldPathEntry;
+using document::TensorDataType;
+using document::TensorFieldValue;
+
+struct MockQuery {
+ std::vector<std::unique_ptr<NearestNeighborQueryNode>> nodes;
+ QueryTermList term_list;
+ MockQuery& add(const vespalib::string& query_tensor_name,
+ uint32_t target_hits,
+ double distance_threshold) {
+ std::unique_ptr<QueryNodeResultBase> base;
+ auto node = std::make_unique<NearestNeighborQueryNode>(std::move(base), query_tensor_name, "my_tensor_field",
+ target_hits, distance_threshold, 7, search::query::Weight(100));
+ nodes.push_back(std::move(node));
+ term_list.push_back(nodes.back().get());
+ return *this;
+ }
+ ~MockQuery() {}
+ const NearestNeighborQueryNode& get(size_t idx) const {
+ assert(idx < nodes.size());
+ return *nodes[idx];
+ }
+ void reset() {
+ for (auto term : term_list) {
+ term->reset();
+ }
+ }
+};
+
+class NearestNeighborSearcherTest : public testing::Test {
+public:
+ vsm::test::MockFieldSearcherEnv env;
+ ValueType tensor_type;
+ TensorDataType data_type;
+ SquaredEuclideanDistance dist_func;
+ vsm::FieldIdT field_id;
+ NearestNeighborFieldSearcher searcher;
+ MockQuery query;
+
+ NearestNeighborSearcherTest()
+ : env(),
+ tensor_type(ValueType::from_spec("tensor(x[2])")),
+ data_type(tensor_type),
+ dist_func(CellType::DOUBLE),
+ field_id(2),
+ searcher(field_id, DistanceMetric::Euclidean),
+ query()
+ {
+ env.field_paths->resize(field_id + 1);
+ (*env.field_paths)[field_id].push_back(std::make_unique<FieldPathEntry>(data_type, "my_tensor_field"));
+ }
+ void set_query_tensor(const vespalib::string& query_tensor_name,
+ const vespalib::string& spec_expr) {
+ search::fef::indexproperties::type::QueryFeature::set(env.index_env.getProperties(), query_tensor_name, tensor_type.to_spec());
+ auto tensor = SimpleValue::from_spec(TensorSpec::from_expr(spec_expr));
+ vespalib::nbostream stream;
+ vespalib::eval::encode_value(*tensor, stream);
+ env.query_props.add(query_tensor_name, vespalib::stringref(stream.peek(), stream.size()));
+ }
+ void prepare() {
+ env.prepare(searcher, query.term_list);
+ }
+ void match(const vespalib::string& spec_expr) {
+ TensorFieldValue fv(data_type);
+ auto tensor = SimpleValue::from_spec(TensorSpec::from_expr(spec_expr));
+ fv = std::move(tensor);
+ query.reset();
+ searcher.onValue(fv);
+ }
+ void expect_match(const vespalib::string& spec_expr, double exp_square_distance, const NearestNeighborQueryNode& node) {
+ match(spec_expr);
+ expect_match(exp_square_distance, node);
+ }
+ void expect_match(double exp_square_distance, const NearestNeighborQueryNode& node) {
+ double exp_raw_score = dist_func.to_rawscore(exp_square_distance);
+ EXPECT_TRUE(node.evaluate());
+ EXPECT_DOUBLE_EQ(exp_square_distance, node.get_distance().value());
+ EXPECT_DOUBLE_EQ(exp_raw_score, node.get_raw_score().value());
+ }
+ void expect_not_match(const vespalib::string& spec_expr, const NearestNeighborQueryNode& node) {
+ match(spec_expr);
+ EXPECT_FALSE(node.evaluate());
+ }
+};
+
+TEST_F(NearestNeighborSearcherTest, distance_heap_keeps_the_best_target_hits)
+{
+ query.add("qt1", 2, 100.0);
+ const auto& node = query.get(0);
+ set_query_tensor("qt1", "tensor(x[2]):[1,3]");
+ prepare();
+
+ expect_match("tensor(x[2]):[1,7]", (7-3)*(7-3), node);
+ expect_match("tensor(x[2]):[1,9]", (9-3)*(9-3), node);
+
+ // The distance limit is now (9-3)*(9-3) = 36, so this is not good enough.
+ expect_not_match("tensor(x[2]):[1,10]", node);
+
+ expect_match("tensor(x[2]):[1,5]", (5-3)*(5-3), node);
+
+ // The distance limit is now (7-3)*(7-3) = 16, so this is not good enough.
+ expect_not_match("tensor(x[2]):[1,8]", node);
+
+ // This is not considered a document match as get_raw_score() is not called,
+ // and the distance heap is not updated.
+ match("tensor(x[2]):[1,4]");
+ EXPECT_EQ(1, node.get_distance().value());
+ EXPECT_TRUE(node.evaluate());
+
+ // The distance limit is still (7-3)*(7-3) = 16, so this is in fact good enough.
+ expect_match("tensor(x[2]):[1,6]", (6-3)*(6-3), node);
+
+ // The distance limit is (6-3)*(6-3) = 4, and a similar distance is a match.
+ expect_match("tensor(x[2]):[1,6]", (6-3)*(6-3), node);
+}
+
+TEST_F(NearestNeighborSearcherTest, raw_score_calculated_with_distance_threshold)
+{
+ query.add("qt1", 10, 3.0);
+ const auto& node = query.get(0);
+ set_query_tensor("qt1", "tensor(x[2]):[1,3]");
+ prepare();
+
+ expect_match("tensor(x[2]):[1,5]", (5-3)*(5-3), node);
+ expect_match("tensor(x[2]):[1,6]", (6-3)*(6-3), node);
+
+ // This is not a match since ((7-3)*(7-3) = 16) is larger than the internal distance threshold of (3*3 = 9).
+ expect_not_match("tensor(x[2]):[1,7]", node);
+}
+
+TEST_F(NearestNeighborSearcherTest, raw_score_calculated_for_two_query_operators)
+{
+ query.add("qt1", 10, 3.0);
+ query.add("qt2", 10, 4.0);
+ set_query_tensor("qt1", "tensor(x[2]):[1,3]");
+ set_query_tensor("qt2", "tensor(x[2]):[1,4]");
+ prepare();
+
+ match("tensor(x[2]):[1,5]");
+ expect_match((5-3)*(5-3), query.get(0));
+ expect_match((5-4)*(5-4), query.get(1));
+
+ match("tensor(x[2]):[1,7]");
+ // This is not a match since ((7-3)*(7-3) = 16) is larger than the internal distance threshold of (3*3 = 9).
+ EXPECT_FALSE(query.get(0).evaluate());
+ expect_match((7-4)*(7-4), query.get(1));
+}
+
+TEST_F(NearestNeighborSearcherTest, distance_metric_from_string)
+{
+ using NNFS = NearestNeighborFieldSearcher;
+ EXPECT_EQ(DistanceMetric::Euclidean, NNFS::distance_metric_from_string("EUCLIDEAN"));
+ EXPECT_EQ(DistanceMetric::Angular, NNFS::distance_metric_from_string("ANGULAR"));
+ EXPECT_EQ(DistanceMetric::GeoDegrees, NNFS::distance_metric_from_string("GEODEGREES"));
+ EXPECT_EQ(DistanceMetric::InnerProduct, NNFS::distance_metric_from_string("INNERPRODUCT"));
+ EXPECT_EQ(DistanceMetric::Hamming, NNFS::distance_metric_from_string("HAMMING"));
+ EXPECT_EQ(DistanceMetric::Euclidean, NNFS::distance_metric_from_string("not_available"));
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/streamingvisitors/src/tests/rank_processor/CMakeLists.txt b/streamingvisitors/src/tests/rank_processor/CMakeLists.txt
new file mode 100644
index 00000000000..6ae79c6382d
--- /dev/null
+++ b/streamingvisitors/src/tests/rank_processor/CMakeLists.txt
@@ -0,0 +1,9 @@
+# Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+vespa_add_executable(streamingvisitors_rank_processor_test_app TEST
+ SOURCES
+ rank_processor_test.cpp
+ DEPENDS
+ streamingvisitors
+ GTest::GTest
+)
+vespa_add_test(NAME streamingvisitors_rank_processor_test_app COMMAND streamingvisitors_rank_processor_test_app)
diff --git a/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp b/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp
new file mode 100644
index 00000000000..4d425d9dedd
--- /dev/null
+++ b/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp
@@ -0,0 +1,101 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include <vespa/searchvisitor/rankprocessor.h>
+#include <vespa/searchlib/query/streaming/query.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
+#include <vespa/searchlib/query/tree/querybuilder.h>
+#include <vespa/searchlib/query/tree/simplequery.h>
+#include <vespa/searchlib/query/tree/stackdumpcreator.h>
+#include <vespa/searchvisitor/querytermdata.h>
+#include <vespa/vespalib/gtest/gtest.h>
+
+using search::fef::MatchData;
+using search::fef::TermFieldHandle;
+using search::fef::TermFieldMatchData;
+using search::query::Weight;
+using search::query::QueryBuilder;
+using search::query::SimpleQueryNodeTypes;
+using search::query::StackDumpCreator;
+using search::streaming::NearestNeighborQueryNode;
+using search::streaming::Query;
+using streaming::RankProcessor;
+using streaming::QueryTermData;
+using streaming::QueryTermDataFactory;
+using streaming::QueryWrapper;
+
+class RankProcessorTest : public testing::Test
+{
+protected:
+ QueryTermDataFactory _factory;
+ std::unique_ptr<Query> _query;
+ std::unique_ptr<QueryWrapper> _query_wrapper;
+
+ RankProcessorTest();
+ ~RankProcessorTest() override;
+
+ void build_query(QueryBuilder<SimpleQueryNodeTypes> &builder);
+};
+
+RankProcessorTest::RankProcessorTest()
+ : testing::Test(),
+ _factory(),
+ _query(),
+ _query_wrapper()
+{
+}
+
+RankProcessorTest::~RankProcessorTest() = default;
+
+void
+RankProcessorTest::build_query(QueryBuilder<SimpleQueryNodeTypes> &builder)
+{
+ auto build_node = builder.build();
+ auto stack_dump = StackDumpCreator::create(*build_node);
+ _query = std::make_unique<Query>(_factory, stack_dump);
+ _query_wrapper = std::make_unique<QueryWrapper>(*_query);
+}
+
+class MockRawScoreCalculator : public search::streaming::NearestNeighborQueryNode::RawScoreCalculator {
+public:
+ double to_raw_score(double distance) override { return distance * 2; }
+};
+
+TEST_F(RankProcessorTest, unpack_match_data_for_nearest_neighbor_query_node)
+{
+ QueryBuilder<SimpleQueryNodeTypes> builder;
+ constexpr double distance_threshold = 35.5;
+ constexpr int32_t id = 42;
+ constexpr int32_t weight = 1;
+ constexpr uint32_t target_num_hits = 100;
+ constexpr bool allow_approximate = false;
+ constexpr uint32_t explore_additional_hits = 800;
+ builder.add_nearest_neighbor_term("qtensor", "field", id, Weight(weight), target_num_hits, allow_approximate, explore_additional_hits, distance_threshold);
+ build_query(builder);
+ auto& term_list = _query_wrapper->getTermList();
+ EXPECT_EQ(1u, term_list.size());
+ auto node = dynamic_cast<NearestNeighborQueryNode*>(term_list.front().getTerm());
+ EXPECT_NE(nullptr, node);
+ MockRawScoreCalculator calc;
+ node->set_raw_score_calc(&calc);
+ auto& qtd = static_cast<QueryTermData &>(node->getQueryItem());
+ auto& td = qtd.getTermData();
+ constexpr TermFieldHandle handle = 27;
+ constexpr uint32_t field_id = 12;
+ td.addField(field_id).setHandle(handle);
+ auto md = MatchData::makeTestInstance(handle + 1, handle + 1);
+ auto tfmd = md->resolveTermField(handle);
+ auto invalid_id = TermFieldMatchData::invalidId();
+ EXPECT_EQ(invalid_id, tfmd->getDocId());
+ RankProcessor::unpack_match_data(1, *md, *_query_wrapper);
+ EXPECT_EQ(invalid_id, tfmd->getDocId());
+ constexpr double distance = 1.5;
+ node->set_distance(distance);
+ RankProcessor::unpack_match_data(2, *md, *_query_wrapper);
+ EXPECT_EQ(2, tfmd->getDocId());
+ EXPECT_EQ(distance * 2, tfmd->getRawScore());
+ node->reset();
+ RankProcessor::unpack_match_data(3, *md, *_query_wrapper);
+ EXPECT_EQ(2, tfmd->getDocId());
+}
+
+GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/streamingvisitors/src/tests/searcher/CMakeLists.txt b/streamingvisitors/src/tests/searcher/CMakeLists.txt
index 2277f5ef55f..d43c1b58d33 100644
--- a/streamingvisitors/src/tests/searcher/CMakeLists.txt
+++ b/streamingvisitors/src/tests/searcher/CMakeLists.txt
@@ -3,6 +3,8 @@ vespa_add_executable(vsm_searcher_test_app TEST
SOURCES
searcher_test.cpp
DEPENDS
+ searchlib
+ searchlib_test
streamingvisitors
)
vespa_add_test(NAME vsm_searcher_test_app COMMAND vsm_searcher_test_app)
diff --git a/streamingvisitors/src/tests/searcher/searcher_test.cpp b/streamingvisitors/src/tests/searcher/searcher_test.cpp
index 607e418ac83..6c4ac69298e 100644
--- a/streamingvisitors/src/tests/searcher/searcher_test.cpp
+++ b/streamingvisitors/src/tests/searcher/searcher_test.cpp
@@ -2,19 +2,20 @@
#include <vespa/vespalib/testkit/testapp.h>
+#include <vespa/document/fieldvalue/fieldvalues.h>
+#include <vespa/searchlib/query/streaming/queryterm.h>
+#include <vespa/vsm/searcher/boolfieldsearcher.h>
#include <vespa/vsm/searcher/fieldsearcher.h>
#include <vespa/vsm/searcher/floatfieldsearcher.h>
#include <vespa/vsm/searcher/futf8strchrfieldsearcher.h>
#include <vespa/vsm/searcher/intfieldsearcher.h>
-#include <vespa/vsm/searcher/boolfieldsearcher.h>
-#include <vespa/vsm/searcher/utf8flexiblestringfieldsearcher.h>
+#include <vespa/vsm/searcher/mock_field_searcher_env.h>
#include <vespa/vsm/searcher/utf8exactstringfieldsearcher.h>
+#include <vespa/vsm/searcher/utf8flexiblestringfieldsearcher.h>
#include <vespa/vsm/searcher/utf8substringsearcher.h>
#include <vespa/vsm/searcher/utf8substringsnippetmodifier.h>
#include <vespa/vsm/searcher/utf8suffixstringfieldsearcher.h>
#include <vespa/vsm/vsm/snippetmodifier.h>
-#include <vespa/searchlib/query/streaming/queryterm.h>
-#include <vespa/document/fieldvalue/fieldvalues.h>
using namespace document;
using search::streaming::HitList;
@@ -102,7 +103,7 @@ struct SnippetModifierSetup
{
Query query;
UTF8SubstringSnippetModifier::SP searcher;
- SharedSearcherBuf buf;
+ test::MockFieldSearcherEnv env;
SnippetModifier modifier;
explicit SnippetModifierSetup(const StringList & terms);
~SnippetModifierSetup();
@@ -111,10 +112,10 @@ struct SnippetModifierSetup
SnippetModifierSetup::SnippetModifierSetup(const StringList & terms)
: query(terms),
searcher(new UTF8SubstringSnippetModifier()),
- buf(new SearcherBuf(8)),
+ env(),
modifier(searcher)
{
- searcher->prepare(query.qtl, buf);
+ env.prepare(*searcher, query.qtl);
}
SnippetModifierSetup::~SnippetModifierSetup() = default;
@@ -310,8 +311,8 @@ performSearch(FieldSearcher & fs, const StringList & query, const FieldValue & f
Query q(query);
// prepare field searcher
- SharedSearcherBuf ssb = SharedSearcherBuf(new SearcherBuf());
- fs.prepare(q.qtl, ssb);
+ test::MockFieldSearcherEnv env;
+ env.prepare(fs, q.qtl);
// setup document
SharedFieldPathMap sfim(new FieldPathMapT());
@@ -786,39 +787,40 @@ TEST("snippet modifier manager") {
indexMap["i1"].push_back(1);
indexMap["i2"].push_back(0);
indexMap["i2"].push_back(1);
+ test::MockFieldSearcherEnv env;
{
SnippetModifierManager man;
Query query(StringList().add("i0:foo"));
- man.setup(query.qtl, specMap, indexMap);
+ man.setup(query.qtl, specMap, indexMap, *env.field_paths, env.query_env);
assertQueryTerms(man, 0, StringList().add("foo"));
assertQueryTerms(man, 1, StringList());
}
{
SnippetModifierManager man;
Query query(StringList().add("i1:foo"));
- man.setup(query.qtl, specMap, indexMap);
+ man.setup(query.qtl, specMap, indexMap, *env.field_paths, env.query_env);
assertQueryTerms(man, 0, StringList());
assertQueryTerms(man, 1, StringList());
}
{
SnippetModifierManager man;
Query query(StringList().add("i1:*foo*"));
- man.setup(query.qtl, specMap, indexMap);
+ man.setup(query.qtl, specMap, indexMap, *env.field_paths, env.query_env);
assertQueryTerms(man, 0, StringList());
assertQueryTerms(man, 1, StringList().add("foo"));
}
{
SnippetModifierManager man;
Query query(StringList().add("i2:foo").add("i2:*bar*"));
- man.setup(query.qtl, specMap, indexMap);
+ man.setup(query.qtl, specMap, indexMap, *env.field_paths, env.query_env);
assertQueryTerms(man, 0, StringList().add("foo").add("bar"));
assertQueryTerms(man, 1, StringList().add("bar"));
}
{ // check buffer sizes
SnippetModifierManager man;
Query query(StringList().add("i2:foo").add("i2:*bar*"));
- man.setup(query.qtl, specMap, indexMap);
+ man.setup(query.qtl, specMap, indexMap, *env.field_paths, env.query_env);
{
SnippetModifier * sm = static_cast<SnippetModifier *>(man.getModifiers().getModifier(0));
UTF8SubstringSnippetModifier * searcher = sm->getSearcher().get();
diff --git a/streamingvisitors/src/vespa/searchvisitor/hitcollector.cpp b/streamingvisitors/src/vespa/searchvisitor/hitcollector.cpp
index 10e6c6aa68a..7b4e3cb0208 100644
--- a/streamingvisitors/src/vespa/searchvisitor/hitcollector.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/hitcollector.cpp
@@ -10,8 +10,8 @@
#include <vespa/log/log.h>
LOG_SETUP(".searchvisitor.hitcollector");
-using search::FeatureSet;
using search::fef::MatchData;
+using vespalib::FeatureSet;
using vdslib::SearchResult;
namespace streaming {
diff --git a/streamingvisitors/src/vespa/searchvisitor/hitcollector.h b/streamingvisitors/src/vespa/searchvisitor/hitcollector.h
index 6ce7459adfd..2918f815811 100644
--- a/streamingvisitors/src/vespa/searchvisitor/hitcollector.h
+++ b/streamingvisitors/src/vespa/searchvisitor/hitcollector.h
@@ -2,13 +2,13 @@
#pragma once
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchlib/common/stringmap.h>
#include <vespa/searchlib/fef/matchdata.h>
#include <vespa/vdslib/container/searchresult.h>
#include <vespa/vsm/common/docsum.h>
#include <vespa/vsm/common/storagedocument.h>
#include <vespa/vespalib/stllike/string.h>
+#include <vespa/vespalib/util/featureset.h>
namespace search { namespace fef { class FeatureResolver; } }
@@ -132,9 +132,9 @@ public:
* @param rankProgram the rank program used to calculate all features.
* @param resolver feature resolver, gives feature names and values
**/
- search::FeatureSet::SP getFeatureSet(IRankProgram &rankProgram,
- const search::fef::FeatureResolver &resolver,
- const search::StringStringMap &feature_rename_map);
+ vespalib::FeatureSet::SP getFeatureSet(IRankProgram &rankProgram,
+ const search::fef::FeatureResolver &resolver,
+ const search::StringStringMap &feature_rename_map);
};
diff --git a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.cpp b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.cpp
index 538f3efe44a..81df2b5492f 100644
--- a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.cpp
@@ -22,13 +22,16 @@ IndexEnvironment::IndexEnvironment(IndexEnvironment &&) noexcept = default;
IndexEnvironment::~IndexEnvironment() = default;
bool
-IndexEnvironment::addField(const vespalib::string & name, bool isAttribute)
+IndexEnvironment::addField(const vespalib::string& name,
+ bool isAttribute,
+ search::fef::FieldInfo::DataType data_type)
{
if (getFieldByName(name) != nullptr) {
return false;
}
FieldInfo info(isAttribute ? FieldType::ATTRIBUTE : FieldType::INDEX,
FieldInfo::CollectionType::SINGLE, name, _fields.size());
+ info.set_data_type(data_type);
info.addAttribute(); // we are able to produce needed attributes at query time
_fields.push_back(info);
_fieldNames[info.name()] = info.id();
diff --git a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h
index af037d87076..ef679cacdf0 100644
--- a/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h
+++ b/streamingvisitors/src/vespa/searchvisitor/indexenvironment.h
@@ -83,7 +83,9 @@ public:
return nullptr;
}
- bool addField(const vespalib::string & name, bool isAttribute);
+ bool addField(const vespalib::string& name,
+ bool isAttribute,
+ search::fef::FieldInfo::DataType data_type);
search::fef::Properties & getProperties() { return _properties; }
diff --git a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
index 706325a0f7a..81a2a48fb4d 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/rankmanager.cpp
@@ -2,6 +2,7 @@
#include "rankmanager.h"
#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/fef/fieldinfo.h>
#include <vespa/searchlib/fef/functiontablefactory.h>
#include <vespa/vespalib/util/stringfmt.h>
#include <vespa/vespalib/util/exception.h>
@@ -40,6 +41,16 @@ RankManager::Snapshot::addProperties(const vespa::config::search::RankProfilesCo
}
}
+FieldInfo::DataType
+to_data_type(VsmfieldsConfig::Fieldspec::Searchmethod search_method)
+{
+ if (search_method == VsmfieldsConfig::Fieldspec::Searchmethod::NEAREST_NEIGHBOR) {
+ return FieldInfo::DataType::TENSOR;
+ }
+ // This is the default FieldInfo data type if not specified.
+ return FieldInfo::DataType::DOUBLE;
+}
+
void
RankManager::Snapshot::detectFields(const VsmfieldsHandle & fields)
{
@@ -49,7 +60,7 @@ RankManager::Snapshot::detectFields(const VsmfieldsHandle & fields)
LOG(debug, "Adding field of type '%s' and name '%s' with id '%u' the index environment.",
isAttribute ? "ATTRIBUTE" : "INDEX", fs.name.c_str(), i);
// This id must match the vsm specific field id
- _protoEnv.addField(fs.name, isAttribute);
+ _protoEnv.addField(fs.name, isAttribute, to_data_type(fs.searchmethod));
}
}
diff --git a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp
index 24925bd67ee..3ce137bffe5 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp
@@ -4,12 +4,13 @@
#include "rankprocessor.h"
#include <vespa/searchlib/fef/handle.h>
#include <vespa/searchlib/fef/simpletermfielddata.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
#include <vespa/vsm/vsm/fieldsearchspec.h>
#include <cmath>
#include <vespa/log/log.h>
LOG_SETUP(".searchvisitor.rankprocessor");
-using search::FeatureSet;
+using vespalib::FeatureSet;
using search::fef::FeatureHandle;
using search::fef::ITermData;
using search::fef::ITermFieldData;
@@ -55,24 +56,28 @@ RankProcessor::initQueryEnvironment()
{
QueryWrapper::TermList & terms = _query.getTermList();
- for (uint32_t i = 0; i < terms.size(); ++i) {
- if (terms[i].isGeoPosTerm()) {
- const vespalib::string & fieldName = terms[i].getTerm()->index();
- const vespalib::string & locStr = terms[i].getTerm()->getTermString();
+ for (auto& term : terms) {
+ if (term.isGeoPosTerm()) {
+ const vespalib::string & fieldName = term.getTerm()->index();
+ const vespalib::string & locStr = term.getTerm()->getTermString();
_queryEnv.addGeoLocation(fieldName, locStr);
}
- if (!terms[i].isPhraseTerm() || terms[i].isFirstPhraseTerm()) { // register 1 term data per phrase
- QueryTermData & qtd = dynamic_cast<QueryTermData &>(terms[i].getTerm()->getQueryItem());
+ if (!term.isPhraseTerm() || term.isFirstPhraseTerm()) { // register 1 term data per phrase
+ QueryTermData & qtd = dynamic_cast<QueryTermData &>(term.getTerm()->getQueryItem());
- qtd.getTermData().setWeight(terms[i].getTerm()->weight());
- qtd.getTermData().setUniqueId(terms[i].getTerm()->uniqueId());
- if (terms[i].isFirstPhraseTerm()) {
- qtd.getTermData().setPhraseLength(terms[i].getParent()->width());
+ qtd.getTermData().setWeight(term.getTerm()->weight());
+ qtd.getTermData().setUniqueId(term.getTerm()->uniqueId());
+ if (term.isFirstPhraseTerm()) {
+ qtd.getTermData().setPhraseLength(term.getParent()->width());
} else {
qtd.getTermData().setPhraseLength(1);
}
+ auto* nn_term = term.getTerm()->as_nearest_neighbor_query_node();
+ if (nn_term != nullptr) {
+ qtd.getTermData().set_query_tensor_name(nn_term->get_query_tensor_name());
+ }
- vespalib::string expandedIndexName = vsm::FieldSearchSpecMap::stripNonFields(terms[i].getTerm()->index());
+ vespalib::string expandedIndexName = vsm::FieldSearchSpecMap::stripNonFields(term.getTerm()->index());
const RankManager::View *view = _rankManagerSnapshot->getView(expandedIndexName);
if (view != nullptr) {
RankManager::View::const_iterator iter = view->begin();
@@ -82,17 +87,17 @@ RankProcessor::initQueryEnvironment()
}
} else {
LOG(warning, "Could not find a view for index '%s'. Ranking no fields.",
- getIndexName(terms[i].getTerm()->index(), expandedIndexName).c_str());
+ getIndexName(term.getTerm()->index(), expandedIndexName).c_str());
}
LOG(debug, "Setup query term '%s:%s' (%s)",
- getIndexName(terms[i].getTerm()->index(), expandedIndexName).c_str(),
- terms[i].getTerm()->getTerm(),
- terms[i].isFirstPhraseTerm() ? "phrase" : "term");
+ getIndexName(term.getTerm()->index(), expandedIndexName).c_str(),
+ term.getTerm()->getTerm(),
+ term.isFirstPhraseTerm() ? "phrase" : "term");
_queryEnv.addTerm(&qtd.getTermData());
} else {
LOG(debug, "Ignore query term '%s:%s' (part of phrase)",
- terms[i].getTerm()->index().c_str(), terms[i].getTerm()->getTerm());
+ term.getTerm()->index().c_str(), term.getTerm()->getTerm());
}
}
_rankSetup.prepareSharedState(_queryEnv, _queryEnv.getObjectStore());
@@ -227,14 +232,27 @@ void
RankProcessor::unpackMatchData(uint32_t docId)
{
_docId = docId;
- unpackMatchData(*_match_data);
+ unpack_match_data(docId, *_match_data, _query);
}
void
-RankProcessor::unpackMatchData(MatchData &matchData)
+RankProcessor::unpack_match_data(uint32_t docid, MatchData &matchData, QueryWrapper& query)
{
- for (QueryWrapper::Term & term: _query.getTermList()) {
- if (!term.isPhraseTerm() || term.isFirstPhraseTerm()) { // consider 1 term data per phrase
+ for (QueryWrapper::Term & term: query.getTermList()) {
+ auto nn_node = term.getTerm()->as_nearest_neighbor_query_node();
+ if (nn_node != nullptr) {
+ auto raw_score = nn_node->get_raw_score();
+ if (raw_score.has_value()) {
+ auto& qtd = static_cast<QueryTermData &>(term.getTerm()->getQueryItem());
+ auto& td = qtd.getTermData();
+ if (td.numFields() == 1u) {
+ auto tfd = td.field(0u);
+ auto tmd = matchData.resolveTermField(tfd.getHandle());
+ assert(tmd != nullptr);
+ tmd->setRawScore(docid, raw_score.value());
+ }
+ }
+ } else if (!term.isPhraseTerm() || term.isFirstPhraseTerm()) { // consider 1 term data per phrase
bool isPhrase = term.isFirstPhraseTerm();
QueryTermData & qtd = static_cast<QueryTermData &>(term.getTerm()->getQueryItem());
const ITermData &td = qtd.getTermData();
@@ -266,8 +284,8 @@ RankProcessor::unpackMatchData(MatchData &matchData)
tmd = matchData.resolveTermField(tfd->getHandle());
tmd->setFieldId(fieldId);
// reset field match data, but only once per docId
- if (tmd->getDocId() != _docId) {
- tmd->reset(_docId);
+ if (tmd->getDocId() != docid) {
+ tmd->reset(docid);
}
}
// find fieldLen for new field
diff --git a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
index c2b3d8adedf..c74a2d1e3ee 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
+++ b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
@@ -50,8 +50,6 @@ private:
**/
void init(bool forRanking, size_t wantedHitCount);
- void unpackMatchData(search::fef::MatchData &matchData);
-
public:
using UP = std::unique_ptr<RankProcessor>;
@@ -65,14 +63,16 @@ public:
void initForRanking(size_t wantedHitCount);
void initForDumping(size_t wantedHitCount);
void unpackMatchData(uint32_t docId);
+ static void unpack_match_data(uint32_t docid, search::fef::MatchData& matchData, QueryWrapper& query);
void runRankProgram(uint32_t docId);
- search::FeatureSet::SP calculateFeatureSet();
+ vespalib::FeatureSet::SP calculateFeatureSet();
void fillSearchResult(vdslib::SearchResult & searchResult);
const search::fef::MatchData &getMatchData() const { return *_match_data; }
void setRankScore(double score) { _score = score; }
double getRankScore() const { return _score; }
HitCollector & getHitCollector() { return *_hitCollector; }
uint32_t getDocId() const { return _docId; }
+ search::fef::IQueryEnvironment& get_query_env() { return _queryEnv; }
};
} // namespace streaming
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
index d0e3d1f038b..8980bc1f54d 100644
--- a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
@@ -7,6 +7,7 @@
#include <vespa/persistence/spi/docentry.h>
#include <vespa/document/datatype/positiondatatype.h>
#include <vespa/document/datatype/documenttype.h>
+#include <vespa/document/datatype/tensor_data_type.h>
#include <vespa/document/datatype/weightedsetdatatype.h>
#include <vespa/document/datatype/mapdatatype.h>
#include <vespa/searchlib/aggregation/modifiers.h>
@@ -14,6 +15,7 @@
#include <vespa/searchlib/common/packets.h>
#include <vespa/searchlib/uca/ucaconverter.h>
#include <vespa/searchlib/features/setup.h>
+#include <vespa/searchlib/tensor/tensor_ext_attribute.h>
#include <vespa/searchcommon/attribute/config.h>
#include <vespa/vespalib/geo/zcurve.h>
#include <vespa/vespalib/objects/nbostream.h>
@@ -99,6 +101,16 @@ createMultiValueAttribute(const vespalib::string & name, const document::FieldVa
return {};
}
+const document::TensorDataType*
+get_tensor_type(const document::FieldValue& fv)
+{
+ auto tfv = dynamic_cast<const document::TensorFieldValue*>(&fv);
+ if (tfv == nullptr) {
+ return nullptr;
+ }
+ return dynamic_cast<const document::TensorDataType*>(tfv->getDataType());
+}
+
AttributeVector::SP
createAttribute(const vespalib::string & name, const document::FieldValue & fv)
{
@@ -111,6 +123,12 @@ createAttribute(const vespalib::string & name, const document::FieldValue & fv)
return std::make_shared<search::SingleStringExtAttribute>(name);
} else if (fv.isA(document::FieldValue::Type::RAW)) {
return std::make_shared<search::attribute::SingleRawExtAttribute>(name);
+ } else if (fv.isA(document::FieldValue::Type::TENSOR) && get_tensor_type(fv) != nullptr) {
+ search::attribute::Config cfg(search::attribute::BasicType::TENSOR, search::attribute::CollectionType::SINGLE);
+ auto tdt = get_tensor_type(fv);
+ assert(tdt != nullptr);
+ cfg.setTensorType(tdt->getTensorType());
+ return std::make_shared<search::tensor::TensorExtAttribute>(name, cfg);
} else {
LOG(debug, "Can not make an attribute out of %s of type '%s'.", name.c_str(), fv.className());
}
@@ -369,7 +387,6 @@ void SearchVisitor::init(const Parameters & params)
StringFieldIdTMap fieldsInQuery;
setupFieldSearchers(additionalFields, fieldsInQuery);
- setupSnippetModifiers();
setupScratchDocument(fieldsInQuery);
@@ -382,9 +399,18 @@ void SearchVisitor::init(const Parameters & params)
const RankManager * rm = _env.getRankManager(searchCluster);
_rankController.setRankManagerSnapshot(rm->getSnapshot());
_rankController.setupRankProcessors(_query, location, wantedSummaryCount, _attrMan, _attributeFields);
- // Depends on hitCollector setup.
+
+ // This depends on _fieldPathMap (from setupScratchDocument),
+ // and IQueryEnvironment (from setupRankProcessors).
+ setupSnippetModifiers();
+
+ // Depends on hitCollector setup and _snippetModifierManager
setupDocsumObjects();
+ // This depends on _fieldPathMap (from setupScratchDocument),
+ // and IQueryEnvironment (from setupRankProcessors).
+ prepare_field_searchers();
+
} else {
LOG(warning, "No query received");
}
@@ -444,6 +470,14 @@ SearchVisitor::AttributeInserter::onPrimitive(uint32_t, const Content & c)
} else if (_attribute.is_raw_type()) {
auto raw_value = value.getAsRaw();
attr.add(vespalib::ConstArrayRef<char>(raw_value.first, raw_value.second), c.getWeight());
+ } else if (_attribute.isTensorType()) {
+ auto tfvalue = dynamic_cast<const document::TensorFieldValue*>(&value);
+ if (tfvalue != nullptr) {
+ auto tensor = tfvalue->getAsTensorPtr();
+ if (tensor != nullptr) {
+ attr.add(*tensor, c.getWeight());
+ }
+ }
} else {
assert(false && "We got an attribute vector that is of an unknown type");
}
@@ -630,14 +664,14 @@ SearchVisitor::RankController::onCompletedVisiting(vsm::GetDocsumsStateCallback
// calculate summary features and set them on the callback object
if (!_rankSetup->getSummaryFeatures().empty()) {
LOG(debug, "Calculate summary features");
- search::FeatureSet::SP sf = _rankProcessor->calculateFeatureSet();
+ vespalib::FeatureSet::SP sf = _rankProcessor->calculateFeatureSet();
docsumsStateCallback.setSummaryFeatures(sf);
}
// calculate rank features and set them on the callback object
if (_dumpFeatures) {
LOG(debug, "Calculate rank features");
- search::FeatureSet::SP rf = _dumpProcessor->calculateFeatureSet();
+ vespalib::FeatureSet::SP rf = _dumpProcessor->calculateFeatureSet();
docsumsStateCallback.setRankFeatures(rf);
}
}
@@ -706,9 +740,14 @@ SearchVisitor::setupFieldSearchers(const std::vector<vespalib::string> & additio
_fieldSearchSpecMap.buildFieldsInQuery(_query, fieldsInQuery);
// Connect field names in the query to field searchers
_fieldSearchSpecMap.buildSearcherMap(fieldsInQuery.map(), _fieldSearcherMap);
+}
+void
+SearchVisitor::prepare_field_searchers()
+{
// prepare the field searchers
- _fieldSearcherMap.prepare(_fieldSearchSpecMap.documentTypeMap(), _searchBuffer, _query);
+ _fieldSearcherMap.prepare(_fieldSearchSpecMap.documentTypeMap(), _searchBuffer, _query,
+ *_fieldPathMap, _rankController.getRankProcessor()->get_query_env());
}
void
@@ -716,7 +755,8 @@ SearchVisitor::setupSnippetModifiers()
{
QueryTermList qtl;
_query.getLeafs(qtl);
- _snippetModifierManager.setup(qtl, _fieldSearchSpecMap.specMap(), _fieldSearchSpecMap.documentTypeMap().begin()->second);
+ _snippetModifierManager.setup(qtl, _fieldSearchSpecMap.specMap(), _fieldSearchSpecMap.documentTypeMap().begin()->second,
+ *_fieldPathMap, _rankController.getRankProcessor()->get_query_env());
}
void
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h
index 06ab2eaeaaa..80df69f756e 100644
--- a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h
+++ b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h
@@ -38,7 +38,7 @@ namespace streaming {
* @class storage::SearchVisitor
*
* @brief Visitor that applies a search query to visitor data and
- * converts them to a SearchResultCommand and a DocumentSummaryCommand.
+ * converts them to a QueryResultCommand.
**/
class SearchVisitor : public storage::Visitor {
public:
@@ -260,6 +260,13 @@ private:
vsm::StringFieldIdTMap & fieldsInQuery);
/**
+ * Prepare the field searchers for the given query.
+ * This includes connecting the query terms searching a given field to that field searcher,
+ * and setting up objects in the field searcher needed when matching later on.
+ **/
+ void prepare_field_searchers();
+
+ /**
* Setup snippet modifiers for the fields where we have substring search.
* The modifiers will be used when generating docsum.
**/
diff --git a/streamingvisitors/src/vespa/vsm/common/document.h b/streamingvisitors/src/vespa/vsm/common/document.h
index de9ab052aa1..365d0e33ed0 100644
--- a/streamingvisitors/src/vespa/vsm/common/document.h
+++ b/streamingvisitors/src/vespa/vsm/common/document.h
@@ -13,7 +13,7 @@ namespace vespalib {
namespace vsm {
/// Type to identify fields in documents.
-using FieldIdT = unsigned int;
+using FieldIdT = uint32_t;
/// A type to represent a list of FieldIds.
using FieldIdTList = std::vector<FieldIdT>;
/// A type to represent all the fields contained in all the indexs.
diff --git a/streamingvisitors/src/vespa/vsm/config/vsmfields.def b/streamingvisitors/src/vespa/vsm/config/vsmfields.def
index 5e943c9274d..a7bd1f03f85 100644
--- a/streamingvisitors/src/vespa/vsm/config/vsmfields.def
+++ b/streamingvisitors/src/vespa/vsm/config/vsmfields.def
@@ -12,7 +12,7 @@ searchall int default=1
fieldspec[].name string
## The search method for a given field. Note: same field in 2 different document types must match on type if not a random result might be expected.
-fieldspec[].searchmethod enum { NONE, BOOL, AUTOUTF8, UTF8, SSE2UTF8, INT8, INT16, INT32, INT64, FLOAT16, FLOAT, DOUBLE, GEOPOS } default=AUTOUTF8
+fieldspec[].searchmethod enum { NONE, BOOL, AUTOUTF8, UTF8, SSE2UTF8, INT8, INT16, INT32, INT64, FLOAT16, FLOAT, DOUBLE, GEOPOS, NEAREST_NEIGHBOR } default=AUTOUTF8
fieldspec[].arg1 string default=""
## Maximum number of chars to search per field.
diff --git a/streamingvisitors/src/vespa/vsm/searcher/CMakeLists.txt b/streamingvisitors/src/vespa/vsm/searcher/CMakeLists.txt
index 0a2a9ec21d2..730cba4abba 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/CMakeLists.txt
+++ b/streamingvisitors/src/vespa/vsm/searcher/CMakeLists.txt
@@ -15,6 +15,7 @@ vespa_add_library(vsm_vsmsearcher OBJECT
futf8strchrfieldsearcher.cpp
geo_pos_field_searcher.cpp
intfieldsearcher.cpp
+ nearest_neighbor_field_searcher.cpp
strchrfieldsearcher.cpp
utf8flexiblestringfieldsearcher.cpp
utf8strchrfieldsearcher.cpp
diff --git a/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.cpp
index 8c9b556e593..47b88429f06 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.cpp
@@ -25,10 +25,13 @@ BoolFieldSearcher::BoolFieldSearcher(FieldIdT fId) :
BoolFieldSearcher::~BoolFieldSearcher() = default;
-void BoolFieldSearcher::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf)
+void BoolFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
_terms.clear();
- FieldSearcher::prepare(qtl, buf);
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
for (const QueryTerm * qt : qtl) {
if (TRUE == qt->getTerm()) {
_terms.push_back(true);
diff --git a/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.h b/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.h
index f6afef9e507..01c907888c6 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/boolfieldsearcher.h
@@ -11,7 +11,10 @@ public:
std::unique_ptr<FieldSearcher> duplicate() const override;
BoolFieldSearcher(FieldIdT fId);
~BoolFieldSearcher();
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
void onValue(const document::FieldValue & fv) override;
private:
std::vector<bool> _terms;
diff --git a/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp
index aad4f9d5aa2..9a89d0bebae 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp
@@ -61,13 +61,12 @@ void FieldSearcherBase::prepare(const QueryTermList & qtl)
_qtlFastBuffer.resize(sizeof(*_qtlFast)*(_qtl.size()+1), 0x13);
_qtlFast = reinterpret_cast<v16qi *>(reinterpret_cast<unsigned long>(&_qtlFastBuffer[0]+15) & ~0xf);
_qtlFastSize = 0;
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- const QueryTerm & qt = **it;
- memcpy(&_qtlFast[_qtlFastSize++], qt.getTerm(), std::min(size_t(16), qt.termLen()));
+ for (auto qt : _qtl) {
+ memcpy(&_qtlFast[_qtlFastSize++], qt->getTerm(), std::min(size_t(16), qt->termLen()));
}
}
-FieldSearcher::FieldSearcher(const FieldIdT & fId, bool defaultPrefix) :
+FieldSearcher::FieldSearcher(FieldIdT fId, bool defaultPrefix) :
FieldSearcherBase(),
_field(fId),
_matchType(defaultPrefix ? PREFIX : REGULAR),
@@ -89,23 +88,24 @@ FieldSearcher::~FieldSearcher() = default;
bool FieldSearcher::search(const StorageDocument & doc)
{
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
- QueryTerm::FieldInfo & fInfo = qt.getFieldInfo(field());
- fInfo.setHitOffset(qt.getHitList().size());
+ for (auto qt : _qtl) {
+ QueryTerm::FieldInfo & fInfo = qt->getFieldInfo(field());
+ fInfo.setHitOffset(qt->getHitList().size());
}
onSearch(doc);
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
- QueryTerm::FieldInfo & fInfo = qt.getFieldInfo(field());
- fInfo.setHitCount(qt.getHitList().size() - fInfo.getHitOffset());
+ for(auto qt : _qtl) {
+ QueryTerm::FieldInfo & fInfo = qt->getFieldInfo(field());
+ fInfo.setHitCount(qt->getHitList().size() - fInfo.getHitOffset());
fInfo.setFieldLength(_words);
}
_words = 0;
return true;
}
-void FieldSearcher::prepare(QueryTermList & qtl, const SharedSearcherBuf &)
+void FieldSearcher::prepare(QueryTermList& qtl,
+ const SharedSearcherBuf&,
+ const vsm::FieldPathMapT&,
+ search::fef::IQueryEnvironment&)
{
FieldSearcherBase::prepare(qtl);
prepareFieldId();
@@ -129,9 +129,8 @@ size_t FieldSearcher::countWords(const FieldRef & f)
void FieldSearcher::prepareFieldId()
{
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
- qt.resizeFieldId(field());
+ for(auto qt : _qtl) {
+ qt->resizeFieldId(field());
}
}
@@ -220,31 +219,35 @@ void FieldSearcher::init()
}
}
-void FieldIdTSearcherMap::prepare(const DocumentTypeIndexFieldMapT & difm, const SharedSearcherBuf & searcherBuf, Query & query)
+void FieldIdTSearcherMap::prepare(const DocumentTypeIndexFieldMapT& difm,
+ const SharedSearcherBuf& searcherBuf,
+ Query& query,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
QueryTermList qtl;
query.getLeafs(qtl);
vespalib::string tmp;
- for (FieldIdTSearcherMap::iterator it = begin(), mt = end(); it != mt; it++) {
+ for (auto& searcher : *this) {
QueryTermList onlyInIndex;
- FieldIdT fid = (*it)->field();
- for (QueryTermList::iterator qt = qtl.begin(), mqt = qtl.end(); qt != mqt; qt++) {
- QueryTerm * q = *qt;
- for (DocumentTypeIndexFieldMapT::const_iterator dt(difm.begin()), dmt(difm.end()); dt != dmt; dt++) {
- const IndexFieldMapT & fim = dt->second;
- IndexFieldMapT::const_iterator found = fim.find(FieldSearchSpecMap::stripNonFields(q->index()));
+ FieldIdT fid = searcher->field();
+ for (auto qt : qtl) {
+ for (const auto& doc_type_elem : difm) {
+ const IndexFieldMapT & fim = doc_type_elem.second;
+ auto found = fim.find(FieldSearchSpecMap::stripNonFields(qt->index()));
if (found != fim.end()) {
const FieldIdTList & index = found->second;
- if ((find(index.begin(), index.end(), fid) != index.end()) && (find(onlyInIndex.begin(), onlyInIndex.end(), q) == onlyInIndex.end())) {
- onlyInIndex.push_back(q);
+ if ((find(index.begin(), index.end(), fid) != index.end()) && (find(onlyInIndex.begin(), onlyInIndex.end(), qt) == onlyInIndex.end())) {
+ onlyInIndex.push_back(qt);
}
} else {
- LOG(debug, "Could not find the requested index=%s in the index config map. Query does not fit search definition.", q->index().c_str());
+ LOG(debug, "Could not find the requested index=%s in the index config map. Query does not fit search definition.",
+ qt->index().c_str());
}
}
}
/// Should perhaps do a unique on onlyInIndex
- (*it)->prepare(onlyInIndex, searcherBuf);
+ searcher->prepare(onlyInIndex, searcherBuf, field_paths, query_env);
if (LOG_WOULD_LOG(spam)) {
char tmpBuf[16];
snprintf(tmpBuf, sizeof(tmpBuf), "%d", fid);
diff --git a/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h
index 55aabf3c0cf..abc2bc9d870 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h
@@ -7,6 +7,8 @@
#include <vespa/vsm/common/storagedocument.h>
#include <vespa/vespalib/util/array.h>
+namespace search::fef { class IQueryEnvironment; }
+
namespace vsm {
using termcount_t = size_t;
@@ -50,13 +52,17 @@ public:
EXACT
};
- FieldSearcher(const FieldIdT & fId, bool defaultPrefix=false);
+ FieldSearcher(FieldIdT fId, bool defaultPrefix=false);
~FieldSearcher() override;
virtual std::unique_ptr<FieldSearcher> duplicate() const = 0;
bool search(const StorageDocument & doc);
- virtual void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf);
- const FieldIdT & field() const { return _field; }
- void field(const FieldIdT & v) { _field = v; prepareFieldId(); }
+ virtual void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env);
+
+ FieldIdT field() const { return _field; }
+ void field(FieldIdT v) { _field = v; prepareFieldId(); }
bool prefix() const { return _matchType == PREFIX; }
bool substring() const { return _matchType == SUBSTRING; }
bool suffix() const { return _matchType == SUFFIX; }
@@ -142,7 +148,11 @@ using FieldIdTSearcherMapT = std::vector<FieldSearcherContainer>;
class FieldIdTSearcherMap : public FieldIdTSearcherMapT
{
public:
- void prepare(const DocumentTypeIndexFieldMapT & difm, const SharedSearcherBuf & searcherBuf, search::streaming::Query & query);
+ void prepare(const DocumentTypeIndexFieldMapT& difm,
+ const SharedSearcherBuf& searcherBuf,
+ search::streaming::Query& query,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env);
};
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
index 02d8bd8c12a..578fc9fe0e5 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
@@ -29,12 +29,14 @@ template<typename T>
FloatFieldSearcherT<T>::~FloatFieldSearcherT() {}
template<typename T>
-void FloatFieldSearcherT<T>::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf)
+void FloatFieldSearcherT<T>::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
_floatTerm.clear();
- FieldSearcher::prepare(qtl, buf);
- for (QueryTermList::const_iterator it=qtl.begin(); it < qtl.end(); it++) {
- const QueryTerm * qt = *it;
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
+ for (auto qt : qtl) {
size_t sz(qt->termLen());
if (sz) {
double low;
diff --git a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.h b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.h
index 80f79fd6491..a25f7a74d64 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.h
@@ -11,7 +11,10 @@ class FloatFieldSearcherT : public FieldSearcher
public:
FloatFieldSearcherT(FieldIdT fId=0);
~FloatFieldSearcherT();
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
void onValue(const document::FieldValue & fv) override;
protected:
class FloatInfo
diff --git a/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp
index db93bda7778..43ecba29b33 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp
@@ -28,10 +28,14 @@ GeoPosFieldSearcher::GeoPosFieldSearcher(FieldIdT fId) :
GeoPosFieldSearcher::~GeoPosFieldSearcher() {}
-void GeoPosFieldSearcher::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf) {
+void GeoPosFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
+{
_geoPosTerm.clear();
- FieldSearcher::prepare(qtl, buf);
- for (const QueryTerm * qt : qtl) {
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
+ for (auto qt : qtl) {
const vespalib::string & str = qt->getTermString();
GeoLocationParser parser;
bool valid = parser.parseNoField(str);
diff --git a/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.h b/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.h
index 307be1dacab..b3d09594c01 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.h
@@ -10,7 +10,10 @@ class GeoPosFieldSearcher : public FieldSearcher {
public:
GeoPosFieldSearcher(FieldIdT fId=0);
~GeoPosFieldSearcher();
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
void onValue(const document::FieldValue & fv) override;
void onStructValue(const document::StructFieldValue & fv) override;
std::unique_ptr<FieldSearcher> duplicate() const override;
diff --git a/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
index 8cfb8e6df14..0fb71a3c3c6 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
@@ -19,12 +19,14 @@ IntFieldSearcher::IntFieldSearcher(FieldIdT fId) :
IntFieldSearcher::~IntFieldSearcher() = default;
-void IntFieldSearcher::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf)
+void IntFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
_intTerm.clear();
- FieldSearcher::prepare(qtl, buf);
- for (QueryTermList::const_iterator it=qtl.begin(); it < qtl.end(); it++) {
- const QueryTerm * qt = *it;
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
+ for (auto qt : qtl) {
size_t sz(qt->termLen());
if (sz) {
int64_t low;
diff --git a/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.h b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.h
index 6645a5d719a..f9ae098dae5 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.h
@@ -11,7 +11,10 @@ public:
std::unique_ptr<FieldSearcher> duplicate() const override;
IntFieldSearcher(FieldIdT fId=0);
~IntFieldSearcher();
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
void onValue(const document::FieldValue & fv) override;
protected:
class IntInfo
diff --git a/streamingvisitors/src/vespa/vsm/searcher/mock_field_searcher_env.h b/streamingvisitors/src/vespa/vsm/searcher/mock_field_searcher_env.h
new file mode 100644
index 00000000000..0c872ad120f
--- /dev/null
+++ b/streamingvisitors/src/vespa/vsm/searcher/mock_field_searcher_env.h
@@ -0,0 +1,48 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "fieldsearcher.h"
+#include <vespa/searchlib/fef/properties.h>
+#include <vespa/searchlib/fef/tablemanager.h>
+#include <vespa/searchlib/test/mock_attribute_manager.h>
+#include <vespa/searchvisitor/indexenvironment.h>
+#include <vespa/searchvisitor/queryenvironment.h>
+#include <vespa/vsm/common/storagedocument.h>
+
+namespace vsm::test {
+
+/**
+ * Mock of the objects needed to prepare a FieldSearcher.
+ * Only used for unit testing.
+ */
+struct MockFieldSearcherEnv {
+ SharedSearcherBuf buf;
+ vsm::SharedFieldPathMap field_paths;
+ search::fef::TableManager table_mgr;
+ streaming::IndexEnvironment index_env;
+ search::attribute::test::MockAttributeManager attr_mgr;
+ search::fef::Properties query_props;
+ streaming::QueryEnvironment query_env;
+ MockFieldSearcherEnv()
+ : buf(new SearcherBuf(8)),
+ field_paths(std::make_shared<FieldPathMapT>()),
+ table_mgr(),
+ index_env(table_mgr),
+ attr_mgr(),
+ query_props(),
+ query_env("", index_env, query_props, &attr_mgr)
+ {}
+ ~MockFieldSearcherEnv() {}
+ void prepare(FieldSearcher& searcher, search::streaming::QueryTermList& qtl) {
+ searcher.prepare(qtl, buf, *field_paths, query_env);
+ }
+ void prepare(FieldIdTSearcherMap& searcher_map,
+ const DocumentTypeIndexFieldMapT& difm,
+ search::streaming::Query& query) {
+ searcher_map.prepare(difm, buf, query, *field_paths, query_env);
+ }
+};
+
+}
+
diff --git a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp
new file mode 100644
index 00000000000..db4ee12438e
--- /dev/null
+++ b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp
@@ -0,0 +1,160 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#include "nearest_neighbor_field_searcher.h"
+#include <vespa/document/datatype/datatype.h>
+#include <vespa/document/datatype/tensor_data_type.h>
+#include <vespa/document/fieldvalue/tensorfieldvalue.h>
+#include <vespa/searchcommon/attribute/config.h>
+#include <vespa/searchlib/fef/iqueryenvironment.h>
+#include <vespa/searchlib/fef/query_value.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
+#include <vespa/searchlib/tensor/distance_calculator.h>
+#include <vespa/searchlib/tensor/distance_function.h>
+#include <vespa/searchlib/tensor/distance_function_factory.h>
+#include <vespa/searchlib/tensor/tensor_ext_attribute.h>
+#include <vespa/vespalib/util/exceptions.h>
+#include <vespa/vespalib/util/issue.h>
+
+using search::attribute::BasicType;
+using search::attribute::CollectionType;
+using search::attribute::Config;
+using search::fef::QueryValue;
+using search::tensor::DistanceCalculator;
+using search::tensor::TensorExtAttribute;
+using search::tensor::make_distance_function;
+using vespalib::eval::ValueType;
+
+namespace {
+
+constexpr uint32_t scratch_docid = 0;
+
+std::unique_ptr<TensorExtAttribute>
+make_attribute(const ValueType& tensor_type)
+{
+ Config cfg(BasicType::TENSOR, CollectionType::SINGLE);
+ cfg.setTensorType(tensor_type);
+ auto result = std::make_unique<TensorExtAttribute>("nnfs_attr", cfg);
+ uint32_t docid;
+ result->addDoc(docid);
+ assert(docid == scratch_docid);
+ return result;
+}
+
+}
+
+namespace vsm {
+
+NearestNeighborFieldSearcher::NodeAndCalc::NodeAndCalc(search::streaming::NearestNeighborQueryNode* node_in,
+ std::unique_ptr<search::tensor::DistanceCalculator> calc_in)
+ : node(node_in),
+ calc(std::move(calc_in)),
+ heap(node->get_target_hits())
+{
+ node->set_raw_score_calc(this);
+ heap.set_distance_threshold(calc->function().convert_threshold(node->get_distance_threshold()));
+}
+
+double
+NearestNeighborFieldSearcher::NodeAndCalc::to_raw_score(double distance)
+{
+ heap.used(distance);
+ return calc->function().to_rawscore(distance);
+}
+
+NearestNeighborFieldSearcher::NearestNeighborFieldSearcher(FieldIdT fid,
+ search::attribute::DistanceMetric metric)
+ : FieldSearcher(fid),
+ _metric(metric),
+ _attr(),
+ _calcs()
+{
+}
+
+NearestNeighborFieldSearcher::~NearestNeighborFieldSearcher() = default;
+
+std::unique_ptr<FieldSearcher>
+NearestNeighborFieldSearcher::duplicate() const
+{
+ return std::make_unique<NearestNeighborFieldSearcher>(field(), _metric);
+}
+
+void
+NearestNeighborFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
+{
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
+ const auto* tensor_type = field_paths[field()].back().getDataType().cast_tensor();
+ if (tensor_type == nullptr) {
+ vespalib::Issue::report("Data type for field %u is '%s', but expected it to be a tensor type",
+ field(), field_paths[field()].back().getDataType().toString().c_str());
+ }
+ _attr = make_attribute(tensor_type->getTensorType());
+ _calcs.clear();
+ for (auto term : qtl) {
+ auto* nn_term = term->as_nearest_neighbor_query_node();
+ if (nn_term == nullptr) {
+ vespalib::Issue::report("Query term (%s) searching field %u is NOT a NearestNeighborQueryNode",
+ term->getClassName().c_str(), field());
+ continue;
+ }
+ auto query_value = QueryValue::from_config(nn_term->get_query_tensor_name(), query_env.getIndexEnvironment());
+ query_value.prepare_shared_state(query_env, query_env.getObjectStore());
+ const auto* tensor_value = query_value.lookup_value(query_env.getObjectStore());
+ if (tensor_value == nullptr) {
+ vespalib::Issue::report("Could not find query tensor for NearestNeighborQueryNode(%s, %s)",
+ nn_term->index().c_str(), nn_term->get_query_tensor_name().c_str());
+ continue;
+ }
+ try {
+ auto calc = DistanceCalculator::make_with_validation(*_attr, *tensor_value);
+ _calcs.push_back(std::make_unique<NodeAndCalc>(nn_term, std::move(calc)));
+ } catch (const vespalib::IllegalArgumentException& ex) {
+ vespalib::Issue::report("Could not create DistanceCalculator for NearestNeighborQueryNode(%s, %s): %s",
+ nn_term->index().c_str(), nn_term->get_query_tensor_name().c_str(), ex.what());
+ }
+ }
+}
+
+void
+NearestNeighborFieldSearcher::onValue(const document::FieldValue& fv)
+{
+ if (fv.isA(document::FieldValue::Type::TENSOR)) {
+ const auto* tfv = dynamic_cast<const document::TensorFieldValue*>(&fv);
+ if (tfv && tfv->getAsTensorPtr()) {
+ _attr->add(*tfv->getAsTensorPtr(), 1);
+ for (auto& elem : _calcs) {
+ double distance_limit = elem->heap.distanceLimit();
+ double distance = elem->calc->calc_with_limit(scratch_docid, distance_limit);
+ if (distance <= distance_limit) {
+ elem->node->set_distance(distance);
+ }
+ }
+ }
+ }
+}
+
+search::attribute::DistanceMetric
+NearestNeighborFieldSearcher::distance_metric_from_string(const vespalib::string& value)
+{
+ using search::attribute::DistanceMetric;
+ // Valid string values must match the definition of DistanceMetric in
+ // config-model/src/main/java/com/yahoo/schema/document/Attribute.java
+ if (value == "EUCLIDEAN") {
+ return DistanceMetric::Euclidean;
+ } else if (value == "ANGULAR") {
+ return DistanceMetric::Angular;
+ } else if (value == "GEODEGREES") {
+ return DistanceMetric::GeoDegrees;
+ } else if (value == "INNERPRODUCT") {
+ return DistanceMetric::InnerProduct;
+ } else if (value == "HAMMING") {
+ return DistanceMetric::Hamming;
+ }
+ vespalib::Issue::report("Distance metric '%s' is not supported. Using 'euclidean' instead", value.c_str());
+ return DistanceMetric::Euclidean;
+}
+
+}
+
diff --git a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h
new file mode 100644
index 00000000000..d5d751cd637
--- /dev/null
+++ b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h
@@ -0,0 +1,58 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+
+#pragma once
+
+#include "fieldsearcher.h"
+#include <vespa/eval/eval/value_type.h>
+#include <vespa/searchcommon/attribute/distance_metric.h>
+#include <vespa/searchlib/query/streaming/nearest_neighbor_query_node.h>
+#include <vespa/searchlib/queryeval/nearest_neighbor_distance_heap.h>
+#include <vespa/searchlib/tensor/distance_calculator.h>
+#include <vespa/searchlib/tensor/tensor_ext_attribute.h>
+
+namespace search::fef { class IQueryEnvironment; }
+
+namespace search::tensor {
+class TensorExtAttribute;
+}
+
+namespace vsm {
+
+/**
+ * Class used to perform exact nearest neighbor search over the streamed values of a tensor field.
+ *
+ * The raw score from the distance calculation is stored in NearestNeighborQueryNode instances
+ * searching this field.
+ */
+class NearestNeighborFieldSearcher : public FieldSearcher {
+private:
+ class NodeAndCalc : search::streaming::NearestNeighborQueryNode::RawScoreCalculator {
+ public:
+ search::streaming::NearestNeighborQueryNode* node;
+ std::unique_ptr<search::tensor::DistanceCalculator> calc;
+ search::queryeval::NearestNeighborDistanceHeap heap;
+ NodeAndCalc(search::streaming::NearestNeighborQueryNode* node_in,
+ std::unique_ptr<search::tensor::DistanceCalculator> calc_in);
+
+ double to_raw_score(double distance) override;
+ };
+ search::attribute::DistanceMetric _metric;
+ std::unique_ptr<search::tensor::TensorExtAttribute> _attr;
+ std::vector<std::unique_ptr<NodeAndCalc>> _calcs;
+
+public:
+ NearestNeighborFieldSearcher(FieldIdT fid,
+ search::attribute::DistanceMetric metric);
+ ~NearestNeighborFieldSearcher();
+
+ std::unique_ptr<FieldSearcher> duplicate() const override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
+ void onValue(const document::FieldValue& fv) override;
+
+ static search::attribute::DistanceMetric distance_metric_from_string(const vespalib::string& value);
+};
+
+}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
index 1c4ff78ff4a..6a46e4604be 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
@@ -7,9 +7,12 @@ using search::streaming::QueryTermList;
namespace vsm {
-void StrChrFieldSearcher::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf)
+void StrChrFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
- FieldSearcher::prepare(qtl, buf);
+ FieldSearcher::prepare(qtl, buf, field_paths, query_env);
}
void StrChrFieldSearcher::onValue(const document::FieldValue & fv)
@@ -31,10 +34,9 @@ bool StrChrFieldSearcher::matchDoc(const FieldRef & fieldRef)
_words += countWords(fieldRef);
}
} else {
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
- if (fieldRef.size() >= qt.termLen()) {
- _words += matchTerm(fieldRef, qt);
+ for (auto qt : _qtl) {
+ if (fieldRef.size() >= qt->termLen()) {
+ _words += matchTerm(fieldRef, *qt);
} else {
_words += countWords(fieldRef);
}
@@ -46,7 +48,7 @@ bool StrChrFieldSearcher::matchDoc(const FieldRef & fieldRef)
size_t StrChrFieldSearcher::shortestTerm() const
{
size_t mintsz(_qtl.front()->termLen());
- for(QueryTermList::const_iterator it=_qtl.begin()+1, mt=_qtl.end(); it != mt; it++) {
+ for (auto it=_qtl.begin()+1, mt=_qtl.end(); it != mt; it++) {
const QueryTerm & qt = **it;
mintsz = std::min(mintsz, qt.termLen());
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.h b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.h
index 0155c79cddf..c155aaefea9 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.h
@@ -11,7 +11,10 @@ public:
StrChrFieldSearcher() : FieldSearcher(0) { }
StrChrFieldSearcher(FieldIdT fId) : FieldSearcher(fId) { }
void onValue(const document::FieldValue & fv) override;
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
private:
size_t shortestTerm() const;
bool matchDoc(const FieldRef & field);
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8exactstringfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8exactstringfieldsearcher.cpp
index 977602a691c..a7ad02fa9d9 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8exactstringfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8exactstringfieldsearcher.cpp
@@ -17,9 +17,8 @@ size_t
UTF8ExactStringFieldSearcher::matchTerms(const FieldRef & f, const size_t mintsz)
{
(void) mintsz;
- for (QueryTermList::iterator it = _qtl.begin(), mt = _qtl.end(); it != mt; ++it) {
- QueryTerm & qt = **it;
- matchTermExact(f, qt);
+ for (auto qt : _qtl) {
+ matchTermExact(f, *qt);
}
return 1;
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8flexiblestringfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8flexiblestringfieldsearcher.cpp
index 9aef99f9fa1..5809738456f 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8flexiblestringfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8flexiblestringfieldsearcher.cpp
@@ -20,8 +20,8 @@ UTF8FlexibleStringFieldSearcher::matchTerms(const FieldRef & f, const size_t min
{
(void) mintsz;
size_t words = 0;
- for (QueryTermList::iterator it = _qtl.begin(); it != _qtl.end(); ++it) {
- words = matchTerm(f, **it);
+ for (auto qt : _qtl) {
+ words = matchTerm(f, *qt);
}
return words;
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8strchrfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8strchrfieldsearcher.cpp
index 0d93009655c..e8ac87b836b 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8strchrfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8strchrfieldsearcher.cpp
@@ -29,15 +29,14 @@ UTF8StrChrFieldSearcher::matchTerms(const FieldRef & f, const size_t mintsz)
for( ; n < e; ) {
if (!*n) { _zeroCount++; n++; }
n = tokenize(n, _buf->capacity(), fn, fl);
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
+ for (auto qt : _qtl) {
const cmptype_t * term;
- termsize_t tsz = qt.term(term);
- if ((tsz <= fl) && (prefix() || qt.isPrefix() || (tsz == fl))) {
+ termsize_t tsz = qt->term(term);
+ if ((tsz <= fl) && (prefix() || qt->isPrefix() || (tsz == fl))) {
const cmptype_t *tt=term, *et=term+tsz;
for (const cmptype_t *fnt=fn; (tt < et) && (*tt == *fnt); tt++, fnt++);
if (tt == et) {
- addHit(qt, words);
+ addHit(*qt, words);
}
}
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.cpp
index 148cdf2c0c3..f991722d623 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.cpp
@@ -236,9 +236,12 @@ UTF8StringFieldSearcherBase::UTF8StringFieldSearcherBase(FieldIdT fId) :
UTF8StringFieldSearcherBase::~UTF8StringFieldSearcherBase() {}
void
-UTF8StringFieldSearcherBase::prepare(QueryTermList & qtl, const SharedSearcherBuf & buf)
+UTF8StringFieldSearcherBase::prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
- StrChrFieldSearcher::prepare(qtl, buf);
+ StrChrFieldSearcher::prepare(qtl, buf, field_paths, query_env);
_buf = buf;
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.h b/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.h
index f540a7ac457..a017b501660 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8stringfieldsearcherbase.h
@@ -107,7 +107,10 @@ public:
UTF8StringFieldSearcherBase();
UTF8StringFieldSearcherBase(FieldIdT fId);
~UTF8StringFieldSearcherBase();
- void prepare(search::streaming::QueryTermList & qtl, const SharedSearcherBuf & buf) override;
+ void prepare(search::streaming::QueryTermList& qtl,
+ const SharedSearcherBuf& buf,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env) override;
/**
* Matches the given query term against the given word using suffix match strategy.
*
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8substringsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8substringsearcher.cpp
index fd327d3a3df..adcf7a937c1 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8substringsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8substringsearcher.cpp
@@ -29,15 +29,14 @@ UTF8SubStringFieldSearcher::matchTerms(const FieldRef & f, const size_t mintsz)
const cmptype_t * fre = fe - mintsz;
termcount_t words(0);
for(words = 0; fn <= fre; ) {
- for(QueryTermList::iterator it=_qtl.begin(), mt=_qtl.end(); it != mt; it++) {
- QueryTerm & qt = **it;
+ for (auto qt : _qtl) {
const cmptype_t * term;
- termsize_t tsz = qt.term(term);
+ termsize_t tsz = qt->term(term);
const cmptype_t *tt=term, *et=term+tsz, *fnt=fn;
for (; (tt < et) && (*tt == *fnt); tt++, fnt++);
if (tt == et) {
- addHit(qt, words);
+ addHit(*qt, words);
}
}
if ( ! Fast_UnicodeUtil::IsWordChar(*fn++) ) {
diff --git a/streamingvisitors/src/vespa/vsm/searcher/utf8substringsnippetmodifier.cpp b/streamingvisitors/src/vespa/vsm/searcher/utf8substringsnippetmodifier.cpp
index 9046c0063d5..89388c01354 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/utf8substringsnippetmodifier.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/utf8substringsnippetmodifier.cpp
@@ -41,10 +41,9 @@ UTF8SubstringSnippetModifier::matchTerms(const FieldRef & f, const size_t mintsz
const cmptype_t * drend = dend - mintsz;
termcount_t words = 0;
for(; ditr <= drend; ) {
- for (QueryTermList::iterator itr = _qtl.begin(); itr != _qtl.end(); ++itr) {
- QueryTerm & qt = **itr;
+ for (auto qt : _qtl) {
const cmptype_t * term;
- termsize_t tsz = qt.term(term);
+ termsize_t tsz = qt->term(term);
const cmptype_t * titr = term;
const cmptype_t * tend = term + tsz;
@@ -58,7 +57,7 @@ UTF8SubstringSnippetModifier::matchTerms(const FieldRef & f, const size_t mintsz
// If we have overlapping matches only the first one will be considered.
insertSeparators(mbegin, mend);
}
- addHit(qt, words);
+ addHit(*qt, words);
}
}
if ( ! Fast_UnicodeUtil::IsWordChar(*ditr++) ) {
diff --git a/streamingvisitors/src/vespa/vsm/vsm/fieldsearchspec.cpp b/streamingvisitors/src/vespa/vsm/vsm/fieldsearchspec.cpp
index 7043e63ec87..98ed8a26938 100644
--- a/streamingvisitors/src/vespa/vsm/vsm/fieldsearchspec.cpp
+++ b/streamingvisitors/src/vespa/vsm/vsm/fieldsearchspec.cpp
@@ -1,17 +1,18 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
#include "fieldsearchspec.h"
+#include <vespa/vespalib/stllike/asciistream.h>
+#include <vespa/vsm/searcher/boolfieldsearcher.h>
+#include <vespa/vsm/searcher/floatfieldsearcher.h>
+#include <vespa/vsm/searcher/futf8strchrfieldsearcher.h>
+#include <vespa/vsm/searcher/geo_pos_field_searcher.h>
+#include <vespa/vsm/searcher/intfieldsearcher.h>
+#include <vespa/vsm/searcher/nearest_neighbor_field_searcher.h>
+#include <vespa/vsm/searcher/utf8exactstringfieldsearcher.h>
#include <vespa/vsm/searcher/utf8flexiblestringfieldsearcher.h>
#include <vespa/vsm/searcher/utf8strchrfieldsearcher.h>
#include <vespa/vsm/searcher/utf8substringsearcher.h>
#include <vespa/vsm/searcher/utf8suffixstringfieldsearcher.h>
-#include <vespa/vsm/searcher/utf8exactstringfieldsearcher.h>
-#include <vespa/vsm/searcher/futf8strchrfieldsearcher.h>
-#include <vespa/vsm/searcher/intfieldsearcher.h>
-#include <vespa/vsm/searcher/boolfieldsearcher.h>
-#include <vespa/vsm/searcher/floatfieldsearcher.h>
-#include <vespa/vsm/searcher/geo_pos_field_searcher.h>
-#include <vespa/vespalib/stllike/asciistream.h>
#include <regex>
#include <vespa/log/log.h>
@@ -109,6 +110,10 @@ FieldSearchSpec::FieldSearchSpec(const FieldIdT & fid, const vespalib::string &
case VsmfieldsConfig::Fieldspec::Searchmethod::GEOPOS:
_searcher = std::make_unique<GeoPosFieldSearcher>(fid);
break;
+ case VsmfieldsConfig::Fieldspec::Searchmethod::NEAREST_NEIGHBOR:
+ auto dm = NearestNeighborFieldSearcher::distance_metric_from_string(arg1);
+ _searcher = std::make_unique<NearestNeighborFieldSearcher>(fid, dm);
+ break;
}
if (_searcher) {
setMatchType(_searcher, arg1);
diff --git a/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.cpp b/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.cpp
index 1993f1cf657..2d2d3f24bc6 100644
--- a/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.cpp
+++ b/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.cpp
@@ -99,9 +99,11 @@ SnippetModifierManager::SnippetModifierManager() :
SnippetModifierManager::~SnippetModifierManager() {}
void
-SnippetModifierManager::setup(const QueryTermList & queryTerms,
- const FieldSearchSpecMapT & specMap,
- const IndexFieldMapT & indexMap)
+SnippetModifierManager::setup(const QueryTermList& queryTerms,
+ const FieldSearchSpecMapT& specMap,
+ const IndexFieldMapT& indexMap,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env)
{
FieldQueryTermMap fqtm;
@@ -130,7 +132,7 @@ SnippetModifierManager::setup(const QueryTermList & queryTerms,
for (auto & entry : _modifiers.map()) {
FieldIdT fId = entry.first;
SnippetModifier & smod = static_cast<SnippetModifier &>(*entry.second);
- smod.getSearcher()->prepare(fqtm[fId], _searchBuf);
+ smod.getSearcher()->prepare(fqtm[fId], _searchBuf, field_paths, query_env);
}
}
diff --git a/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.h b/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.h
index 1f8cb952948..c6ca115bda2 100644
--- a/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.h
+++ b/streamingvisitors/src/vespa/vsm/vsm/snippetmodifier.h
@@ -99,9 +99,14 @@ public:
* @param queryTerms the query terms to take into consideration.
* @param specMap mapping from field id to search spec objects.
* @param fieldMap mapping from index (used in the query) to a list of field ids.
+ * @param field_paths mapping from field id to document::FieldPath.
+ * @param query_env query environment containg e.g. query tensors.
**/
- void setup(const search::streaming::QueryTermList & queryTerms,
- const FieldSearchSpecMapT & specMap, const IndexFieldMapT & fieldMap);
+ void setup(const search::streaming::QueryTermList& queryTerms,
+ const FieldSearchSpecMapT& specMap,
+ const IndexFieldMapT& fieldMap,
+ const vsm::FieldPathMapT& field_paths,
+ search::fef::IQueryEnvironment& query_env);
const FieldModifierMap & getModifiers() const { return _modifiers; }
};
diff --git a/streamingvisitors/src/vespa/vsm/vsm/vsm-adapter.h b/streamingvisitors/src/vespa/vsm/vsm/vsm-adapter.h
index 77ed9573e54..ba87ccfef05 100644
--- a/streamingvisitors/src/vespa/vsm/vsm/vsm-adapter.h
+++ b/streamingvisitors/src/vespa/vsm/vsm/vsm-adapter.h
@@ -5,11 +5,11 @@
#include <vespa/searchlib/query/base.h>
#include <vespa/vsm/config/vsm-cfif.h>
#include <vespa/config-summary.h>
-#include <vespa/searchlib/common/featureset.h>
#include <vespa/searchsummary/docsummary/docsumwriter.h>
#include <vespa/searchsummary/docsummary/docsumstate.h>
#include <vespa/searchsummary/docsummary/idocsumenvironment.h>
#include <vespa/juniper/rpinterface.h>
+#include <vespa/vespalib/util/featureset.h>
using search::docsummary::ResultConfig;
using search::docsummary::ResultClass;
@@ -28,8 +28,8 @@ class IMatchingElementsFiller;
class GetDocsumsStateCallback : public search::docsummary::GetDocsumsStateCallback
{
private:
- search::FeatureSet::SP _summaryFeatures;
- search::FeatureSet::SP _rankFeatures;
+ vespalib::FeatureSet::SP _summaryFeatures;
+ vespalib::FeatureSet::SP _rankFeatures;
std::unique_ptr<IMatchingElementsFiller> _matching_elements_filler;
public:
@@ -37,8 +37,8 @@ public:
void fillSummaryFeatures(GetDocsumsState& state) override;
void fillRankFeatures(GetDocsumsState& state) override;
std::unique_ptr<search::MatchingElements> fill_matching_elements(const search::MatchingElementsFields& fields) override;
- void setSummaryFeatures(const search::FeatureSet::SP & sf) { _summaryFeatures = sf; }
- void setRankFeatures(const search::FeatureSet::SP & rf) { _rankFeatures = rf; }
+ void setSummaryFeatures(const vespalib::FeatureSet::SP & sf) { _summaryFeatures = sf; }
+ void setRankFeatures(const vespalib::FeatureSet::SP & rf) { _rankFeatures = rf; }
void set_matching_elements_filler(std::unique_ptr<IMatchingElementsFiller> matching_elements_filler);
~GetDocsumsStateCallback() override;
};
diff --git a/vdslib/src/main/java/com/yahoo/vdslib/distribution/Distribution.java b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Distribution.java
index 9a451ac56ec..a83e2a4f89c 100644
--- a/vdslib/src/main/java/com/yahoo/vdslib/distribution/Distribution.java
+++ b/vdslib/src/main/java/com/yahoo/vdslib/distribution/Distribution.java
@@ -187,11 +187,11 @@ public class Distribution {
}
private int getStorageSeed(BucketId bucket, ClusterState state) {
- int seed = (int) lastNBits(bucket.getRawId(), state.getDistributionBitCount());
+ int seed = (int)lastNBits(bucket.getRawId(), state.getDistributionBitCount());
if (bucket.getUsedBits() > 33) {
int usedBits = bucket.getUsedBits() - 1;
- seed ^= lastNBits(bucket.getRawId() >> 32, usedBits - 32) << 6;
+ seed ^= (int)lastNBits(bucket.getRawId() >> 32, usedBits - 32) << 6;
}
return seed;
}
diff --git a/vdslib/src/tests/distribution/distributiontest.cpp b/vdslib/src/tests/distribution/distributiontest.cpp
index a10aca45b33..b5c756aece9 100644
--- a/vdslib/src/tests/distribution/distributiontest.cpp
+++ b/vdslib/src/tests/distribution/distributiontest.cpp
@@ -17,8 +17,9 @@
#include <vespa/vespalib/util/size_literals.h>
#include <gmock/gmock.h>
#include <chrono>
-#include <thread>
#include <fstream>
+#include <iterator>
+#include <thread>
using namespace ::testing;
@@ -434,7 +435,8 @@ TEST(DistributionTest, test_distribution)
_distribution[i].second = distr.getIdealStorageNodes(
systemState, document::BucketId(26, i));
sort(_distribution[i].second.begin(), _distribution[i].second.end());
- unique(_distribution[i].second.begin(), _distribution[i].second.end());
+ auto unique_nodes = std::distance(_distribution[i].second.begin(), unique(_distribution[i].second.begin(), _distribution[i].second.end()));
+ _distribution[i].second.resize(unique_nodes);
for (unsigned j = 0; j < _distribution[i].second.size(); j++) {
_nodeCount[_distribution[i].second[j]]++;
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/DefaultSignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/DefaultSignedIdentityDocument.java
new file mode 100644
index 00000000000..c2ab22f4921
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/DefaultSignedIdentityDocument.java
@@ -0,0 +1,14 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api;
+
+public record DefaultSignedIdentityDocument(String signature, int signingKeyVersion, int documentVersion,
+ String data, IdentityDocument identityDocument) implements SignedIdentityDocument {
+
+ public DefaultSignedIdentityDocument {
+ identityDocument = EntityBindingsMapper.fromIdentityDocumentData(data);
+ }
+
+ public DefaultSignedIdentityDocument(String signature, int signingKeyVersion, int documentVersion, String data) {
+ this(signature,signingKeyVersion,documentVersion, data, null);
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
index 2d77d2ceda1..a695e10a29c 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapper.java
@@ -6,8 +6,12 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.bindings.DefaultSignedIdentityDocumentEntity;
+import com.yahoo.vespa.athenz.identityprovider.api.bindings.IdentityDocumentEntity;
+import com.yahoo.vespa.athenz.identityprovider.api.bindings.LegacySignedIdentityDocumentEntity;
import com.yahoo.vespa.athenz.identityprovider.api.bindings.SignedIdentityDocumentEntity;
import com.yahoo.vespa.athenz.utils.AthenzIdentities;
+import com.yahoo.yolean.Exceptions;
import java.io.IOException;
import java.io.InputStream;
@@ -16,6 +20,8 @@ import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
+import java.time.Instant;
+import java.util.Base64;
import java.util.Optional;
import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.fromDottedString;
@@ -24,6 +30,7 @@ import static com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId.
* Utility class for mapping objects model types and their Jackson binding versions.
*
* @author bjorncs
+ * @author mortent
*/
public class EntityBindingsMapper {
@@ -48,39 +55,60 @@ public class EntityBindingsMapper {
}
public static SignedIdentityDocument toSignedIdentityDocument(SignedIdentityDocumentEntity entity) {
- return new SignedIdentityDocument(
- entity.signature(),
- entity.signingKeyVersion(),
- fromDottedString(entity.providerUniqueId()),
- new AthenzService(entity.providerService()),
- entity.documentVersion(),
- entity.configServerHostname(),
- entity.instanceHostname(),
- entity.createdAt(),
- entity.ipAddresses(),
- IdentityType.fromId(entity.identityType()),
- Optional.ofNullable(entity.clusterType()).map(ClusterType::from).orElse(null),
- entity.ztsUrl(),
- Optional.ofNullable(entity.serviceIdentity()).map(AthenzIdentities::from).orElse(null),
- entity.unknownAttributes());
+ if (entity instanceof LegacySignedIdentityDocumentEntity docEntity) {
+ IdentityDocument doc = new IdentityDocument(
+ fromDottedString(docEntity.providerUniqueId()),
+ new AthenzService(docEntity.providerService()),
+ docEntity.configServerHostname(),
+ docEntity.instanceHostname(),
+ docEntity.createdAt(),
+ docEntity.ipAddresses(),
+ IdentityType.fromId(docEntity.identityType()),
+ Optional.ofNullable(docEntity.clusterType()).map(ClusterType::from).orElse(null),
+ docEntity.ztsUrl(),
+ Optional.ofNullable(docEntity.serviceIdentity()).map(AthenzIdentities::from).orElse(null),
+ docEntity.unknownAttributes());
+ return new LegacySignedIdentityDocument(
+ docEntity.signature(),
+ docEntity.signingKeyVersion(),
+ entity.documentVersion(),
+ doc);
+ } else if (entity instanceof DefaultSignedIdentityDocumentEntity docEntity) {
+ return new DefaultSignedIdentityDocument(docEntity.signature(),
+ docEntity.signingKeyVersion(),
+ docEntity.documentVersion(),
+ docEntity.data());
+ } else {
+ throw new IllegalArgumentException("Unknown signed identity document type: " + entity.getClass().getName());
+ }
}
public static SignedIdentityDocumentEntity toSignedIdentityDocumentEntity(SignedIdentityDocument model) {
- return new SignedIdentityDocumentEntity(
- model.signature(),
- model.signingKeyVersion(),
- model.providerUniqueId().asDottedString(),
- model.providerService().getFullName(),
- model.documentVersion(),
- model.configServerHostname(),
- model.instanceHostname(),
- model.createdAt(),
- model.ipAddresses(),
- model.identityType().id(),
- Optional.ofNullable(model.clusterType()).map(ClusterType::toConfigValue).orElse(null),
- model.ztsUrl(),
- Optional.ofNullable(model.serviceIdentity()).map(AthenzIdentity::getFullName).orElse(null),
- model.unknownAttributes());
+ if (model instanceof LegacySignedIdentityDocument legacyModel) {
+ IdentityDocument idDoc = legacyModel.identityDocument();
+ return new LegacySignedIdentityDocumentEntity(
+ legacyModel.signature(),
+ legacyModel.signingKeyVersion(),
+ idDoc.providerUniqueId().asDottedString(),
+ idDoc.providerService().getFullName(),
+ legacyModel.documentVersion(),
+ idDoc.configServerHostname(),
+ idDoc.instanceHostname(),
+ idDoc.createdAt(),
+ idDoc.ipAddresses(),
+ idDoc.identityType().id(),
+ Optional.ofNullable(idDoc.clusterType()).map(ClusterType::toConfigValue).orElse(null),
+ idDoc.ztsUrl(),
+ Optional.ofNullable(idDoc.serviceIdentity()).map(AthenzIdentity::getFullName).orElse(null),
+ idDoc.unknownAttributes());
+ } else if (model instanceof DefaultSignedIdentityDocument defaultModel){
+ return new DefaultSignedIdentityDocumentEntity(defaultModel.signature(),
+ defaultModel.signingKeyVersion(),
+ defaultModel.documentVersion(),
+ defaultModel.data());
+ } else {
+ throw new IllegalArgumentException("Unsupported model type: " + model.getClass().getName());
+ }
}
public static SignedIdentityDocument readSignedIdentityDocumentFromFile(Path file) {
@@ -104,4 +132,40 @@ public class EntityBindingsMapper {
}
}
+ public static IdentityDocument fromIdentityDocumentData(String data) {
+ byte[] decoded = Base64.getDecoder().decode(data);
+ IdentityDocumentEntity docEntity = Exceptions.uncheck(() -> mapper.readValue(decoded, IdentityDocumentEntity.class));
+ return new IdentityDocument(
+ fromDottedString(docEntity.providerUniqueId()),
+ new AthenzService(docEntity.providerService()),
+ docEntity.configServerHostname(),
+ docEntity.instanceHostname(),
+ docEntity.createdAt(),
+ docEntity.ipAddresses(),
+ IdentityType.fromId(docEntity.identityType()),
+ Optional.ofNullable(docEntity.clusterType()).map(ClusterType::from).orElse(null),
+ docEntity.ztsUrl(),
+ Optional.ofNullable(docEntity.serviceIdentity()).map(AthenzIdentities::from).orElse(null),
+ docEntity.unknownAttributes());
+ }
+
+ public static String toIdentityDocmentData(IdentityDocument identityDocument) {
+ IdentityDocumentEntity documentEntity = new IdentityDocumentEntity(
+ identityDocument.providerUniqueId().asDottedString(),
+ identityDocument.providerService().getFullName(),
+ identityDocument.configServerHostname(),
+ identityDocument.instanceHostname(),
+ identityDocument.createdAt(),
+ identityDocument.ipAddresses(),
+ identityDocument.identityType().id(),
+ Optional.ofNullable(identityDocument.clusterType()).map(ClusterType::toConfigValue).orElse(null),
+ identityDocument.ztsUrl(),
+ identityDocument.serviceIdentity().getFullName());
+ try {
+ byte[] bytes = mapper.writeValueAsBytes(documentEntity);
+ return Base64.getEncoder().encodeToString(bytes);
+ } catch (JsonProcessingException e) {
+ throw new RuntimeException("Error during serialization of identity document.", e);
+ }
+ }
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java
new file mode 100644
index 00000000000..577584db185
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocument.java
@@ -0,0 +1,54 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api;
+
+import com.yahoo.vespa.athenz.api.AthenzIdentity;
+import com.yahoo.vespa.athenz.api.AthenzService;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * Represents an unsigned identity document
+ * @author mortent
+ */
+public record IdentityDocument(VespaUniqueInstanceId providerUniqueId, AthenzService providerService, String configServerHostname,
+ String instanceHostname, Instant createdAt, Set<String> ipAddresses,
+ IdentityType identityType, ClusterType clusterType, String ztsUrl,
+ AthenzIdentity serviceIdentity, Map<String, Object> unknownAttributes) {
+
+ public IdentityDocument {
+ ipAddresses = Set.copyOf(ipAddresses);
+
+ Map<String, Object> nonNull = new HashMap<>();
+ unknownAttributes.forEach((key, value) -> {
+ if (value != null) nonNull.put(key, value);
+ });
+ // Map.copyOf() does not allow null values
+ unknownAttributes = Map.copyOf(nonNull);
+ }
+
+ public IdentityDocument(VespaUniqueInstanceId providerUniqueId, AthenzService providerService, String configServerHostname,
+ String instanceHostname, Instant createdAt, Set<String> ipAddresses,
+ IdentityType identityType, ClusterType clusterType, String ztsUrl,
+ AthenzIdentity serviceIdentity) {
+ this(providerUniqueId, providerService, configServerHostname, instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity, Map.of());
+ }
+
+
+ public IdentityDocument withServiceIdentity(AthenzService athenzService) {
+ return new IdentityDocument(
+ this.providerUniqueId,
+ this.providerService,
+ this.configServerHostname,
+ this.instanceHostname,
+ this.createdAt,
+ this.ipAddresses,
+ this.identityType,
+ this.clusterType,
+ this.ztsUrl,
+ athenzService,
+ this.unknownAttributes);
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocumentClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocumentClient.java
index 5a0f77ec765..a3c2f0264d3 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocumentClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/IdentityDocumentClient.java
@@ -1,12 +1,15 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.api;
+import java.util.Optional;
+import java.util.OptionalInt;
+
/**
* A client that communicates that fetches an identity document.
*
* @author bjorncs
*/
public interface IdentityDocumentClient {
- SignedIdentityDocument getNodeIdentityDocument(String host);
- SignedIdentityDocument getTenantIdentityDocument(String host);
+ SignedIdentityDocument getNodeIdentityDocument(String host, int documentVersion);
+ Optional<SignedIdentityDocument> getTenantIdentityDocument(String host, int documentVersion);
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/LegacySignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/LegacySignedIdentityDocument.java
new file mode 100644
index 00000000000..220bc72a017
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/LegacySignedIdentityDocument.java
@@ -0,0 +1,6 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api;
+
+public record LegacySignedIdentityDocument(String signature, int signingKeyVersion, int documentVersion,
+ IdentityDocument identityDocument) implements SignedIdentityDocument {
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
index de78d81cd1b..4e3bd8dee91 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/SignedIdentityDocument.java
@@ -1,54 +1,20 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.api;
-import com.yahoo.vespa.athenz.api.AthenzIdentity;
-import com.yahoo.vespa.athenz.api.AthenzService;
-
-import java.net.URL;
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
/**
* A signed identity document.
- * The {@link #unknownAttributes()} member provides forward compatibility and ensures any new/unknown fields are kept intact when serialized to JSON.
- *
* @author bjorncs
+ * @author mortent
*/
-public record SignedIdentityDocument(String signature, int signingKeyVersion, VespaUniqueInstanceId providerUniqueId,
- AthenzService providerService, int documentVersion, String configServerHostname,
- String instanceHostname, Instant createdAt, Set<String> ipAddresses,
- IdentityType identityType, ClusterType clusterType, String ztsUrl,
- AthenzIdentity serviceIdentity, Map<String, Object> unknownAttributes) {
-
- public SignedIdentityDocument {
- ipAddresses = Set.copyOf(ipAddresses);
-
- Map<String, Object> nonNull = new HashMap<>();
- unknownAttributes.forEach((key, value) -> {
- if (value != null) nonNull.put(key, value);
- });
- // Map.copyOf() does not allow null values
- unknownAttributes = Map.copyOf(nonNull);
- }
-
- public SignedIdentityDocument(String signature, int signingKeyVersion, VespaUniqueInstanceId providerUniqueId,
- AthenzService providerService, int documentVersion, String configServerHostname,
- String instanceHostname, Instant createdAt, Set<String> ipAddresses,
- IdentityType identityType, ClusterType clusterType, String ztsUrl, AthenzIdentity serviceIdentity) {
- this(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname,
- instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity, Map.of());
- }
-
- public static final int DEFAULT_DOCUMENT_VERSION = 3;
-
- public boolean outdated() { return documentVersion < DEFAULT_DOCUMENT_VERSION; }
+public interface SignedIdentityDocument {
- public SignedIdentityDocument withServiceIdentity(AthenzIdentity identity) {
- return new SignedIdentityDocument(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname, instanceHostname, createdAt,
- ipAddresses, identityType, clusterType, ztsUrl, identity);
- }
+ int LEGACY_DEFAULT_DOCUMENT_VERSION = 3;
+ int DEFAULT_DOCUMENT_VERSION = 4;
+ default boolean outdated() { return documentVersion() < LEGACY_DEFAULT_DOCUMENT_VERSION; }
+ IdentityDocument identityDocument();
+ String signature();
+ int signingKeyVersion();
+ int documentVersion();
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/DefaultSignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/DefaultSignedIdentityDocumentEntity.java
new file mode 100644
index 00000000000..3aaff011415
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/DefaultSignedIdentityDocumentEntity.java
@@ -0,0 +1,12 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api.bindings;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+public record DefaultSignedIdentityDocumentEntity(
+ @JsonProperty("signature") String signature,
+ @JsonProperty("signing-key-version") int signingKeyVersion,
+ @JsonProperty("document-version") int documentVersion,
+ @JsonProperty("data") String data)
+ implements SignedIdentityDocumentEntity {
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java
new file mode 100644
index 00000000000..946eacc67eb
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/IdentityDocumentEntity.java
@@ -0,0 +1,51 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api.bindings;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author bjorncs
+ * @author mortent
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record IdentityDocumentEntity(String providerUniqueId, String providerService,
+ String configServerHostname, String instanceHostname, Instant createdAt, Set<String> ipAddresses,
+ String identityType, String clusterType, String ztsUrl, String serviceIdentity, Map<String, Object> unknownAttributes) {
+
+ @JsonCreator
+ public IdentityDocumentEntity(@JsonProperty("provider-unique-id") String providerUniqueId,
+ @JsonProperty("provider-service") String providerService,
+ @JsonProperty("configserver-hostname") String configServerHostname,
+ @JsonProperty("instance-hostname") String instanceHostname,
+ @JsonProperty("created-at") Instant createdAt,
+ @JsonProperty("ip-addresses") Set<String> ipAddresses,
+ @JsonProperty("identity-type") String identityType,
+ @JsonProperty("cluster-type") String clusterType,
+ @JsonProperty("zts-url") String ztsUrl,
+ @JsonProperty("service-identity") String serviceIdentity) {
+ this(providerUniqueId, providerService, configServerHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity, new HashMap<>());
+ }
+
+ @JsonProperty("provider-unique-id") @Override public String providerUniqueId() { return providerUniqueId; }
+ @JsonProperty("provider-service") @Override public String providerService() { return providerService; }
+ @JsonProperty("configserver-hostname") @Override public String configServerHostname() { return configServerHostname; }
+ @JsonProperty("instance-hostname") @Override public String instanceHostname() { return instanceHostname; }
+ @JsonProperty("created-at") @Override public Instant createdAt() { return createdAt; }
+ @JsonProperty("ip-addresses") @Override public Set<String> ipAddresses() { return ipAddresses; }
+ @JsonProperty("identity-type") @Override public String identityType() { return identityType; }
+ @JsonProperty("cluster-type") @Override public String clusterType() { return clusterType; }
+ @JsonProperty("zts-url") @Override public String ztsUrl() { return ztsUrl; }
+ @JsonProperty("service-identity") @Override public String serviceIdentity() { return serviceIdentity; }
+ @JsonAnyGetter @Override public Map<String, Object> unknownAttributes() { return unknownAttributes; }
+ @JsonAnySetter public void set(String name, Object value) { unknownAttributes.put(name, value); }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/LegacySignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/LegacySignedIdentityDocumentEntity.java
new file mode 100644
index 00000000000..e00ab9978f6
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/LegacySignedIdentityDocumentEntity.java
@@ -0,0 +1,57 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.api.bindings;
+
+import com.fasterxml.jackson.annotation.JsonAnyGetter;
+import com.fasterxml.jackson.annotation.JsonAnySetter;
+import com.fasterxml.jackson.annotation.JsonCreator;
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.annotation.JsonProperty;
+
+import java.time.Instant;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * @author bjorncs
+ */
+@JsonInclude(JsonInclude.Include.NON_NULL)
+public record LegacySignedIdentityDocumentEntity (
+ String signature, int signingKeyVersion, String providerUniqueId, String providerService, int documentVersion,
+ String configServerHostname, String instanceHostname, Instant createdAt, Set<String> ipAddresses,
+ String identityType, String clusterType, String ztsUrl, String serviceIdentity, Map<String, Object> unknownAttributes) implements SignedIdentityDocumentEntity {
+
+ @JsonCreator
+ public LegacySignedIdentityDocumentEntity(@JsonProperty("signature") String signature,
+ @JsonProperty("signing-key-version") int signingKeyVersion,
+ @JsonProperty("provider-unique-id") String providerUniqueId,
+ @JsonProperty("provider-service") String providerService,
+ @JsonProperty("document-version") int documentVersion,
+ @JsonProperty("configserver-hostname") String configServerHostname,
+ @JsonProperty("instance-hostname") String instanceHostname,
+ @JsonProperty("created-at") Instant createdAt,
+ @JsonProperty("ip-addresses") Set<String> ipAddresses,
+ @JsonProperty("identity-type") String identityType,
+ @JsonProperty("cluster-type") String clusterType,
+ @JsonProperty("zts-url") String ztsUrl,
+ @JsonProperty("service-identity") String serviceIdentity) {
+ this(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity, new HashMap<>());
+ }
+
+ @JsonProperty("signature") @Override public String signature() { return signature; }
+ @JsonProperty("signing-key-version") @Override public int signingKeyVersion() { return signingKeyVersion; }
+ @JsonProperty("provider-unique-id") @Override public String providerUniqueId() { return providerUniqueId; }
+ @JsonProperty("provider-service") @Override public String providerService() { return providerService; }
+ @JsonProperty("document-version") @Override public int documentVersion() { return documentVersion; }
+ @JsonProperty("configserver-hostname") @Override public String configServerHostname() { return configServerHostname; }
+ @JsonProperty("instance-hostname") @Override public String instanceHostname() { return instanceHostname; }
+ @JsonProperty("created-at") @Override public Instant createdAt() { return createdAt; }
+ @JsonProperty("ip-addresses") @Override public Set<String> ipAddresses() { return ipAddresses; }
+ @JsonProperty("identity-type") @Override public String identityType() { return identityType; }
+ @JsonProperty("cluster-type") @Override public String clusterType() { return clusterType; }
+ @JsonProperty("zts-url") @Override public String ztsUrl() { return ztsUrl; }
+ @JsonProperty("service-identity") @Override public String serviceIdentity() { return serviceIdentity; }
+ @JsonAnyGetter @Override public Map<String, Object> unknownAttributes() { return unknownAttributes; }
+ @JsonAnySetter public void set(String name, Object value) { unknownAttributes.put(name, value); }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
index fc0dff3b97b..174c76f7fa9 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/api/bindings/SignedIdentityDocumentEntity.java
@@ -1,57 +1,77 @@
// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
package com.yahoo.vespa.athenz.identityprovider.api.bindings;
-import com.fasterxml.jackson.annotation.JsonAnyGetter;
-import com.fasterxml.jackson.annotation.JsonAnySetter;
-import com.fasterxml.jackson.annotation.JsonCreator;
-import com.fasterxml.jackson.annotation.JsonInclude;
-import com.fasterxml.jackson.annotation.JsonProperty;
-
-import java.time.Instant;
-import java.util.HashMap;
-import java.util.Map;
-import java.util.Set;
-
-/**
- * @author bjorncs
- */
-@JsonInclude(JsonInclude.Include.NON_NULL)
-public record SignedIdentityDocumentEntity(
- String signature, int signingKeyVersion, String providerUniqueId, String providerService, int documentVersion,
- String configServerHostname, String instanceHostname, Instant createdAt, Set<String> ipAddresses,
- String identityType, String clusterType, String ztsUrl, String serviceIdentity, Map<String, Object> unknownAttributes) {
-
- @JsonCreator
- public SignedIdentityDocumentEntity(@JsonProperty("signature") String signature,
- @JsonProperty("signing-key-version") int signingKeyVersion,
- @JsonProperty("provider-unique-id") String providerUniqueId,
- @JsonProperty("provider-service") String providerService,
- @JsonProperty("document-version") int documentVersion,
- @JsonProperty("configserver-hostname") String configServerHostname,
- @JsonProperty("instance-hostname") String instanceHostname,
- @JsonProperty("created-at") Instant createdAt,
- @JsonProperty("ip-addresses") Set<String> ipAddresses,
- @JsonProperty("identity-type") String identityType,
- @JsonProperty("cluster-type") String clusterType,
- @JsonProperty("zts-url") String ztsUrl,
- @JsonProperty("service-identity") String serviceIdentity) {
- this(signature, signingKeyVersion, providerUniqueId, providerService, documentVersion, configServerHostname,
- instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity, new HashMap<>());
- }
- @JsonProperty("signature") @Override public String signature() { return signature; }
- @JsonProperty("signing-key-version") @Override public int signingKeyVersion() { return signingKeyVersion; }
- @JsonProperty("provider-unique-id") @Override public String providerUniqueId() { return providerUniqueId; }
- @JsonProperty("provider-service") @Override public String providerService() { return providerService; }
- @JsonProperty("document-version") @Override public int documentVersion() { return documentVersion; }
- @JsonProperty("configserver-hostname") @Override public String configServerHostname() { return configServerHostname; }
- @JsonProperty("instance-hostname") @Override public String instanceHostname() { return instanceHostname; }
- @JsonProperty("created-at") @Override public Instant createdAt() { return createdAt; }
- @JsonProperty("ip-addresses") @Override public Set<String> ipAddresses() { return ipAddresses; }
- @JsonProperty("identity-type") @Override public String identityType() { return identityType; }
- @JsonProperty("cluster-type") @Override public String clusterType() { return clusterType; }
- @JsonProperty("zts-url") @Override public String ztsUrl() { return ztsUrl; }
- @JsonProperty("service-identity") @Override public String serviceIdentity() { return serviceIdentity; }
- @JsonAnyGetter @Override public Map<String, Object> unknownAttributes() { return unknownAttributes; }
- @JsonAnySetter public void set(String name, Object value) { unknownAttributes.put(name, value); }
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+import com.fasterxml.jackson.annotation.JsonTypeInfo;
+import com.fasterxml.jackson.databind.DatabindContext;
+import com.fasterxml.jackson.databind.JavaType;
+import com.fasterxml.jackson.databind.annotation.JsonTypeIdResolver;
+import com.fasterxml.jackson.databind.jsontype.TypeIdResolver;
+import com.fasterxml.jackson.databind.type.TypeFactory;
+import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
+
+import java.io.IOException;
+import java.util.Objects;
+
+@JsonIgnoreProperties(ignoreUnknown = true)
+@JsonTypeInfo(use = JsonTypeInfo.Id.CUSTOM, property = "document-version", visible = true)
+@JsonTypeIdResolver(SignedIdentityDocumentEntityTypeResolver.class)
+public interface SignedIdentityDocumentEntity {
+ int documentVersion();
}
+
+class SignedIdentityDocumentEntityTypeResolver implements TypeIdResolver {
+ JavaType javaType;
+
+ @Override
+ public void init(JavaType javaType) {
+ this.javaType = javaType;
+ }
+
+ @Override
+ public String idFromValue(Object o) {
+ return idFromValueAndType(o, o.getClass());
+ }
+
+ @Override
+ public String idFromValueAndType(Object o, Class<?> aClass) {
+ if (Objects.isNull(o)) {
+ throw new IllegalArgumentException("Cannot serialize null oject");
+ } else {
+ if (o instanceof SignedIdentityDocumentEntity s) {
+ return Integer.toString(s.documentVersion());
+ } else {
+ throw new IllegalArgumentException("Cannot serialize class: " + o.getClass());
+ }
+ }
+ }
+
+ @Override
+ public String idFromBaseType() {
+ return idFromValueAndType(null, javaType.getRawClass());
+ }
+
+ @Override
+ public JavaType typeFromId(DatabindContext databindContext, String s) throws IOException {
+ try {
+ int version = Integer.parseInt(s);
+ Class<? extends SignedIdentityDocumentEntity> cls = version <= SignedIdentityDocument.LEGACY_DEFAULT_DOCUMENT_VERSION
+ ? LegacySignedIdentityDocumentEntity.class
+ : DefaultSignedIdentityDocumentEntity.class;
+ return TypeFactory.defaultInstance().constructSpecializedType(javaType,cls);
+ } catch (NumberFormatException e) {
+ throw new IllegalArgumentException("Unable to deserialize document with version: \"%s\"".formatted(s));
+ }
+ }
+
+ @Override
+ public String getDescForKnownTypeIds() {
+ return "Type resolver for SignedIdentityDocumentEntity";
+ }
+
+ @Override
+ public JsonTypeInfo.Id getMechanism() {
+ return JsonTypeInfo.Id.CUSTOM;
+ }
+} \ No newline at end of file
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
index cc9d3b2be65..d26386702d5 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzCredentialsService.java
@@ -11,6 +11,7 @@ import com.yahoo.vespa.athenz.client.zts.InstanceIdentity;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocumentClient;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.tls.AthenzIdentityVerifier;
@@ -74,7 +75,9 @@ class AthenzCredentialsService {
}
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
IdentityDocumentClient identityDocumentClient = createIdentityDocumentClient();
- SignedIdentityDocument document = identityDocumentClient.getTenantIdentityDocument(hostname);
+ // Use legacy version for now.
+ SignedIdentityDocument signedDocument = identityDocumentClient.getTenantIdentityDocument(hostname, SignedIdentityDocument.LEGACY_DEFAULT_DOCUMENT_VERSION).orElseThrow();
+ IdentityDocument document = signedDocument.identityDocument();
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
tenantIdentity,
document.providerUniqueId(),
@@ -87,16 +90,17 @@ class AthenzCredentialsService {
ztsClient.registerInstance(
configserverIdentity,
tenantIdentity,
- EntityBindingsMapper.toAttestationData(document),
+ EntityBindingsMapper.toAttestationData(signedDocument),
csr);
X509Certificate certificate = instanceIdentity.certificate();
- writeCredentialsToDisk(keyPair.getPrivate(), certificate, document);
- return new AthenzCredentials(certificate, keyPair, document);
+ writeCredentialsToDisk(keyPair.getPrivate(), certificate, signedDocument);
+ return new AthenzCredentials(certificate, keyPair, signedDocument);
}
}
- AthenzCredentials updateCredentials(SignedIdentityDocument document, SSLContext sslContext) {
+ AthenzCredentials updateCredentials(SignedIdentityDocument signedDocument, SSLContext sslContext) {
KeyPair newKeyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
+ IdentityDocument document = signedDocument.identityDocument();
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
tenantIdentity,
document.providerUniqueId(),
@@ -112,8 +116,8 @@ class AthenzCredentialsService {
document.providerUniqueId().asDottedString(),
csr);
X509Certificate certificate = instanceIdentity.certificate();
- writeCredentialsToDisk(newKeyPair.getPrivate(), certificate, document);
- return new AthenzCredentials(certificate, newKeyPair, document);
+ writeCredentialsToDisk(newKeyPair.getPrivate(), certificate, signedDocument);
+ return new AthenzCredentials(certificate, newKeyPair, signedDocument);
}
}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
index b9f9f3862c2..77aaf17419d 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImpl.java
@@ -11,7 +11,9 @@ import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
import com.yahoo.jdisc.Metric;
import com.yahoo.metrics.ContainerMetrics;
+import com.yahoo.security.AutoReloadingX509KeyManager;
import com.yahoo.security.KeyStoreBuilder;
+import com.yahoo.security.KeyUtils;
import com.yahoo.security.MutableX509KeyManager;
import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.SslContextBuilder;
@@ -24,9 +26,9 @@ import com.yahoo.vespa.athenz.api.ZToken;
import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient;
import com.yahoo.vespa.athenz.client.zts.ZtsClient;
import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
-import com.yahoo.vespa.athenz.identity.SiaIdentityProvider;
+import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
+import com.yahoo.vespa.athenz.tls.AthenzX509CertificateUtils;
import com.yahoo.vespa.athenz.utils.SiaUtils;
-import com.yahoo.vespa.defaults.Defaults;
import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedKeyManager;
@@ -38,7 +40,6 @@ import java.security.cert.X509Certificate;
import java.time.Clock;
import java.time.Duration;
import java.time.Instant;
-import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -65,7 +66,6 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
// TODO Make some of these values configurable through config. Match requested expiration of register/update requests.
// TODO These should match the requested expiration
- static final Duration UPDATE_PERIOD = Duration.ofDays(1);
static final Duration AWAIT_TERMINTATION_TIMEOUT = Duration.ofSeconds(90);
private final static Duration ROLE_SSL_CONTEXT_EXPIRY = Duration.ofHours(2);
// TODO CMS expects 10min or less token ttl. Use 10min default until we have configurable expiry
@@ -73,20 +73,17 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
// TODO Make path to trust store paths config
private static final Path CLIENT_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/yahoo_certificate_bundle.pem");
- private static final Path ATHENZ_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem");
public static final String CERTIFICATE_EXPIRY_METRIC_NAME = ContainerMetrics.ATHENZ_TENANT_CERT_EXPIRY_SECONDS.baseName();
- private volatile AthenzCredentials credentials;
private final Metric metric;
private final Path trustStore;
- private final AthenzCredentialsService athenzCredentialsService;
private final ScheduledExecutorService scheduler;
private final Clock clock;
private final AthenzService identity;
private final URI ztsEndpoint;
- private final MutableX509KeyManager identityKeyManager = new MutableX509KeyManager();
+ private final AutoReloadingX509KeyManager autoReloadingX509KeyManager;
private final SSLContext identitySslContext;
private final LoadingCache<AthenzRole, X509Certificate> roleSslCertCache;
private final Map<AthenzRole, MutableX509KeyManager> roleKeyManagerCache;
@@ -98,40 +95,32 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
@Inject
public AthenzIdentityProviderImpl(IdentityConfig config, Metric metric) {
- this(config,
- metric,
- CLIENT_TRUST_STORE,
- new AthenzCredentialsService(config,
- createNodeIdentityProvider(config),
- Defaults.getDefaults().vespaHostname(),
- Clock.systemUTC()),
- new ScheduledThreadPoolExecutor(1),
- Clock.systemUTC());
+ this(config, metric, CLIENT_TRUST_STORE, new ScheduledThreadPoolExecutor(1), Clock.systemUTC(), createAutoReloadingX509KeyManager(config));
}
// Test only
AthenzIdentityProviderImpl(IdentityConfig config,
Metric metric,
Path trustStore,
- AthenzCredentialsService athenzCredentialsService,
ScheduledExecutorService scheduler,
- Clock clock) {
+ Clock clock,
+ AutoReloadingX509KeyManager autoReloadingX509KeyManager) {
this.metric = metric;
this.trustStore = trustStore;
- this.athenzCredentialsService = athenzCredentialsService;
this.scheduler = scheduler;
this.clock = clock;
this.identity = new AthenzService(config.domain(), config.service());
this.ztsEndpoint = URI.create(config.ztsUrl());
- roleSslCertCache = crateAutoReloadableCache(ROLE_SSL_CONTEXT_EXPIRY, this::requestRoleCertificate, this.scheduler);
- roleKeyManagerCache = new HashMap<>();
- roleSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
- domainSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
- domainSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
- roleSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
+ this.roleSslCertCache = crateAutoReloadableCache(ROLE_SSL_CONTEXT_EXPIRY, this::requestRoleCertificate, this.scheduler);
+ this.roleKeyManagerCache = new HashMap<>();
+ this.roleSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
+ this.domainSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
+ this.domainSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
+ this.roleSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
this.csrGenerator = new CsrGenerator(config.athenzDnsSuffix(), config.configserverIdentityName());
- this.identitySslContext = createIdentitySslContext(identityKeyManager, trustStore);
- registerInstance();
+ this.autoReloadingX509KeyManager = autoReloadingX509KeyManager;
+ this.identitySslContext = createIdentitySslContext(autoReloadingX509KeyManager, trustStore);
+ this.scheduler.scheduleAtFixedRate(this::reportMetrics, 0, 5, TimeUnit.MINUTES);
}
private static <KEY, VALUE> LoadingCache<KEY, VALUE> createCache(Duration expiry, Function<KEY, VALUE> cacheLoader) {
@@ -165,16 +154,6 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
.build();
}
- private void registerInstance() {
- try {
- updateIdentityCredentials(this.athenzCredentialsService.registerInstance());
- this.scheduler.scheduleAtFixedRate(this::refreshCertificate, UPDATE_PERIOD.toMinutes(), UPDATE_PERIOD.toMinutes(), TimeUnit.MINUTES);
- this.scheduler.scheduleAtFixedRate(this::reportMetrics, 0, 5, TimeUnit.MINUTES);
- } catch (Throwable t) {
- throw new AthenzIdentityProviderException("Could not retrieve Athenz credentials", t);
- }
- }
-
@Override
public AthenzService identity() {
return identity;
@@ -197,13 +176,13 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
@Override
public X509CertificateWithKey getIdentityCertificateWithKey() {
- AthenzCredentials copy = this.credentials;
- return new X509CertificateWithKey(copy.getCertificate(), copy.getKeyPair().getPrivate());
+ var copy = this.autoReloadingX509KeyManager.getCurrentCertificateWithKey();
+ return new X509CertificateWithKey(copy.certificate(), copy.privateKey());
}
- @Override public Path certificatePath() { return athenzCredentialsService.certificatePath(); }
+ @Override public Path certificatePath() { return SiaUtils.getCertificateFile(identity); }
- @Override public Path privateKeyPath() { return athenzCredentialsService.privateKeyPath(); }
+ @Override public Path privateKeyPath() { return SiaUtils.getPrivateKeyFile(identity); }
@Override
public SSLContext getRoleSslContext(String domain, String role) {
@@ -262,7 +241,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
@Override
public PrivateKey getPrivateKey() {
- return credentials.getKeyPair().getPrivate();
+ return autoReloadingX509KeyManager.getPrivateKey(AutoReloadingX509KeyManager.CERTIFICATE_ALIAS);
}
@Override
@@ -272,7 +251,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
@Override
public List<X509Certificate> getIdentityCertificate() {
- return Collections.singletonList(credentials.getCertificate());
+ return List.of(autoReloadingX509KeyManager.getCertificateChain(AutoReloadingX509KeyManager.CERTIFICATE_ALIAS));
}
@Override
@@ -288,19 +267,15 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
}
}
- private void updateIdentityCredentials(AthenzCredentials credentials) {
- this.credentials = credentials;
- this.identityKeyManager.updateKeystore(
- KeyStoreBuilder.withType(PKCS12)
- .withKeyEntry("default", credentials.getKeyPair().getPrivate(), credentials.getCertificate())
- .build(),
- new char[0]);
- }
-
private X509Certificate requestRoleCertificate(AthenzRole role) {
- var doc = credentials.getIdentityDocument();
+ var credentials = autoReloadingX509KeyManager.getCurrentCertificateWithKey();
+ var athenzUniqueInstanceId = VespaUniqueInstanceId.fromDottedString(
+ AthenzX509CertificateUtils.getInstanceId(credentials.certificate())
+ .orElseThrow()
+ );
+ var keyPair = KeyUtils.toKeyPair(credentials.privateKey());
Pkcs10Csr csr = csrGenerator.generateRoleCsr(
- identity, role, doc.providerUniqueId(), doc.clusterType(), credentials.getKeyPair());
+ identity, role, athenzUniqueInstanceId, null, keyPair);
try (ZtsClient client = createZtsClient()) {
X509Certificate roleCertificate = client.getRoleCertificate(role, csr);
updateRoleKeyManager(role, roleCertificate);
@@ -313,7 +288,7 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
MutableX509KeyManager keyManager = roleKeyManagerCache.computeIfAbsent(role, r -> new MutableX509KeyManager());
keyManager.updateKeystore(
KeyStoreBuilder.withType(PKCS12)
- .withKeyEntry("default", credentials.getKeyPair().getPrivate(), certificate)
+ .withKeyEntry("default", autoReloadingX509KeyManager.getCurrentCertificateWithKey().privateKey(), certificate)
.build(),
new char[0]);
}
@@ -346,6 +321,11 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
return new DefaultZtsClient.Builder(ztsEndpoint).withSslContext(getIdentitySslContext()).build();
}
+ private static AutoReloadingX509KeyManager createAutoReloadingX509KeyManager(IdentityConfig config) {
+ var tenantIdentity = new AthenzService(config.domain(), config.service());
+ return AutoReloadingX509KeyManager.fromPemFiles(SiaUtils.getPrivateKeyFile(tenantIdentity), SiaUtils.getCertificateFile(tenantIdentity));
+ }
+
@Override
public void deconstruct() {
try {
@@ -356,32 +336,13 @@ public final class AthenzIdentityProviderImpl extends AbstractComponent implemen
}
}
- private static SiaIdentityProvider createNodeIdentityProvider(IdentityConfig config) {
- return new SiaIdentityProvider(
- new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, CLIENT_TRUST_STORE);
- }
-
- private boolean isExpired(AthenzCredentials credentials) {
- return clock.instant().isAfter(getExpirationTime(credentials));
- }
-
- private static Instant getExpirationTime(AthenzCredentials credentials) {
- return credentials.getCertificate().getNotAfter().toInstant();
- }
-
- void refreshCertificate() {
- try {
- updateIdentityCredentials(isExpired(credentials)
- ? athenzCredentialsService.registerInstance()
- : athenzCredentialsService.updateCredentials(credentials.getIdentityDocument(), identitySslContext));
- } catch (Throwable t) {
- log.log(Level.WARNING, "Failed to update credentials: " + t.getMessage(), t);
- }
+ private static Instant getExpirationTime(X509Certificate certificate) {
+ return certificate.getNotAfter().toInstant();
}
void reportMetrics() {
try {
- Instant expirationTime = getExpirationTime(credentials);
+ Instant expirationTime = getExpirationTime(autoReloadingX509KeyManager.getCurrentCertificateWithKey().certificate());
Duration remainingLifetime = Duration.between(clock.instant(), expirationTime);
metric.set(CERTIFICATE_EXPIRY_METRIC_NAME, remainingLifetime.getSeconds(), null);
} catch (Throwable t) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderProvider.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderProvider.java
new file mode 100644
index 00000000000..66dad931815
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderProvider.java
@@ -0,0 +1,38 @@
+package com.yahoo.vespa.athenz.identityprovider.client;
+
+import com.yahoo.container.core.identity.IdentityConfig;
+import com.yahoo.container.di.componentgraph.Provider;
+import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
+import com.yahoo.jdisc.Metric;
+
+import javax.inject.Inject;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+/**
+ * @author olaa
+ */
+public class AthenzIdentityProviderProvider implements Provider<AthenzIdentityProvider> {
+
+ private final Path NODE_ADMIN_MANAGED_IDENTITY_DOCUMENT = Paths.get("/var/lib/sia/vespa-tenant-identity-document.json");
+ private final AthenzIdentityProvider athenzIdentityProvider;
+
+ @Inject
+ public AthenzIdentityProviderProvider(IdentityConfig config, Metric metric) {
+ if (Files.exists(NODE_ADMIN_MANAGED_IDENTITY_DOCUMENT))
+ athenzIdentityProvider = new AthenzIdentityProviderImpl(config, metric);
+ else
+ athenzIdentityProvider = new LegacyAthenzIdentityProviderImpl(config, metric);
+ }
+
+ @Override
+ public void deconstruct() {
+ athenzIdentityProvider.deconstruct();
+ }
+
+ @Override
+ public AthenzIdentityProvider get() {
+ return athenzIdentityProvider;
+ }
+}
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
index 5b884e3dfb3..f95a3335c24 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
@@ -23,6 +23,7 @@ import java.io.IOException;
import java.io.UncheckedIOException;
import java.net.URI;
import java.time.Duration;
+import java.util.Optional;
import java.util.function.Supplier;
/**
@@ -56,16 +57,16 @@ public class DefaultIdentityDocumentClient implements IdentityDocumentClient {
}
@Override
- public SignedIdentityDocument getNodeIdentityDocument(String host) {
- return getIdentityDocument(host, "node");
+ public SignedIdentityDocument getNodeIdentityDocument(String host, int documentVersion) {
+ return getIdentityDocument(host, "node", documentVersion).orElseThrow();
}
@Override
- public SignedIdentityDocument getTenantIdentityDocument(String host) {
- return getIdentityDocument(host, "tenant");
+ public Optional<SignedIdentityDocument> getTenantIdentityDocument(String host, int documentVersion) {
+ return getIdentityDocument(host, "tenant", documentVersion);
}
- private SignedIdentityDocument getIdentityDocument(String host, String type) {
+ private Optional<SignedIdentityDocument> getIdentityDocument(String host, String type, int documentVersion) {
try (CloseableHttpClient client = createHttpClient(sslContextSupplier.get(), hostnameVerifier)) {
URI uri = configserverUri
@@ -76,13 +77,16 @@ public class DefaultIdentityDocumentClient implements IdentityDocumentClient {
.setUri(uri)
.addHeader("Connection", "close")
.addHeader("Accept", "application/json")
+ .addParameter("documentVersion", Integer.toString(documentVersion))
.build();
try (CloseableHttpResponse response = client.execute(request)) {
String responseContent = EntityUtils.toString(response.getEntity());
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode >= 200 && statusCode <= 299) {
SignedIdentityDocumentEntity entity = objectMapper.readValue(responseContent, SignedIdentityDocumentEntity.class);
- return EntityBindingsMapper.toSignedIdentityDocument(entity);
+ return Optional.of(EntityBindingsMapper.toSignedIdentityDocument(entity));
+ } else if (statusCode == 404) {
+ return Optional.empty();
} else {
throw new RuntimeException(
String.format(
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
index 019f73fc6bf..11b30585933 100644
--- a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSigner.java
@@ -4,7 +4,10 @@ package com.yahoo.vespa.athenz.identityprovider.client;
import com.yahoo.security.SignatureUtils;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.identityprovider.api.DefaultSignedIdentityDocument;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
+import com.yahoo.vespa.athenz.identityprovider.api.LegacySignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
@@ -19,7 +22,7 @@ import java.util.Base64;
import java.util.Set;
import java.util.TreeSet;
-import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION;
+import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.LEGACY_DEFAULT_DOCUMENT_VERSION;
import static java.nio.charset.StandardCharsets.UTF_8;
/**
@@ -29,8 +32,25 @@ import static java.nio.charset.StandardCharsets.UTF_8;
*/
public class IdentityDocumentSigner {
+ public String generateSignature(String identityDocumentData, PrivateKey privateKey) {
+ try {
+ Signature signer = SignatureUtils.createSigner(privateKey);
+ signer.initSign(privateKey);
+ signer.update(identityDocumentData.getBytes(UTF_8));
+ byte[] signature = signer.sign();
+ return Base64.getEncoder().encodeToString(signature);
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ public String generateLegacySignature(IdentityDocument doc, PrivateKey privateKey) {
+ return generateSignature(doc.providerUniqueId(), doc.providerService(), doc.configServerHostname(),
+ doc.instanceHostname(), doc.createdAt(), doc.ipAddresses(), doc.identityType(), privateKey, doc.serviceIdentity());
+ }
+
// Cluster type is ignored due to old Vespa versions not forwarding unknown fields in signed identity document
- public String generateSignature(VespaUniqueInstanceId providerUniqueId,
+ private String generateSignature(VespaUniqueInstanceId providerUniqueId,
AthenzService providerService,
String configServerHostname,
String instanceHostname,
@@ -54,14 +74,32 @@ public class IdentityDocumentSigner {
}
public boolean hasValidSignature(SignedIdentityDocument doc, PublicKey publicKey) {
+ if (doc instanceof LegacySignedIdentityDocument signedDoc) {
+ return validateLegacySignature(signedDoc, publicKey);
+ } else if (doc instanceof DefaultSignedIdentityDocument signedDoc) {
+ try {
+ Signature signer = SignatureUtils.createVerifier(publicKey);
+ signer.initVerify(publicKey);
+ signer.update(signedDoc.data().getBytes(UTF_8));
+ return signer.verify(Base64.getDecoder().decode(doc.signature()));
+ } catch (GeneralSecurityException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ throw new IllegalArgumentException("Unknown identity document type: " + doc.getClass().getName());
+ }
+ }
+
+ private boolean validateLegacySignature(SignedIdentityDocument doc, PublicKey publicKey) {
try {
+ IdentityDocument iddoc = doc.identityDocument();
Signature signer = SignatureUtils.createVerifier(publicKey);
signer.initVerify(publicKey);
writeToSigner(
- signer, doc.providerUniqueId(), doc.providerService(), doc.configServerHostname(),
- doc.instanceHostname(), doc.createdAt(), doc.ipAddresses(), doc.identityType());
- if (doc.documentVersion() >= DEFAULT_DOCUMENT_VERSION) {
- writeToSigner(signer, doc.serviceIdentity());
+ signer, iddoc.providerUniqueId(), iddoc.providerService(), iddoc.configServerHostname(),
+ iddoc.instanceHostname(), iddoc.createdAt(), iddoc.ipAddresses(), iddoc.identityType());
+ if (doc.documentVersion() >= LEGACY_DEFAULT_DOCUMENT_VERSION) {
+ writeToSigner(signer, iddoc.serviceIdentity());
}
return signer.verify(Base64.getDecoder().decode(doc.signature()));
} catch (GeneralSecurityException e) {
diff --git a/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImpl.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImpl.java
new file mode 100644
index 00000000000..d699564a4ee
--- /dev/null
+++ b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImpl.java
@@ -0,0 +1,392 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.client;
+
+import com.google.common.cache.CacheBuilder;
+import com.google.common.cache.CacheLoader;
+import com.google.common.cache.LoadingCache;
+import com.yahoo.component.AbstractComponent;
+import com.yahoo.component.annotation.Inject;
+import com.yahoo.container.core.identity.IdentityConfig;
+import com.yahoo.container.jdisc.athenz.AthenzIdentityProvider;
+import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.metrics.ContainerMetrics;
+import com.yahoo.security.KeyStoreBuilder;
+import com.yahoo.security.MutableX509KeyManager;
+import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.security.SslContextBuilder;
+import com.yahoo.security.X509CertificateWithKey;
+import com.yahoo.vespa.athenz.api.AthenzAccessToken;
+import com.yahoo.vespa.athenz.api.AthenzDomain;
+import com.yahoo.vespa.athenz.api.AthenzRole;
+import com.yahoo.vespa.athenz.api.AthenzService;
+import com.yahoo.vespa.athenz.api.ZToken;
+import com.yahoo.vespa.athenz.client.zts.DefaultZtsClient;
+import com.yahoo.vespa.athenz.client.zts.ZtsClient;
+import com.yahoo.vespa.athenz.identity.ServiceIdentityProvider;
+import com.yahoo.vespa.athenz.identity.SiaIdentityProvider;
+import com.yahoo.vespa.athenz.utils.SiaUtils;
+import com.yahoo.vespa.defaults.Defaults;
+
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.X509ExtendedKeyManager;
+import java.net.URI;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.security.PrivateKey;
+import java.security.cert.X509Certificate;
+import java.time.Clock;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.concurrent.ScheduledThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Function;
+import java.util.logging.Level;
+import java.util.logging.Logger;
+
+import static com.yahoo.security.KeyStoreType.PKCS12;
+
+/**
+ * A {@link AthenzIdentityProvider} / {@link ServiceIdentityProvider} component that provides the tenant identity.
+ *
+ * @author mortent
+ * @author bjorncs
+ */
+// This class should probably not implement ServiceIdentityProvider,
+// as that interface is intended for providing the node's identity, not the tenant's application identity.
+public final class LegacyAthenzIdentityProviderImpl extends AbstractComponent implements AthenzIdentityProvider, ServiceIdentityProvider {
+
+ private static final Logger log = Logger.getLogger(LegacyAthenzIdentityProviderImpl.class.getName());
+
+ // TODO Make some of these values configurable through config. Match requested expiration of register/update requests.
+ // TODO These should match the requested expiration
+ static final Duration UPDATE_PERIOD = Duration.ofDays(1);
+ static final Duration AWAIT_TERMINTATION_TIMEOUT = Duration.ofSeconds(90);
+ private final static Duration ROLE_SSL_CONTEXT_EXPIRY = Duration.ofHours(2);
+ // TODO CMS expects 10min or less token ttl. Use 10min default until we have configurable expiry
+ private final static Duration ROLE_TOKEN_EXPIRY = Duration.ofMinutes(10);
+
+ // TODO Make path to trust store paths config
+ private static final Path CLIENT_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/yahoo_certificate_bundle.pem");
+ private static final Path ATHENZ_TRUST_STORE = Paths.get("/opt/yahoo/share/ssl/certs/athenz_certificate_bundle.pem");
+
+ public static final String CERTIFICATE_EXPIRY_METRIC_NAME = ContainerMetrics.ATHENZ_TENANT_CERT_EXPIRY_SECONDS.baseName();
+
+ private volatile AthenzCredentials credentials;
+ private final Metric metric;
+ private final Path trustStore;
+ private final AthenzCredentialsService athenzCredentialsService;
+ private final ScheduledExecutorService scheduler;
+ private final Clock clock;
+ private final AthenzService identity;
+ private final URI ztsEndpoint;
+
+ private final MutableX509KeyManager identityKeyManager = new MutableX509KeyManager();
+ private final SSLContext identitySslContext;
+ private final LoadingCache<AthenzRole, X509Certificate> roleSslCertCache;
+ private final Map<AthenzRole, MutableX509KeyManager> roleKeyManagerCache;
+ private final LoadingCache<AthenzRole, ZToken> roleSpecificRoleTokenCache;
+ private final LoadingCache<AthenzDomain, ZToken> domainSpecificRoleTokenCache;
+ private final LoadingCache<AthenzDomain, AthenzAccessToken> domainSpecificAccessTokenCache;
+ private final LoadingCache<List<AthenzRole>, AthenzAccessToken> roleSpecificAccessTokenCache;
+ private final CsrGenerator csrGenerator;
+
+ @Inject
+ public LegacyAthenzIdentityProviderImpl(IdentityConfig config, Metric metric) {
+ this(config,
+ metric,
+ CLIENT_TRUST_STORE,
+ new AthenzCredentialsService(config,
+ createNodeIdentityProvider(config),
+ Defaults.getDefaults().vespaHostname(),
+ Clock.systemUTC()),
+ new ScheduledThreadPoolExecutor(1),
+ Clock.systemUTC());
+ }
+
+ // Test only
+ LegacyAthenzIdentityProviderImpl(IdentityConfig config,
+ Metric metric,
+ Path trustStore,
+ AthenzCredentialsService athenzCredentialsService,
+ ScheduledExecutorService scheduler,
+ Clock clock) {
+ this.metric = metric;
+ this.trustStore = trustStore;
+ this.athenzCredentialsService = athenzCredentialsService;
+ this.scheduler = scheduler;
+ this.clock = clock;
+ this.identity = new AthenzService(config.domain(), config.service());
+ this.ztsEndpoint = URI.create(config.ztsUrl());
+ roleSslCertCache = crateAutoReloadableCache(ROLE_SSL_CONTEXT_EXPIRY, this::requestRoleCertificate, this.scheduler);
+ roleKeyManagerCache = new HashMap<>();
+ roleSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
+ domainSpecificRoleTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createRoleToken);
+ domainSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
+ roleSpecificAccessTokenCache = createCache(ROLE_TOKEN_EXPIRY, this::createAccessToken);
+ this.csrGenerator = new CsrGenerator(config.athenzDnsSuffix(), config.configserverIdentityName());
+ this.identitySslContext = createIdentitySslContext(identityKeyManager, trustStore);
+ registerInstance();
+ }
+
+ private static <KEY, VALUE> LoadingCache<KEY, VALUE> createCache(Duration expiry, Function<KEY, VALUE> cacheLoader) {
+ return CacheBuilder.newBuilder()
+ .refreshAfterWrite(expiry.dividedBy(2).toMinutes(), TimeUnit.MINUTES)
+ .expireAfterWrite(expiry.toMinutes(), TimeUnit.MINUTES)
+ .build(new CacheLoader<KEY, VALUE>() {
+ @Override
+ public VALUE load(KEY key) {
+ return cacheLoader.apply(key);
+ }
+ });
+ }
+
+ private static <KEY, VALUE> LoadingCache<KEY, VALUE> crateAutoReloadableCache(Duration expiry, Function<KEY, VALUE> cacheLoader, ScheduledExecutorService scheduler) {
+ LoadingCache<KEY, VALUE> cache = createCache(expiry, cacheLoader);
+
+ // The cache above will reload it's contents if and only if a request for the key is made. Scheduling
+ // a cache reloader to reload all keys in this cache.
+ scheduler.scheduleAtFixedRate(() -> { cache.asMap().keySet().forEach(cache::getUnchecked);},
+ expiry.dividedBy(4).toMinutes(),
+ expiry.dividedBy(4).toMinutes(),
+ TimeUnit.MINUTES);
+ return cache;
+ }
+
+ private static SSLContext createIdentitySslContext(X509ExtendedKeyManager keyManager, Path trustStore) {
+ return new SslContextBuilder()
+ .withKeyManager(keyManager)
+ .withTrustStore(trustStore)
+ .build();
+ }
+
+ private void registerInstance() {
+ try {
+ updateIdentityCredentials(this.athenzCredentialsService.registerInstance());
+ this.scheduler.scheduleAtFixedRate(this::refreshCertificate, UPDATE_PERIOD.toMinutes(), UPDATE_PERIOD.toMinutes(), TimeUnit.MINUTES);
+ this.scheduler.scheduleAtFixedRate(this::reportMetrics, 0, 5, TimeUnit.MINUTES);
+ } catch (Throwable t) {
+ throw new AthenzIdentityProviderException("Could not retrieve Athenz credentials", t);
+ }
+ }
+
+ @Override
+ public AthenzService identity() {
+ return identity;
+ }
+
+ @Override
+ public String domain() {
+ return identity.getDomain().getName();
+ }
+
+ @Override
+ public String service() {
+ return identity.getName();
+ }
+
+ @Override
+ public SSLContext getIdentitySslContext() {
+ return identitySslContext;
+ }
+
+ @Override
+ public X509CertificateWithKey getIdentityCertificateWithKey() {
+ AthenzCredentials copy = this.credentials;
+ return new X509CertificateWithKey(copy.getCertificate(), copy.getKeyPair().getPrivate());
+ }
+
+ @Override public Path certificatePath() { return athenzCredentialsService.certificatePath(); }
+
+ @Override public Path privateKeyPath() { return athenzCredentialsService.privateKeyPath(); }
+
+ @Override
+ public SSLContext getRoleSslContext(String domain, String role) {
+ try {
+ AthenzRole athenzRole = new AthenzRole(new AthenzDomain(domain), role);
+ // Make sure to request a certificate which triggers creating a new key manager for this role
+ X509Certificate x509Certificate = getRoleCertificate(athenzRole);
+ MutableX509KeyManager keyManager = roleKeyManagerCache.get(athenzRole);
+ return new SslContextBuilder()
+ .withKeyManager(keyManager)
+ .withTrustStore(trustStore)
+ .build();
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve role certificate: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getRoleToken(String domain) {
+ try {
+ return domainSpecificRoleTokenCache.get(new AthenzDomain(domain)).getRawToken();
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve role token: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getRoleToken(String domain, String role) {
+ try {
+ return roleSpecificRoleTokenCache.get(new AthenzRole(domain, role)).getRawToken();
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve role token: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getAccessToken(String domain) {
+ try {
+ return domainSpecificAccessTokenCache.get(new AthenzDomain(domain)).value();
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve access token: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public String getAccessToken(String domain, List<String> roles) {
+ try {
+ List<AthenzRole> roleList = roles.stream()
+ .map(roleName -> new AthenzRole(domain, roleName))
+ .toList();
+ return roleSpecificAccessTokenCache.get(roleList).value();
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve access token: " + e.getMessage(), e);
+ }
+ }
+
+ @Override
+ public PrivateKey getPrivateKey() {
+ return credentials.getKeyPair().getPrivate();
+ }
+
+ @Override
+ public Path trustStorePath() {
+ return trustStore;
+ }
+
+ @Override
+ public List<X509Certificate> getIdentityCertificate() {
+ return Collections.singletonList(credentials.getCertificate());
+ }
+
+ @Override
+ public X509Certificate getRoleCertificate(String domain, String role) {
+ return getRoleCertificate(new AthenzRole(new AthenzDomain(domain), role));
+ }
+
+ private X509Certificate getRoleCertificate(AthenzRole athenzRole) {
+ try {
+ return roleSslCertCache.get(athenzRole);
+ } catch (Exception e) {
+ throw new AthenzIdentityProviderException("Could not retrieve role certificate: " + e.getMessage(), e);
+ }
+ }
+
+ private void updateIdentityCredentials(AthenzCredentials credentials) {
+ this.credentials = credentials;
+ this.identityKeyManager.updateKeystore(
+ KeyStoreBuilder.withType(PKCS12)
+ .withKeyEntry("default", credentials.getKeyPair().getPrivate(), credentials.getCertificate())
+ .build(),
+ new char[0]);
+ }
+
+ private X509Certificate requestRoleCertificate(AthenzRole role) {
+ var doc = credentials.getIdentityDocument().identityDocument();
+ Pkcs10Csr csr = csrGenerator.generateRoleCsr(
+ identity, role, doc.providerUniqueId(), doc.clusterType(), credentials.getKeyPair());
+ try (ZtsClient client = createZtsClient()) {
+ X509Certificate roleCertificate = client.getRoleCertificate(role, csr);
+ updateRoleKeyManager(role, roleCertificate);
+ log.info(String.format("Requester role certificate for role %s, expires: %s", role.toResourceNameString(), roleCertificate.getNotAfter().toInstant().toString()));
+ return roleCertificate;
+ }
+ }
+
+ private void updateRoleKeyManager(AthenzRole role, X509Certificate certificate) {
+ MutableX509KeyManager keyManager = roleKeyManagerCache.computeIfAbsent(role, r -> new MutableX509KeyManager());
+ keyManager.updateKeystore(
+ KeyStoreBuilder.withType(PKCS12)
+ .withKeyEntry("default", credentials.getKeyPair().getPrivate(), certificate)
+ .build(),
+ new char[0]);
+ }
+
+ private ZToken createRoleToken(AthenzRole athenzRole) {
+ try (ZtsClient client = createZtsClient()) {
+ return client.getRoleToken(athenzRole, ROLE_TOKEN_EXPIRY);
+ }
+ }
+
+ private ZToken createRoleToken(AthenzDomain domain) {
+ try (ZtsClient client = createZtsClient()) {
+ return client.getRoleToken(domain, ROLE_TOKEN_EXPIRY);
+ }
+ }
+
+ private AthenzAccessToken createAccessToken(AthenzDomain domain) {
+ try (ZtsClient client = createZtsClient()) {
+ return client.getAccessToken(domain);
+ }
+ }
+
+ private AthenzAccessToken createAccessToken(List<AthenzRole> roles) {
+ try (ZtsClient client = createZtsClient()) {
+ return client.getAccessToken(roles);
+ }
+ }
+
+ private DefaultZtsClient createZtsClient() {
+ return new DefaultZtsClient.Builder(ztsEndpoint).withSslContext(getIdentitySslContext()).build();
+ }
+
+ @Override
+ public void deconstruct() {
+ try {
+ scheduler.shutdownNow();
+ scheduler.awaitTermination(AWAIT_TERMINTATION_TIMEOUT.getSeconds(), TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ private static SiaIdentityProvider createNodeIdentityProvider(IdentityConfig config) {
+ return new SiaIdentityProvider(
+ new AthenzService(config.nodeIdentityName()), SiaUtils.DEFAULT_SIA_DIRECTORY, CLIENT_TRUST_STORE);
+ }
+
+ private boolean isExpired(AthenzCredentials credentials) {
+ return clock.instant().isAfter(getExpirationTime(credentials));
+ }
+
+ private static Instant getExpirationTime(AthenzCredentials credentials) {
+ return credentials.getCertificate().getNotAfter().toInstant();
+ }
+
+ void refreshCertificate() {
+ try {
+ updateIdentityCredentials(isExpired(credentials)
+ ? athenzCredentialsService.registerInstance()
+ : athenzCredentialsService.updateCredentials(credentials.getIdentityDocument(), identitySslContext));
+ } catch (Throwable t) {
+ log.log(Level.WARNING, "Failed to update credentials: " + t.getMessage(), t);
+ }
+ }
+
+ void reportMetrics() {
+ try {
+ Instant expirationTime = getExpirationTime(credentials);
+ Duration remainingLifetime = Duration.between(clock.instant(), expirationTime);
+ metric.set(CERTIFICATE_EXPIRY_METRIC_NAME, remainingLifetime.getSeconds(), null);
+ } catch (Throwable t) {
+ log.log(Level.WARNING, "Failed to update metrics: " + t.getMessage(), t);
+ }
+ }
+}
+
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapperTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapperTest.java
index 2a68f6fd231..513fb4cdbd3 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapperTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/api/EntityBindingsMapperTest.java
@@ -5,8 +5,11 @@ package com.yahoo.vespa.athenz.identityprovider.api;
import org.junit.jupiter.api.Test;
import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertInstanceOf;
import static org.junit.jupiter.api.Assertions.assertTrue;
/**
@@ -15,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
class EntityBindingsMapperTest {
@Test
- public void persists_unknown_json_members() throws IOException {
+ public void legacy_persists_unknown_json_members() throws IOException {
var originalJson =
"""
{
@@ -36,7 +39,8 @@ class EntityBindingsMapperTest {
}
""";
var entity = EntityBindingsMapper.fromString(originalJson);
- assertEquals(2, entity.unknownAttributes().size(), entity.unknownAttributes().toString());
+ assertInstanceOf(LegacySignedIdentityDocument.class, entity);
+ assertEquals(2, entity.identityDocument().unknownAttributes().size(), entity.identityDocument().unknownAttributes().toString());
var json = EntityBindingsMapper.toAttestationData(entity);
var expectedMemberInJson = "member-in-unknown-object";
@@ -45,4 +49,39 @@ class EntityBindingsMapperTest {
assertEquals(EntityBindingsMapper.mapper.readTree(originalJson), EntityBindingsMapper.mapper.readTree(json));
}
+ @Test
+ public void reads_unknown_json_members() throws IOException {
+ var iddoc = """
+ {
+ "provider-unique-id": "0.cluster.instance.app.tenant.us-west-1.test.node",
+ "provider-service": "domain.service",
+ "configserver-hostname": "cfg",
+ "instance-hostname": "host",
+ "created-at": 12345.0,
+ "ip-addresses": [],
+ "identity-type": "node",
+ "cluster-type": "admin",
+ "zts-url": "https://zts.url/",
+ "unknown-string": "string-value",
+ "unknown-object": { "member-in-unknown-object": 123 }
+ }
+ """;
+ var originalJson =
+ """
+ {
+ "signature": "sig",
+ "signing-key-version": 0,
+ "document-version": 4,
+ "data": "%s"
+ }
+ """.formatted(Base64.getEncoder().encodeToString(iddoc.getBytes(StandardCharsets.UTF_8)));
+ var entity = EntityBindingsMapper.fromString(originalJson);
+ assertEquals(2, entity.identityDocument().unknownAttributes().size(), entity.identityDocument().unknownAttributes().toString());
+ var json = EntityBindingsMapper.toAttestationData(entity);
+
+ // For the new iddoc format the identity document should be unchanged during serialization/deserialization,
+ // i.e the signed identity document should be unchanged
+ assertEquals(EntityBindingsMapper.mapper.readTree(originalJson), EntityBindingsMapper.mapper.readTree(json));
+ }
+
} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
index c9d2ea581bb..108da9e0136 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/AthenzIdentityProviderImplTest.java
@@ -2,8 +2,8 @@
package com.yahoo.vespa.athenz.identityprovider.client;
import com.yahoo.container.core.identity.IdentityConfig;
-import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
import com.yahoo.jdisc.Metric;
+import com.yahoo.security.AutoReloadingX509KeyManager;
import com.yahoo.security.KeyAlgorithm;
import com.yahoo.security.KeyStoreBuilder;
import com.yahoo.security.KeyStoreType;
@@ -13,13 +13,13 @@ import com.yahoo.security.Pkcs10Csr;
import com.yahoo.security.Pkcs10CsrBuilder;
import com.yahoo.security.SignatureAlgorithm;
import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.security.X509CertificateWithKey;
import com.yahoo.test.ManualClock;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import javax.security.auth.x500.X500Principal;
-
import java.io.File;
import java.io.IOException;
import java.math.BigInteger;
@@ -33,17 +33,12 @@ import java.util.Date;
import java.util.concurrent.ScheduledExecutorService;
import java.util.function.Supplier;
-import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
-/**
- * @author mortent
- * @author bjorncs
- */
public class AthenzIdentityProviderImplTest {
@TempDir
@@ -85,58 +80,25 @@ public class AthenzIdentityProviderImplTest {
}
@Test
- void component_creation_fails_when_credentials_not_found() {
- assertThrows(AthenzIdentityProviderException.class, () -> {
- AthenzCredentialsService credentialService = mock(AthenzCredentialsService.class);
- when(credentialService.registerInstance())
- .thenThrow(new RuntimeException("athenz unavailable"));
-
- new AthenzIdentityProviderImpl(IDENTITY_CONFIG, mock(Metric.class), trustStoreFile, credentialService, mock(ScheduledExecutorService.class), new ManualClock(Instant.EPOCH));
- });
- }
-
- @Test
- void metrics_updated_on_refresh() {
+ void certificate_expiry_metric_is_reported() {
ManualClock clock = new ManualClock(Instant.EPOCH);
Metric metric = mock(Metric.class);
-
- AthenzCredentialsService athenzCredentialsService = mock(AthenzCredentialsService.class);
-
+ AutoReloadingX509KeyManager keyManager = mock(AutoReloadingX509KeyManager.class);
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
X509Certificate certificate = getCertificate(keyPair, getExpirationSupplier(clock));
+ when(keyManager.getCurrentCertificateWithKey()).thenReturn(new X509CertificateWithKey(certificate, keyPair.getPrivate()));
- when(athenzCredentialsService.registerInstance())
- .thenReturn(new AthenzCredentials(certificate, keyPair, null));
-
- when(athenzCredentialsService.updateCredentials(any(), any()))
- .thenThrow(new RuntimeException("#1"))
- .thenThrow(new RuntimeException("#2"))
- .thenReturn(new AthenzCredentials(certificate, keyPair, null));
-
- AthenzIdentityProviderImpl identityProvider =
- new AthenzIdentityProviderImpl(IDENTITY_CONFIG, metric, trustStoreFile, athenzCredentialsService, mock(ScheduledExecutorService.class), clock);
-
+ AthenzIdentityProviderImpl identityProvider = new AthenzIdentityProviderImpl(IDENTITY_CONFIG, metric, trustStoreFile, mock(ScheduledExecutorService.class), clock, keyManager);
identityProvider.reportMetrics();
verify(metric).set(eq(AthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());
- // Advance 1 day, refresh fails, cert is 1 day old
clock.advance(Duration.ofDays(1));
- identityProvider.refreshCertificate();
identityProvider.reportMetrics();
verify(metric).set(eq(AthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(1)).getSeconds()), any());
- // Advance 1 more day, refresh fails, cert is 2 days old
clock.advance(Duration.ofDays(1));
- identityProvider.refreshCertificate();
identityProvider.reportMetrics();
verify(metric).set(eq(AthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(2)).getSeconds()), any());
-
- // Advance 1 more day, refresh succeds, cert is new
- clock.advance(Duration.ofDays(1));
- identityProvider.refreshCertificate();
- identityProvider.reportMetrics();
- verify(metric).set(eq(AthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());
-
}
private Supplier<Date> getExpirationSupplier(ManualClock clock) {
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
index ff85cb79f02..acb0905700f 100644
--- a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/IdentityDocumentSignerTest.java
@@ -6,10 +6,13 @@ import com.yahoo.security.KeyUtils;
import com.yahoo.vespa.athenz.api.AthenzIdentity;
import com.yahoo.vespa.athenz.api.AthenzService;
import com.yahoo.vespa.athenz.identityprovider.api.ClusterType;
+import com.yahoo.vespa.athenz.identityprovider.api.DefaultSignedIdentityDocument;
+import com.yahoo.vespa.athenz.identityprovider.api.EntityBindingsMapper;
+import com.yahoo.vespa.athenz.identityprovider.api.IdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.IdentityType;
+import com.yahoo.vespa.athenz.identityprovider.api.LegacySignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument;
import com.yahoo.vespa.athenz.identityprovider.api.VespaUniqueInstanceId;
-import com.yahoo.vespa.athenz.utils.AthenzIdentities;
import org.junit.jupiter.api.Test;
import java.security.KeyPair;
@@ -18,6 +21,7 @@ import java.util.Arrays;
import java.util.HashSet;
import static com.yahoo.vespa.athenz.identityprovider.api.IdentityType.TENANT;
+import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.LEGACY_DEFAULT_DOCUMENT_VERSION;
import static com.yahoo.vespa.athenz.identityprovider.api.SignedIdentityDocument.DEFAULT_DOCUMENT_VERSION;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
@@ -42,32 +46,53 @@ public class IdentityDocumentSignerTest {
private static final AthenzIdentity serviceIdentity = new AthenzService("vespa", "node");
@Test
- void generates_and_validates_signature() {
+ void legacy_generates_and_validates_signature() {
IdentityDocumentSigner signer = new IdentityDocumentSigner();
+ IdentityDocument identityDocument = new IdentityDocument(
+ id, providerService, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
String signature =
- signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt,
- ipAddresses, identityType, keyPair.getPrivate(), serviceIdentity);
+ signer.generateLegacySignature(identityDocument, keyPair.getPrivate());
- SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
- signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
- instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
+ SignedIdentityDocument signedIdentityDocument = new LegacySignedIdentityDocument(
+ signature, KEY_VERSION, LEGACY_DEFAULT_DOCUMENT_VERSION, identityDocument);
assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic()));
}
@Test
- void ignores_cluster_type_and_zts_url() {
+ void generates_and_validates_signature() {
IdentityDocumentSigner signer = new IdentityDocumentSigner();
+ IdentityDocument identityDocument = new IdentityDocument(
+ id, providerService, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
+ String data = EntityBindingsMapper.toIdentityDocmentData(identityDocument);
String signature =
- signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt,
- ipAddresses, identityType, keyPair.getPrivate(), serviceIdentity);
+ signer.generateSignature(data, keyPair.getPrivate());
- var docWithoutIgnoredFields = new SignedIdentityDocument(
- signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
- instanceHostname, createdAt, ipAddresses, identityType, null, null, serviceIdentity);
- var docWithIgnoredFields = new SignedIdentityDocument(
- signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
+ SignedIdentityDocument signedIdentityDocument = new DefaultSignedIdentityDocument(
+ signature, KEY_VERSION, DEFAULT_DOCUMENT_VERSION, data);
+
+ assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic()));
+ }
+
+ @Test
+ void legacy_ignores_cluster_type_and_zts_url() {
+ IdentityDocumentSigner signer = new IdentityDocumentSigner();
+ IdentityDocument identityDocument = new IdentityDocument(
+ id, providerService, configserverHostname,
instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
+ IdentityDocument withoutIgnoredFields = new IdentityDocument(
+ id, providerService, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, null, null, serviceIdentity);
+
+ String signature =
+ signer.generateLegacySignature(identityDocument, keyPair.getPrivate());
+
+ var docWithoutIgnoredFields = new LegacySignedIdentityDocument(
+ signature, KEY_VERSION, LEGACY_DEFAULT_DOCUMENT_VERSION, withoutIgnoredFields);
+ var docWithIgnoredFields = new LegacySignedIdentityDocument(
+ signature, KEY_VERSION, LEGACY_DEFAULT_DOCUMENT_VERSION, identityDocument);
assertTrue(signer.hasValidSignature(docWithoutIgnoredFields, keyPair.getPublic()));
assertEquals(docWithIgnoredFields.signature(), docWithoutIgnoredFields.signature());
@@ -76,16 +101,15 @@ public class IdentityDocumentSignerTest {
@Test
void validates_signature_for_new_and_old_versions() {
IdentityDocumentSigner signer = new IdentityDocumentSigner();
+ IdentityDocument identityDocument = new IdentityDocument(
+ id, providerService, configserverHostname,
+ instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
String signature =
- signer.generateSignature(id, providerService, configserverHostname, instanceHostname, createdAt,
- ipAddresses, identityType, keyPair.getPrivate(), serviceIdentity);
+ signer.generateLegacySignature(identityDocument, keyPair.getPrivate());
- SignedIdentityDocument signedIdentityDocument = new SignedIdentityDocument(
- signature, KEY_VERSION, id, providerService, DEFAULT_DOCUMENT_VERSION, configserverHostname,
- instanceHostname, createdAt, ipAddresses, identityType, clusterType, ztsUrl, serviceIdentity);
+ SignedIdentityDocument signedIdentityDocument = new LegacySignedIdentityDocument(
+ signature, KEY_VERSION, LEGACY_DEFAULT_DOCUMENT_VERSION, identityDocument);
assertTrue(signer.hasValidSignature(signedIdentityDocument, keyPair.getPublic()));
-
}
-
} \ No newline at end of file
diff --git a/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImplTest.java b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImplTest.java
new file mode 100644
index 00000000000..75dc42cd4a6
--- /dev/null
+++ b/vespa-athenz/src/test/java/com/yahoo/vespa/athenz/identityprovider/client/LegacyAthenzIdentityProviderImplTest.java
@@ -0,0 +1,160 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package com.yahoo.vespa.athenz.identityprovider.client;
+
+import com.yahoo.container.core.identity.IdentityConfig;
+import com.yahoo.container.jdisc.athenz.AthenzIdentityProviderException;
+import com.yahoo.jdisc.Metric;
+import com.yahoo.security.KeyAlgorithm;
+import com.yahoo.security.KeyStoreBuilder;
+import com.yahoo.security.KeyStoreType;
+import com.yahoo.security.KeyStoreUtils;
+import com.yahoo.security.KeyUtils;
+import com.yahoo.security.Pkcs10Csr;
+import com.yahoo.security.Pkcs10CsrBuilder;
+import com.yahoo.security.SignatureAlgorithm;
+import com.yahoo.security.X509CertificateBuilder;
+import com.yahoo.test.ManualClock;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import javax.security.auth.x500.X500Principal;
+
+import java.io.File;
+import java.io.IOException;
+import java.math.BigInteger;
+import java.nio.file.Path;
+import java.security.KeyPair;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.time.temporal.ChronoUnit;
+import java.util.Date;
+import java.util.concurrent.ScheduledExecutorService;
+import java.util.function.Supplier;
+
+import static org.junit.jupiter.api.Assertions.assertThrows;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+/**
+ * @author mortent
+ * @author bjorncs
+ */
+public class LegacyAthenzIdentityProviderImplTest {
+
+ @TempDir
+ public File tempDir;
+
+ public static final Duration certificateValidity = Duration.ofDays(30);
+
+ private static final IdentityConfig IDENTITY_CONFIG =
+ new IdentityConfig(new IdentityConfig.Builder()
+ .service("tenantService")
+ .domain("tenantDomain")
+ .nodeIdentityName("vespa.tenant")
+ .configserverIdentityName("vespa.configserver")
+ .loadBalancerAddress("cfg")
+ .ztsUrl("https:localhost:4443/zts/v1")
+ .athenzDnsSuffix("dev-us-north-1.vespa.cloud"));
+
+ private final KeyPair caKeypair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ private Path trustStoreFile;
+ private X509Certificate caCertificate;
+
+ @BeforeEach
+ public void createTrustStoreFile() throws IOException {
+ caCertificate = X509CertificateBuilder
+ .fromKeypair(
+ caKeypair,
+ new X500Principal("CN=mydummyca"),
+ Instant.EPOCH,
+ Instant.EPOCH.plus(10000, ChronoUnit.DAYS),
+ SignatureAlgorithm.SHA256_WITH_ECDSA,
+ BigInteger.ONE)
+ .build();
+ trustStoreFile = File.createTempFile("junit", null, tempDir).toPath();
+ KeyStoreUtils.writeKeyStoreToFile(
+ KeyStoreBuilder.withType(KeyStoreType.JKS)
+ .withKeyEntry("default", caKeypair.getPrivate(), caCertificate)
+ .build(),
+ trustStoreFile);
+ }
+
+ @Test
+ void component_creation_fails_when_credentials_not_found() {
+ assertThrows(AthenzIdentityProviderException.class, () -> {
+ AthenzCredentialsService credentialService = mock(AthenzCredentialsService.class);
+ when(credentialService.registerInstance())
+ .thenThrow(new RuntimeException("athenz unavailable"));
+
+ new LegacyAthenzIdentityProviderImpl(IDENTITY_CONFIG, mock(Metric.class), trustStoreFile, credentialService, mock(ScheduledExecutorService.class), new ManualClock(Instant.EPOCH));
+ });
+ }
+
+ @Test
+ void metrics_updated_on_refresh() {
+ ManualClock clock = new ManualClock(Instant.EPOCH);
+ Metric metric = mock(Metric.class);
+
+ AthenzCredentialsService athenzCredentialsService = mock(AthenzCredentialsService.class);
+
+ KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.EC);
+ X509Certificate certificate = getCertificate(keyPair, getExpirationSupplier(clock));
+
+ when(athenzCredentialsService.registerInstance())
+ .thenReturn(new AthenzCredentials(certificate, keyPair, null));
+
+ when(athenzCredentialsService.updateCredentials(any(), any()))
+ .thenThrow(new RuntimeException("#1"))
+ .thenThrow(new RuntimeException("#2"))
+ .thenReturn(new AthenzCredentials(certificate, keyPair, null));
+
+ LegacyAthenzIdentityProviderImpl identityProvider =
+ new LegacyAthenzIdentityProviderImpl(IDENTITY_CONFIG, metric, trustStoreFile, athenzCredentialsService, mock(ScheduledExecutorService.class), clock);
+
+ identityProvider.reportMetrics();
+ verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());
+
+ // Advance 1 day, refresh fails, cert is 1 day old
+ clock.advance(Duration.ofDays(1));
+ identityProvider.refreshCertificate();
+ identityProvider.reportMetrics();
+ verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(1)).getSeconds()), any());
+
+ // Advance 1 more day, refresh fails, cert is 2 days old
+ clock.advance(Duration.ofDays(1));
+ identityProvider.refreshCertificate();
+ identityProvider.reportMetrics();
+ verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.minus(Duration.ofDays(2)).getSeconds()), any());
+
+ // Advance 1 more day, refresh succeds, cert is new
+ clock.advance(Duration.ofDays(1));
+ identityProvider.refreshCertificate();
+ identityProvider.reportMetrics();
+ verify(metric).set(eq(LegacyAthenzIdentityProviderImpl.CERTIFICATE_EXPIRY_METRIC_NAME), eq(certificateValidity.getSeconds()), any());
+
+ }
+
+ private Supplier<Date> getExpirationSupplier(ManualClock clock) {
+ return () -> new Date(clock.instant().plus(certificateValidity).toEpochMilli());
+ }
+
+ private X509Certificate getCertificate(KeyPair keyPair, Supplier<Date> expiry) {
+ Pkcs10Csr csr = Pkcs10CsrBuilder.fromKeypair(new X500Principal("CN=dummy"), keyPair, SignatureAlgorithm.SHA256_WITH_ECDSA)
+ .build();
+ return X509CertificateBuilder
+ .fromCsr(csr,
+ caCertificate.getSubjectX500Principal(),
+ Instant.EPOCH,
+ expiry.get().toInstant(),
+ caKeypair.getPrivate(),
+ SignatureAlgorithm.SHA256_WITH_ECDSA,
+ BigInteger.ONE)
+ .build();
+ }
+
+}
diff --git a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
index 95f1179b1a3..1c2db273653 100644
--- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
+++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
@@ -34,12 +34,20 @@ com.google.protobuf:protobuf-java:3.21.7
com.ibm.icu:icu4j:70.1
com.intellij:annotations:9.0.4
com.microsoft.onnxruntime:onnxruntime:1.13.1
+com.squareup.okhttp3:okhttp:3.14.9
+com.squareup.okio:okio:1.17.2
+com.squareup.retrofit2:adapter-rxjava2:2.9.0
+com.squareup.retrofit2:converter-jackson:2.9.0
+com.squareup.retrofit2:retrofit:2.9.0
com.sun.activation:javax.activation:1.2.0
com.sun.istack:istack-commons-runtime:3.0.8
com.sun.xml.bind:jaxb-core:2.3.0
com.sun.xml.bind:jaxb-impl:2.3.0
com.sun.xml.fastinfoset:FastInfoset:1.2.16
com.thaiopensource:jing:20091111
+com.theokanning.openai-gpt3-java:api:0.12.0
+com.theokanning.openai-gpt3-java:client:0.12.0
+com.theokanning.openai-gpt3-java:service:0.12.0
com.yahoo.athenz:athenz-auth-core:1.10.54
com.yahoo.athenz:athenz-client-common:1.10.54
com.yahoo.athenz:athenz-zms-core:1.10.54
@@ -69,6 +77,7 @@ io.netty:netty-transport-native-epoll:4.1.86.Final
io.netty:netty-transport-native-unix-common:4.1.86.Final
io.prometheus:simpleclient:0.6.0
io.prometheus:simpleclient_common:0.6.0
+io.reactivex.rxjava2:rxjava:2.0.0
javax.annotation:javax.annotation-api:1.2
javax.inject:javax.inject:1
javax.servlet:javax.servlet-api:3.1.0
@@ -145,20 +154,20 @@ org.codehaus.plexus:plexus-sec-dispatcher:2.0
org.codehaus.plexus:plexus-utils:3.3.1
org.eclipse.collections:eclipse-collections:11.0.0
org.eclipse.collections:eclipse-collections-api:11.0.0
-org.eclipse.jetty:jetty-alpn-client:11.0.14
-org.eclipse.jetty:jetty-alpn-java-server:11.0.14
-org.eclipse.jetty:jetty-alpn-server:11.0.14
-org.eclipse.jetty:jetty-client:11.0.14
-org.eclipse.jetty:jetty-http:11.0.14
-org.eclipse.jetty:jetty-io:11.0.14
-org.eclipse.jetty:jetty-jmx:11.0.14
-org.eclipse.jetty:jetty-security:11.0.14
-org.eclipse.jetty:jetty-server:11.0.14
-org.eclipse.jetty:jetty-servlet:11.0.14
-org.eclipse.jetty:jetty-util:11.0.14
-org.eclipse.jetty.http2:http2-common:11.0.14
-org.eclipse.jetty.http2:http2-hpack:11.0.14
-org.eclipse.jetty.http2:http2-server:11.0.14
+org.eclipse.jetty:jetty-alpn-client:11.0.15
+org.eclipse.jetty:jetty-alpn-java-server:11.0.15
+org.eclipse.jetty:jetty-alpn-server:11.0.15
+org.eclipse.jetty:jetty-client:11.0.15
+org.eclipse.jetty:jetty-http:11.0.15
+org.eclipse.jetty:jetty-io:11.0.15
+org.eclipse.jetty:jetty-jmx:11.0.15
+org.eclipse.jetty:jetty-security:11.0.15
+org.eclipse.jetty:jetty-server:11.0.15
+org.eclipse.jetty:jetty-servlet:11.0.15
+org.eclipse.jetty:jetty-util:11.0.15
+org.eclipse.jetty.http2:http2-common:11.0.15
+org.eclipse.jetty.http2:http2-hpack:11.0.15
+org.eclipse.jetty.http2:http2-server:11.0.15
org.eclipse.jetty.toolchain:jetty-jakarta-servlet-api:5.0.2
org.eclipse.sisu:org.eclipse.sisu.inject:0.3.5
org.eclipse.sisu:org.eclipse.sisu.plexus:0.3.5
@@ -182,7 +191,7 @@ org.glassfish.jersey.media:jersey-media-multipart:2.25
org.hdrhistogram:HdrHistogram:2.1.12
org.iq80.snappy:snappy:0.4
org.javassist:javassist:3.20.0-GA
-org.json:json:20220320
+org.json:json:20230227
org.junit.jupiter:junit-jupiter-api:5.8.1
org.junit.jupiter:junit-jupiter-engine:5.8.1
org.junit.platform:junit-platform-commons:1.8.1
@@ -201,6 +210,7 @@ org.ow2.asm:asm-commons:9.3
org.ow2.asm:asm-tree:9.3
org.ow2.asm:asm-util:9.3
org.questdb:questdb:6.2
+org.reactivestreams:reactive-streams:1.0.3
org.slf4j:jcl-over-slf4j:1.7.32
org.slf4j:log4j-over-slf4j:1.7.32
org.slf4j:slf4j-api:1.7.32
diff --git a/vespa-documentgen-plugin/etc/music/music.sd b/vespa-documentgen-plugin/etc/music/music.sd
index 205614db67d..8c2d324697c 100644
--- a/vespa-documentgen-plugin/etc/music/music.sd
+++ b/vespa-documentgen-plugin/etc/music/music.sd
@@ -32,6 +32,14 @@ search music {
indexing: summary | index
}
+ field tags type weightedset<string> {
+ indexing: attribute | summary
+ attribute: fast-search
+ weightedset {
+ create-if-nonexistent
+ }
+ }
+
}
rank-profile default inherits default {
@@ -48,6 +56,11 @@ search music {
}
+ document-summary tags {
+ summary tags type weightedset<string> {
+ full
+ }
+ }
}
diff --git a/vespa-documentgen-plugin/src/test/java/com/yahoo/vespa/DocumentGenTest.java b/vespa-documentgen-plugin/src/test/java/com/yahoo/vespa/DocumentGenTest.java
index 2a7eb261a0b..f55b226b11b 100644
--- a/vespa-documentgen-plugin/src/test/java/com/yahoo/vespa/DocumentGenTest.java
+++ b/vespa-documentgen-plugin/src/test/java/com/yahoo/vespa/DocumentGenTest.java
@@ -21,9 +21,11 @@ public class DocumentGenTest {
DocumentGenMojo mojo = new DocumentGenMojo();
mojo.execute(new File("etc/music/"), new File("target/generated-test-sources/vespa-documentgen-plugin/"), "com.yahoo.vespa.document");
Map<String, Schema> searches = mojo.getSearches();
- assertEquals(searches.size(),1);
+ assertEquals(searches.size(), 1);
assertEquals(searches.get("music").getDocument("music").getField("title").getDataType(), DataType.STRING);
assertEquals(searches.get("music").getDocument("music").getField("eitheror").getDataType(), DataType.BOOL);
+ assertEquals(searches.get("music").getDocument("music").getField("tags").getDataType(),
+ searches.get("music").getSummaries().get("tags").getSummaryField("tags").getDataType());
}
@Test
diff --git a/vespa-feed-client-api/pom.xml b/vespa-feed-client-api/pom.xml
index 5509c339eee..0782bb6b28e 100644
--- a/vespa-feed-client-api/pom.xml
+++ b/vespa-feed-client-api/pom.xml
@@ -42,6 +42,12 @@
<configuration>
<release>${vespaClients.jdk.releaseVersion}</release>
<showDeprecation>true</showDeprecation>
+ <compilerArgs> <!-- Remove (to use default) when not compiling for 8 -->
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-rawtypes</arg>
+ <arg>-Xlint:-unchecked</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
</configuration>
</plugin>
<plugin>
diff --git a/vespa-feed-client-cli/pom.xml b/vespa-feed-client-cli/pom.xml
index 46679906fc4..b917a39b675 100644
--- a/vespa-feed-client-cli/pom.xml
+++ b/vespa-feed-client-cli/pom.xml
@@ -53,6 +53,12 @@
<configuration>
<release>${vespaClients.jdk.releaseVersion}</release>
<showDeprecation>true</showDeprecation>
+ <compilerArgs> <!-- Remove (to use default) when not compiling for 8 -->
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-rawtypes</arg>
+ <arg>-Xlint:-unchecked</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
</configuration>
</plugin>
<plugin>
diff --git a/vespa-feed-client/pom.xml b/vespa-feed-client/pom.xml
index 01b9b00b8a0..b6440653a78 100644
--- a/vespa-feed-client/pom.xml
+++ b/vespa-feed-client/pom.xml
@@ -65,6 +65,12 @@
<goal>compile</goal>
</goals>
<configuration>
+ <compilerArgs> <!-- Remove (to use default) when not compiling for 8 -->
+ <arg>-Xlint:all</arg>
+ <arg>-Xlint:-rawtypes</arg>
+ <arg>-Xlint:-unchecked</arg>
+ <arg>-Xlint:-serial</arg>
+ </compilerArgs>
<release>${vespaClients.jdk.releaseVersion}</release>
<showDeprecation>true</showDeprecation>
</configuration>
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java
index 3192bb4f225..8e7bf59cd0f 100644
--- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java
+++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/ApacheCluster.java
@@ -86,7 +86,8 @@ class ApacheCluster implements Cluster {
SimpleHttpRequest request = new SimpleHttpRequest(wrapped.method(), wrapped.path());
request.setScheme(endpoint.url.getScheme());
request.setAuthority(new URIAuthority(endpoint.url.getHost(), portOf(endpoint.url)));
- request.setConfig(requestConfig);
+ long timeoutMillis = wrapped.timeout() == null ? 190_000 : wrapped.timeout().toMillis() * 11 / 10 + 1_000;
+ request.setConfig(RequestConfig.copy(requestConfig).setResponseTimeout(Timeout.ofMilliseconds(timeoutMillis)).build());
defaultHeaders.forEach(request::setHeader);
wrapped.headers().forEach((name, value) -> request.setHeader(name, value.get()));
if (wrapped.body() != null) {
@@ -104,11 +105,11 @@ class ApacheCluster implements Cluster {
@Override public void failed(Exception ex) { vessel.completeExceptionally(ex); }
@Override public void cancelled() { vessel.cancel(false); }
});
- long timeoutMillis = wrapped.timeout() == null ? 200_000 : wrapped.timeout().toMillis() * 11 / 10 + 1_000;
- Future<?> cancellation = timeoutExecutor.schedule(() -> {
- future.cancel(true);
- vessel.cancel(true);
- }, timeoutMillis, TimeUnit.MILLISECONDS);
+ // We've seen some requests time out, even with a response timeout,
+ // so we schedule this to be absolutely sure we don't hang (for ever).
+ Future<?> cancellation = timeoutExecutor.schedule(() -> { future.cancel(true); vessel.cancel(true); },
+ timeoutMillis + 10_000,
+ TimeUnit.MILLISECONDS);
vessel.whenComplete((__, ___) -> cancellation.cancel(true));
}
catch (Throwable thrown) {
@@ -196,8 +197,7 @@ class ApacheCluster implements Cluster {
private static RequestConfig createRequestConfig(FeedClientBuilderImpl b) {
RequestConfig.Builder builder = RequestConfig.custom()
.setConnectTimeout(Timeout.ofSeconds(10))
- .setConnectionRequestTimeout(Timeout.DISABLED)
- .setResponseTimeout(Timeout.ofSeconds(190));
+ .setConnectionRequestTimeout(Timeout.DISABLED);
if (b.proxy != null) builder.setProxy(new HttpHost(b.proxy.getScheme(), b.proxy.getHost(), b.proxy.getPort()));
return builder.build();
}
diff --git a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpRequestStrategy.java b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpRequestStrategy.java
index ce86ad59ffe..4e2256f40b4 100644
--- a/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpRequestStrategy.java
+++ b/vespa-feed-client/src/main/java/ai/vespa/feed/client/impl/HttpRequestStrategy.java
@@ -9,6 +9,7 @@ import ai.vespa.feed.client.FeedException;
import ai.vespa.feed.client.HttpResponse ;
import ai.vespa.feed.client.OperationStats;
+import javax.net.ssl.SSLException;
import java.io.IOException;
import java.nio.channels.CancelledKeyException;
import java.util.Map;
@@ -138,9 +139,9 @@ class HttpRequestStrategy implements RequestStrategy {
*/
private boolean retry(HttpRequest request, Throwable thrown, int attempt) {
breaker.failure(thrown);
- if ( (thrown instanceof IOException) // General IO problems.
- || (thrown instanceof CancellationException) // TLS session disconnect.
- || (thrown instanceof CancelledKeyException)) { // Selection cancelled.
+ if ( (thrown instanceof IOException && ! (thrown instanceof SSLException)) // General IO problems, but not SSL, which is irrecoverable.
+ || (thrown instanceof CancellationException) // TLS session disconnect.
+ || (thrown instanceof CancelledKeyException)) { // Selection cancelled.
log.log(FINER, thrown, () -> "Failed attempt " + attempt + " at " + request);
return retry(request, attempt);
}
diff --git a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
index fe1e2b46830..45c93ec0755 100644
--- a/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
+++ b/vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java
@@ -132,6 +132,7 @@ import static java.util.stream.Collectors.toUnmodifiableMap;
public class DocumentV1ApiHandler extends AbstractRequestHandler {
private static final Duration defaultTimeout = Duration.ofSeconds(180); // Match document API default timeout.
+ private static final Duration handlerTimeout = Duration.ofMillis(100); // Extra time to allow for handler, JDisc and jetty to complete.
private static final Logger log = Logger.getLogger(DocumentV1ApiHandler.class.getName());
private static final Parser<Integer> integerParser = Integer::parseInt;
@@ -175,7 +176,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private static final String TO_TIMESTAMP = "toTimestamp";
private final Clock clock;
- private final Duration handlerTimeout;
+ private final Duration visitTimeout;
private final Metric metric;
private final DocumentApiMetrics metrics;
private final DocumentOperationParser parser;
@@ -204,11 +205,11 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
documentManagerConfig, executorConfig, clusterListConfig, bucketSpacesConfig);
}
- DocumentV1ApiHandler(Clock clock, Duration handlerTimeout, Metric metric, MetricReceiver metricReceiver, DocumentAccess access,
+ DocumentV1ApiHandler(Clock clock, Duration visitTimeout, Metric metric, MetricReceiver metricReceiver, DocumentAccess access,
DocumentmanagerConfig documentmanagerConfig, DocumentOperationExecutorConfig executorConfig,
ClusterListConfig clusterListConfig, AllClustersBucketSpacesConfig bucketSpacesConfig) {
this.clock = clock;
- this.handlerTimeout = handlerTimeout;
+ this.visitTimeout = visitTimeout;
this.parser = new DocumentOperationParser(documentmanagerConfig);
this.metric = metric;
this.metrics = new DocumentApiMetrics(metricReceiver, "documentV1");
@@ -237,9 +238,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
HttpRequest request = (HttpRequest) rawRequest;
try {
// Set a higher HTTP layer timeout than the document API timeout, to prefer triggering the latter.
- request.setTimeout( getProperty(request, TIMEOUT, timeoutMillisParser).orElse(defaultTimeout.toMillis())
- + handlerTimeout.toMillis(),
- MILLISECONDS);
+ request.setTimeout(doomMillis(request) - clock.millis(), MILLISECONDS);
Path requestPath = Path.withoutValidation(request.getUri()); // No segment validation here, as document IDs can be anything.
for (String path : handlers.keySet()) {
@@ -267,7 +266,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
@Override
public void handleTimeout(Request request, ResponseHandler responseHandler) {
- timeout((HttpRequest) request, "Timeout after " + (request.getTimeout(MILLISECONDS) - handlerTimeout.toMillis()) + "ms", responseHandler);
+ HttpRequest httpRequest = (HttpRequest) request;
+ timeout(httpRequest, "Timeout after " + (getProperty(httpRequest, TIMEOUT, timeoutMillisParser).orElse(defaultTimeout.toMillis())) + "ms", responseHandler);
}
@Override
@@ -523,26 +523,19 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private DocumentOperationParameters parametersFromRequest(HttpRequest request, String... names) {
DocumentOperationParameters parameters = getProperty(request, TRACELEVEL, integerParser).map(parameters()::withTraceLevel)
.orElse(parameters());
- parameters = getProperty(request, TIMEOUT, timeoutMillisParser).map(clock.instant()::plusMillis)
- .map(parameters::withDeadline)
- .orElse(parameters);
- for (String name : names) switch (name) {
- case CLUSTER:
- parameters = getProperty(request, CLUSTER).map(cluster -> resolveCluster(Optional.of(cluster), clusters).name())
- .map(parameters::withRoute)
- .orElse(parameters);
- break;
- case FIELD_SET:
- parameters = getProperty(request, FIELD_SET).map(parameters::withFieldSet)
- .orElse(parameters);
- break;
- case ROUTE:
- parameters = getProperty(request, ROUTE).map(parameters::withRoute)
- .orElse(parameters);
- break;
- default:
- throw new IllegalArgumentException("Unrecognized document operation parameter name '" + name + "'");
- }
+ parameters = parameters.withDeadline(Instant.ofEpochMilli(doomMillis(request)).minus(handlerTimeout));
+ for (String name : names)
+ parameters = switch (name) {
+ case CLUSTER ->
+ getProperty(request, CLUSTER)
+ .map(cluster -> resolveCluster(Optional.of(cluster), clusters).name())
+ .map(parameters::withRoute)
+ .orElse(parameters);
+ case FIELD_SET -> getProperty(request, FIELD_SET).map(parameters::withFieldSet).orElse(parameters);
+ case ROUTE -> getProperty(request, ROUTE).map(parameters::withRoute).orElse(parameters);
+ default ->
+ throw new IllegalArgumentException("Unrecognized document operation parameter name '" + name + "'");
+ };
return parameters;
}
@@ -630,10 +623,6 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private boolean first = true;
private ContentChannel channel;
- private JsonResponse(ResponseHandler handler) throws IOException {
- this(handler, null);
- }
-
private JsonResponse(ResponseHandler handler, HttpRequest request) throws IOException {
this.handler = handler;
this.request = request;
@@ -642,11 +631,6 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
/** Creates a new JsonResponse with path and id fields written. */
- static JsonResponse create(DocumentPath path, ResponseHandler handler) throws IOException {
- return create(path, handler, null);
- }
-
- /** Creates a new JsonResponse with path and id fields written. */
static JsonResponse create(DocumentPath path, ResponseHandler handler, HttpRequest request) throws IOException {
JsonResponse response = new JsonResponse(handler, request);
response.writePathId(path.rawPath());
@@ -749,23 +733,17 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
}
private boolean tensorShortForm() {
- if (request != null &&
- request.parameters().containsKey("format.tensors") &&
- ( request.parameters().get("format.tensors").contains("long")
- || request.parameters().get("format.tensors").contains("long-value"))) {
- return false;
- }
- return true; // default
+ return request == null ||
+ !request.parameters().containsKey("format.tensors") ||
+ (!request.parameters().get("format.tensors").contains("long")
+ && !request.parameters().get("format.tensors").contains("long-value"));// default
}
private boolean tensorDirectValues() {
- if (request != null &&
- request.parameters().containsKey("format.tensors") &&
- ( request.parameters().get("format.tensors").contains("short-value")
- || request.parameters().get("format.tensors").contains("long-value"))) {
- return true;
- }
- return false; // TODO: Flip default on Vespa 9
+ return request != null &&
+ request.parameters().containsKey("format.tensors") &&
+ (request.parameters().get("format.tensors").contains("short-value")
+ || request.parameters().get("format.tensors").contains("long-value"));// TODO: Flip default on Vespa 9
}
synchronized void writeSingleDocument(Document document) throws IOException {
@@ -1168,9 +1146,8 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
// ------------------------------------------------- Visits ------------------------------------------------
private VisitorParameters parseGetParameters(HttpRequest request, DocumentPath path, boolean streamed) {
- int wantedDocumentCount = Math.min(streamed ? Integer.MAX_VALUE : 1 << 10,
- getProperty(request, WANTED_DOCUMENT_COUNT, integerParser)
- .orElse(streamed ? Integer.MAX_VALUE : 1));
+ int wantedDocumentCount = getProperty(request, WANTED_DOCUMENT_COUNT, integerParser)
+ .orElse(streamed ? Integer.MAX_VALUE : 1);
if (wantedDocumentCount <= 0)
throw new IllegalArgumentException("wantedDocumentCount must be positive");
@@ -1189,16 +1166,15 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
parameters.setFieldSet(getProperty(request, FIELD_SET).orElse(path.documentType().map(type -> type + ":[document]").orElse(DocumentOnly.NAME)));
parameters.setMaxTotalHits(wantedDocumentCount);
parameters.visitInconsistentBuckets(true);
- long timeoutMs = Math.max(1, request.getTimeout(MILLISECONDS) - handlerTimeout.toMillis());
if (streamed) {
StaticThrottlePolicy throttlePolicy = new DynamicThrottlePolicy().setMinWindowSize(1).setWindowSizeIncrement(1);
concurrency.ifPresent(throttlePolicy::setMaxPendingCount);
parameters.setThrottlePolicy(throttlePolicy);
- parameters.setTimeoutMs(timeoutMs); // Ensure visitor eventually completes.
+ parameters.setTimeoutMs(visitTimeout(request)); // Ensure visitor eventually completes.
}
else {
parameters.setThrottlePolicy(new StaticThrottlePolicy().setMaxPendingCount(Math.min(100, concurrency.orElse(1))));
- parameters.setSessionTimeoutMs(timeoutMs);
+ parameters.setSessionTimeoutMs(visitTimeout(request));
}
return parameters;
}
@@ -1209,10 +1185,16 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
VisitorParameters parameters = parseCommonParameters(request, path, Optional.of(requireProperty(request, CLUSTER)));
parameters.setThrottlePolicy(new DynamicThrottlePolicy().setMinWindowSize(1).setWindowSizeIncrement(1));
long timeChunk = getProperty(request, TIME_CHUNK, timeoutMillisParser).orElse(60_000L);
- parameters.setSessionTimeoutMs(Math.max(1, Math.min(timeChunk, request.getTimeout(MILLISECONDS) - handlerTimeout.toMillis())));
+ parameters.setSessionTimeoutMs(Math.min(timeChunk, visitTimeout(request)));
return parameters;
}
+ private long visitTimeout(HttpRequest request) {
+ return Math.max(1,
+ Math.max(doomMillis(request) - clock.millis() - visitTimeout.toMillis(),
+ 9 * (doomMillis(request) - clock.millis()) / 10 - handlerTimeout.toMillis()));
+ }
+
private VisitorParameters parseCommonParameters(HttpRequest request, DocumentPath path, Optional<String> cluster) {
VisitorParameters parameters = new VisitorParameters(Stream.of(getProperty(request, SELECTION),
path.documentType(),
@@ -1226,7 +1208,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
getProperty(request, TRACELEVEL, integerParser).ifPresent(parameters::setTraceLevel);
- getProperty(request, CONTINUATION).map(ProgressToken::fromSerializedString).ifPresent(parameters::setResumeToken);
+ getProperty(request, CONTINUATION, ProgressToken::fromSerializedString).ifPresent(parameters::setResumeToken);
parameters.setPriority(DocumentProtocol.Priority.NORMAL_4);
getProperty(request, FROM_TIMESTAMP, unsignedLongParser).ifPresent(parameters::setFromTimestamp);
@@ -1366,7 +1348,7 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
AtomicReference<String> error = new AtomicReference<>(); // Set if error occurs during processing of visited documents.
callback.onStart(response, fullyApplied);
VisitorControlHandler controller = new VisitorControlHandler() {
- final ScheduledFuture<?> abort = streaming ? visitDispatcher.schedule(this::abort, request.getTimeout(MILLISECONDS), MILLISECONDS) : null;
+ final ScheduledFuture<?> abort = streaming ? visitDispatcher.schedule(this::abort, visitTimeout(request), MILLISECONDS) : null;
final AtomicReference<VisitorSession> session = new AtomicReference<>();
@Override public void setSession(VisitorControlSession session) { // Workaround for broken session API ಠ_ಠ
super.setSession(session);
@@ -1447,6 +1429,12 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
// ------------------------------------------------ Helpers ------------------------------------------------
+ private static long doomMillis(HttpRequest request) {
+ long createdAtMillis = request.creationTime(MILLISECONDS);
+ long requestTimeoutMillis = getProperty(request, TIMEOUT, timeoutMillisParser).orElse(defaultTimeout.toMillis());
+ return createdAtMillis + requestTimeoutMillis;
+ }
+
private static String requireProperty(HttpRequest request, String name) {
return getProperty(request, name)
.orElseThrow(() -> new IllegalArgumentException("Must specify '" + name + "' at '" + request.getUri().getRawPath() + "'"));
@@ -1546,11 +1534,11 @@ public class DocumentV1ApiHandler extends AbstractRequestHandler {
private static Map<String, StorageCluster> parseClusters(ClusterListConfig clusters, AllClustersBucketSpacesConfig buckets) {
return clusters.storage().stream()
- .collect(toUnmodifiableMap(storage -> storage.name(),
+ .collect(toUnmodifiableMap(ClusterListConfig.Storage::name,
storage -> new StorageCluster(storage.name(),
buckets.cluster(storage.name())
.documentType().entrySet().stream()
- .collect(toMap(entry -> entry.getKey(),
+ .collect(toMap(Map.Entry::getKey,
entry -> entry.getValue().bucketSpace())))));
}
diff --git a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
index 851a0949266..1d81c45daf1 100644
--- a/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
+++ b/vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java
@@ -89,6 +89,7 @@ import static com.yahoo.jdisc.http.HttpRequest.Method.POST;
import static com.yahoo.jdisc.http.HttpRequest.Method.PUT;
import static java.nio.charset.StandardCharsets.UTF_8;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -217,11 +218,11 @@ public class DocumentV1ApiTest {
access.expect(parameters -> {
assertEquals("content", parameters.getRoute().toString());
assertEquals("default", parameters.getBucketSpace());
- assertEquals(1024, parameters.getMaxTotalHits());
+ assertEquals(1025, parameters.getMaxTotalHits());
assertEquals(100, ((StaticThrottlePolicy) parameters.getThrottlePolicy()).getMaxPendingCount());
assertEquals("[id]", parameters.getFieldSet());
assertEquals("(all the things)", parameters.getDocumentSelection());
- assertEquals(6000, parameters.getSessionTimeoutMs());
+ assertTrue(6000 <= parameters.getSessionTimeoutMs()); // Static clock in handler < connected time for request, test artefact.
assertEquals(9, parameters.getTraceLevel());
assertEquals(1_000_000, parameters.getFromTimestamp());
assertEquals(2_000_000, parameters.getToTimestamp());
@@ -283,7 +284,7 @@ public class DocumentV1ApiTest {
assertEquals(1, ((StaticThrottlePolicy) parameters.getThrottlePolicy()).getMaxPendingCount());
assertEquals("[id]", parameters.getFieldSet());
assertEquals("(all the things)", parameters.getDocumentSelection());
- assertEquals(6000, parameters.getTimeoutMs());
+ assertTrue(6000 <= parameters.getTimeoutMs()); // Static clock in handler < connected time for request, test artefact.
assertEquals(4, parameters.getSlices());
assertEquals(1, parameters.getSliceId());
assertEquals(0, parameters.getFromTimestamp()); // not set; 0 is default
@@ -812,7 +813,7 @@ public class DocumentV1ApiTest {
// TIMEOUT is a 504
access.session.expect((id, parameters) -> {
- assertEquals(clock.instant().plusSeconds(1000), parameters.deadline().get());
+ assertFalse(clock.instant().plusSeconds(1000).isAfter(parameters.deadline().get())); // Static clock in handler vs real clock in Request.
parameters.responseHandler().get().handleResponse(new Response(0, "timeout", Response.Outcome.TIMEOUT));
return new Result();
});
diff --git a/vespajlib/abi-spec.json b/vespajlib/abi-spec.json
index 48d519b3a62..fe07460c20b 100644
--- a/vespajlib/abi-spec.json
+++ b/vespajlib/abi-spec.json
@@ -3995,5 +3995,113 @@
"public void leaving(com.yahoo.yolean.trace.TraceNode)"
],
"fields" : [ ]
+ },
+ "ai.vespa.llm.LanguageModel" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "interface",
+ "abstract"
+ ],
+ "methods" : [
+ "public abstract java.util.List complete(ai.vespa.llm.completion.Prompt)"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.completion.Completion$FinishReason" : {
+ "superClass" : "java.lang.Enum",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "enum"
+ ],
+ "methods" : [
+ "public static ai.vespa.llm.completion.Completion$FinishReason[] values()",
+ "public static ai.vespa.llm.completion.Completion$FinishReason valueOf(java.lang.String)"
+ ],
+ "fields" : [
+ "public static final enum ai.vespa.llm.completion.Completion$FinishReason length",
+ "public static final enum ai.vespa.llm.completion.Completion$FinishReason stop"
+ ]
+ },
+ "ai.vespa.llm.completion.Completion" : {
+ "superClass" : "java.lang.Record",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "final",
+ "record"
+ ],
+ "methods" : [
+ "public void <init>(java.lang.String, ai.vespa.llm.completion.Completion$FinishReason)",
+ "public java.lang.String text()",
+ "public ai.vespa.llm.completion.Completion$FinishReason finishReason()",
+ "public static ai.vespa.llm.completion.Completion from(java.lang.String)",
+ "public final java.lang.String toString()",
+ "public final int hashCode()",
+ "public final boolean equals(java.lang.Object)"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.completion.Prompt" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public",
+ "abstract"
+ ],
+ "methods" : [
+ "public void <init>()",
+ "public abstract java.lang.String asString()",
+ "public ai.vespa.llm.completion.Prompt append(ai.vespa.llm.completion.Completion)",
+ "public abstract ai.vespa.llm.completion.Prompt append(java.lang.String)"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.completion.StringPrompt" : {
+ "superClass" : "ai.vespa.llm.completion.Prompt",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public java.lang.String asString()",
+ "public ai.vespa.llm.completion.StringPrompt append(java.lang.String)",
+ "public ai.vespa.llm.completion.StringPrompt append(ai.vespa.llm.completion.Completion)",
+ "public java.lang.String toString()",
+ "public static ai.vespa.llm.completion.StringPrompt from(java.lang.String)",
+ "public bridge synthetic ai.vespa.llm.completion.Prompt append(java.lang.String)",
+ "public bridge synthetic ai.vespa.llm.completion.Prompt append(ai.vespa.llm.completion.Completion)"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.test.MockLanguageModel$Builder" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public ai.vespa.llm.test.MockLanguageModel$Builder completer(java.util.function.Function)",
+ "public void <init>()",
+ "public ai.vespa.llm.test.MockLanguageModel build()"
+ ],
+ "fields" : [ ]
+ },
+ "ai.vespa.llm.test.MockLanguageModel" : {
+ "superClass" : "java.lang.Object",
+ "interfaces" : [
+ "ai.vespa.llm.LanguageModel"
+ ],
+ "attributes" : [
+ "public"
+ ],
+ "methods" : [
+ "public void <init>(ai.vespa.llm.test.MockLanguageModel$Builder)",
+ "public java.util.List complete(ai.vespa.llm.completion.Prompt)"
+ ],
+ "fields" : [ ]
}
} \ No newline at end of file
diff --git a/vespajlib/src/main/java/ai/vespa/llm/LanguageModel.java b/vespajlib/src/main/java/ai/vespa/llm/LanguageModel.java
new file mode 100644
index 00000000000..829b74f7bf4
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/LanguageModel.java
@@ -0,0 +1,20 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.llm;
+
+import ai.vespa.llm.completion.Completion;
+import ai.vespa.llm.completion.Prompt;
+import com.yahoo.api.annotations.Beta;
+
+import java.util.List;
+
+/**
+ * Interface to language models.
+ *
+ * @author bratseth
+ */
+@Beta
+public interface LanguageModel {
+
+ List<Completion> complete(Prompt prompt);
+
+}
diff --git a/vespajlib/src/main/java/ai/vespa/llm/completion/Completion.java b/vespajlib/src/main/java/ai/vespa/llm/completion/Completion.java
new file mode 100644
index 00000000000..30645b5151f
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/completion/Completion.java
@@ -0,0 +1,41 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.llm.completion;
+
+import com.yahoo.api.annotations.Beta;
+
+import java.util.Objects;
+
+/**
+ * A completion from a language model.
+ *
+ * @author bratseth
+ */
+@Beta
+public record Completion(String text, FinishReason finishReason) {
+
+ public enum FinishReason {
+
+ /** The maximum length of a completion was reached. */
+ length,
+
+ /** The completion is the predicted ending of the prompt. */
+ stop
+
+ }
+
+ public Completion(String text, FinishReason finishReason) {
+ this.text = Objects.requireNonNull(text);
+ this.finishReason = Objects.requireNonNull(finishReason);
+ }
+
+ /** Returns the generated text completion. */
+ public String text() { return text; }
+
+ /** Returns the reason this completion ended. */
+ public FinishReason finishReason() { return finishReason; }
+
+ public static Completion from(String text) {
+ return new Completion(text, FinishReason.stop);
+ }
+
+}
diff --git a/vespajlib/src/main/java/ai/vespa/llm/completion/Prompt.java b/vespajlib/src/main/java/ai/vespa/llm/completion/Prompt.java
new file mode 100644
index 00000000000..d5d0247d6b0
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/completion/Prompt.java
@@ -0,0 +1,23 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.llm.completion;
+
+import com.yahoo.api.annotations.Beta;
+
+/**
+ * A prompt that can be given to a large language model to generate a completion.
+ *
+ * @author bratseth
+ */
+@Beta
+public abstract class Prompt {
+
+ public abstract String asString();
+
+ /** Returns a new prompt with the text of the given completion appended. */
+ public Prompt append(Completion completion) {
+ return append(completion.text());
+ }
+
+ public abstract Prompt append(String text);
+
+}
diff --git a/vespajlib/src/main/java/ai/vespa/llm/completion/StringPrompt.java b/vespajlib/src/main/java/ai/vespa/llm/completion/StringPrompt.java
new file mode 100644
index 00000000000..e8392ca992e
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/completion/StringPrompt.java
@@ -0,0 +1,43 @@
+package ai.vespa.llm.completion;
+
+import com.yahoo.api.annotations.Beta;
+
+import java.util.Objects;
+
+/**
+ * A prompt which just consists of a string.
+ *
+ * @author bratseth
+ */
+@Beta
+public class StringPrompt extends Prompt {
+
+ private final String string;
+
+ private StringPrompt(String string) {
+ this.string = Objects.requireNonNull(string);
+ }
+
+ @Override
+ public String asString() { return string; }
+
+ @Override
+ public StringPrompt append(String text) {
+ return StringPrompt.from(string + text);
+ }
+
+ @Override
+ public StringPrompt append(Completion completion) {
+ return append(completion.text());
+ }
+
+ @Override
+ public String toString() {
+ return string;
+ }
+
+ public static StringPrompt from(String string) {
+ return new StringPrompt(string);
+ }
+
+}
diff --git a/vespajlib/src/main/java/ai/vespa/llm/completion/package-info.java b/vespajlib/src/main/java/ai/vespa/llm/completion/package-info.java
new file mode 100644
index 00000000000..79898c694ca
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/completion/package-info.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package ai.vespa.llm.completion;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
+
+/**
+ * Classes for generating text completions with language models.
+ */ \ No newline at end of file
diff --git a/vespajlib/src/main/java/ai/vespa/llm/package-info.java b/vespajlib/src/main/java/ai/vespa/llm/package-info.java
new file mode 100644
index 00000000000..04fc24c51ee
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/package-info.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package ai.vespa.llm;
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage;
+
+/**
+ * API for working with large language models.
+ */ \ No newline at end of file
diff --git a/vespajlib/src/main/java/ai/vespa/llm/test/MockLanguageModel.java b/vespajlib/src/main/java/ai/vespa/llm/test/MockLanguageModel.java
new file mode 100644
index 00000000000..d47f43c55b2
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/test/MockLanguageModel.java
@@ -0,0 +1,44 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+package ai.vespa.llm.test;
+
+import ai.vespa.llm.LanguageModel;
+import ai.vespa.llm.completion.Completion;
+import ai.vespa.llm.completion.Prompt;
+import com.yahoo.api.annotations.Beta;
+
+import java.util.List;
+import java.util.function.Function;
+
+/**
+ * @author bratseth
+ */
+@Beta
+public class MockLanguageModel implements LanguageModel {
+
+ private final Function<Prompt, List<Completion>> completer;
+
+ public MockLanguageModel(Builder builder) {
+ completer = builder.completer;
+ }
+
+ @Override
+ public List<Completion> complete(Prompt prompt) {
+ return completer.apply(prompt);
+ }
+
+ public static class Builder {
+
+ private Function<Prompt, List<Completion>> completer = prompt -> List.of(Completion.from(""));
+
+ public Builder completer(Function<Prompt, List<Completion>> completer) {
+ this.completer = completer;
+ return this;
+ }
+
+ public Builder() {}
+
+ public MockLanguageModel build() { return new MockLanguageModel(this); }
+
+ }
+
+}
diff --git a/vespajlib/src/main/java/ai/vespa/llm/test/package-info.java b/vespajlib/src/main/java/ai/vespa/llm/test/package-info.java
new file mode 100644
index 00000000000..0d51815fd6d
--- /dev/null
+++ b/vespajlib/src/main/java/ai/vespa/llm/test/package-info.java
@@ -0,0 +1,11 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+@ExportPackage
+@PublicApi
+package ai.vespa.llm.test;
+
+/**
+ * Tools for writing tests when working with large language models.
+ */
+
+import com.yahoo.api.annotations.PublicApi;
+import com.yahoo.osgi.annotation.ExportPackage; \ No newline at end of file
diff --git a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
index 33e46ebc75f..31f4038c16e 100644
--- a/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
+++ b/vespajlib/src/main/java/com/yahoo/concurrent/maintenance/Maintainer.java
@@ -37,15 +37,17 @@ public abstract class Maintainer implements Runnable {
private final AtomicBoolean shutDown = new AtomicBoolean();
private final boolean ignoreCollision;
private final Clock clock;
+ private final Double successFactorBaseline;
public Maintainer(String name, Duration interval, Clock clock, JobControl jobControl,
- JobMetrics jobMetrics, List<String> clusterHostnames, boolean ignoreCollision) {
+ JobMetrics jobMetrics, List<String> clusterHostnames, boolean ignoreCollision, Double successFactorBaseline) {
this.name = name;
this.interval = requireInterval(interval);
this.jobControl = Objects.requireNonNull(jobControl);
this.jobMetrics = Objects.requireNonNull(jobMetrics);
this.ignoreCollision = ignoreCollision;
this.clock = clock;
+ this.successFactorBaseline = successFactorBaseline;
var startedAt = clock.instant();
Objects.requireNonNull(clusterHostnames);
Duration initialDelay = staggeredDelay(interval, startedAt, HostName.getLocalhost(), clusterHostnames)
@@ -55,6 +57,10 @@ public abstract class Maintainer implements Runnable {
jobControl.started(name(), this);
}
+ public Maintainer(String name, Duration interval, Clock clock, JobControl jobControl,
+ JobMetrics jobMetrics, List<String> clusterHostnames, boolean ignoreCollision) {
+ this(name, interval, clock, jobControl, jobMetrics, clusterHostnames, ignoreCollision, 1.0);
+ }
@Override
public void run() {
lockAndMaintain(false);
@@ -91,16 +97,16 @@ public abstract class Maintainer implements Runnable {
/**
* Called once each time this maintenance job should run.
*
- * @return the degree to which the run was successful - a number between 0 (no success), to 1 (complete success).
+ * @return the degree to which the run was deviated from the successFactorBaseline - a number between -1 (no success), to 0 (complete success).
* Note that this indicates whether something is wrong, so e.g if the call did nothing because it should do
- * nothing, 1.0 should be returned.
+ * nothing, 0.0 should be returned.
*/
protected abstract double maintain();
- /** Convenience methods to convert attempts and failures into a success factor */
- protected final double asSuccessFactor(int attempts, int failures) {
+ /** Convenience methods to convert attempts and failures into a success factor, and return */
+ protected final double asSuccessFactorDeviation(int attempts, int failures) {
double factor = attempts == 0 ? 1.0 : 1 - (double)failures / attempts;
- return new BigDecimal(factor).setScale(2, RoundingMode.HALF_UP).doubleValue();
+ return new BigDecimal(factor - successFactorBaseline).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
/** Returns the interval at which this job is set to run */
@@ -111,14 +117,14 @@ public abstract class Maintainer implements Runnable {
if (!force && !jobControl.isActive(name())) return;
log.log(Level.FINE, () -> "Running " + this.getClass().getSimpleName());
- double successFactor = 0;
+ double successFactorDeviation = 0;
long startTime = clock.millis();
try (var lock = jobControl.lockJob(name())) {
- successFactor = maintain();
+ successFactorDeviation = maintain();
}
catch (UncheckedTimeoutException e) {
if (ignoreCollision)
- successFactor = 1;
+ successFactorDeviation = 0;
else
log.log(Level.WARNING, this + " collided with another run. Will retry in " + interval);
}
@@ -127,7 +133,7 @@ public abstract class Maintainer implements Runnable {
}
finally {
long endTime = clock.millis();
- jobMetrics.completed(name(), successFactor, endTime - startTime);
+ jobMetrics.completed(name(), successFactorDeviation, endTime - startTime);
}
log.log(Level.FINE, () -> "Finished " + this.getClass().getSimpleName());
}
diff --git a/vespajlib/src/main/java/com/yahoo/slime/BinaryEncoder.java b/vespajlib/src/main/java/com/yahoo/slime/BinaryEncoder.java
index f12496f7a76..e0b4fb2c672 100644
--- a/vespajlib/src/main/java/com/yahoo/slime/BinaryEncoder.java
+++ b/vespajlib/src/main/java/com/yahoo/slime/BinaryEncoder.java
@@ -28,7 +28,7 @@ final class BinaryEncoder implements ArrayTraverser, ObjectSymbolTraverser {
byte next = (byte)(value & 0x7f);
value >>>= 7; // unsigned shift
while (value != 0) {
- next |= 0x80;
+ next |= (byte)0x80;
out.put(next);
next = (byte)(value & 0x7f);
value >>>= 7;
diff --git a/vespajlib/src/main/java/com/yahoo/slime/BinaryView.java b/vespajlib/src/main/java/com/yahoo/slime/BinaryView.java
index 0e111d42061..1b4468d18bb 100644
--- a/vespajlib/src/main/java/com/yahoo/slime/BinaryView.java
+++ b/vespajlib/src/main/java/com/yahoo/slime/BinaryView.java
@@ -14,15 +14,24 @@ public final class BinaryView implements Inspector {
private final byte[] data;
private final SymbolTable names;
- private final DecodeIndex index;
+ private final long[] index;
private final int self;
- private BinaryView(byte[] data, SymbolTable names, DecodeIndex index, int self) {
+ private BinaryView(byte[] data, SymbolTable names, long[] index, int self) {
this.data = data;
this.names = names;
this.index = index;
this.self = self;
}
+ private int byte_offset(int idx) {
+ return (int)(index[idx] >> 33) & 0x7fff_ffff;
+ }
+ private int first_child(int idx) {
+ return (int)(index[idx] >> 2) & 0x7fff_ffff;
+ }
+ private int ext_bits(int idx) {
+ return (int)index[idx] & 0x3;
+ }
private int peek_cmpr_int(int idx) {
long next = data[idx++];
long value = (next & 0x7f);
@@ -92,8 +101,8 @@ public final class BinaryView implements Inspector {
}
private Inspector find_field(int pos, int len, int sym) {
for (int i = 0; i < len; ++i) {
- int idx = index.getByteOffset(pos + i);
- if (peek_cmpr_int(idx - (index.getExtBits(pos + i) + 1)) == sym) {
+ int idx = byte_offset(pos + i);
+ if (peek_cmpr_int(idx - (ext_bits(pos + i) + 1)) == sym) {
return new BinaryView(data, names, index, pos + i);
}
}
@@ -102,110 +111,110 @@ public final class BinaryView implements Inspector {
@Override public boolean valid() { return true; }
@Override public void ifValid(Consumer<Inspector> consumer) { consumer.accept(this); }
- @Override public Type type() { return decode_type(data[index.getByteOffset(self)]); }
+ @Override public Type type() { return decode_type(data[byte_offset(self)]); }
@Override public int children() {
return switch (type()) {
- case OBJECT, ARRAY -> extract_children(index.getByteOffset(self));
+ case OBJECT, ARRAY -> extract_children(byte_offset(self));
default -> 0;
};
}
@Override public int entries() {
return switch (type()) {
- case ARRAY -> extract_children(index.getByteOffset(self));
+ case ARRAY -> extract_children(byte_offset(self));
default -> 0;
};
}
@Override public int fields() {
return switch (type()) {
- case OBJECT -> extract_children(index.getByteOffset(self));
+ case OBJECT -> extract_children(byte_offset(self));
default -> 0;
};
}
@Override public boolean asBool() {
return switch (type()) {
- case BOOL -> (decode_meta(data[index.getByteOffset(self)]) != 0);
+ case BOOL -> (decode_meta(data[byte_offset(self)]) != 0);
default -> false;
};
}
@Override public long asLong() {
return switch (type()) {
- case LONG -> extract_long(index.getByteOffset(self));
- case DOUBLE -> (long)extract_double(index.getByteOffset(self));
+ case LONG -> extract_long(byte_offset(self));
+ case DOUBLE -> (long)extract_double(byte_offset(self));
default -> 0;
};
}
@Override public double asDouble() {
return switch (type()) {
- case LONG -> extract_long(index.getByteOffset(self));
- case DOUBLE -> extract_double(index.getByteOffset(self));
+ case LONG -> extract_long(byte_offset(self));
+ case DOUBLE -> extract_double(byte_offset(self));
default -> 0.0;
};
}
@Override public String asString() {
return switch (type()) {
- case STRING -> extract_string(index.getByteOffset(self));
+ case STRING -> extract_string(byte_offset(self));
default -> Value.emptyString;
};
}
@Override public byte[] asUtf8() {
return switch (type()) {
- case STRING -> extract_bytes(index.getByteOffset(self));
+ case STRING -> extract_bytes(byte_offset(self));
default -> Value.emptyData;
};
}
@Override public byte[] asData() {
return switch (type()) {
- case DATA -> extract_bytes(index.getByteOffset(self));
+ case DATA -> extract_bytes(byte_offset(self));
default -> Value.emptyData;
};
}
@Override public void accept(Visitor v) {
switch (type()) {
case NIX: v.visitNix(); break;
- case BOOL: v.visitBool(decode_meta(data[index.getByteOffset(self)]) != 0); break;
- case LONG: v.visitLong(extract_long(index.getByteOffset(self))); break;
- case DOUBLE: v.visitDouble(extract_double(index.getByteOffset(self))); break;
- case STRING: v.visitString(extract_bytes(index.getByteOffset(self))); break;
- case DATA: v.visitData(extract_bytes(index.getByteOffset(self))); break;
+ case BOOL: v.visitBool(decode_meta(data[byte_offset(self)]) != 0); break;
+ case LONG: v.visitLong(extract_long(byte_offset(self))); break;
+ case DOUBLE: v.visitDouble(extract_double(byte_offset(self))); break;
+ case STRING: v.visitString(extract_bytes(byte_offset(self))); break;
+ case DATA: v.visitData(extract_bytes(byte_offset(self))); break;
case ARRAY: v.visitArray(this); break;
case OBJECT: v.visitObject(this); break;
default: throw new RuntimeException("should not be reached");
}
}
@Override public void traverse(ArrayTraverser at) {
- int pos = index.getFirstChild(self);
+ int pos = first_child(self);
int len = entries();
for (int i = 0; i < len; ++i) {
at.entry(i, new BinaryView(data, names, index, pos + i));
}
}
@Override public void traverse(ObjectSymbolTraverser ot) {
- int pos = index.getFirstChild(self);
+ int pos = first_child(self);
int len = fields();
for (int i = 0; i < len; ++i) {
- int sym = peek_cmpr_int(index.getByteOffset(pos + i) - (index.getExtBits(pos + i) + 1));
+ int sym = peek_cmpr_int(byte_offset(pos + i) - (ext_bits(pos + i) + 1));
ot.field(sym, new BinaryView(data, names, index, pos + i));
}
}
@Override public void traverse(ObjectTraverser ot) {
- int pos = index.getFirstChild(self);
+ int pos = first_child(self);
int len = fields();
for (int i = 0; i < len; ++i) {
- int sym = peek_cmpr_int(index.getByteOffset(pos + i) - (index.getExtBits(pos + i) + 1));
+ int sym = peek_cmpr_int(byte_offset(pos + i) - (ext_bits(pos + i) + 1));
ot.field(names.inspect(sym), new BinaryView(data, names, index, pos + i));
}
}
@Override public Inspector entry(int idx) {
int limit = entries();
if (idx >= 0 && idx < limit) {
- return new BinaryView(data, names, index, index.getFirstChild(self) + idx);
+ return new BinaryView(data, names, index, first_child(self) + idx);
}
return NixValue.invalid();
}
@Override public Inspector field(int sym) {
int limit = fields();
if (limit > 0 && sym != SymbolTable.INVALID) {
- return find_field(index.getFirstChild(self), limit, sym);
+ return find_field(first_child(self), limit, sym);
}
return NixValue.invalid();
}
@@ -214,7 +223,7 @@ public final class BinaryView implements Inspector {
if (limit > 0) {
int sym = names.lookup(name);
if (sym != SymbolTable.INVALID) {
- return find_field(index.getFirstChild(self), limit, sym);
+ return find_field(first_child(self), limit, sym);
}
}
return NixValue.invalid();
@@ -243,11 +252,11 @@ public final class BinaryView implements Inspector {
break; }
case ARRAY: {
int size = input.read_size(meta);
- if (size > input.getBacking().length - index.size()) {
+ int firstChild = index.tryReserveChildren(size, index.used() + 1, input.getPosition());
+ if (firstChild < 0) {
input.fail("decode index too big");
return;
}
- int firstChild = index.reserve(size);
index.set(self, pos, firstChild, extBits);
for (int i = 0; i < size; ++i) {
buildIndex(input, index, firstChild + i, 0);
@@ -255,11 +264,11 @@ public final class BinaryView implements Inspector {
break; }
case OBJECT: {
int size = input.read_size(meta);
- if (size > input.getBacking().length - index.size()) {
+ int firstChild = index.tryReserveChildren(size, index.used() + 1, input.getPosition());
+ if (firstChild < 0) {
input.fail("decode index too big");
return;
}
- int firstChild = index.reserve(size);
index.set(self, pos, firstChild, extBits);
for (int i = 0; i < size; ++i) {
int childExtBits = input.skip_cmpr_int();
@@ -274,19 +283,16 @@ public final class BinaryView implements Inspector {
}
}
- static Inspector inspectImpl(BufferedInput input) {
+ public static Inspector inspect(byte[] data) {
+ var input = new BufferedInput(data);
var names = new SymbolTable();
- var index = new DecodeIndex();
BinaryDecoder.decodeSymbolTable(input, names);
- buildIndex(input, index, index.reserve(1), 0);
+ var index = new DecodeIndex(input.getBacking().length, input.getPosition());
+ buildIndex(input, index, 0, 0);
if (input.failed()) {
- return NixValue.invalid();
+ throw new IllegalArgumentException("bad input: " + input.getErrorMessage());
}
- return new BinaryView(input.getBacking(), names, index, 0);
- }
-
- public static Inspector inspect(byte[] data) {
- return inspectImpl(new BufferedInput(data));
+ return new BinaryView(input.getBacking(), names, index.getBacking(), 0);
}
static int peek_cmpr_int_for_testing(byte[] data, int idx) {
@@ -304,4 +310,13 @@ public final class BinaryView implements Inspector {
static double extract_double_for_testing(byte[] data, int idx) {
return new BinaryView(data, null, null, -1).extract_double(idx);
}
+ static int byte_offset_for_testing(DecodeIndex index, int idx) {
+ return new BinaryView(null, null, index.getBacking(), -1).byte_offset(idx);
+ }
+ static int first_child_for_testing(DecodeIndex index, int idx) {
+ return new BinaryView(null, null, index.getBacking(), -1).first_child(idx);
+ }
+ static int ext_bits_for_testing(DecodeIndex index, int idx) {
+ return new BinaryView(null, null, index.getBacking(), -1).ext_bits(idx);
+ }
}
diff --git a/vespajlib/src/main/java/com/yahoo/slime/DecodeIndex.java b/vespajlib/src/main/java/com/yahoo/slime/DecodeIndex.java
index 17c7a86730e..645eac3b4d9 100644
--- a/vespajlib/src/main/java/com/yahoo/slime/DecodeIndex.java
+++ b/vespajlib/src/main/java/com/yahoo/slime/DecodeIndex.java
@@ -6,23 +6,47 @@ package com.yahoo.slime;
* encoded in binary format.
**/
final class DecodeIndex {
- static final int initial_capacity = 16;
- private long[] data = new long[initial_capacity];
- private int reserved = 0;
-
- private int adjustSize(int minSize) {
- int capacity = initial_capacity;
- while (capacity < minSize) {
- capacity = capacity << 1;
+ private long[] data;
+ private int reserved;
+ private int used = 0;
+ private final int totalSize;
+ private final int rootOffset;
+
+ private int binarySize() { return totalSize - rootOffset; }
+
+ private int adjustSize(int minSize, int maxSize, int cnt, int byteOffset) {
+ double density = (double)cnt / (double)(byteOffset - rootOffset);
+ double estSize = 1.1 * density * binarySize();
+ double expSize = 1.25 * data.length;
+ double wantedSize = (estSize > expSize) ? estSize : expSize;
+ if (wantedSize < minSize) {
+ return minSize;
+ }
+ if (wantedSize > maxSize) {
+ return maxSize;
}
- return capacity;
+ return (int)wantedSize;
}
- int reserve(int n) {
+ DecodeIndex(int totalSize, int rootOffset) {
+ this.totalSize = totalSize;
+ this.rootOffset = rootOffset;
+ int initialCapacity = Math.max(16, binarySize() / 24);
+ data = new long[initialCapacity];
+ reserved = 1;
+ }
+
+ long[] getBacking() { return data; }
+
+ int tryReserveChildren(int n, int cnt, int byteOffset) {
int offset = reserved;
- if (reserved + n > data.length) {
+ if (n > data.length - reserved) {
+ final int maxSize = (totalSize - byteOffset) + cnt;
+ if (n > maxSize - reserved) {
+ return -1; // error; too much space requested
+ }
long[] old = data;
- data = new long[adjustSize(reserved + n)];
+ data = new long[adjustSize(reserved + n, maxSize, cnt, byteOffset)];
System.arraycopy(old, 0, data, 0, reserved);
}
reserved += n;
@@ -30,22 +54,13 @@ final class DecodeIndex {
}
int size() { return reserved; }
+ int used() { return used; }
+ int capacity() { return data.length; }
void set(int idx, int byteOffset, int firstChild, int extBits) {
data[idx] = (long)(byteOffset & 0x7fff_ffff) << 33 |
(long)(firstChild & 0x7fff_ffff) << 2 |
extBits & 0x3;
- }
-
- int getByteOffset(int idx) {
- return (int)(data[idx] >> 33) & 0x7fff_ffff;
- }
-
- int getFirstChild(int idx) {
- return (int)(data[idx] >> 2) & 0x7fff_ffff;
- }
-
- int getExtBits(int idx) {
- return (int)data[idx] & 0x3;
+ ++used;
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java b/vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java
index f9230ab6df6..6acd0679da2 100644
--- a/vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java
+++ b/vespajlib/src/main/java/com/yahoo/slime/SlimeUtils.java
@@ -166,81 +166,32 @@ public class SlimeUtils {
false);
}
- private static class Equal {
- protected final Inspector rhsInspector;
-
- protected boolean equal = true;
-
- public Equal(Inspector rhsInspector) { this.rhsInspector = rhsInspector; }
-
- public boolean isEqual() { return equal; }
- }
-
- private static class EqualArray extends Equal implements ArrayTraverser {
- public EqualArray(Inspector rhsInspector) { super(rhsInspector); }
-
- @Override
- public void entry(int idx, Inspector inspector) {
- if (equal) {
- equal = inspector.equalTo(rhsInspector.entry(idx));
- }
- }
- }
-
- private static class EqualObject extends Equal implements ObjectTraverser {
- public EqualObject(Inspector rhsInspector) { super(rhsInspector); }
-
- @Override
- public void field(String name, Inspector inspector) {
- if (equal) {
- equal = inspector.equalTo(rhsInspector.field(name));
- }
- }
- }
-
public static boolean equalTo(Inspector a, Inspector b) {
- boolean equal = a.type() == b.type();
-
- if (equal) {
- switch (a.type()) {
- case NIX:
- equal = a.valid() == b.valid();
- break;
- case BOOL:
- equal = a.asBool() == b.asBool();
- break;
- case LONG:
- equal = a.asLong() == b.asLong();
- break;
- case DOUBLE:
- equal = Double.compare(a.asDouble(), b.asDouble()) == 0;
- break;
- case STRING:
- equal = a.asString().equals(b.asString());
- break;
- case DATA:
- equal = Arrays.equals(a.asData(), b.asData());
- break;
- case ARRAY:
- {
- var traverser = new EqualArray(b);
- a.traverse(traverser);
- equal = traverser.isEqual() && (a.entries() == b.entries());
- }
- break;
- case OBJECT:
- {
- var traverser = new EqualObject(b);
- a.traverse(traverser);
- equal = traverser.isEqual() && (a.fields() == b.fields());
+ if (a.type() != b.type()) return false;
+
+ switch (a.type()) {
+ case NIX: return a.valid() == b.valid();
+ case BOOL: return a.asBool() == b.asBool();
+ case LONG: return a.asLong() == b.asLong();
+ case DOUBLE: return Double.compare(a.asDouble(), b.asDouble()) == 0;
+ case STRING: return a.asString().equals(b.asString());
+ case DATA: return Arrays.equals(a.asData(), b.asData());
+ case ARRAY: {
+ if (a.entries() != b.entries()) return false;
+ for (int i = 0; i < a.entries(); i++) {
+ if (!equalTo(a.entry(i), b.entry(i))) return false;
}
- break;
- default:
- assert(false);
- break;
+ return true;
}
+ case OBJECT: {
+ if (a.fields() != b.fields()) return false;
+ boolean[] equal = new boolean[]{ true };
+ a.traverse((String key, Inspector value) -> {
+ if (equal[0] && !equalTo(value, b.field(key))) equal[0] = false;
+ });
+ return equal[0];
+ }
+ default: throw new IllegalStateException("Unexpected type: " + a.type());
}
-
- return equal;
}
}
diff --git a/vespajlib/src/main/java/com/yahoo/tensor/functions/ReduceJoin.java b/vespajlib/src/main/java/com/yahoo/tensor/functions/ReduceJoin.java
index 11996b6a23d..de1c30e6414 100644
--- a/vespajlib/src/main/java/com/yahoo/tensor/functions/ReduceJoin.java
+++ b/vespajlib/src/main/java/com/yahoo/tensor/functions/ReduceJoin.java
@@ -333,7 +333,7 @@ public class ReduceJoin<NAMETYPE extends Name> extends CompositeTensorFunction<N
private final long[] bounds;
private final long[] iterator;
- private int remaining;
+ private long remaining;
MultiDimensionIterator(TensorType type) {
bounds = new long[type.dimensions().size()];
diff --git a/vespajlib/src/test/java/ai/vespa/llm/completion/CompletionTest.java b/vespajlib/src/test/java/ai/vespa/llm/completion/CompletionTest.java
new file mode 100644
index 00000000000..1c794c64d1a
--- /dev/null
+++ b/vespajlib/src/test/java/ai/vespa/llm/completion/CompletionTest.java
@@ -0,0 +1,40 @@
+package ai.vespa.llm.completion;
+
+import ai.vespa.llm.completion.Completion;
+import ai.vespa.llm.completion.Prompt;
+import ai.vespa.llm.completion.StringPrompt;
+import ai.vespa.llm.test.MockLanguageModel;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+import java.util.function.Function;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Tests completion with a mock completer.
+ *
+ * @author bratseth
+ */
+public class CompletionTest {
+
+ @Test
+ public void testCompletion() {
+ Function<Prompt, List<Completion>> completer = in ->
+ switch (in.asString()) {
+ case "Complete this: " -> List.of(Completion.from("The completion"));
+ default -> throw new RuntimeException("Cannot complete '" + in + "'");
+ };
+ var llm = new MockLanguageModel.Builder().completer(completer).build();
+
+ String input = "Complete this: ";
+ StringPrompt prompt = StringPrompt.from(input);
+ for (int i = 0; i < 10; i++) {
+ var completion = llm.complete(prompt).get(0);
+ prompt = prompt.append(completion);
+ if (completion.finishReason() == Completion.FinishReason.stop) break;
+ }
+ assertEquals("Complete this: The completion", prompt.asString());
+ }
+
+}
diff --git a/vespajlib/src/test/java/com/yahoo/io/FatalErrorHandlerTestCase.java b/vespajlib/src/test/java/com/yahoo/io/FatalErrorHandlerTestCase.java
deleted file mode 100644
index dab91b6a995..00000000000
--- a/vespajlib/src/test/java/com/yahoo/io/FatalErrorHandlerTestCase.java
+++ /dev/null
@@ -1,58 +0,0 @@
-// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
-package com.yahoo.io;
-
-import static org.junit.Assert.*;
-
-import java.security.Permission;
-
-import org.junit.After;
-import org.junit.Before;
-import org.junit.Test;
-
-/**
- * Just to remove noise from the coverage report.
- *
- * @author Steinar Knutsen
- */
-public class FatalErrorHandlerTestCase {
- @SuppressWarnings("removal")
- private static final class AvoidExiting extends SecurityManager {
-
- @Override
- public void checkPermission(Permission perm) {
- }
-
- @Override
- public void checkExit(int status) {
- throw new SecurityException();
- }
-
- }
-
- private FatalErrorHandler h;
-
- @Before
- @SuppressWarnings("removal")
- public void setUp() throws Exception {
- h = new FatalErrorHandler();
- System.setSecurityManager(new AvoidExiting());
- }
-
- @After
- @SuppressWarnings("removal")
- public void tearDown() throws Exception {
- System.setSecurityManager(null);
- }
-
- @Test
- public final void testHandle() {
- boolean caught = false;
- try {
- h.handle(new Throwable(), "abc");
- } catch (SecurityException e) {
- caught = true;
- }
- assertTrue(caught);
- }
-
-}
diff --git a/vespajlib/src/test/java/com/yahoo/slime/BinaryViewTest.java b/vespajlib/src/test/java/com/yahoo/slime/BinaryViewTest.java
index 920a25b96c9..99c63c91afc 100644
--- a/vespajlib/src/test/java/com/yahoo/slime/BinaryViewTest.java
+++ b/vespajlib/src/test/java/com/yahoo/slime/BinaryViewTest.java
@@ -100,7 +100,15 @@ public class BinaryViewTest {
assertEquals(300, arr.entries());
return arr;
}
- static final int numShapes = numLeafs + 6;
+ static Cursor insert10SimpleHits(Inserter dst) {
+ var arr = dst.insertARRAY();
+ for (int i = 0; i < 10; ++i) {
+ var obj = arr.addObject();
+ obj.setLong("id", 123456);
+ }
+ return arr;
+ }
+ static final int numShapes = numLeafs + 7;
static Cursor insertRoot(Slime dst, int shape) {
var root = new SlimeInserter(dst);
if (shape < numLeafs) {
@@ -113,6 +121,7 @@ public class BinaryViewTest {
case (numLeafs + 3) -> insertOuterArray(root);
case (numLeafs + 4) -> insertManySymbols(root);
case (numLeafs + 5) -> insertLargeArray(root);
+ case (numLeafs + 6) -> insert10SimpleHits(root);
default -> NixValue.invalid();
};
}
@@ -297,66 +306,49 @@ public class BinaryViewTest {
}
}
+ void assertFail(byte[] data, String reason) {
+ try {
+ var view = BinaryView.inspect(data);
+ fail("expected exception");
+ } catch (IllegalArgumentException e) {
+ assertEquals("bad input: " + reason, e.getMessage());
+ }
+ }
+
@Test public void testTrivialView() {
byte[] data = {0, 0};
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
+ var view = BinaryView.inspect(data);
assertTrue(view.valid());
assertEquals(Type.NIX, view.type());
- assertFalse(input.failed());
}
@Test public void testUnderflow() {
byte[] data = {};
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("underflow", input.getErrorMessage());
+ assertFail(data, "underflow");
}
@Test public void testMultiByteUnderflow() {
byte[] data = { 0, encode_type_and_meta(Type.STRING.ID, 3), 65 };
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("underflow", input.getErrorMessage());
+ assertFail(data, "underflow");
}
@Test public void testCompressedIntOverflow() {
byte[] data = { -1, -1, -1, -1, 8 };
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("compressed int overflow", input.getErrorMessage());
+ assertFail(data, "compressed int overflow");
}
@Test public void testExtBitsOverflow() {
byte[] data = { 0, encode_type_and_meta(Type.OBJECT.ID, 2), -1, -1, -1, -1, 1 };
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("symbol id too big", input.getErrorMessage());
+ assertFail(data, "symbol id too big");
}
@Test public void testDecodeIndexOverflowArray() {
- byte[] data = { 0, encode_type_and_meta(Type.ARRAY.ID, 4) };
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("decode index too big", input.getErrorMessage());
+ byte[] data = { 0, encode_type_and_meta(Type.ARRAY.ID, 20) };
+ assertFail(data, "decode index too big");
}
@Test public void testDecodeIndexOverflowObject() {
- byte[] data = { 0, encode_type_and_meta(Type.OBJECT.ID, 4) };
- var input = new BufferedInput(data);
- var view = BinaryView.inspectImpl(input);
- assertFalse(view.valid());
- assertTrue(input.failed());
- assertEquals("decode index too big", input.getErrorMessage());
+ byte[] data = { 0, encode_type_and_meta(Type.OBJECT.ID, 20) };
+ assertFail(data, "decode index too big");
}
}
diff --git a/vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java b/vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java
index 223701fa2fd..c34a718d2bb 100644
--- a/vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java
+++ b/vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java
@@ -3,83 +3,236 @@ package com.yahoo.slime;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.fail;
+import static com.yahoo.slime.BinaryView.byte_offset_for_testing;
+import static com.yahoo.slime.BinaryView.first_child_for_testing;
+import static com.yahoo.slime.BinaryView.ext_bits_for_testing;
public class DecodeIndexTest {
+ int checkCapacity(DecodeIndex index, int oldCapacity) {
+ int capacity = index.capacity();
+ if (oldCapacity == -1) {
+ System.out.println("DecodeIndex initial capacity " + capacity);
+ } else if (capacity != oldCapacity) {
+ System.out.println("DecodeIndex capacity increased to " + capacity);
+ }
+ return capacity;
+ }
+
@Test
public void testSimpleUsage() {
- DecodeIndex index = new DecodeIndex();
- int val1 = index.reserve(1);
- int val2 = index.reserve(3);
- int val3 = index.reserve(2);
- assertEquals(0, val1);
+ DecodeIndex index = new DecodeIndex(100, 10);
+ assertEquals(1, index.size());
+ int capacity = checkCapacity(index, -1);
+ int root = 0;
+ capacity = checkCapacity(index, capacity);
+ int val2 = index.tryReserveChildren(3, 1, 15);
+ capacity = checkCapacity(index, capacity);
+ int val3 = index.tryReserveChildren(2, 2, 20);
+ capacity = checkCapacity(index, capacity);
assertEquals(1, val2);
assertEquals(4, val3);
assertEquals(6, index.size());
- index.set(val1 + 0, 0, val2, 0);
+ index.set(root, 0, val2, 0);
index.set(val2 + 0, 100, 0, 1);
index.set(val2 + 1, 200, val3, 2);
index.set(val2 + 2, 300, 0, 3);
index.set(val3 + 0, 400, 0, 0);
index.set(val3 + 1, 500, 0, 0);
for (int i = 0; i < 6; i++) {
- assertEquals(i * 100, index.getByteOffset(i));
+ assertEquals(i * 100, byte_offset_for_testing(index, i));
if (i == 0) {
- assertEquals(1, index.getFirstChild(i));
+ assertEquals(1, first_child_for_testing(index, i));
} else if (i == 2) {
- assertEquals(4, index.getFirstChild(i));
+ assertEquals(4, first_child_for_testing(index, i));
} else {
- assertEquals(0, index.getFirstChild(i));
+ assertEquals(0, first_child_for_testing(index, i));
}
if (i < 4) {
- assertEquals(i, index.getExtBits(i));
+ assertEquals(i, ext_bits_for_testing(index, i));
} else {
- assertEquals(0, index.getExtBits(i));
+ assertEquals(0, ext_bits_for_testing(index, i));
}
}
}
@Test
public void testManyValues() {
- DecodeIndex index = new DecodeIndex();
int outer = 47;
int inner = 73;
- int expectOffset = 0;
+ int symSize = 128;
+ int bytesPerValue = 5;
+ DecodeIndex index = new DecodeIndex(symSize + inner * outer * bytesPerValue, symSize);
+ int capacity = checkCapacity(index, -1);
+ int indexOffset = 1;
+ int binaryOffset = symSize + bytesPerValue;
+ int expectOffset = 1;
for (int i = 0; i < outer; i++) {
- int offset = index.reserve(inner);
+ int offset = index.tryReserveChildren(inner, indexOffset, binaryOffset);
+ capacity = checkCapacity(index, capacity);
assertEquals(expectOffset, offset);
expectOffset += inner;
for (int j = 0; j < inner; j++) {
index.set(offset + j, (i * j), (i + j), (j & 3));
+ ++indexOffset;
+ binaryOffset += bytesPerValue;
}
}
- assertEquals(inner * outer, expectOffset);
- assertEquals(inner * outer, index.size());
+ assertEquals(1 + inner * outer, expectOffset);
+ assertEquals(1 + inner * outer, index.size());
for (int i = 0; i < outer; i++) {
for (int j = 0; j < inner; j++) {
- int offset = i * inner + j;
- assertEquals(i * j, index.getByteOffset(offset));
- assertEquals(i + j, index.getFirstChild(offset));
- assertEquals(j & 3, index.getExtBits(offset));
+ int offset = 1 + i * inner + j;
+ assertEquals(i * j, byte_offset_for_testing(index, offset));
+ assertEquals(i + j, first_child_for_testing(index, offset));
+ assertEquals(j & 3, ext_bits_for_testing(index, offset));
}
}
}
@Test
public void testOverflowNoBleed() {
- DecodeIndex index = new DecodeIndex();
- index.reserve(3);
+ DecodeIndex index = new DecodeIndex(100, 10);
+ index.tryReserveChildren(2, 1, 20);
+ assertEquals(3, index.size());
index.set(0, 0xffff_ffff, 0, 0);
index.set(1, 0, 0xffff_ffff, 0);
index.set(2, 0, 0, 0xffff_ffff);
- assertEquals(0x7fff_ffff, index.getByteOffset(0));
- assertEquals(0, index.getByteOffset(1));
- assertEquals(0, index.getByteOffset(2));
- assertEquals(0, index.getFirstChild(0));
- assertEquals(0x7fff_ffff, index.getFirstChild(1));
- assertEquals(0, index.getFirstChild(2));
- assertEquals(0, index.getExtBits(0));
- assertEquals(0, index.getExtBits(1));
- assertEquals(3, index.getExtBits(2));
+ assertEquals(0x7fff_ffff, byte_offset_for_testing(index, 0));
+ assertEquals(0, byte_offset_for_testing(index, 1));
+ assertEquals(0, byte_offset_for_testing(index, 2));
+ assertEquals(0, first_child_for_testing(index, 0));
+ assertEquals(0x7fff_ffff, first_child_for_testing(index, 1));
+ assertEquals(0, first_child_for_testing(index, 2));
+ assertEquals(0, ext_bits_for_testing(index, 0));
+ assertEquals(0, ext_bits_for_testing(index, 1));
+ assertEquals(3, ext_bits_for_testing(index, 2));
+ }
+
+ @Test
+ public void testMinimalInitialCapacity() {
+ DecodeIndex index = new DecodeIndex(2, 1);
+ assertEquals(16, index.capacity());
+ }
+
+ @Test
+ public void testInitialCapacityEstimate() {
+ DecodeIndex index = new DecodeIndex((33 * 24) + 167, 167);
+ assertEquals(33, index.capacity());
+ }
+
+ void assertWithinRange(int low, int high, int actual) {
+ if (actual >= low && actual <= high) {
+ System.out.println("value " + actual + " in range [" + low + "," + high + "]");
+ } else {
+ fail("value " + actual + " not in range [" + low + "," + high + "]");
+ }
+ }
+
+ void assertGreater(int limit, int actual) {
+ if (actual > limit) {
+ System.out.println("value " + actual + " is greater than " + limit);
+ } else {
+ fail("value " + actual + " is not greater than " + limit);
+ }
+ }
+
+ void assertLess(int limit, int actual) {
+ if (actual < limit) {
+ System.out.println("value " + actual + " is less than " + limit);
+ } else {
+ fail("value " + actual + " is not less than " + limit);
+ }
+ }
+
+ DecodeIndex prepareIndex(int symSize, int numValues) {
+ DecodeIndex index = new DecodeIndex((numValues * 24) + symSize, symSize);
+ assertEquals(1, index.tryReserveChildren(numValues - 1, 1, symSize + 24));
+ assertEquals(numValues, index.size());
+ assertEquals(numValues, index.capacity());
+ return index;
+ }
+
+ @Test
+ public void testDensityBasedCapacityEstimate() {
+ var index = prepareIndex(167, 33);
+ double exp = 1.25 * index.capacity();
+ assertEquals(33, index.tryReserveChildren(10, 20, 167 + (20 * 4)));
+ int doneCnt = 20;
+ double bytesPerObject = 4.0;
+ int pendingData = (33 * 24) - (20 * 4);
+ double est = (doneCnt + pendingData / bytesPerObject);
+ int maxSize = doneCnt + pendingData;
+ assertGreater((int)(exp * 1.05), index.capacity());
+ assertWithinRange((int)(1.05 * est), (int)(1.15 * est), index.capacity());
+ assertLess(maxSize, index.capacity());
+ }
+
+ @Test
+ public void testExpCapacityGrowth() {
+ var index = prepareIndex(167, 33);
+ double exp = 1.25 * index.capacity();
+ assertEquals(33, index.tryReserveChildren(1, 20, 167 + (20 * 32)));
+ int doneCnt = 20;
+ double bytesPerObject = 32.0;
+ int pendingData = (33 * 24) - (20 * 32);
+ double est = (doneCnt + pendingData / bytesPerObject);
+ int maxSize = doneCnt + pendingData;
+ assertWithinRange((int)(0.95 * exp), (int)(1.05 * exp), index.capacity());
+ assertGreater((int)(est * 1.15), index.capacity());
+ assertLess(maxSize, index.capacity());
+ }
+
+ @Test
+ public void testMinCapacityGrowth() {
+ var index = prepareIndex(167, 33);
+ double exp = 1.25 * index.capacity();
+ assertEquals(33, index.tryReserveChildren(20, 20, 167 + (20 * 32)));
+ int doneCnt = 20;
+ double bytesPerObject = 32.0;
+ int pendingData = (33 * 24) - (20 * 32);
+ double est = (doneCnt + pendingData / bytesPerObject);
+ int maxSize = doneCnt + pendingData;
+ assertGreater((int)(exp * 1.05), index.capacity());
+ assertGreater((int)(est * 1.15), index.capacity());
+ assertEquals(33 + 20, index.capacity());
+ assertLess(maxSize, index.capacity());
+ }
+
+ @Test
+ public void testMaxCapacityGrowth() {
+ var index = prepareIndex(167, 33);
+ double exp = 1.25 * index.capacity();
+ assertEquals(33, index.tryReserveChildren(1, 32, 167 + (33 * 24) - 3));
+ int minSize = 33 + 1;
+ int maxSize = 32 + 3;
+ assertLess((int)(exp * 0.95), index.capacity());
+ assertGreater(minSize, index.capacity());
+ assertEquals(maxSize, index.capacity());
+ }
+
+ @Test
+ public void testMinMaxCapacityGrowth() {
+ var index = prepareIndex(167, 33);
+ assertEquals(-1, index.tryReserveChildren(5, 32, 167 + (33 * 24) - 3));
+ assertEquals(33, index.capacity());
+ }
+
+ @Test
+ public void testExpNanCapacityGrowth() {
+ var index = prepareIndex(167, 33);
+ double exp = 1.25 * index.capacity();
+ assertEquals(33, index.tryReserveChildren(1, 0, 167));
+ assertWithinRange((int)(0.95 * exp), (int)(1.05 * exp), index.capacity());
+ }
+
+ @Test
+ public void testMaxInfCapacityGrowth() {
+ var index = prepareIndex(167, 17);
+ double exp = 1.25 * index.capacity();
+ assertEquals(17, index.tryReserveChildren(1, 10, 167));
+ int maxSize = 10 + (17 * 24);
+ assertEquals(maxSize, index.capacity());
}
}
diff --git a/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp b/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
index 44decb9bf91..31113f2b4f2 100644
--- a/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
+++ b/vespalib/src/tests/btree/btree-stress/btree_stress_test.cpp
@@ -53,7 +53,7 @@ public:
~RealIntStore();
EntryRef add(uint32_t value) { return _store.addEntry(value); }
AtomicEntryRef add_relaxed(uint32_t value) { return AtomicEntryRef(add(value)); }
- void hold(const AtomicEntryRef& ref) { _store.holdElem(ref.load_relaxed(), 1); }
+ void hold(const AtomicEntryRef& ref) { _store.hold_entry(ref.load_relaxed()); }
EntryRef move(EntryRef ref);
void assign_generation(generation_t current_gen) { _store.assign_generation(current_gen); }
void reclaim_memory(generation_t gen) { _store.reclaim_memory(gen); }
diff --git a/vespalib/src/tests/btree/btree_test.cpp b/vespalib/src/tests/btree/btree_test.cpp
index b8da9ea6042..afad3523fa3 100644
--- a/vespalib/src/tests/btree/btree_test.cpp
+++ b/vespalib/src/tests/btree/btree_test.cpp
@@ -1065,7 +1065,7 @@ adjustAllocatedBytes(size_t nodeCount, size_t nodeSize)
TEST_F(BTreeTest, require_that_memory_usage_is_calculated)
{
constexpr size_t BASE_ALLOCATED = 28744u;
- constexpr size_t BASE_USED = 24984;
+ constexpr size_t BASE_USED = 24936;
typedef BTreeNodeAllocator<int32_t, int8_t,
btree::NoAggregated,
MyTraits::INTERNAL_SLOTS, MyTraits::LEAF_SLOTS> NodeAllocator;
diff --git a/vespalib/src/tests/datastore/array_store/array_store_test.cpp b/vespalib/src/tests/datastore/array_store/array_store_test.cpp
index ccc3ab88c31..1df03f6eb0a 100644
--- a/vespalib/src/tests/datastore/array_store/array_store_test.cpp
+++ b/vespalib/src/tests/datastore/array_store/array_store_test.cpp
@@ -29,16 +29,16 @@ constexpr float ALLOC_GROW_FACTOR = 0.2;
}
-template <typename TestT, typename EntryT, typename RefT = EntryRefT<19> >
+template <typename TestT, typename ElemT, typename RefT = EntryRefT<19> >
struct ArrayStoreTest : public TestT
{
using EntryRefType = RefT;
- using ArrayStoreType = ArrayStore<EntryT, RefT>;
+ using ArrayStoreType = ArrayStore<ElemT, RefT>;
using LargeArray = typename ArrayStoreType::LargeArray;
using ConstArrayRef = typename ArrayStoreType::ConstArrayRef;
- using EntryVector = std::vector<EntryT>;
- using value_type = EntryT;
- using ReferenceStore = vespalib::hash_map<EntryRef, EntryVector>;
+ using ElemVector = std::vector<ElemT>;
+ using value_type = ElemT;
+ using ReferenceStore = vespalib::hash_map<EntryRef, ElemVector>;
AllocStats stats;
ArrayStoreType store;
@@ -61,11 +61,11 @@ struct ArrayStoreTest : public TestT
add_using_allocate(false)
{}
~ArrayStoreTest() override;
- void assertAdd(const EntryVector &input) {
+ void assertAdd(const ElemVector &input) {
EntryRef ref = add(input);
assertGet(ref, input);
}
- EntryRef add(const EntryVector &input) {
+ EntryRef add(const ElemVector &input) {
EntryRef result;
if (add_using_allocate) {
result = store.allocate(input.size());
@@ -82,16 +82,16 @@ struct ArrayStoreTest : public TestT
refStore.insert(std::make_pair(result, input));
return result;
}
- void assertGet(EntryRef ref, const EntryVector &exp) const {
+ void assertGet(EntryRef ref, const ElemVector &exp) const {
ConstArrayRef act = store.get(ref);
- EXPECT_EQ(exp, EntryVector(act.begin(), act.end()));
+ EXPECT_EQ(exp, ElemVector(act.begin(), act.end()));
}
void remove(EntryRef ref) {
ASSERT_EQ(1u, refStore.count(ref));
store.remove(ref);
refStore.erase(ref);
}
- void remove(const EntryVector &input) {
+ void remove(const ElemVector &input) {
remove(getEntryRef(input));
}
uint32_t getBufferId(EntryRef ref) const {
@@ -99,14 +99,14 @@ struct ArrayStoreTest : public TestT
}
void assertBufferState(EntryRef ref, const MemStats& expStats) {
EXPECT_EQ(expStats._used, store.bufferState(ref).size());
- EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_elems());
- EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_elems());
+ EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_entries());
+ EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_entries());
}
void assert_buffer_stats(EntryRef ref, const TestBufferStats& exp_stats) {
const auto& state = store.bufferState(ref);
EXPECT_EQ(exp_stats._used, state.size());
- EXPECT_EQ(exp_stats._hold, state.stats().hold_elems());
- EXPECT_EQ(exp_stats._dead, state.stats().dead_elems());
+ EXPECT_EQ(exp_stats._hold, state.stats().hold_entries());
+ EXPECT_EQ(exp_stats._dead, state.stats().dead_entries());
EXPECT_EQ(exp_stats._extra_used, state.stats().extra_used_bytes());
EXPECT_EQ(exp_stats._extra_hold, state.stats().extra_hold_bytes());
}
@@ -121,7 +121,7 @@ struct ArrayStoreTest : public TestT
assertGet(elem.first, elem.second);
}
}
- void assert_ref_reused(const EntryVector& first, const EntryVector& second, bool should_reuse) {
+ void assert_ref_reused(const ElemVector& first, const ElemVector& second, bool should_reuse) {
EntryRef ref1 = add(first);
remove(ref1);
reclaim_memory();
@@ -129,7 +129,7 @@ struct ArrayStoreTest : public TestT
EXPECT_EQ(should_reuse, (ref2 == ref1));
assertGet(ref2, second);
}
- EntryRef getEntryRef(const EntryVector &input) {
+ EntryRef getEntryRef(const ElemVector &input) {
for (auto itr = refStore.begin(); itr != refStore.end(); ++itr) {
if (itr->second == input) {
return itr->first;
@@ -160,12 +160,12 @@ struct ArrayStoreTest : public TestT
}
refStore = compactedRefStore;
}
- size_t entrySize() const { return sizeof(EntryT); }
+ size_t elem_size() const { return sizeof(ElemT); }
size_t largeArraySize() const { return sizeof(LargeArray); }
};
-template <typename TestT, typename EntryT, typename RefT>
-ArrayStoreTest<TestT, EntryT, RefT>::~ArrayStoreTest() = default;
+template <typename TestT, typename ElemT, typename RefT>
+ArrayStoreTest<TestT, ElemT, RefT>::~ArrayStoreTest() = default;
struct TestParam {
bool add_using_allocate;
@@ -214,8 +214,8 @@ TEST_P(NumberStoreTest, control_static_sizes) {
EXPECT_EQ(240u + sizeof_deque, sizeof(NumberStoreTest::ArrayStoreType::DataStoreType));
EXPECT_EQ(104u, sizeof(NumberStoreTest::ArrayStoreType::SmallBufferType));
MemoryUsage usage = store.getMemoryUsage();
- EXPECT_EQ(202120u, usage.allocatedBytes());
- EXPECT_EQ(197752u, usage.usedBytes());
+ EXPECT_EQ(202116u, usage.allocatedBytes());
+ EXPECT_EQ(197656u, usage.usedBytes());
}
TEST_P(NumberStoreTest, add_and_get_small_arrays_of_trivial_type)
@@ -246,15 +246,15 @@ TEST_F(StringStoreTest, add_and_get_large_arrays_of_non_trivial_type)
assertAdd({"ddd", "eee", "ffff", "gggg", "hhhh"});
}
-TEST_P(NumberStoreTest, elements_are_put_on_hold_when_a_small_array_is_removed)
+TEST_P(NumberStoreTest, entries_are_put_on_hold_when_a_small_array_is_removed)
{
EntryRef ref = add({1,2,3});
- assertBufferState(ref, MemStats().used(3).hold(0));
+ assertBufferState(ref, MemStats().used(1).hold(0));
store.remove(ref);
- assertBufferState(ref, MemStats().used(3).hold(3));
+ assertBufferState(ref, MemStats().used(1).hold(1));
}
-TEST_P(NumberStoreTest, elements_are_put_on_hold_when_a_large_array_is_removed)
+TEST_P(NumberStoreTest, entries_are_put_on_hold_when_a_large_array_is_removed)
{
EntryRef ref = add({1,2,3,4});
// Note: The first buffer has the first element reserved -> we expect 2 elements used here.
@@ -319,7 +319,7 @@ test_compaction(NumberStoreBasicTest &f)
f.remove(f.add({5,5}));
f.reclaim_memory();
f.assertBufferState(size1Ref, MemStats().used(1).dead(0));
- f.assertBufferState(size2Ref, MemStats().used(4).dead(2));
+ f.assertBufferState(size2Ref, MemStats().used(2).dead(1));
f.assertBufferState(size3Ref, MemStats().used(2).dead(1)); // Note: First element is reserved
uint32_t size1BufferId = f.getBufferId(size1Ref);
uint32_t size2BufferId = f.getBufferId(size2Ref);
@@ -363,8 +363,8 @@ void testCompaction(NumberStoreTest &f, bool compactMemory, bool compactAddressS
f.remove(f.add({7}));
f.reclaim_memory();
f.assertBufferState(size1Ref, MemStats().used(3).dead(2));
- f.assertBufferState(size2Ref, MemStats().used(2).dead(0));
- f.assertBufferState(size3Ref, MemStats().used(6).dead(3));
+ f.assertBufferState(size2Ref, MemStats().used(1).dead(0));
+ f.assertBufferState(size3Ref, MemStats().used(2).dead(1));
uint32_t size1BufferId = f.getBufferId(size1Ref);
uint32_t size2BufferId = f.getBufferId(size2Ref);
uint32_t size3BufferId = f.getBufferId(size3Ref);
@@ -434,22 +434,22 @@ TEST_P(NumberStoreTest, used_onHold_and_dead_memory_usage_is_tracked_for_small_a
{
MemStats exp(store.getMemoryUsage());
add({1,2,3});
- assertMemoryUsage(exp.used(entrySize() * 3));
+ assertMemoryUsage(exp.used(elem_size() * 3));
remove({1,2,3});
- assertMemoryUsage(exp.hold(entrySize() * 3));
+ assertMemoryUsage(exp.hold(elem_size() * 3));
reclaim_memory();
- assertMemoryUsage(exp.holdToDead(entrySize() * 3));
+ assertMemoryUsage(exp.holdToDead(elem_size() * 3));
}
TEST_P(NumberStoreTest, used_onHold_and_dead_memory_usage_is_tracked_for_large_arrays)
{
MemStats exp(store.getMemoryUsage());
add({1,2,3,4});
- assertMemoryUsage(exp.used(largeArraySize() + entrySize() * 4));
+ assertMemoryUsage(exp.used(largeArraySize() + elem_size() * 4));
remove({1,2,3,4});
- assertMemoryUsage(exp.hold(largeArraySize() + entrySize() * 4));
+ assertMemoryUsage(exp.hold(largeArraySize() + elem_size() * 4));
reclaim_memory();
- assertMemoryUsage(exp.decUsed(entrySize() * 4).decHold(largeArraySize() + entrySize() * 4).
+ assertMemoryUsage(exp.decUsed(elem_size() * 4).decHold(largeArraySize() + elem_size() * 4).
dead(largeArraySize()));
}
diff --git a/vespalib/src/tests/datastore/array_store_config/array_store_config_test.cpp b/vespalib/src/tests/datastore/array_store_config/array_store_config_test.cpp
index fcf1acd5cd3..171e7216638 100644
--- a/vespalib/src/tests/datastore/array_store_config/array_store_config_test.cpp
+++ b/vespalib/src/tests/datastore/array_store_config/array_store_config_test.cpp
@@ -22,32 +22,32 @@ struct Fixture
Fixture(uint32_t maxSmallArrayTypeId,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer)
+ size_t min_num_entries_for_new_buffer)
: cfg(ArrayStoreConfig::optimizeForHugePage(maxSmallArrayTypeId,
[](size_t type_id) noexcept { return type_id; },
hugePageSize, smallPageSize,
sizeof(int), EntryRefType::offsetSize(),
- minNumArraysForNewBuffer,
+ min_num_entries_for_new_buffer,
ALLOC_GROW_FACTOR)) { }
- void assertSpec(uint32_t type_id, uint32_t numArraysForNewBuffer) {
+ void assertSpec(uint32_t type_id, uint32_t num_entries_for_new_buffer) {
assertSpec(type_id, AllocSpec(0, EntryRefType::offsetSize(),
- numArraysForNewBuffer, ALLOC_GROW_FACTOR));
+ num_entries_for_new_buffer, ALLOC_GROW_FACTOR));
}
void assertSpec(uint32_t type_id, const AllocSpec &expSpec) {
const auto& actSpec = cfg.spec_for_type_id(type_id);
- EXPECT_EQUAL(expSpec.minArraysInBuffer, actSpec.minArraysInBuffer);
- EXPECT_EQUAL(expSpec.maxArraysInBuffer, actSpec.maxArraysInBuffer);
- EXPECT_EQUAL(expSpec.numArraysForNewBuffer, actSpec.numArraysForNewBuffer);
+ EXPECT_EQUAL(expSpec.min_entries_in_buffer, actSpec.min_entries_in_buffer);
+ EXPECT_EQUAL(expSpec.max_entries_in_buffer, actSpec.max_entries_in_buffer);
+ EXPECT_EQUAL(expSpec.num_entries_for_new_buffer, actSpec.num_entries_for_new_buffer);
EXPECT_EQUAL(expSpec.allocGrowFactor, actSpec.allocGrowFactor);
}
};
AllocSpec
-makeSpec(size_t minArraysInBuffer,
- size_t maxArraysInBuffer,
- size_t numArraysForNewBuffer)
+makeSpec(size_t min_entries_in_buffer,
+ size_t max_entries_in_buffer,
+ size_t num_entries_for_new_buffer)
{
- return AllocSpec(minArraysInBuffer, maxArraysInBuffer, numArraysForNewBuffer, ALLOC_GROW_FACTOR);
+ return AllocSpec(min_entries_in_buffer, max_entries_in_buffer, num_entries_for_new_buffer, ALLOC_GROW_FACTOR);
}
constexpr size_t KB = 1024;
diff --git a/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp b/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp
index 09b2590a5f3..fec8d5949f8 100644
--- a/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp
+++ b/vespalib/src/tests/datastore/buffer_stats/buffer_stats_test.cpp
@@ -9,10 +9,10 @@ using namespace vespalib::datastore;
TEST(BufferStatsTest, buffer_stats_to_memory_stats)
{
InternalBufferStats buf;
- buf.set_alloc_elems(17);
+ buf.set_alloc_entries(17);
buf.pushed_back(7);
- buf.set_dead_elems(5);
- buf.set_hold_elems(3);
+ buf.set_dead_entries(5);
+ buf.set_hold_entries(3);
buf.inc_extra_used_bytes(13);
buf.inc_extra_hold_bytes(11);
@@ -20,10 +20,10 @@ TEST(BufferStatsTest, buffer_stats_to_memory_stats)
constexpr size_t es = 8;
buf.add_to_mem_stats(es, mem);
- EXPECT_EQ(17, mem._allocElems);
- EXPECT_EQ(7, mem._usedElems);
- EXPECT_EQ(5, mem._deadElems);
- EXPECT_EQ(3, mem._holdElems);
+ EXPECT_EQ(17, mem._alloc_entries);
+ EXPECT_EQ(7, mem._used_entries);
+ EXPECT_EQ(5, mem._dead_entries);
+ EXPECT_EQ(3, mem._hold_entries);
EXPECT_EQ(17 * es + 13, mem._allocBytes);
EXPECT_EQ(7 * es + 13, mem._usedBytes);
EXPECT_EQ(5 * es, mem._deadBytes);
diff --git a/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp b/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
index de7d899e68a..9f7535a3676 100644
--- a/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
+++ b/vespalib/src/tests/datastore/buffer_type/buffer_type_test.cpp
@@ -7,40 +7,40 @@ using namespace vespalib::datastore;
using IntBufferType = BufferType<int>;
constexpr uint32_t ARRAYS_SIZE(4);
-constexpr uint32_t MAX_ARRAYS(128);
-constexpr uint32_t NUM_ARRAYS_FOR_NEW_BUFFER(0);
+constexpr uint32_t MAX_ENTRIES(128);
+constexpr uint32_t NUM_ENTRIES_FOR_NEW_BUFFER(0);
struct Setup {
- uint32_t _minArrays;
- std::atomic<ElemCount> _usedElems;
- ElemCount _neededElems;
- std::atomic<ElemCount> _deadElems;
+ uint32_t _min_entries;
+ std::atomic<EntryCount> _used_entries;
+ EntryCount _needed_entries;
+ std::atomic<EntryCount> _dead_entries;
uint32_t _bufferId;
float _allocGrowFactor;
bool _resizing;
Setup()
- : _minArrays(0),
- _usedElems(0),
- _neededElems(0),
- _deadElems(0),
+ : _min_entries(0),
+ _used_entries(0),
+ _needed_entries(0),
+ _dead_entries(0),
_bufferId(1),
_allocGrowFactor(0.5),
_resizing(false)
{}
Setup(const Setup& rhs) noexcept;
- Setup &minArrays(uint32_t value) { _minArrays = value; return *this; }
- Setup &used(size_t value) { _usedElems = value; return *this; }
- Setup &needed(size_t value) { _neededElems = value; return *this; }
- Setup &dead(size_t value) { _deadElems = value; return *this; }
+ Setup &min_entries(uint32_t value) { _min_entries = value; return *this; }
+ Setup &used(size_t value) { _used_entries = value; return *this; }
+ Setup &needed(size_t value) { _needed_entries = value; return *this; }
+ Setup &dead(size_t value) { _dead_entries = value; return *this; }
Setup &bufferId(uint32_t value) { _bufferId = value; return *this; }
Setup &resizing(bool value) { _resizing = value; return *this; }
};
Setup::Setup(const Setup& rhs) noexcept
- : _minArrays(rhs._minArrays),
- _usedElems(rhs._usedElems.load(std::memory_order_relaxed)),
- _neededElems(rhs._neededElems),
- _deadElems(rhs._deadElems.load(std::memory_order_relaxed)),
+ : _min_entries(rhs._min_entries),
+ _used_entries(rhs._used_entries.load(std::memory_order_relaxed)),
+ _needed_entries(rhs._needed_entries),
+ _dead_entries(rhs._dead_entries.load(std::memory_order_relaxed)),
_bufferId(rhs._bufferId),
_allocGrowFactor(rhs._allocGrowFactor),
_resizing(rhs._resizing)
@@ -53,7 +53,7 @@ struct Fixture {
int buffer[ARRAYS_SIZE];
Fixture(const Setup &setup_)
: setups(),
- bufferType(ARRAYS_SIZE, setup_._minArrays, MAX_ARRAYS, NUM_ARRAYS_FOR_NEW_BUFFER, setup_._allocGrowFactor),
+ bufferType(ARRAYS_SIZE, setup_._min_entries, MAX_ENTRIES, NUM_ENTRIES_FOR_NEW_BUFFER, setup_._allocGrowFactor),
buffer()
{
setups.reserve(4);
@@ -61,121 +61,121 @@ struct Fixture {
}
~Fixture() {
for (auto& setup : setups) {
- bufferType.onHold(setup._bufferId, &setup._usedElems, &setup._deadElems);
- bufferType.onFree(setup._usedElems);
+ bufferType.on_hold(setup._bufferId, &setup._used_entries, &setup._dead_entries);
+ bufferType.on_free(setup._used_entries);
}
}
Setup& curr_setup() {
return setups.back();
}
void add_setup(const Setup& setup_in) {
- // The buffer type stores pointers to ElemCount (from Setup) and we must ensure these do not move in memory.
+ // The buffer type stores pointers to EntryCount (from Setup) and we must ensure these do not move in memory.
assert(setups.size() < setups.capacity());
setups.push_back(setup_in);
}
void onActive() {
- bufferType.onActive(curr_setup()._bufferId, &curr_setup()._usedElems, &curr_setup()._deadElems, &buffer[0]);
+ bufferType.on_active(curr_setup()._bufferId, &curr_setup()._used_entries, &curr_setup()._dead_entries, &buffer[0]);
}
- size_t arraysToAlloc() {
- return bufferType.calcArraysToAlloc(curr_setup()._bufferId, curr_setup()._neededElems, curr_setup()._resizing);
+ size_t entries_to_alloc() {
+ return bufferType.calc_entries_to_alloc(curr_setup()._bufferId, curr_setup()._needed_entries, curr_setup()._resizing);
}
- void assertArraysToAlloc(size_t exp) {
+ void assert_entries_to_alloc(size_t exp) {
onActive();
- EXPECT_EQUAL(exp, arraysToAlloc());
+ EXPECT_EQUAL(exp, entries_to_alloc());
}
};
void
-assertArraysToAlloc(size_t exp, const Setup &setup)
+assert_entries_to_alloc(size_t exp, const Setup &setup)
{
Fixture f(setup);
- f.assertArraysToAlloc(exp);
+ f.assert_entries_to_alloc(exp);
}
-TEST("require that complete arrays are allocated")
+TEST("require that entries are allocated")
{
- TEST_DO(assertArraysToAlloc(1, Setup().needed(1)));
- TEST_DO(assertArraysToAlloc(1, Setup().needed(2)));
- TEST_DO(assertArraysToAlloc(1, Setup().needed(3)));
- TEST_DO(assertArraysToAlloc(1, Setup().needed(4)));
- TEST_DO(assertArraysToAlloc(2, Setup().needed(5)));
+ TEST_DO(assert_entries_to_alloc(1, Setup().needed(1)));
+ TEST_DO(assert_entries_to_alloc(2, Setup().needed(2)));
+ TEST_DO(assert_entries_to_alloc(3, Setup().needed(3)));
+ TEST_DO(assert_entries_to_alloc(4, Setup().needed(4)));
+ TEST_DO(assert_entries_to_alloc(5, Setup().needed(5)));
}
-TEST("require that reserved elements are taken into account when not resizing")
+TEST("require that reserved entries are taken into account when not resizing")
{
- TEST_DO(assertArraysToAlloc(2, Setup().needed(1).bufferId(0)));
- TEST_DO(assertArraysToAlloc(2, Setup().needed(4).bufferId(0)));
- TEST_DO(assertArraysToAlloc(3, Setup().needed(5).bufferId(0)));
+ TEST_DO(assert_entries_to_alloc(2, Setup().needed(1).bufferId(0)));
+ TEST_DO(assert_entries_to_alloc(5, Setup().needed(4).bufferId(0)));
+ TEST_DO(assert_entries_to_alloc(6, Setup().needed(5).bufferId(0)));
}
-TEST("require that arrays to alloc is based on currently used elements (no resizing)")
+TEST("require that entries to alloc is based on currently used entries (no resizing)")
{
- TEST_DO(assertArraysToAlloc(2, Setup().used(4 * 4).needed(4)));
- TEST_DO(assertArraysToAlloc(4, Setup().used(8 * 4).needed(4)));
+ TEST_DO(assert_entries_to_alloc(2, Setup().used(4).needed(1)));
+ TEST_DO(assert_entries_to_alloc(4, Setup().used(8).needed(1)));
}
-TEST("require that arrays to alloc is based on currently used elements (with resizing)")
+TEST("require that entries to alloc is based on currently used entries (with resizing)")
{
- TEST_DO(assertArraysToAlloc(4 + 2, Setup().used(4 * 4).needed(4).resizing(true)));
- TEST_DO(assertArraysToAlloc(8 + 4, Setup().used(8 * 4).needed(4).resizing(true)));
- TEST_DO(assertArraysToAlloc(4 + 3, Setup().used(4 * 4).needed(3 * 4).resizing(true)));
+ TEST_DO(assert_entries_to_alloc(4 + 2, Setup().used(4).needed(1).resizing(true)));
+ TEST_DO(assert_entries_to_alloc(8 + 4, Setup().used(8).needed(1).resizing(true)));
+ TEST_DO(assert_entries_to_alloc(4 + 3, Setup().used(4).needed(3).resizing(true)));
}
-TEST("require that arrays to alloc always contain elements needed")
+TEST("require that entries to alloc always contain entries needed")
{
- TEST_DO(assertArraysToAlloc(2, Setup().used(4 * 4).needed(2 * 4)));
- TEST_DO(assertArraysToAlloc(3, Setup().used(4 * 4).needed(3 * 4)));
- TEST_DO(assertArraysToAlloc(4, Setup().used(4 * 4).needed(4 * 4)));
+ TEST_DO(assert_entries_to_alloc(2, Setup().used(4).needed(2)));
+ TEST_DO(assert_entries_to_alloc(3, Setup().used(4).needed(3)));
+ TEST_DO(assert_entries_to_alloc(4, Setup().used(4).needed(4)));
}
-TEST("require that arrays to alloc is capped to max arrays")
+TEST("require that entries to alloc is capped to max entries")
{
- TEST_DO(assertArraysToAlloc(127, Setup().used(254 * 4).needed(4)));
- TEST_DO(assertArraysToAlloc(128, Setup().used(256 * 4).needed(4)));
- TEST_DO(assertArraysToAlloc(128, Setup().used(258 * 4).needed(8)));
+ TEST_DO(assert_entries_to_alloc(127, Setup().used(254).needed(1)));
+ TEST_DO(assert_entries_to_alloc(128, Setup().used(256).needed(1)));
+ TEST_DO(assert_entries_to_alloc(128, Setup().used(258).needed(2)));
}
TEST("require that arrays to alloc is capped to min arrays")
{
- TEST_DO(assertArraysToAlloc(16, Setup().used(30 * 4).needed(4).minArrays(16)));
- TEST_DO(assertArraysToAlloc(16, Setup().used(32 * 4).needed(4).minArrays(16)));
- TEST_DO(assertArraysToAlloc(17, Setup().used(34 * 4).needed(4).minArrays(16)));
+ TEST_DO(assert_entries_to_alloc(16, Setup().used(30).needed(1).min_entries(16)));
+ TEST_DO(assert_entries_to_alloc(16, Setup().used(32).needed(1).min_entries(16)));
+ TEST_DO(assert_entries_to_alloc(17, Setup().used(34).needed(1).min_entries(16)));
}
-TEST("arrays to alloc considers used elements across all active buffers of same type (no resizing)")
+TEST("entries to alloc considers used entries across all active buffers of same type (no resizing)")
{
- Fixture f(Setup().used(6 * 4));
- f.assertArraysToAlloc(6 * 0.5);
- f.add_setup(Setup().used(8 * 4).bufferId(2));
- f.assertArraysToAlloc((6 + 8) * 0.5);
- f.add_setup(Setup().used(10 * 4).bufferId(3));
- f.assertArraysToAlloc((6 + 8 + 10) * 0.5);
+ Fixture f(Setup().used(6));
+ f.assert_entries_to_alloc(6 * 0.5);
+ f.add_setup(Setup().used(8).bufferId(2));
+ f.assert_entries_to_alloc((6 + 8) * 0.5);
+ f.add_setup(Setup().used(10).bufferId(3));
+ f.assert_entries_to_alloc((6 + 8 + 10) * 0.5);
}
-TEST("arrays to alloc considers used elements across all active buffers of same type when resizing")
+TEST("entries to alloc considers used entries across all active buffers of same type when resizing")
{
- Fixture f(Setup().used(6 * 4));
- f.assertArraysToAlloc(6 * 0.5);
- f.add_setup(Setup().used(8 * 4).resizing(true).bufferId(2));
- f.assertArraysToAlloc(8 + (6 + 8) * 0.5);
+ Fixture f(Setup().used(6));
+ f.assert_entries_to_alloc(6 * 0.5);
+ f.add_setup(Setup().used(8).resizing(true).bufferId(2));
+ f.assert_entries_to_alloc(8 + (6 + 8) * 0.5);
}
-TEST("arrays to alloc considers (and subtracts) dead elements across all active buffers of same type (no resizing)")
+TEST("entries to alloc considers (and subtracts) dead entries across all active buffers of same type (no resizing)")
{
- Fixture f(Setup().used(6 * 4).dead(2 * 4));
- f.assertArraysToAlloc((6 - 2) * 0.5);
- f.add_setup(Setup().used(12 * 4).dead(4 * 4).bufferId(2));
- f.assertArraysToAlloc((6 - 2 + 12 - 4) * 0.5);
- f.add_setup(Setup().used(20 * 4).dead(6 * 4).bufferId(3));
- f.assertArraysToAlloc((6 - 2 + 12 - 4 + 20 - 6) * 0.5);
+ Fixture f(Setup().used(6).dead(2));
+ f.assert_entries_to_alloc((6 - 2) * 0.5);
+ f.add_setup(Setup().used(12).dead(4).bufferId(2));
+ f.assert_entries_to_alloc((6 - 2 + 12 - 4) * 0.5);
+ f.add_setup(Setup().used(20).dead(6).bufferId(3));
+ f.assert_entries_to_alloc((6 - 2 + 12 - 4 + 20 - 6) * 0.5);
}
TEST("arrays to alloc considers (and subtracts) dead elements across all active buffers of same type when resizing")
{
- Fixture f(Setup().used(6 * 4).dead(2 * 4));
- f.assertArraysToAlloc((6 - 2) * 0.5);
- f.add_setup(Setup().used(12 * 4).dead(4 * 4).resizing(true).bufferId(2));
- f.assertArraysToAlloc(12 + (6 - 2 + 12 - 4) * 0.5);
+ Fixture f(Setup().used(6).dead(2));
+ f.assert_entries_to_alloc((6 - 2) * 0.5);
+ f.add_setup(Setup().used(12).dead(4).resizing(true).bufferId(2));
+ f.assert_entries_to_alloc(12 + (6 - 2 + 12 - 4) * 0.5);
}
TEST_MAIN() { TEST_RUN_ALL(); }
diff --git a/vespalib/src/tests/datastore/datastore/datastore_test.cpp b/vespalib/src/tests/datastore/datastore/datastore_test.cpp
index df347267c7e..9e27ed37dd3 100644
--- a/vespalib/src/tests/datastore/datastore/datastore_test.cpp
+++ b/vespalib/src/tests/datastore/datastore/datastore_test.cpp
@@ -26,8 +26,8 @@ public:
void holdBuffer(uint32_t bufferId) {
ParentType::holdBuffer(bufferId);
}
- void holdElem(EntryRef ref, uint64_t len) {
- ParentType::holdElem(ref, len);
+ void hold_entry(EntryRef ref) {
+ ParentType::hold_entry(ref);
}
void assign_generation(generation_t current_gen) {
ParentType::assign_generation(current_gen);
@@ -35,8 +35,8 @@ public:
void reclaim_entry_refs(generation_t oldest_used_gen) override {
ParentType::reclaim_entry_refs(oldest_used_gen);
}
- void ensureBufferCapacity(size_t sizeNeeded) {
- ParentType::ensureBufferCapacity(0, sizeNeeded);
+ void ensure_buffer_capacity(size_t entries_needed) {
+ ParentType::ensure_buffer_capacity(0, entries_needed);
}
void enableFreeLists() {
ParentType::enableFreeLists();
@@ -66,10 +66,10 @@ class GrowStore
BufferType<DataType> _type;
uint32_t _typeId;
public:
- GrowStore(size_t arraySize, size_t minArrays, size_t maxArrays, size_t numArraysForNewBuffer)
+ GrowStore(size_t arraySize, size_t min_entries, size_t max_entries, size_t num_entries_for_new_buffer)
: _store(),
- _firstType(1, 1, maxArrays, 0, ALLOC_GROW_FACTOR),
- _type(arraySize, minArrays, maxArrays, numArraysForNewBuffer, ALLOC_GROW_FACTOR),
+ _firstType(1, 1, max_entries, 0, ALLOC_GROW_FACTOR),
+ _type(arraySize, min_entries, max_entries, num_entries_for_new_buffer, ALLOC_GROW_FACTOR),
_typeId(0)
{
(void) _store.addType(&_firstType);
@@ -87,7 +87,7 @@ public:
while (sizes.size() < bufs) {
RefType iRef = (_type.getArraySize() == 1) ?
(_store.template allocator<DataType>(_typeId).alloc().ref) :
- (_store.template allocator<DataType>(_typeId).allocArray(_type.getArraySize()).ref);
+ (_store.template allocator<DataType>(_typeId).allocArray().ref);
int bufferId = iRef.bufferId();
if (bufferId != prevBufferId) {
if (prevBufferId >= 0) {
@@ -126,7 +126,7 @@ public:
while (buffers.size() < bufs) {
RefType iRef = (_type.getArraySize() == 1) ?
(_store.template allocator<DataType>(_typeId).alloc().ref) :
- (_store.template allocator<DataType>(_typeId).allocArray(_type.getArraySize()).ref);
+ (_store.template allocator<DataType>(_typeId).allocArray().ref);
int buffer_id = iRef.bufferId();
if (buffers.empty() || buffers.back() != buffer_id) {
buffers.push_back(buffer_id);
@@ -143,10 +143,10 @@ void
assertMemStats(const MemoryStats &exp,
const MemoryStats &act)
{
- EXPECT_EQ(exp._allocElems, act._allocElems);
- EXPECT_EQ(exp._usedElems, act._usedElems);
- EXPECT_EQ(exp._deadElems, act._deadElems);
- EXPECT_EQ(exp._holdElems, act._holdElems);
+ EXPECT_EQ(exp._alloc_entries, act._alloc_entries);
+ EXPECT_EQ(exp._used_entries, act._used_entries);
+ EXPECT_EQ(exp._dead_entries, act._dead_entries);
+ EXPECT_EQ(exp._hold_entries, act._hold_entries);
EXPECT_EQ(exp._freeBuffers, act._freeBuffers);
EXPECT_EQ(exp._activeBuffers, act._activeBuffers);
EXPECT_EQ(exp._holdBuffers, act._holdBuffers);
@@ -304,13 +304,13 @@ TEST(DataStoreTest, require_that_we_can_hold_and_trim_elements)
{
MyStore s;
MyRef r1 = s.addEntry(1);
- s.holdElem(r1, 1);
+ s.hold_entry(r1);
s.assign_generation(10);
MyRef r2 = s.addEntry(2);
- s.holdElem(r2, 1);
+ s.hold_entry(r2);
s.assign_generation(20);
MyRef r3 = s.addEntry(3);
- s.holdElem(r3, 1);
+ s.hold_entry(r3);
s.assign_generation(30);
EXPECT_EQ(1, s.getEntry(r1));
EXPECT_EQ(2, s.getEntry(r2));
@@ -358,11 +358,11 @@ TEST(DataStoreTest, require_that_we_can_use_free_lists)
MyStore s;
s.enableFreeLists();
auto r1 = s.addEntry(1);
- s.holdElem(r1, 1);
+ s.hold_entry(r1);
s.assign_generation(10);
auto r2 = s.addEntry(2);
expect_successive_refs(r1, r2);
- s.holdElem(r2, 1);
+ s.hold_entry(r2);
s.assign_generation(20);
s.reclaim_entry_refs(11);
auto r3 = s.addEntry(3); // reuse r1
@@ -389,21 +389,21 @@ TEST(DataStoreTest, require_that_we_can_use_free_lists_with_raw_allocator)
s.enableFreeLists();
auto allocator = s.freeListRawAllocator<int>(grow_store.typeId());
- auto h1 = allocator.alloc(3);
- auto h2 = allocator.alloc(3);
+ auto h1 = allocator.alloc(1);
+ auto h2 = allocator.alloc(1);
expect_successive_handles(h1, h2);
- s.holdElem(h1.ref, 3);
- s.holdElem(h2.ref, 3);
+ s.hold_entry(h1.ref);
+ s.hold_entry(h2.ref);
s.assign_generation(10);
s.reclaim_entry_refs(11);
- auto h3 = allocator.alloc(3); // reuse h2.ref from free list
+ auto h3 = allocator.alloc(1); // reuse h2.ref from free list
EXPECT_EQ(h2, h3);
- auto h4 = allocator.alloc(3); // reuse h1.ref from free list
+ auto h4 = allocator.alloc(1); // reuse h1.ref from free list
EXPECT_EQ(h1, h4);
- auto h5 = allocator.alloc(3);
+ auto h5 = allocator.alloc(1);
expect_successive_handles(h2, h5);
expect_successive_handles(h3, h5);
}
@@ -412,10 +412,10 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
{
MyStore s;
MemoryStats m;
- m._allocElems = MyRef::offsetSize();
- m._usedElems = 1; // ref = 0 is reserved
- m._deadElems = 1; // ref = 0 is reserved
- m._holdElems = 0;
+ m._alloc_entries = MyRef::offsetSize();
+ m._used_entries = 1; // ref = 0 is reserved
+ m._dead_entries = 1; // ref = 0 is reserved
+ m._hold_entries = 0;
m._activeBuffers = 1;
m._freeBuffers = MyRef::numBuffers() - 1;
m._holdBuffers = 0;
@@ -423,7 +423,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
// add entry
MyRef r = s.addEntry(10);
- m._usedElems++;
+ m._used_entries++;
assertMemStats(m, s.getMemStats());
// hold buffer
@@ -431,9 +431,9 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
s.addEntry(30);
s.holdBuffer(r.bufferId());
s.assign_generation(100);
- m._usedElems += 2;
- m._holdElems = m._usedElems;
- m._deadElems = 0;
+ m._used_entries += 2;
+ m._hold_entries = m._used_entries;
+ m._dead_entries = 0;
m._activeBuffers--;
m._holdBuffers++;
assertMemStats(m, s.getMemStats());
@@ -441,17 +441,17 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
// new active buffer
s.switch_primary_buffer();
s.addEntry(40);
- m._allocElems += MyRef::offsetSize();
- m._usedElems++;
+ m._alloc_entries += MyRef::offsetSize();
+ m._used_entries++;
m._activeBuffers++;
m._freeBuffers--;
// trim hold buffer
s.reclaim_memory(101);
- m._allocElems -= MyRef::offsetSize();
- m._usedElems = 1;
- m._deadElems = 0;
- m._holdElems = 0;
+ m._alloc_entries -= MyRef::offsetSize();
+ m._used_entries = 1;
+ m._dead_entries = 0;
+ m._hold_entries = 0;
m._freeBuffers = MyRef::numBuffers() - 1;
m._holdBuffers = 0;
assertMemStats(m, s.getMemStats());
@@ -466,7 +466,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
{ // increase extra hold bytes
auto prev_stats = s.getMemStats();
- s.get_active_buffer_state().hold_elems(0, 30);
+ s.get_active_buffer_state().hold_entries(0, 30);
auto curr_stats = s.getMemStats();
EXPECT_EQ(prev_stats._holdBytes + 30, curr_stats._holdBytes);
}
@@ -475,7 +475,7 @@ TEST(DataStoreTest, require_that_memory_stats_are_calculated)
TEST(DataStoreTest, require_that_memory_usage_is_calculated)
{
constexpr size_t BASE_ALLOCATED = 4228;
- constexpr size_t BASE_USED = 308;
+ constexpr size_t BASE_USED = 284;
MyStore s;
MyRef r = s.addEntry(10);
s.addEntry(20);
@@ -494,7 +494,7 @@ TEST(DataStoreTest, require_that_memory_usage_is_calculated)
TEST(DataStoreTest, require_that_we_can_disable_elemement_hold_list)
{
constexpr size_t BASE_ALLOCATED = 4228;
- constexpr size_t BASE_USED = 308;
+ constexpr size_t BASE_USED = 284;
MyStore s;
MyRef r1 = s.addEntry(10);
MyRef r2 = s.addEntry(20);
@@ -505,14 +505,14 @@ TEST(DataStoreTest, require_that_we_can_disable_elemement_hold_list)
EXPECT_EQ(4 * sizeof(int) + BASE_USED, m.usedBytes());
EXPECT_EQ(1 * sizeof(int), m.deadBytes());
EXPECT_EQ(0 * sizeof(int), m.allocatedBytesOnHold());
- s.holdElem(r1, 1);
+ s.hold_entry(r1);
m = s.getMemoryUsage();
EXPECT_EQ(MyRef::offsetSize() * sizeof(int) + BASE_ALLOCATED, m.allocatedBytes());
EXPECT_EQ(4 * sizeof(int) + BASE_USED, m.usedBytes());
EXPECT_EQ(1 * sizeof(int), m.deadBytes());
EXPECT_EQ(1 * sizeof(int), m.allocatedBytesOnHold());
- s.disableElemHoldList();
- s.holdElem(r2, 1);
+ s.disable_entry_hold_list();
+ s.hold_entry(r2);
m = s.getMemoryUsage();
EXPECT_EQ(MyRef::offsetSize() * sizeof(int) + BASE_ALLOCATED, m.allocatedBytes());
EXPECT_EQ(4 * sizeof(int) + BASE_USED, m.usedBytes());
@@ -529,11 +529,11 @@ namespace {
void assertGrowStats(GrowthStats expSizes,
GrowthStats expFirstBufSizes,
size_t expInitMemUsage,
- size_t minArrays, size_t numArraysForNewBuffer, size_t maxArrays = 128)
+ size_t min_entries, size_t num_entries_for_new_buffer, size_t max_entries = 128)
{
- EXPECT_EQ(expSizes, IntGrowStore(1, minArrays, maxArrays, numArraysForNewBuffer).getGrowthStats(expSizes.size()));
- EXPECT_EQ(expFirstBufSizes, IntGrowStore(1, minArrays, maxArrays, numArraysForNewBuffer).getFirstBufGrowStats());
- EXPECT_EQ(expInitMemUsage, IntGrowStore(1, minArrays, maxArrays, numArraysForNewBuffer).getMemoryUsage().allocatedBytes());
+ EXPECT_EQ(expSizes, IntGrowStore(1, min_entries, max_entries, num_entries_for_new_buffer).getGrowthStats(expSizes.size()));
+ EXPECT_EQ(expFirstBufSizes, IntGrowStore(1, min_entries, max_entries, num_entries_for_new_buffer).getFirstBufGrowStats());
+ EXPECT_EQ(expInitMemUsage, IntGrowStore(1, min_entries, max_entries, num_entries_for_new_buffer).getMemoryUsage().allocatedBytes());
}
}
@@ -574,10 +574,10 @@ namespace {
template <typename DataType>
void assertGrowStats(GrowthStats expSizes, uint32_t arraySize)
{
- uint32_t minArrays = 2048;
- uint32_t maxArrays = RefType15::offsetSize();
- uint32_t numArraysForNewBuffer = 2048;
- GrowStore<DataType, RefType15> store(arraySize, minArrays, maxArrays, numArraysForNewBuffer);
+ uint32_t min_entries = 2048;
+ uint32_t max_entries = RefType15::offsetSize();
+ uint32_t num_entries_for_new_buffer = 2048;
+ GrowStore<DataType, RefType15> store(arraySize, min_entries, max_entries, num_entries_for_new_buffer);
EXPECT_EQ(expSizes, store.getGrowthStats(expSizes.size()));
}
@@ -594,14 +594,14 @@ TEST(DataStoreTest, require_that_offset_in_EntryRefT_is_within_bounds_when_alloc
* 3) Round up bytes to alloc to match the underlying allocator (power of 2 if less than huge page size):
* After this we might end up with more bytes than the offset in EntryRef can handle. In this case this is 32768.
* 4) Cap bytes to alloc to the max offset EntryRef can handle.
- * The max bytes to alloc is: maxArrays * arraySize * elementSize.
+ * The max bytes to alloc is: max_entries * arraySize * elementSize.
*/
- assertGrowStats<uint8_t>({8192,16384,16384,65536,65536,98304,98304,98304,98304,98304,98304,98304}, 3);
- assertGrowStats<uint8_t>({16384,16384,65536,65536,131072,131072,163840,163840,163840,163840,163840,163840}, 5);
- assertGrowStats<uint8_t>({16384,32768,32768,131072,131072,229376,229376,229376,229376,229376,229376,229376}, 7);
- assertGrowStats<uint32_t>({8192,16384,16384,65536,65536,98304,98304,98304,98304,98304,98304,98304}, 3);
- assertGrowStats<uint32_t>({16384,16384,65536,65536,131072,131072,163840,163840,163840,163840,163840,163840}, 5);
- assertGrowStats<uint32_t>({16384,32768,32768,131072,131072,229376,229376,229376,229376,229376,229376,229376}, 7);
+ assertGrowStats<uint8_t>({2730,5461,5461,21845,21845,32768,32768,32768,32768,32768,32768,32768}, 3);
+ assertGrowStats<uint8_t>({3276,3276,13107,13107,26214,26214,32768,32768,32768,32768,32768,32768}, 5);
+ assertGrowStats<uint8_t>({2340,4681,4681,18724,18724,32768,32768,32768,32768,32768,32768,32768}, 7);
+ assertGrowStats<uint32_t>({2730,5461,5461,21845,21845,32768,32768,32768,32768,32768,32768,32768}, 3);
+ assertGrowStats<uint32_t>({3276,3276,13107,13107,26214,26214,32768,32768,32768,32768,32768,32768}, 5);
+ assertGrowStats<uint32_t>({2340,4681,4681,18724,18724,32768,32768,32768,32768,32768,32768,32768}, 7);
}
namespace {
@@ -667,9 +667,9 @@ TEST(DataStoreTest, can_reuse_active_buffer_as_primary_buffer)
TEST(DataStoreTest, control_static_sizes) {
EXPECT_EQ(88, sizeof(BufferTypeBase));
EXPECT_EQ(24, sizeof(FreeList));
- EXPECT_EQ(56, sizeof(BufferFreeList));
+ EXPECT_EQ(48, sizeof(BufferFreeList));
EXPECT_EQ(1, sizeof(BufferState::State));
- EXPECT_EQ(144, sizeof(BufferState));
+ EXPECT_EQ(120, sizeof(BufferState));
BufferState bs;
EXPECT_EQ(0, bs.size());
}
@@ -685,11 +685,11 @@ void test_free_element_to_held_buffer(bool before_hold_buffer)
EXPECT_EQ(1u, s.primary_buffer_id());
if (before_hold_buffer) {
- s.holdElem(ref, 1);
+ s.hold_entry(ref);
}
s.holdBuffer(0); // hold last buffer
if (!before_hold_buffer) {
- ASSERT_DEATH({ s.holdElem(ref, 1); }, "isActive\\(\\)");
+ ASSERT_DEATH({ s.hold_entry(ref); }, "isActive\\(\\)");
}
s.assign_generation(100);
s.reclaim_memory(101);
diff --git a/vespalib/src/tests/datastore/free_list/free_list_test.cpp b/vespalib/src/tests/datastore/free_list/free_list_test.cpp
index 44e11b2316b..ec14d0dd28c 100644
--- a/vespalib/src/tests/datastore/free_list/free_list_test.cpp
+++ b/vespalib/src/tests/datastore/free_list/free_list_test.cpp
@@ -8,20 +8,18 @@
using namespace vespalib::datastore;
using MyEntryRef = EntryRefT<8, 4>;
-constexpr uint32_t array_size = 6;
struct FreeListTest : public testing::Test
{
FreeList list;
- std::atomic<ElemCount> dead_elems;
+ std::atomic<EntryCount> dead_entries;
std::vector<BufferFreeList> bufs;
FreeListTest()
: list(),
bufs()
{
for (size_t i = 0; i < 3; ++i) {
- bufs.emplace_back(dead_elems);
- bufs.back().set_array_size(array_size);
+ bufs.emplace_back(dead_entries);
}
}
void TearDown() override {
@@ -126,13 +124,13 @@ TEST_F(FreeListTest, buffer_free_list_can_be_disabled_and_detached_when_not_curr
EXPECT_TRUE(list.empty());
}
-TEST_F(FreeListTest, dead_elems_count_is_updated_when_popping_an_entry)
+TEST_F(FreeListTest, dead_entries_count_is_updated_when_popping_an_entry)
{
enable(0);
push_entry({10, 0});
- dead_elems.store(18, std::memory_order_relaxed);
+ dead_entries.store(18, std::memory_order_relaxed);
pop_entry();
- EXPECT_EQ(18 - array_size, dead_elems.load(std::memory_order_relaxed));
+ EXPECT_EQ(17, dead_entries.load(std::memory_order_relaxed));
}
GTEST_MAIN_RUN_ALL_TESTS()
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 5ccf9a8908c..a09a7213bf5 100644
--- a/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store/unique_store_test.cpp
@@ -96,8 +96,8 @@ struct TestBase : public ::testing::Test {
}
void assertBufferState(EntryRef ref, const TestBufferStats expStats) const {
EXPECT_EQ(expStats._used, store.bufferState(ref).size());
- EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_elems());
- EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_elems());
+ EXPECT_EQ(expStats._hold, store.bufferState(ref).stats().hold_entries());
+ EXPECT_EQ(expStats._dead, store.bufferState(ref).stats().dead_entries());
}
void assertStoreContent() const {
for (const auto &elem : refStore) {
@@ -147,10 +147,7 @@ struct TestBase : public ::testing::Test {
auto getBuilder(uint32_t uniqueValuesHint) { return store.getBuilder(uniqueValuesHint); }
auto getEnumerator(bool sort_unique_values) { return store.getEnumerator(sort_unique_values); }
size_t get_reserved(EntryRef ref) {
- return store.bufferState(ref).getTypeHandler()->getReservedElements(getBufferId(ref));
- }
- size_t get_array_size(EntryRef ref) {
- return store.bufferState(ref).getArraySize();
+ return store.bufferState(ref).getTypeHandler()->get_reserved_entries(getBufferId(ref));
}
};
@@ -309,29 +306,27 @@ TYPED_TEST(TestBase, can_add_and_get_values)
}
}
-TYPED_TEST(TestBase, elements_are_put_on_hold_when_value_is_removed)
+TYPED_TEST(TestBase, entries_are_put_on_hold_when_value_is_removed)
{
EntryRef ref = this->add(this->values()[0]);
size_t reserved = this->get_reserved(ref);
- size_t array_size = this->get_array_size(ref);
- this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(1 + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(1 + reserved).hold(1).dead(reserved));
}
-TYPED_TEST(TestBase, elements_are_reference_counted)
+TYPED_TEST(TestBase, entries_are_reference_counted)
{
EntryRef ref = this->add(this->values()[0]);
EntryRef ref2 = this->add(this->values()[0]);
EXPECT_EQ(ref.ref(), ref2.ref());
- // Note: The first buffer have the first element reserved -> we expect 2 elements used here.
+ // Note: The first buffer have the first entry reserved -> we expect 2 entries used here.
size_t reserved = this->get_reserved(ref);
- size_t array_size = this->get_array_size(ref);
- this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(1 + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(0).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(1 + reserved).hold(0).dead(reserved));
this->store.remove(ref);
- this->assertBufferState(ref, TestBufferStats().used(array_size + reserved).hold(array_size).dead(reserved));
+ this->assertBufferState(ref, TestBufferStats().used(1 + reserved).hold(1).dead(reserved));
}
TEST_F(SmallOffsetNumberTest, new_underlying_buffer_is_allocated_when_current_is_full)
@@ -360,8 +355,7 @@ TYPED_TEST(TestBase, store_can_be_compacted)
this->remove(this->add(this->values()[2]));
this->reclaim_memory();
size_t reserved = this->get_reserved(val0Ref);
- size_t array_size = this->get_array_size(val0Ref);
- this->assertBufferState(val0Ref, TestBufferStats().used(reserved + 3 * array_size).dead(reserved + array_size));
+ this->assertBufferState(val0Ref, TestBufferStats().used(reserved + 3).dead(reserved + 1));
uint32_t val1BufferId = this->getBufferId(val0Ref);
EXPECT_EQ(2u, this->refStore.size());
@@ -389,8 +383,7 @@ TYPED_TEST(TestBase, store_can_be_instantiated_with_builder)
EntryRef val0Ref = builder.mapEnumValueToEntryRef(1);
EntryRef val1Ref = builder.mapEnumValueToEntryRef(2);
size_t reserved = this->get_reserved(val0Ref);
- size_t array_size = this->get_array_size(val0Ref);
- this->assertBufferState(val0Ref, TestBufferStats().used(2 * array_size + reserved).dead(reserved)); // Note: First element is reserved
+ this->assertBufferState(val0Ref, TestBufferStats().used(2 + reserved).dead(reserved)); // Note: First entry is reserved
EXPECT_TRUE(val0Ref.valid());
EXPECT_TRUE(val1Ref.valid());
EXPECT_NE(val0Ref.ref(), val1Ref.ref());
@@ -472,13 +465,13 @@ TEST_F(DoubleTest, nan_is_handled)
TEST_F(DoubleTest, control_memory_usage) {
static constexpr size_t sizeof_deque = vespalib::datastore::DataStoreBase::sizeof_entry_ref_hold_list_deque;
EXPECT_EQ(368u + sizeof_deque, sizeof(store));
- EXPECT_EQ(144u, sizeof(BufferState));
+ EXPECT_EQ(120u, sizeof(BufferState));
EXPECT_EQ(28740u, store.get_values_memory_usage().allocatedBytes());
- EXPECT_EQ(24804u, store.get_values_memory_usage().usedBytes());
+ EXPECT_EQ(24780u, store.get_values_memory_usage().usedBytes());
EXPECT_EQ(126952u, store.get_dictionary_memory_usage().allocatedBytes());
- EXPECT_EQ(25248u, store.get_dictionary_memory_usage().usedBytes());
+ EXPECT_EQ(25200u, store.get_dictionary_memory_usage().usedBytes());
EXPECT_EQ(155692u, store.getMemoryUsage().allocatedBytes());
- EXPECT_EQ(50052, store.getMemoryUsage().usedBytes());
+ EXPECT_EQ(49980u, store.getMemoryUsage().usedBytes());
}
GTEST_MAIN_RUN_ALL_TESTS()
diff --git a/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp b/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
index 7d4451556c8..8ea7f807f56 100644
--- a/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
+++ b/vespalib/src/tests/datastore/unique_store_string_allocator/unique_store_string_allocator_test.cpp
@@ -62,8 +62,8 @@ struct TestBase : public ::testing::Test {
void assert_buffer_state(EntryRef ref, const TestBufferStats expStats) {
auto & stats = buffer_state(ref).stats();
EXPECT_EQ(expStats._used, buffer_state(ref).size());
- EXPECT_EQ(expStats._hold, stats.hold_elems());
- EXPECT_EQ(expStats._dead, stats.dead_elems());
+ EXPECT_EQ(expStats._hold, stats.hold_entries());
+ EXPECT_EQ(expStats._dead, stats.dead_entries());
EXPECT_EQ(expStats._extra_used, stats.extra_used_bytes());
EXPECT_EQ(expStats._extra_hold, stats.extra_hold_bytes());
}
@@ -83,14 +83,14 @@ TEST_F(StringTest, can_add_and_get_values)
assert_add(spaces1000.c_str());
}
-TEST_F(StringTest, elements_are_put_on_hold_when_value_is_removed)
+TEST_F(StringTest, entries_are_put_on_hold_when_value_is_removed)
{
EntryRef ref = add(small.c_str());
- assert_buffer_state(ref, TestBufferStats().used(16).hold(0).dead(0));
+ assert_buffer_state(ref, TestBufferStats().used(1).hold(0).dead(0));
remove(ref);
- assert_buffer_state(ref, TestBufferStats().used(16).hold(16).dead(0));
+ assert_buffer_state(ref, TestBufferStats().used(1).hold(1).dead(0));
reclaim_memory();
- assert_buffer_state(ref, TestBufferStats().used(16).hold(0).dead(16));
+ assert_buffer_state(ref, TestBufferStats().used(1).hold(0).dead(1));
}
TEST_F(StringTest, extra_bytes_used_is_tracked)
@@ -139,7 +139,7 @@ TEST_F(StringTest, free_list_is_used_when_enabled)
EntryRef ref4 = add(spaces1000.c_str());
EXPECT_EQ(ref1, ref3);
EXPECT_EQ(ref2, ref4);
- assert_buffer_state(ref1, TestBufferStats().used(16).hold(0).dead(0));
+ assert_buffer_state(ref1, TestBufferStats().used(1).hold(0).dead(0));
assert_buffer_state(ref2, TestBufferStats().used(2).hold(0).dead(1).extra_used(1001));
}
@@ -155,7 +155,7 @@ TEST_F(StringTest, free_list_is_not_used_when_disabled)
EntryRef ref4 = add(spaces1000.c_str());
EXPECT_NE(ref1, ref3);
EXPECT_NE(ref2, ref4);
- assert_buffer_state(ref1, TestBufferStats().used(32).hold(0).dead(16));
+ assert_buffer_state(ref1, TestBufferStats().used(2).hold(0).dead(1));
assert_buffer_state(ref2, TestBufferStats().used(3).hold(0).dead(2).extra_used(1001));
}
@@ -173,7 +173,7 @@ TEST_F(StringTest, free_list_is_never_used_for_move_on_compact)
EntryRef ref6 = move_on_compact(ref2);
EXPECT_NE(ref5, ref3);
EXPECT_NE(ref6, ref4);
- assert_buffer_state(ref1, TestBufferStats().used(48).hold(0).dead(16));
+ assert_buffer_state(ref1, TestBufferStats().used(3).hold(0).dead(1));
assert_buffer_state(ref2, TestBufferStats().used(4).hold(0).dead(2).extra_used(2002));
}
diff --git a/vespalib/src/tests/signalhandler/CMakeLists.txt b/vespalib/src/tests/signalhandler/CMakeLists.txt
index 4f78eb2e82d..88be14f994f 100644
--- a/vespalib/src/tests/signalhandler/CMakeLists.txt
+++ b/vespalib/src/tests/signalhandler/CMakeLists.txt
@@ -5,6 +5,11 @@ vespa_add_library(vespalib_signalhandler_test_my_shared_library TEST
DEPENDS
vespalib
)
+
+# Don't convert call to jump when returning a value from a function with
+# a compatible stack.
+set_source_files_properties(my_shared_library.cpp PROPERTIES COMPILE_OPTIONS "-fno-optimize-sibling-calls")
+
vespa_add_executable(vespalib_signalhandler_test_app TEST
SOURCES
signalhandler_test.cpp
diff --git a/vespalib/src/vespa/vespalib/btree/btree.h b/vespalib/src/vespa/vespalib/btree/btree.h
index 32b538b65ec..c2f5aac01b7 100644
--- a/vespalib/src/vespa/vespalib/btree/btree.h
+++ b/vespalib/src/vespa/vespalib/btree/btree.h
@@ -61,9 +61,9 @@ public:
}
void
- disableElemHoldList()
+ disable_entry_hold_list()
{
- _alloc.disableElemHoldList();
+ _alloc.disable_entry_hold_list();
}
// Inherit doc from BTreeRoot
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
index 784e95e3817..b537602c703 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.h
@@ -60,8 +60,8 @@ public:
_nodeStore.disableFreeLists();
}
- void disableElemHoldList() {
- _nodeStore.disableElemHoldList();
+ void disable_entry_hold_list() {
+ _nodeStore.disable_entry_hold_list();
}
/**
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
index a38b68afe73..d23c8fc2054 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreenodeallocator.hpp
@@ -162,7 +162,7 @@ holdNode(BTreeNode::Ref nodeRef,
InternalNodeType *node)
{
if (node->getFrozen()) {
- _nodeStore.holdElem(nodeRef);
+ _nodeStore.hold_entry(nodeRef);
} else {
node->clean();
_internalHoldUntilFreeze.push_back(nodeRef);
@@ -178,7 +178,7 @@ holdNode(BTreeNode::Ref nodeRef,
LeafNodeType *node)
{
if (node->getFrozen()) {
- _nodeStore.holdElem(nodeRef);
+ _nodeStore.hold_entry(nodeRef);
} else {
node->clean();
_leafHoldUntilFreeze.push_back(nodeRef);
@@ -235,7 +235,7 @@ freeze()
InternalNodeType *inode = mapInternalRef(i);
(void) inode;
assert(inode->getFrozen());
- _nodeStore.holdElem(i);
+ _nodeStore.hold_entry(i);
}
_internalHoldUntilFreeze.clear();
}
@@ -245,7 +245,7 @@ freeze()
LeafNodeType *lnode = mapLeafRef(i);
(void) lnode;
assert(lnode->getFrozen());
- _nodeStore.holdElem(i);
+ _nodeStore.hold_entry(i);
}
_leafHoldUntilFreeze.clear();
}
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.h b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
index c59092bf75c..38bf4e5ed4e 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.h
@@ -29,16 +29,16 @@ class BTreeNodeBufferType : public datastore::BufferType<EntryType, FrozenBtreeN
using ParentType = datastore::BufferType<EntryType, FrozenBtreeNode<EntryType>>;
using ParentType::empty_entry;
using ParentType::_arraySize;
- using ElemCount = typename ParentType::ElemCount;
+ using EntryCount = typename ParentType::EntryCount;
using CleanContext = typename ParentType::CleanContext;
public:
- BTreeNodeBufferType(uint32_t minArrays, uint32_t maxArrays)
- : ParentType(1, minArrays, maxArrays)
+ BTreeNodeBufferType(uint32_t min_entries, uint32_t max_entries)
+ : ParentType(1, min_entries, max_entries)
{ }
- void initializeReservedElements(void *buffer, ElemCount reservedElements) override;
+ void initialize_reserved_entries(void *buffer, EntryCount reserved_entries) override;
- void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
+ void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
};
@@ -79,7 +79,7 @@ public:
~BTreeNodeStore();
void disableFreeLists() { _store.disableFreeLists(); }
- void disableElemHoldList() { _store.disableElemHoldList(); }
+ void disable_entry_hold_list() { _store.disable_entry_hold_list(); }
static bool isValidRef(EntryRef ref) { return ref.valid(); }
@@ -152,8 +152,8 @@ public:
return _store.freeListAllocator<InternalNodeType, BTreeNodeReclaimer>(NODETYPE_INTERNAL).alloc(rhs);
}
- void holdElem(EntryRef ref) {
- _store.holdElem(ref, 1);
+ void hold_entry(EntryRef ref) {
+ _store.hold_entry(ref);
}
std::unique_ptr<vespalib::datastore::CompactingBuffers> start_compact_worst(const CompactionStrategy& compaction_strategy);
diff --git a/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp b/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
index a1ffb4d445d..99054f35d61 100644
--- a/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreenodestore.hpp
@@ -11,11 +11,11 @@ namespace vespalib::btree {
template <typename EntryType>
void
-BTreeNodeBufferType<EntryType>::initializeReservedElements(void *buffer, ElemCount reservedElements)
+BTreeNodeBufferType<EntryType>::initialize_reserved_entries(void *buffer, EntryCount reserved_entries)
{
- ParentType::initializeReservedElements(buffer, reservedElements);
+ ParentType::initialize_reserved_entries(buffer, reserved_entries);
EntryType *e = static_cast<EntryType *>(buffer);
- for (size_t j = reservedElements; j != 0; --j) {
+ for (size_t j = reserved_entries; j != 0; --j) {
e->freeze();
++e;
}
@@ -24,10 +24,10 @@ BTreeNodeBufferType<EntryType>::initializeReservedElements(void *buffer, ElemCou
template <typename EntryType>
void
-BTreeNodeBufferType<EntryType>::cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext)
+BTreeNodeBufferType<EntryType>::clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext)
{
EntryType *e = static_cast<EntryType *>(buffer) + offset;
- for (size_t j = numElems; j != 0; --j) {
+ for (size_t j = num_entries; j != 0; --j) {
e->cleanFrozen();
++e;
}
diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.h b/vespalib/src/vespa/vespalib/btree/btreestore.h
index 9fdade850f1..7dd839f1529 100644
--- a/vespalib/src/vespa/vespalib/btree/btreestore.h
+++ b/vespalib/src/vespa/vespalib/btree/btreestore.h
@@ -50,8 +50,8 @@ public:
using CompactionSpec = datastore::CompactionSpec;
using CompactionStrategy = datastore::CompactionStrategy;
using EntryRef = datastore::EntryRef;
- template <typename EntryType>
- using BufferType = datastore::BufferType<EntryType>;
+ template <typename ElemT>
+ using BufferType = datastore::BufferType<ElemT>;
using BufferState = datastore::BufferState;
static constexpr uint32_t clusterLimit = 8;
@@ -105,9 +105,9 @@ public:
_allocator.disableFreeLists();
}
- void disableElemHoldList() {
- _store.disableElemHoldList();
- _allocator.disableElemHoldList();
+ void disable_entry_hold_list() {
+ _store.disable_entry_hold_list();
+ _allocator.disable_entry_hold_list();
}
BTreeTypeRefPair allocNewBTree() {
diff --git a/vespalib/src/vespa/vespalib/btree/btreestore.hpp b/vespalib/src/vespa/vespalib/btree/btreestore.hpp
index a19d0b34aa6..90c302af5e4 100644
--- a/vespalib/src/vespa/vespalib/btree/btreestore.hpp
+++ b/vespalib/src/vespa/vespalib/btree/btreestore.hpp
@@ -74,7 +74,7 @@ allocNewKeyData(uint32_t clusterSize)
{
assert(clusterSize >= 1 && clusterSize <= clusterLimit);
uint32_t typeId = clusterSize - 1;
- return _store.allocator<KeyDataType>(typeId).allocArray(clusterSize);
+ return _store.allocator<KeyDataType>(typeId).allocArray();
}
@@ -87,7 +87,7 @@ allocKeyData(uint32_t clusterSize)
{
assert(clusterSize >= 1 && clusterSize <= clusterLimit);
uint32_t typeId = clusterSize - 1;
- return _store.freeListAllocator<KeyDataType, datastore::DefaultReclaimer<KeyDataType>>(typeId).allocArray(clusterSize);
+ return _store.freeListAllocator<KeyDataType, datastore::DefaultReclaimer<KeyDataType>>(typeId).allocArray();
}
@@ -156,7 +156,7 @@ makeTree(EntryRef &ref,
lNode->freeze();
BTreeTypeRefPair tPair(allocBTree());
tPair.data->setRoots(lPair.ref);
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = tPair.ref;
}
@@ -176,7 +176,7 @@ makeArray(EntryRef &ref, EntryRef root, LeafNodeType *leafNode)
kd->setData(leafNode->getData(idx));
}
assert(kd == kPair.data + clusterSize);
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
if (!leafNode->getFrozen()) {
leafNode->freeze();
}
@@ -255,7 +255,7 @@ insert(EntryRef &ref,
kd->setData(i->getData());
}
assert(kd == kPair.data + clusterSize + 1);
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = kPair.ref;
return true;
}
@@ -284,7 +284,7 @@ insert(EntryRef &ref,
lNode->freeze();
BTreeTypeRefPair tPair(allocBTree());
tPair.data->setRoots(lPair.ref); // allow immediate access to readers
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = tPair.ref;
return true;
#endif
@@ -339,7 +339,7 @@ remove(EntryRef &ref,
if (oldi == olde || comp(key, oldi->_key))
return false; // not found
if (clusterSize == 1) {
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
ref = EntryRef();
return true;
}
@@ -357,7 +357,7 @@ remove(EntryRef &ref,
kd->setData(i->getData());
}
assert(kd == kPair.data + clusterSize - 1);
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = kPair.ref;
return true;
}
@@ -670,7 +670,7 @@ applyCluster(EntryRef &ref,
if (newSizeMin <= clusterLimit) {
uint32_t newSize = getNewClusterSize(ob, oe, a, ae, r, re, comp);
if (newSize == 0) {
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = EntryRef();
return true;
}
@@ -678,7 +678,7 @@ applyCluster(EntryRef &ref,
KeyDataTypeRefPair kPair(allocKeyData(newSize));
applyCluster(ob, oe, kPair.data, kPair.data + newSize,
a, ae, r, re, comp);
- _store.holdElem(ref, clusterSize);
+ _store.hold_entry(ref);
ref = kPair.ref;
return true;
}
@@ -735,7 +735,7 @@ normalizeTree(EntryRef &ref,
{
EntryRef root = tree->getRoot();
if (!NodeAllocatorType::isValidRef(root)) {
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
ref = EntryRef();
return;
}
@@ -798,10 +798,8 @@ clear(const EntryRef ref)
if (clusterSize == 0) {
BTreeType *tree = getWTreeEntry(iRef);
tree->clear(_allocator);
- _store.holdElem(ref, 1);
- } else {
- _store.holdElem(ref, clusterSize);
}
+ _store.hold_entry(ref);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/allocator.h b/vespalib/src/vespa/vespalib/datastore/allocator.h
index 297270af0f5..30938bdc1c1 100644
--- a/vespalib/src/vespa/vespalib/datastore/allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/allocator.h
@@ -30,7 +30,7 @@ public:
HandleType alloc(Args && ... args);
HandleType allocArray(ConstArrayRef array);
- HandleType allocArray(size_t size);
+ HandleType allocArray();
};
}
diff --git a/vespalib/src/vespa/vespalib/datastore/allocator.hpp b/vespalib/src/vespa/vespalib/datastore/allocator.hpp
index 85f0e842519..fa97ef9a5f5 100644
--- a/vespalib/src/vespa/vespalib/datastore/allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/allocator.hpp
@@ -20,7 +20,7 @@ template <typename ... Args>
typename Allocator<EntryT, RefT>::HandleType
Allocator<EntryT, RefT>::alloc(Args && ... args)
{
- _store.ensureBufferCapacity(_typeId, 1);
+ _store.ensure_buffer_capacity(_typeId, 1);
uint32_t buffer_id = _store.primary_buffer_id(_typeId);
BufferState &state = _store.getBufferState(buffer_id);
assert(state.isActive());
@@ -36,39 +36,35 @@ template <typename EntryT, typename RefT>
typename Allocator<EntryT, RefT>::HandleType
Allocator<EntryT, RefT>::allocArray(ConstArrayRef array)
{
- _store.ensureBufferCapacity(_typeId, array.size());
+ _store.ensure_buffer_capacity(_typeId, 1);
uint32_t buffer_id = _store.primary_buffer_id(_typeId);
BufferState &state = _store.getBufferState(buffer_id);
assert(state.isActive());
assert(state.getArraySize() == array.size());
- size_t oldBufferSize = state.size();
- assert((oldBufferSize % array.size()) == 0);
- RefT ref((oldBufferSize / array.size()), buffer_id);
+ RefT ref(state.size(), buffer_id);
EntryT *buf = _store.template getEntryArray<EntryT>(ref, array.size());
for (size_t i = 0; i < array.size(); ++i) {
new (static_cast<void *>(buf + i)) EntryT(array[i]);
}
- state.stats().pushed_back(array.size());
+ state.stats().pushed_back(1);
return HandleType(ref, buf);
}
template <typename EntryT, typename RefT>
typename Allocator<EntryT, RefT>::HandleType
-Allocator<EntryT, RefT>::allocArray(size_t size)
+Allocator<EntryT, RefT>::allocArray()
{
- _store.ensureBufferCapacity(_typeId, size);
+ _store.ensure_buffer_capacity(_typeId, 1);
uint32_t buffer_id = _store.primary_buffer_id(_typeId);
BufferState &state = _store.getBufferState(buffer_id);
assert(state.isActive());
- assert(state.getArraySize() == size);
- size_t oldBufferSize = state.size();
- assert((oldBufferSize % size) == 0);
- RefT ref((oldBufferSize / size), buffer_id);
- EntryT *buf = _store.template getEntryArray<EntryT>(ref, size);
- for (size_t i = 0; i < size; ++i) {
+ RefT ref(state.size(), buffer_id);
+ auto array_size = state.getArraySize();
+ EntryT *buf = _store.template getEntryArray<EntryT>(ref, array_size);
+ for (size_t i = 0; i < array_size; ++i) {
new (static_cast<void *>(buf + i)) EntryT();
}
- state.stats().pushed_back(size);
+ state.stats().pushed_back(1);
return HandleType(ref, buf);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store.h b/vespalib/src/vespa/vespalib/datastore/array_store.h
index c2b65d72f03..809ac10f6e3 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store.h
+++ b/vespalib/src/vespa/vespalib/datastore/array_store.h
@@ -19,7 +19,7 @@
namespace vespalib::datastore {
/**
- * Datastore for storing arrays of type EntryT that is accessed via a 32-bit EntryRef.
+ * Datastore for storing arrays of type ElemT that is accessed via a 32-bit EntryRef.
*
* The default EntryRef type uses 19 bits for offset (524288 values) and 13 bits for buffer id (8192 buffers).
*
@@ -29,16 +29,18 @@ namespace vespalib::datastore {
*
* The max value of maxSmallArrayTypeId is (2^bufferBits - 1).
*/
-template <typename EntryT, typename RefT = EntryRefT<19>, typename TypeMapperT = ArrayStoreSimpleTypeMapper<EntryT> >
+template <typename ElemT, typename RefT = EntryRefT<19>, typename TypeMapperT = ArrayStoreSimpleTypeMapper<ElemT> >
class ArrayStore : public ICompactable
{
public:
using AllocSpec = ArrayStoreConfig::AllocSpec;
- using ArrayRef = vespalib::ArrayRef<EntryT>;
- using ConstArrayRef = vespalib::ConstArrayRef<EntryT>;
+ using ArrayRef = vespalib::ArrayRef<ElemT>;
+ using ConstArrayRef = vespalib::ConstArrayRef<ElemT>;
using DataStoreType = DataStoreT<RefT>;
- using LargeArray = vespalib::Array<EntryT>;
+ using ElemType = ElemT;
+ using LargeArray = vespalib::Array<ElemT>;
using LargeBufferType = typename TypeMapperT::LargeBufferType;
+ using RefType = RefT;
using SmallBufferType = typename TypeMapperT::SmallBufferType;
using TypeMapper = TypeMapperT;
private:
@@ -58,7 +60,7 @@ private:
EntryRef addLargeArray(const ConstArrayRef &array);
EntryRef allocate_large_array(size_t array_size);
ConstArrayRef getSmallArray(RefT ref, size_t arraySize) const {
- const EntryT *buf = _store.template getEntryArray<EntryT>(ref, arraySize);
+ const ElemT *buf = _store.template getEntryArray<ElemT>(ref, arraySize);
return ConstArrayRef(buf, arraySize);
}
ConstArrayRef getLargeArray(RefT ref) const {
@@ -85,7 +87,7 @@ public:
}
/**
- * Allocate an array of the given size without any instantiation of EntryT elements.
+ * Allocate an array of the given size without any instantiation of ElemT elements.
*
* Use get_writable() to get a reference to the array for writing.
*
@@ -148,14 +150,14 @@ public:
static ArrayStoreConfig optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor);
static ArrayStoreConfig optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
const TypeMapper& mapper,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor);
};
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store.hpp b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
index 301cff1e414..8e9fe779ba9 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/array_store.hpp
@@ -15,9 +15,9 @@
namespace vespalib::datastore {
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
void
-ArrayStore<EntryT, RefT, TypeMapperT>::initArrayTypes(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator)
+ArrayStore<ElemT, RefT, TypeMapperT>::initArrayTypes(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator)
{
_largeArrayTypeId = _store.addType(&_largeArrayType);
assert(_largeArrayTypeId == 0);
@@ -31,14 +31,14 @@ ArrayStore<EntryT, RefT, TypeMapperT>::initArrayTypes(const ArrayStoreConfig &cf
}
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
-ArrayStore<EntryT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator)
+template <typename ElemT, typename RefT, typename TypeMapperT>
+ArrayStore<ElemT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator)
: ArrayStore(cfg, memory_allocator, TypeMapper())
{
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
-ArrayStore<EntryT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator,
+template <typename ElemT, typename RefT, typename TypeMapperT>
+ArrayStore<ElemT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, std::shared_ptr<alloc::MemoryAllocator> memory_allocator,
TypeMapper&& mapper)
: _largeArrayTypeId(0),
_maxSmallArrayTypeId(cfg.maxSmallArrayTypeId()),
@@ -56,25 +56,25 @@ ArrayStore<EntryT, RefT, TypeMapperT>::ArrayStore(const ArrayStoreConfig &cfg, s
}
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
vespalib::MemoryUsage
-ArrayStore<EntryT, RefT, TypeMapperT>::getMemoryUsage() const {
+ArrayStore<ElemT, RefT, TypeMapperT>::getMemoryUsage() const {
vespalib::MemoryUsage usage = _store.getMemoryUsage();
usage.incAllocatedBytes(_smallArrayTypes.capacity() * sizeof(SmallBufferType));
usage.incUsedBytes(_smallArrayTypes.size() * sizeof(SmallBufferType));
return usage;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
-ArrayStore<EntryT, RefT, TypeMapperT>::~ArrayStore()
+template <typename ElemT, typename RefT, typename TypeMapperT>
+ArrayStore<ElemT, RefT, TypeMapperT>::~ArrayStore()
{
_store.reclaim_all_memory();
_store.dropBuffers();
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::add(const ConstArrayRef &array)
+ArrayStore<ElemT, RefT, TypeMapperT>::add(const ConstArrayRef &array)
{
if (array.size() == 0) {
return EntryRef();
@@ -86,9 +86,9 @@ ArrayStore<EntryT, RefT, TypeMapperT>::add(const ConstArrayRef &array)
}
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::allocate(size_t array_size)
+ArrayStore<ElemT, RefT, TypeMapperT>::allocate(size_t array_size)
{
if (array_size == 0) {
return EntryRef();
@@ -100,94 +100,93 @@ ArrayStore<EntryT, RefT, TypeMapperT>::allocate(size_t array_size)
}
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::addSmallArray(const ConstArrayRef &array)
+ArrayStore<ElemT, RefT, TypeMapperT>::addSmallArray(const ConstArrayRef &array)
{
uint32_t typeId = _mapper.get_type_id(array.size());
- using NoOpReclaimer = DefaultReclaimer<EntryT>;
- return _store.template freeListAllocator<EntryT, NoOpReclaimer>(typeId).allocArray(array).ref;
+ using NoOpReclaimer = DefaultReclaimer<ElemT>;
+ return _store.template freeListAllocator<ElemT, NoOpReclaimer>(typeId).allocArray(array).ref;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::allocate_small_array(size_t array_size)
+ArrayStore<ElemT, RefT, TypeMapperT>::allocate_small_array(size_t array_size)
{
uint32_t type_id = _mapper.get_type_id(array_size);
- return _store.template freeListRawAllocator<EntryT>(type_id).alloc(array_size).ref;
+ return _store.template freeListRawAllocator<ElemT>(type_id).alloc(1).ref;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::addLargeArray(const ConstArrayRef &array)
+ArrayStore<ElemT, RefT, TypeMapperT>::addLargeArray(const ConstArrayRef &array)
{
using NoOpReclaimer = DefaultReclaimer<LargeArray>;
auto handle = _store.template freeListAllocator<LargeArray, NoOpReclaimer>(_largeArrayTypeId)
.alloc(array.cbegin(), array.cend());
auto& state = _store.getBufferState(RefT(handle.ref).bufferId());
- state.stats().inc_extra_used_bytes(sizeof(EntryT) * array.size());
+ state.stats().inc_extra_used_bytes(sizeof(ElemT) * array.size());
return handle.ref;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::allocate_large_array(size_t array_size)
+ArrayStore<ElemT, RefT, TypeMapperT>::allocate_large_array(size_t array_size)
{
using NoOpReclaimer = DefaultReclaimer<LargeArray>;
auto handle = _store.template freeListAllocator<LargeArray, NoOpReclaimer>(_largeArrayTypeId).alloc(array_size);
auto& state = _store.getBufferState(RefT(handle.ref).bufferId());
- state.stats().inc_extra_used_bytes(sizeof(EntryT) * array_size);
+ state.stats().inc_extra_used_bytes(sizeof(ElemT) * array_size);
return handle.ref;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
void
-ArrayStore<EntryT, RefT, TypeMapperT>::remove(EntryRef ref)
+ArrayStore<ElemT, RefT, TypeMapperT>::remove(EntryRef ref)
{
if (ref.valid()) {
RefT internalRef(ref);
uint32_t typeId = _store.getTypeId(internalRef.bufferId());
if (typeId != _largeArrayTypeId) {
- size_t arraySize = _mapper.get_array_size(typeId);
- _store.holdElem(ref, arraySize);
+ _store.hold_entry(ref);
} else {
- _store.holdElem(ref, 1, sizeof(EntryT) * get(ref).size());
+ _store.hold_entry(ref, sizeof(ElemT) * get(ref).size());
}
}
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
EntryRef
-ArrayStore<EntryT, RefT, TypeMapperT>::move_on_compact(EntryRef ref)
+ArrayStore<ElemT, RefT, TypeMapperT>::move_on_compact(EntryRef ref)
{
return add(get(ref));
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
ICompactionContext::UP
-ArrayStore<EntryT, RefT, TypeMapperT>::compact_worst(const CompactionStrategy &compaction_strategy)
+ArrayStore<ElemT, RefT, TypeMapperT>::compact_worst(const CompactionStrategy &compaction_strategy)
{
auto compacting_buffers = _store.start_compact_worst_buffers(_compaction_spec, compaction_strategy);
return std::make_unique<CompactionContext>(*this, std::move(compacting_buffers));
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
std::unique_ptr<CompactingBuffers>
-ArrayStore<EntryT, RefT, TypeMapperT>::start_compact_worst_buffers(const CompactionStrategy &compaction_strategy)
+ArrayStore<ElemT, RefT, TypeMapperT>::start_compact_worst_buffers(const CompactionStrategy &compaction_strategy)
{
return _store.start_compact_worst_buffers(_compaction_spec, compaction_strategy);
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
vespalib::AddressSpace
-ArrayStore<EntryT, RefT, TypeMapperT>::addressSpaceUsage() const
+ArrayStore<ElemT, RefT, TypeMapperT>::addressSpaceUsage() const
{
return _store.getAddressSpaceUsage();
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
vespalib::MemoryUsage
-ArrayStore<EntryT, RefT, TypeMapperT>::update_stat(const CompactionStrategy& compaction_strategy)
+ArrayStore<ElemT, RefT, TypeMapperT>::update_stat(const CompactionStrategy& compaction_strategy)
{
auto address_space_usage = _store.getAddressSpaceUsage();
auto memory_usage = getMemoryUsage();
@@ -195,20 +194,20 @@ ArrayStore<EntryT, RefT, TypeMapperT>::update_stat(const CompactionStrategy& com
return memory_usage;
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
const BufferState &
-ArrayStore<EntryT, RefT, TypeMapperT>::bufferState(EntryRef ref)
+ArrayStore<ElemT, RefT, TypeMapperT>::bufferState(EntryRef ref)
{
RefT internalRef(ref);
return _store.getBufferState(internalRef.bufferId());
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
ArrayStoreConfig
-ArrayStore<EntryT, RefT, TypeMapperT>::optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
+ArrayStore<ElemT, RefT, TypeMapperT>::optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor)
{
TypeMapper mapper;
@@ -216,26 +215,26 @@ ArrayStore<EntryT, RefT, TypeMapperT>::optimizedConfigForHugePage(uint32_t maxSm
mapper,
hugePageSize,
smallPageSize,
- minNumArraysForNewBuffer,
+ min_num_entries_for_new_buffer,
allocGrowFactor);
}
-template <typename EntryT, typename RefT, typename TypeMapperT>
+template <typename ElemT, typename RefT, typename TypeMapperT>
ArrayStoreConfig
-ArrayStore<EntryT, RefT, TypeMapperT>::optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
+ArrayStore<ElemT, RefT, TypeMapperT>::optimizedConfigForHugePage(uint32_t maxSmallArrayTypeId,
const TypeMapper& mapper,
size_t hugePageSize,
size_t smallPageSize,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor)
{
return ArrayStoreConfig::optimizeForHugePage(mapper.get_max_small_array_type_id(maxSmallArrayTypeId),
[&](uint32_t type_id) noexcept { return mapper.get_array_size(type_id); },
hugePageSize,
smallPageSize,
- sizeof(EntryT),
+ sizeof(ElemT),
RefT::offsetSize(),
- minNumArraysForNewBuffer,
+ min_num_entries_for_new_buffer,
allocGrowFactor);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store_config.cpp b/vespalib/src/vespa/vespalib/datastore/array_store_config.cpp
index d5587841745..1df9354cd6c 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store_config.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/array_store_config.cpp
@@ -49,19 +49,19 @@ ArrayStoreConfig::optimizeForHugePage(uint32_t maxSmallArrayTypeId,
std::function<size_t(uint32_t)> type_id_to_array_size,
size_t hugePageSize,
size_t smallPageSize,
- size_t entrySize,
+ size_t elem_size,
size_t maxEntryRefOffset,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor)
{
AllocSpecVector allocSpecs;
- allocSpecs.emplace_back(0, maxEntryRefOffset, minNumArraysForNewBuffer, allocGrowFactor); // large array spec;
+ allocSpecs.emplace_back(0, maxEntryRefOffset, min_num_entries_for_new_buffer, allocGrowFactor); // large array spec;
for (uint32_t type_id = 1; type_id <= maxSmallArrayTypeId; ++type_id) {
size_t arraySize = type_id_to_array_size(type_id);
- size_t numArraysForNewBuffer = hugePageSize / (entrySize * arraySize);
- numArraysForNewBuffer = capToLimits(numArraysForNewBuffer, minNumArraysForNewBuffer, maxEntryRefOffset);
- numArraysForNewBuffer = alignToSmallPageSize(numArraysForNewBuffer, minNumArraysForNewBuffer, smallPageSize);
- allocSpecs.emplace_back(0, maxEntryRefOffset, numArraysForNewBuffer, allocGrowFactor);
+ size_t num_entries_for_new_buffer = hugePageSize / (elem_size * arraySize);
+ num_entries_for_new_buffer = capToLimits(num_entries_for_new_buffer, min_num_entries_for_new_buffer, maxEntryRefOffset);
+ num_entries_for_new_buffer = alignToSmallPageSize(num_entries_for_new_buffer, min_num_entries_for_new_buffer, smallPageSize);
+ allocSpecs.emplace_back(0, maxEntryRefOffset, num_entries_for_new_buffer, allocGrowFactor);
}
return ArrayStoreConfig(allocSpecs);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store_config.h b/vespalib/src/vespa/vespalib/datastore/array_store_config.h
index a326c00d042..cae241dba10 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store_config.h
+++ b/vespalib/src/vespa/vespalib/datastore/array_store_config.h
@@ -19,21 +19,21 @@ public:
* Specification of buffer allocation strategy for arrays of a given size.
*/
struct AllocSpec {
- // Minimum number of arrays to allocate in a buffer.
- size_t minArraysInBuffer;
- // Maximum number of arrays to allocate in a buffer.
- size_t maxArraysInBuffer;
- // Number of arrays needed before allocating a new buffer instead of just resizing the first one.
- size_t numArraysForNewBuffer;
+ // Minimum number of entries to allocate in a buffer.
+ size_t min_entries_in_buffer;
+ // Maximum number of entries to allocate in a buffer.
+ size_t max_entries_in_buffer;
+ // Number of entries needed before allocating a new buffer instead of just resizing the first one.
+ size_t num_entries_for_new_buffer;
// Grow factor used when allocating a new buffer.
float allocGrowFactor;
- AllocSpec(size_t minArraysInBuffer_,
- size_t maxArraysInBuffer_,
- size_t numArraysForNewBuffer_,
+ AllocSpec(size_t min_entries_in_buffer_,
+ size_t max_entries_in_buffer_,
+ size_t num_entries_for_new_buffer_,
float allocGrowFactor_) noexcept
- : minArraysInBuffer(minArraysInBuffer_),
- maxArraysInBuffer(maxArraysInBuffer_),
- numArraysForNewBuffer(numArraysForNewBuffer_),
+ : min_entries_in_buffer(min_entries_in_buffer_),
+ max_entries_in_buffer(max_entries_in_buffer_),
+ num_entries_for_new_buffer(num_entries_for_new_buffer_),
allocGrowFactor(allocGrowFactor_) {}
};
@@ -76,9 +76,9 @@ public:
std::function<size_t(uint32_t)> type_id_to_array_size,
size_t hugePageSize,
size_t smallPageSize,
- size_t entrySize,
+ size_t elem_size,
size_t maxEntryRefOffset,
- size_t minNumArraysForNewBuffer,
+ size_t min_num_entries_for_new_buffer,
float allocGrowFactor);
};
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store_simple_type_mapper.h b/vespalib/src/vespa/vespalib/datastore/array_store_simple_type_mapper.h
index a0cd7827b2d..5f9728a43cc 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store_simple_type_mapper.h
+++ b/vespalib/src/vespa/vespalib/datastore/array_store_simple_type_mapper.h
@@ -15,11 +15,11 @@ namespace vespalib::datastore {
*
* A more complex mapping can be used by creating a custom mapper and BufferType implementations.
*/
-template <typename EntryT>
+template <typename ElemT>
class ArrayStoreSimpleTypeMapper {
public:
- using SmallBufferType = SmallArrayBufferType<EntryT>;
- using LargeBufferType = LargeArrayBufferType<EntryT>;
+ using SmallBufferType = SmallArrayBufferType<ElemT>;
+ using LargeBufferType = LargeArrayBufferType<ElemT>;
uint32_t get_type_id(size_t array_size) const { return array_size; }
size_t get_array_size(uint32_t type_id) const { return type_id; }
diff --git a/vespalib/src/vespa/vespalib/datastore/array_store_type_mapper.h b/vespalib/src/vespa/vespalib/datastore/array_store_type_mapper.h
index e707627de19..2a406a39bf9 100644
--- a/vespalib/src/vespa/vespalib/datastore/array_store_type_mapper.h
+++ b/vespalib/src/vespa/vespalib/datastore/array_store_type_mapper.h
@@ -12,7 +12,7 @@ namespace vespalib::datastore {
* This class provides mapping between type ids and array sizes needed for
* storing a value with size smaller than or equal to the array size.
*
- * The array sizes vector is a monotic increasing sequence that might end
+ * The array sizes vector is a monotonic strictly increasing sequence that might end
* with exponential growth.
*/
class ArrayStoreTypeMapper
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_free_list.cpp b/vespalib/src/vespa/vespalib/datastore/buffer_free_list.cpp
index 224ed4b0c8f..851db0222c2 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_free_list.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_free_list.cpp
@@ -20,9 +20,8 @@ BufferFreeList::detach()
_free_list->detach(*this);
}
-BufferFreeList::BufferFreeList(std::atomic<ElemCount>& dead_elems)
- : _dead_elems(dead_elems),
- _array_size(0),
+BufferFreeList::BufferFreeList(std::atomic<EntryCount>& dead_entries)
+ : _dead_entries(dead_entries),
_free_list(),
_free_refs()
{
@@ -66,7 +65,7 @@ BufferFreeList::pop_entry() {
if (empty()) {
detach();
}
- _dead_elems.store(_dead_elems.load(std::memory_order_relaxed) - _array_size, std::memory_order_relaxed);
+ _dead_entries.store(_dead_entries.load(std::memory_order_relaxed) - 1, std::memory_order_relaxed);
return ret;
}
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_free_list.h b/vespalib/src/vespa/vespalib/datastore/buffer_free_list.h
index 148ddd8db88..4348a41af04 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_free_list.h
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_free_list.h
@@ -19,8 +19,7 @@ class BufferFreeList {
private:
using EntryRefArray = vespalib::Array<EntryRef>;
- std::atomic<ElemCount>& _dead_elems;
- uint32_t _array_size;
+ std::atomic<EntryCount>& _dead_entries;
FreeList* _free_list;
EntryRefArray _free_refs;
@@ -28,7 +27,7 @@ private:
void detach();
public:
- BufferFreeList(std::atomic<ElemCount>& dead_elems);
+ BufferFreeList(std::atomic<EntryCount>& dead_entrie);
~BufferFreeList();
BufferFreeList(BufferFreeList&&) = default; // Needed for emplace_back() during setup.
BufferFreeList(const BufferFreeList&) = delete;
@@ -37,10 +36,8 @@ public:
void enable(FreeList& free_list);
void disable();
- void set_array_size(uint32_t value) { _array_size = value; }
bool enabled() const { return _free_list != nullptr; }
bool empty() const { return _free_refs.empty(); }
- uint32_t array_size() const { return _array_size; }
void push_entry(EntryRef ref);
EntryRef pop_entry();
};
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp b/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp
index 8d97414626e..0d96e3f6d47 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_stats.cpp
@@ -6,27 +6,27 @@
namespace vespalib::datastore {
BufferStats::BufferStats()
- : _alloc_elems(0),
- _used_elems(0),
- _hold_elems(0),
- _dead_elems(0),
+ : _alloc_entries(0),
+ _used_entries(0),
+ _hold_entries(0),
+ _dead_entries(0),
_extra_used_bytes(0),
_extra_hold_bytes(0)
{
}
void
-BufferStats::add_to_mem_stats(size_t element_size, MemoryStats& stats) const
+BufferStats::add_to_mem_stats(size_t entry_size, MemoryStats& stats) const
{
size_t extra_used = extra_used_bytes();
- stats._allocElems += capacity();
- stats._usedElems += size();
- stats._deadElems += dead_elems();
- stats._holdElems += hold_elems();
- stats._allocBytes += (capacity() * element_size) + extra_used;
- stats._usedBytes += (size() * element_size) + extra_used;
- stats._deadBytes += dead_elems() * element_size;
- stats._holdBytes += (hold_elems() * element_size) + extra_hold_bytes();
+ stats._alloc_entries += capacity();
+ stats._used_entries += size();
+ stats._dead_entries += dead_entries();
+ stats._hold_entries += hold_entries();
+ stats._allocBytes += (capacity() * entry_size) + extra_used;
+ stats._usedBytes += (size() * entry_size) + extra_used;
+ stats._deadBytes += dead_entries() * entry_size;
+ stats._holdBytes += (hold_entries() * entry_size) + extra_hold_bytes();
}
InternalBufferStats::InternalBufferStats()
@@ -37,20 +37,20 @@ InternalBufferStats::InternalBufferStats()
void
InternalBufferStats::clear()
{
- _alloc_elems.store(0, std::memory_order_relaxed);
- _used_elems.store(0, std::memory_order_relaxed);
- _hold_elems.store(0, std::memory_order_relaxed);
- _dead_elems.store(0, std::memory_order_relaxed);
+ _alloc_entries.store(0, std::memory_order_relaxed);
+ _used_entries.store(0, std::memory_order_relaxed);
+ _hold_entries.store(0, std::memory_order_relaxed);
+ _dead_entries.store(0, std::memory_order_relaxed);
_extra_used_bytes.store(0, std::memory_order_relaxed);
_extra_hold_bytes.store(0, std::memory_order_relaxed);
}
void
-InternalBufferStats::dec_hold_elems(size_t value)
+InternalBufferStats::dec_hold_entries(size_t value)
{
- ElemCount elems = hold_elems();
+ EntryCount elems = hold_entries();
assert(elems >= value);
- _hold_elems.store(elems - value, std::memory_order_relaxed);
+ _hold_entries.store(elems - value, std::memory_order_relaxed);
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_stats.h b/vespalib/src/vespa/vespalib/datastore/buffer_stats.h
index 66f8b532c41..1974efa97ec 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_stats.h
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_stats.h
@@ -13,16 +13,16 @@ namespace vespalib::datastore {
*/
class BufferStats {
protected:
- // The number of elements that are allocated in the buffer.
- std::atomic<ElemCount> _alloc_elems;
- // The number of elements (of the allocated) that are used: _used_elems <= _alloc_elems.
- std::atomic<ElemCount> _used_elems;
- // The number of elements (of the used) that are on hold: _hold_elems <= _used_elems.
- // "On hold" is a transitionary state used when removing elements.
- std::atomic<ElemCount> _hold_elems;
- // The number of elements (of the used) that are dead: _dead_elems <= _used_elems.
- // A dead element was first on hold, and is now available for reuse in the free list (if enabled).
- std::atomic<ElemCount> _dead_elems;
+ // The number of entries that are allocated in the buffer.
+ std::atomic<EntryCount> _alloc_entries;
+ // The number of entries (of the allocated) that are used: _used_entries <= _alloc_entries.
+ std::atomic<EntryCount> _used_entries;
+ // The number of entries (of the used) that are on hold: _hold_entries <= _used_entries.
+ // "On hold" is a transitionary state used when removing entries.
+ std::atomic<EntryCount> _hold_entries;
+ // The number of entries (of the used) that are dead: _dead_entries <= _used_entries.
+ // A dead entry was first on hold, and is now available for reuse in the free list (if enabled).
+ std::atomic<EntryCount> _dead_entries;
// Number of bytes that are heap allocated (and used) by elements that are stored in this buffer.
// For simple types this is always 0.
@@ -34,22 +34,22 @@ protected:
public:
BufferStats();
- size_t size() const { return _used_elems.load(std::memory_order_relaxed); }
- size_t capacity() const { return _alloc_elems.load(std::memory_order_relaxed); }
+ size_t size() const { return _used_entries.load(std::memory_order_relaxed); }
+ size_t capacity() const { return _alloc_entries.load(std::memory_order_relaxed); }
size_t remaining() const { return capacity() - size(); }
- void pushed_back(size_t num_elems) {
- _used_elems.store(size() + num_elems, std::memory_order_relaxed);
+ void pushed_back(size_t num_entries) {
+ _used_entries.store(size() + num_entries, std::memory_order_relaxed);
}
- size_t dead_elems() const { return _dead_elems.load(std::memory_order_relaxed); }
- size_t hold_elems() const { return _hold_elems.load(std::memory_order_relaxed); }
+ size_t dead_entries() const { return _dead_entries.load(std::memory_order_relaxed); }
+ size_t hold_entries() const { return _hold_entries.load(std::memory_order_relaxed); }
size_t extra_used_bytes() const { return _extra_used_bytes.load(std::memory_order_relaxed); }
size_t extra_hold_bytes() const { return _extra_hold_bytes.load(std::memory_order_relaxed); }
void inc_extra_used_bytes(size_t value) { _extra_used_bytes.store(extra_used_bytes() + value, std::memory_order_relaxed); }
- void add_to_mem_stats(size_t element_size, MemoryStats& stats) const;
+ void add_to_mem_stats(size_t entry_size, MemoryStats& stats) const;
};
/**
@@ -59,15 +59,15 @@ class InternalBufferStats : public BufferStats {
public:
InternalBufferStats();
void clear();
- void set_alloc_elems(size_t value) { _alloc_elems.store(value, std::memory_order_relaxed); }
- void set_dead_elems(size_t value) { _dead_elems.store(value, std::memory_order_relaxed); }
- void set_hold_elems(size_t value) { _hold_elems.store(value, std::memory_order_relaxed); }
- void inc_dead_elems(size_t value) { _dead_elems.store(dead_elems() + value, std::memory_order_relaxed); }
- void inc_hold_elems(size_t value) { _hold_elems.store(hold_elems() + value, std::memory_order_relaxed); }
- void dec_hold_elems(size_t value);
+ void set_alloc_entries(size_t value) { _alloc_entries.store(value, std::memory_order_relaxed); }
+ void set_dead_entries(size_t value) { _dead_entries.store(value, std::memory_order_relaxed); }
+ void set_hold_entries(size_t value) { _hold_entries.store(value, std::memory_order_relaxed); }
+ void inc_dead_entries(size_t value) { _dead_entries.store(dead_entries() + value, std::memory_order_relaxed); }
+ void inc_hold_entries(size_t value) { _hold_entries.store(hold_entries() + value, std::memory_order_relaxed); }
+ void dec_hold_entries(size_t value);
void inc_extra_hold_bytes(size_t value) { _extra_hold_bytes.store(extra_hold_bytes() + value, std::memory_order_relaxed); }
- std::atomic<ElemCount>& used_elems_ref() { return _used_elems; }
- std::atomic<ElemCount>& dead_elems_ref() { return _dead_elems; }
+ std::atomic<EntryCount>& used_entries_ref() { return _used_entries; }
+ std::atomic<EntryCount>& dead_entries_ref() { return _dead_entries; }
std::atomic<size_t>& extra_used_bytes_ref() { return _extra_used_bytes; }
std::atomic<size_t>& extra_hold_bytes_ref() { return _extra_hold_bytes; }
};
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_type.cpp b/vespalib/src/vespa/vespalib/datastore/buffer_type.cpp
index 4a9ba2d33a8..0d43ede9e62 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_type.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_type.cpp
@@ -27,85 +27,85 @@ BufferTypeBase::CleanContext::extraBytesCleaned(size_t value)
}
BufferTypeBase::BufferTypeBase(uint32_t arraySize,
- uint32_t minArrays,
- uint32_t maxArrays,
- uint32_t numArraysForNewBuffer,
+ uint32_t min_entries,
+ uint32_t max_entries,
+ uint32_t num_entries_for_new_buffer,
float allocGrowFactor) noexcept
: _arraySize(arraySize),
- _minArrays(std::min(minArrays, maxArrays)),
- _maxArrays(maxArrays),
- _numArraysForNewBuffer(std::min(numArraysForNewBuffer, maxArrays)),
+ _min_entries(std::min(min_entries, max_entries)),
+ _max_entries(max_entries),
+ _num_entries_for_new_buffer(std::min(num_entries_for_new_buffer, max_entries)),
_allocGrowFactor(allocGrowFactor),
_holdBuffers(0),
- _holdUsedElems(0),
+ _hold_used_entries(0),
_aggr_counts(),
_active_buffers()
{
}
BufferTypeBase::BufferTypeBase(uint32_t arraySize,
- uint32_t minArrays,
- uint32_t maxArrays) noexcept
- : BufferTypeBase(arraySize, minArrays, maxArrays, 0u, DEFAULT_ALLOC_GROW_FACTOR)
+ uint32_t min_entries,
+ uint32_t max_entries) noexcept
+ : BufferTypeBase(arraySize, min_entries, max_entries, 0u, DEFAULT_ALLOC_GROW_FACTOR)
{
}
BufferTypeBase::~BufferTypeBase()
{
assert(_holdBuffers == 0);
- assert(_holdUsedElems == 0);
+ assert(_hold_used_entries == 0);
assert(_aggr_counts.empty());
assert(_active_buffers.empty());
}
-ElemCount
-BufferTypeBase::getReservedElements(uint32_t bufferId) const
+EntryCount
+BufferTypeBase::get_reserved_entries(uint32_t bufferId) const
{
- return bufferId == 0 ? _arraySize : 0u;
+ return bufferId == 0 ? 1u : 0u;
}
void
-BufferTypeBase::onActive(uint32_t bufferId, std::atomic<ElemCount>* usedElems, std::atomic<ElemCount>* deadElems, void* buffer)
+BufferTypeBase::on_active(uint32_t bufferId, std::atomic<EntryCount>* used_entries, std::atomic<EntryCount>* dead_entries, void* buffer)
{
- _aggr_counts.add_buffer(usedElems, deadElems);
+ _aggr_counts.add_buffer(used_entries, dead_entries);
assert(std::find(_active_buffers.begin(), _active_buffers.end(), bufferId) == _active_buffers.end());
_active_buffers.emplace_back(bufferId);
- size_t reservedElems = getReservedElements(bufferId);
- if (reservedElems != 0u) {
- initializeReservedElements(buffer, reservedElems);
- *usedElems = reservedElems;
- *deadElems = reservedElems;
+ auto reserved_entries = get_reserved_entries(bufferId);
+ if (reserved_entries != 0u) {
+ initialize_reserved_entries(buffer, reserved_entries);
+ *used_entries = reserved_entries;
+ *dead_entries = reserved_entries;
}
}
void
-BufferTypeBase::onHold(uint32_t buffer_id, const std::atomic<ElemCount>* usedElems, const std::atomic<ElemCount>* deadElems)
+BufferTypeBase::on_hold(uint32_t buffer_id, const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries)
{
++_holdBuffers;
auto itr = std::find(_active_buffers.begin(), _active_buffers.end(), buffer_id);
assert(itr != _active_buffers.end());
_active_buffers.erase(itr);
- _aggr_counts.remove_buffer(usedElems, deadElems);
- _holdUsedElems += *usedElems;
+ _aggr_counts.remove_buffer(used_entries, dead_entries);
+ _hold_used_entries += *used_entries;
}
void
-BufferTypeBase::onFree(ElemCount usedElems)
+BufferTypeBase::on_free(EntryCount used_entries)
{
--_holdBuffers;
- assert(_holdUsedElems >= usedElems);
- _holdUsedElems -= usedElems;
+ assert(_hold_used_entries >= used_entries);
+ _hold_used_entries -= used_entries;
}
void
-BufferTypeBase::resume_primary_buffer(uint32_t buffer_id, std::atomic<ElemCount>* used_elems, std::atomic<ElemCount>* dead_elems)
+BufferTypeBase::resume_primary_buffer(uint32_t buffer_id, std::atomic<EntryCount>* used_entries, std::atomic<EntryCount>* dead_entries)
{
auto itr = std::find(_active_buffers.begin(), _active_buffers.end(), buffer_id);
assert(itr != _active_buffers.end());
_active_buffers.erase(itr);
_active_buffers.emplace_back(buffer_id);
- _aggr_counts.remove_buffer(used_elems, dead_elems);
- _aggr_counts.add_buffer(used_elems, dead_elems);
+ _aggr_counts.remove_buffer(used_entries, dead_entries);
+ _aggr_counts.add_buffer(used_entries, dead_entries);
}
const alloc::MemoryAllocator*
@@ -115,17 +115,17 @@ BufferTypeBase::get_memory_allocator() const
}
void
-BufferTypeBase::clampMaxArrays(uint32_t maxArrays)
+BufferTypeBase::clamp_max_entries(uint32_t max_entries)
{
- _maxArrays = std::min(_maxArrays, maxArrays);
- _minArrays = std::min(_minArrays, _maxArrays);
- _numArraysForNewBuffer = std::min(_numArraysForNewBuffer, _maxArrays);
+ _max_entries = std::min(_max_entries, max_entries);
+ _min_entries = std::min(_min_entries, _max_entries);
+ _num_entries_for_new_buffer = std::min(_num_entries_for_new_buffer, _max_entries);
}
size_t
-BufferTypeBase::calcArraysToAlloc(uint32_t bufferId, ElemCount elemsNeeded, bool resizing) const
+BufferTypeBase::calc_entries_to_alloc(uint32_t bufferId, EntryCount free_entries_needed, bool resizing) const
{
- size_t reservedElems = getReservedElements(bufferId);
+ size_t reserved_entries = get_reserved_entries(bufferId);
BufferCounts last_bc;
BufferCounts bc;
if (resizing) {
@@ -134,56 +134,53 @@ BufferTypeBase::calcArraysToAlloc(uint32_t bufferId, ElemCount elemsNeeded, bool
}
}
bc = _aggr_counts.all_buffers();
- assert((bc.used_elems % _arraySize) == 0);
- assert((bc.dead_elems % _arraySize) == 0);
- assert(bc.used_elems >= bc.dead_elems);
- size_t neededArrays = (elemsNeeded + (resizing ? last_bc.used_elems : reservedElems) + _arraySize - 1) / _arraySize;
-
- size_t liveArrays = (bc.used_elems - bc.dead_elems) / _arraySize;
- size_t growArrays = (liveArrays * _allocGrowFactor);
- size_t usedArrays = last_bc.used_elems / _arraySize;
- size_t wantedArrays = std::max((resizing ? usedArrays : 0u) + growArrays,
- static_cast<size_t>(_minArrays));
-
- size_t result = wantedArrays;
- if (result < neededArrays) {
- result = neededArrays;
+ assert(bc.used_entries >= bc.dead_entries);
+ size_t needed_entries = static_cast<size_t>(free_entries_needed) + (resizing ? last_bc.used_entries : reserved_entries);
+ size_t live_entries = (bc.used_entries - bc.dead_entries);
+ size_t grow_entries = (live_entries * _allocGrowFactor);
+ size_t used_entries = last_bc.used_entries;
+ size_t wanted_entries = std::max((resizing ? used_entries : 0u) + grow_entries,
+ static_cast<size_t>(_min_entries));
+
+ size_t result = wanted_entries;
+ if (result < needed_entries) {
+ result = needed_entries;
}
- if (result > _maxArrays) {
- result = _maxArrays;
+ if (result > _max_entries) {
+ result = _max_entries;
}
- if (result < neededArrays) {
+ if (result < needed_entries) {
vespalib::asciistream s;
s << "BufferTypeBase::calcArraysToAlloc(" <<
"bufferId=" << bufferId <<
- ",elemsNeeeded=" << elemsNeeded <<
+ ",free_entries_needed=" << free_entries_needed <<
",resizing=" << (resizing ? "true" : "false") << ")" <<
- " wantedArrays=" << wantedArrays <<
+ " wanted_entries=" << wanted_entries <<
", _arraySize=" << _arraySize <<
- ", _maxArrays=" << _maxArrays <<
- ", reservedElems=" << reservedElems <<
- ", liveArrays=" << liveArrays <<
- ", growArrays=" << growArrays <<
- ", usedArrays=" << usedArrays <<
+ ", _max_entries=" << _max_entries <<
+ ", reserved_entries=" << reserved_entries <<
+ ", live_entries=" << live_entries <<
+ ", grow_entries=" << grow_entries <<
+ ", used_entries=" << used_entries <<
", typeid(*this).name=\"" << typeid(*this).name() << "\"" <<
- ", newArrays=" << result <<
- " < neededArrays=" << neededArrays;;
+ ", new_entries=" << result <<
+ " < needed_entries=" << needed_entries;
throw vespalib::OverflowException(s.c_str());
}
return result;
}
uint32_t
-BufferTypeBase::get_scaled_num_arrays_for_new_buffer() const
+BufferTypeBase::get_scaled_num_entries_for_new_buffer() const
{
uint32_t active_buffers_count = get_active_buffers_count();
- if (active_buffers_count <= 1u || _numArraysForNewBuffer == 0u) {
- return _numArraysForNewBuffer;
+ if (active_buffers_count <= 1u || _num_entries_for_new_buffer == 0u) {
+ return _num_entries_for_new_buffer;
}
double scale_factor = std::pow(1.0 + _allocGrowFactor, active_buffers_count - 1);
- double scaled_result = _numArraysForNewBuffer * scale_factor;
- if (scaled_result >= _maxArrays) {
- return _maxArrays;
+ double scaled_result = _num_entries_for_new_buffer * scale_factor;
+ if (scaled_result >= _max_entries) {
+ return _max_entries;
}
return scaled_result;
}
@@ -194,22 +191,22 @@ BufferTypeBase::AggregatedBufferCounts::AggregatedBufferCounts()
}
void
-BufferTypeBase::AggregatedBufferCounts::add_buffer(const std::atomic<ElemCount>* used_elems, const std::atomic<ElemCount>* dead_elems)
+BufferTypeBase::AggregatedBufferCounts::add_buffer(const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries)
{
for (const auto& elem : _counts) {
- assert(elem.used_ptr != used_elems);
- assert(elem.dead_ptr != dead_elems);
+ assert(elem.used_ptr != used_entries);
+ assert(elem.dead_ptr != dead_entries);
}
- _counts.emplace_back(used_elems, dead_elems);
+ _counts.emplace_back(used_entries, dead_entries);
}
void
-BufferTypeBase::AggregatedBufferCounts::remove_buffer(const std::atomic<ElemCount>* used_elems, const std::atomic<ElemCount>* dead_elems)
+BufferTypeBase::AggregatedBufferCounts::remove_buffer(const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries)
{
auto itr = std::find_if(_counts.begin(), _counts.end(),
- [=](const auto& elem){ return elem.used_ptr == used_elems; });
+ [=](const auto& elem){ return elem.used_ptr == used_entries; });
assert(itr != _counts.end());
- assert(itr->dead_ptr == dead_elems);
+ assert(itr->dead_ptr == dead_entries);
_counts.erase(itr);
}
@@ -219,8 +216,8 @@ BufferTypeBase::AggregatedBufferCounts::last_buffer() const
BufferCounts result;
assert(!_counts.empty());
const auto& last = _counts.back();
- result.used_elems += last.used_ptr->load(std::memory_order_relaxed);
- result.dead_elems += last.dead_ptr->load(std::memory_order_relaxed);
+ result.used_entries += last.used_ptr->load(std::memory_order_relaxed);
+ result.dead_entries += last.dead_ptr->load(std::memory_order_relaxed);
return result;
}
@@ -229,8 +226,8 @@ BufferTypeBase::AggregatedBufferCounts::all_buffers() const
{
BufferCounts result;
for (const auto& elem : _counts) {
- result.used_elems += elem.used_ptr->load(std::memory_order_relaxed);
- result.dead_elems += elem.dead_ptr->load(std::memory_order_relaxed);
+ result.used_entries += elem.used_ptr->load(std::memory_order_relaxed);
+ result.dead_entries += elem.dead_ptr->load(std::memory_order_relaxed);
}
return result;
}
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_type.h b/vespalib/src/vespa/vespalib/datastore/buffer_type.h
index bedbb2c984e..ea52b026228 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_type.h
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_type.h
@@ -10,7 +10,7 @@ namespace vespalib::alloc { class MemoryAllocator; }
namespace vespalib::datastore {
-using ElemCount = uint64_t;
+using EntryCount = uint32_t;
/**
* Abstract class used to manage allocation and de-allocation of a specific data type in underlying memory buffers in a data store.
@@ -22,7 +22,7 @@ using ElemCount = uint64_t;
class BufferTypeBase
{
public:
- using ElemCount = vespalib::datastore::ElemCount;
+ using EntryCount = vespalib::datastore::EntryCount;
class CleanContext {
private:
std::atomic<size_t> &_extraUsedBytes;
@@ -39,53 +39,52 @@ public:
BufferTypeBase & operator=(const BufferTypeBase &rhs) = delete;
BufferTypeBase(BufferTypeBase &&rhs) noexcept = default;
BufferTypeBase & operator=(BufferTypeBase &&rhs) noexcept = default;
- BufferTypeBase(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays) noexcept;
- BufferTypeBase(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays,
- uint32_t numArraysForNewBuffer, float allocGrowFactor) noexcept;
+ BufferTypeBase(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries) noexcept;
+ BufferTypeBase(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries,
+ uint32_t num_entries_for_new_buffer, float allocGrowFactor) noexcept;
virtual ~BufferTypeBase();
- virtual void destroyElements(void *buffer, ElemCount numElems) = 0;
- virtual void fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems) = 0;
+ virtual void destroy_entries(void *buffer, EntryCount num_entries) = 0;
+ virtual void fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries) = 0;
/**
- * Return number of reserved elements at start of buffer, to avoid
- * invalid reference and handle data at negative offset (alignment
- * hacks) as used by dense tensor store.
+ * Return number of reserved entries at start of buffer, to avoid
+ * invalid reference.
*/
- virtual ElemCount getReservedElements(uint32_t bufferId) const;
+ virtual EntryCount get_reserved_entries(uint32_t bufferId) const;
/**
* Initialize reserved elements at start of buffer.
*/
- virtual void initializeReservedElements(void *buffer, ElemCount reservedElements) = 0;
- virtual size_t elementSize() const = 0;
- virtual void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) = 0;
+ virtual void initialize_reserved_entries(void *buffer, EntryCount reserved_entries) = 0;
+ virtual size_t entry_size() const = 0; // Size of entry measured in bytes
+ virtual void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) = 0;
size_t getArraySize() const { return _arraySize; }
- virtual void onActive(uint32_t bufferId, std::atomic<ElemCount>* usedElems, std::atomic<ElemCount>* deadElems, void* buffer);
- void onHold(uint32_t buffer_id, const std::atomic<ElemCount>* usedElems, const std::atomic<ElemCount>* deadElems);
- virtual void onFree(ElemCount usedElems);
- void resume_primary_buffer(uint32_t buffer_id, std::atomic<ElemCount>* used_elems, std::atomic<ElemCount>* dead_elems);
+ virtual void on_active(uint32_t bufferId, std::atomic<EntryCount>* used_entries, std::atomic<EntryCount>* dead_entries, void* buffer);
+ void on_hold(uint32_t buffer_id, const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries);
+ virtual void on_free(EntryCount used_entries);
+ void resume_primary_buffer(uint32_t buffer_id, std::atomic<EntryCount>* used_entries, std::atomic<EntryCount>* dead_entries);
virtual const alloc::MemoryAllocator* get_memory_allocator() const;
/**
- * Calculate number of arrays to allocate for new buffer given how many elements are needed.
+ * Calculate number of entries to allocate for new buffer given how many free entries are needed.
*/
- virtual size_t calcArraysToAlloc(uint32_t bufferId, ElemCount elementsNeeded, bool resizing) const;
+ virtual size_t calc_entries_to_alloc(uint32_t bufferId, EntryCount free_entries_needed, bool resizing) const;
- void clampMaxArrays(uint32_t maxArrays);
+ void clamp_max_entries(uint32_t max_entries);
uint32_t get_active_buffers_count() const { return _active_buffers.size(); }
const std::vector<uint32_t>& get_active_buffers() const noexcept { return _active_buffers; }
- size_t getMaxArrays() const { return _maxArrays; }
- uint32_t get_scaled_num_arrays_for_new_buffer() const;
- uint32_t get_num_arrays_for_new_buffer() const noexcept { return _numArraysForNewBuffer; }
+ size_t get_max_entries() const { return _max_entries; }
+ uint32_t get_scaled_num_entries_for_new_buffer() const;
+ uint32_t get_num_entries_for_new_buffer() const noexcept { return _num_entries_for_new_buffer; }
protected:
struct BufferCounts {
- ElemCount used_elems;
- ElemCount dead_elems;
- BufferCounts() : used_elems(0), dead_elems(0) {}
- BufferCounts(ElemCount used_elems_in, ElemCount dead_elems_in)
- : used_elems(used_elems_in), dead_elems(dead_elems_in)
+ EntryCount used_entries;
+ EntryCount dead_entries;
+ BufferCounts() : used_entries(0), dead_entries(0) {}
+ BufferCounts(EntryCount used_entries_in, EntryCount dead_entries_in)
+ : used_entries(used_entries_in), dead_entries(dead_entries_in)
{}
};
@@ -94,45 +93,48 @@ protected:
*/
class AggregatedBufferCounts {
private:
- struct Element {
- const std::atomic<ElemCount>* used_ptr;
- const std::atomic<ElemCount>* dead_ptr;
- Element() noexcept : used_ptr(nullptr), dead_ptr(nullptr) {}
- Element(const std::atomic<ElemCount>* used_ptr_in, const std::atomic<ElemCount>* dead_ptr_in) noexcept
+ struct ActiveBufferCounts {
+ const std::atomic<EntryCount>* used_ptr;
+ const std::atomic<EntryCount>* dead_ptr;
+ ActiveBufferCounts() noexcept : used_ptr(nullptr), dead_ptr(nullptr) {}
+ ActiveBufferCounts(const std::atomic<EntryCount>* used_ptr_in, const std::atomic<EntryCount>* dead_ptr_in) noexcept
: used_ptr(used_ptr_in), dead_ptr(dead_ptr_in)
{}
};
- std::vector<Element> _counts;
+ std::vector<ActiveBufferCounts> _counts;
public:
AggregatedBufferCounts();
- void add_buffer(const std::atomic<ElemCount>* used_elems, const std::atomic<ElemCount>* dead_elems);
- void remove_buffer(const std::atomic<ElemCount>* used_elems, const std::atomic<ElemCount>* dead_elems);
+ void add_buffer(const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries);
+ void remove_buffer(const std::atomic<EntryCount>* used_entries, const std::atomic<EntryCount>* dead_entries);
BufferCounts last_buffer() const;
BufferCounts all_buffers() const;
bool empty() const { return _counts.empty(); }
};
uint32_t _arraySize; // Number of elements in an allocation unit
- uint32_t _minArrays; // Minimum number of arrays to allocate in a buffer
- uint32_t _maxArrays; // Maximum number of arrays to allocate in a buffer
- // Number of arrays needed before allocating a new buffer instead of just resizing the first one
- uint32_t _numArraysForNewBuffer;
+ uint32_t _min_entries; // Minimum number of entries to allocate in a buffer
+ uint32_t _max_entries; // Maximum number of entries to allocate in a buffer
+ // Number of entries needed before allocating a new buffer instead of just resizing the first one
+ uint32_t _num_entries_for_new_buffer;
float _allocGrowFactor;
uint32_t _holdBuffers;
- size_t _holdUsedElems; // Number of used elements in all held buffers for this type.
+ size_t _hold_used_entries; // Number of used entries in all held buffers for this type.
AggregatedBufferCounts _aggr_counts;
std::vector<uint32_t> _active_buffers;
};
/**
- * Concrete class used to manage allocation and de-allocation of elements of type EntryType in data store buffers.
+ * Concrete class used to manage allocation and de-allocation of elements of type ElemType in data store buffers.
*/
-template <typename EntryType, typename EmptyType = EntryType>
+template <typename ElemT, typename EmptyT = ElemT>
class BufferType : public BufferTypeBase
{
+public:
+ using ElemType = ElemT;
+ using EmptyType = EmptyT;
protected:
- static const EntryType& empty_entry() noexcept;
+ static const ElemType& empty_entry() noexcept;
public:
BufferType() noexcept : BufferType(1,1,1) {}
@@ -140,15 +142,15 @@ public:
BufferType & operator=(const BufferType &rhs) = delete;
BufferType(BufferType && rhs) noexcept = default;
BufferType & operator=(BufferType && rhs) noexcept = default;
- BufferType(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays) noexcept;
- BufferType(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays,
- uint32_t numArraysForNewBuffer, float allocGrowFactor) noexcept;
+ BufferType(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries) noexcept;
+ BufferType(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries,
+ uint32_t num_entries_for_new_buffer, float allocGrowFactor) noexcept;
~BufferType() override;
- void destroyElements(void *buffer, ElemCount numElems) override;
- void fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems) override;
- void initializeReservedElements(void *buffer, ElemCount reservedElements) override;
- void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCxt) override;
- size_t elementSize() const override { return sizeof(EntryType); }
+ void destroy_entries(void *buffer, EntryCount num_entries) override;
+ void fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries) override;
+ void initialize_reserved_entries(void *buffer, EntryCount reserved_entries) override;
+ void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCxt) override;
+ size_t entry_size() const override { return sizeof(ElemType) * _arraySize; }
};
extern template class BufferType<char>;
diff --git a/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp b/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
index 72c8f574a70..60acca5ff39 100644
--- a/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/buffer_type.hpp
@@ -6,78 +6,80 @@
namespace vespalib::datastore {
-template <typename EntryType, typename EmptyType>
-BufferType<EntryType, EmptyType>::BufferType(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays) noexcept
- : BufferTypeBase(arraySize, minArrays, maxArrays)
+template <typename ElemT, typename EmptyT>
+BufferType<ElemT, EmptyT>::BufferType(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries) noexcept
+ : BufferTypeBase(arraySize, min_entries, max_entries)
{ }
-template <typename EntryType, typename EmptyType>
-BufferType<EntryType, EmptyType>::BufferType(uint32_t arraySize, uint32_t minArrays, uint32_t maxArrays,
- uint32_t numArraysForNewBuffer, float allocGrowFactor) noexcept
- : BufferTypeBase(arraySize, minArrays, maxArrays, numArraysForNewBuffer, allocGrowFactor)
+template <typename ElemT, typename EmptyT>
+BufferType<ElemT, EmptyT>::BufferType(uint32_t arraySize, uint32_t min_entries, uint32_t max_entries,
+ uint32_t num_entries_for_new_buffer, float allocGrowFactor) noexcept
+ : BufferTypeBase(arraySize, min_entries, max_entries, num_entries_for_new_buffer, allocGrowFactor)
{ }
-template <typename EntryType, typename EmptyType>
-BufferType<EntryType, EmptyType>::~BufferType() = default;
+template <typename ElemT, typename EmptyT>
+BufferType<ElemT, EmptyT>::~BufferType() = default;
-template <typename EntryType, typename EmptyType>
+template <typename ElemT, typename EmptyT>
void
-BufferType<EntryType, EmptyType>::destroyElements(void *buffer, ElemCount numElems)
+BufferType<ElemT, EmptyT>::destroy_entries(void *buffer, EntryCount num_entries)
{
- EntryType *e = static_cast<EntryType *>(buffer);
- for (size_t j = numElems; j != 0; --j) {
- e->~EntryType();
+ auto num_elems = num_entries * getArraySize();
+ ElemType *e = static_cast<ElemType *>(buffer);
+ for (size_t j = num_elems; j != 0; --j) {
+ e->~ElemType();
++e;
}
}
-template <typename EntryType, typename EmptyType>
+template <typename ElemT, typename EmptyT>
void
-BufferType<EntryType, EmptyType>::fallbackCopy(void *newBuffer,
- const void *oldBuffer,
- ElemCount numElems)
+BufferType<ElemT, EmptyT>::fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries)
{
- EntryType *d = static_cast<EntryType *>(newBuffer);
- const EntryType *s = static_cast<const EntryType *>(oldBuffer);
- for (size_t j = numElems; j != 0; --j) {
- new (static_cast<void *>(d)) EntryType(*s);
+ auto num_elems = num_entries * getArraySize();
+ ElemType *d = static_cast<ElemType *>(newBuffer);
+ const ElemType *s = static_cast<const ElemType *>(oldBuffer);
+ for (size_t j = num_elems; j != 0; --j) {
+ new (static_cast<void *>(d)) ElemType(*s);
++s;
++d;
}
}
-template <typename EntryType, typename EmptyType>
+template <typename ElemT, typename EmptyT>
void
-BufferType<EntryType, EmptyType>::initializeReservedElements(void *buffer, ElemCount reservedElems)
+BufferType<ElemT, EmptyT>::initialize_reserved_entries(void *buffer, EntryCount reserved_entries)
{
- EntryType *e = static_cast<EntryType *>(buffer);
+ auto reserved_elems = reserved_entries * getArraySize();
+ ElemType *e = static_cast<ElemType *>(buffer);
const auto& empty = empty_entry();
- for (size_t j = reservedElems; j != 0; --j) {
- new (static_cast<void *>(e)) EntryType(empty);
+ for (size_t j = reserved_elems; j != 0; --j) {
+ new (static_cast<void *>(e)) ElemType(empty);
++e;
}
}
-template <typename EntryType, typename EmptyType>
+template <typename ElemT, typename EmptyT>
void
-BufferType<EntryType, EmptyType>::cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext)
+BufferType<ElemT, EmptyT>::clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext)
{
- EntryType *e = static_cast<EntryType *>(buffer) + offset;
+ auto num_elems = num_entries * getArraySize();
+ ElemType *e = static_cast<ElemType *>(buffer) + offset * getArraySize();
const auto& empty = empty_entry();
- for (size_t j = numElems; j != 0; --j) {
+ for (size_t j = num_elems; j != 0; --j) {
*e = empty;
++e;
}
}
-template <typename EntryType, typename EmptyType>
-const EntryType&
-BufferType<EntryType, EmptyType>::empty_entry() noexcept
+template <typename ElemT, typename EmptyT>
+const ElemT&
+BufferType<ElemT, EmptyT>::empty_entry() noexcept
{
- // It's possible for EntryType to wrap e.g. an Alloc instance, which has a transitive
+ // It's possible for ElemType to wrap e.g. an Alloc instance, which has a transitive
// dependency on globally constructed allocator object(s). To avoid issues with global
// construction order, initialize the sentinel on the first access.
- static EntryType empty = EmptyType();
+ static ElemType empty = EmptyType();
return empty;
}
diff --git a/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp b/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
index 47fba1ef697..f312596d6f7 100644
--- a/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/bufferstate.cpp
@@ -12,13 +12,13 @@ namespace vespalib::datastore {
BufferState::BufferState()
: _stats(),
- _free_list(_stats.dead_elems_ref()),
+ _free_list(_stats.dead_entries_ref()),
_typeHandler(nullptr),
_buffer(Alloc::alloc(0, MemoryAllocator::HUGEPAGE_SIZE)),
_arraySize(0),
_typeId(0),
_state(State::FREE),
- _disableElemHoldList(false),
+ _disable_entry_hold_list(false),
_compacting(false)
{
}
@@ -28,15 +28,15 @@ BufferState::~BufferState()
assert(getState() == State::FREE);
assert(!_free_list.enabled());
assert(_free_list.empty());
- assert(_stats.hold_elems() == 0);
+ assert(_stats.hold_entries() == 0);
}
namespace {
struct AllocResult {
- size_t elements;
+ size_t entries;
size_t bytes;
- AllocResult(size_t elements_, size_t bytes_) : elements(elements_), bytes(bytes_) {}
+ AllocResult(size_t entries_, size_t bytes_) : entries(entries_), bytes(bytes_) {}
};
size_t
@@ -57,30 +57,30 @@ roundUpToMatchAllocator(size_t sz)
}
AllocResult
-calcAllocation(uint32_t bufferId,
- BufferTypeBase &typeHandler,
- size_t elementsNeeded,
- bool resizing)
+calc_allocation(uint32_t bufferId,
+ BufferTypeBase &typeHandler,
+ size_t free_entries_needed,
+ bool resizing)
{
- size_t allocArrays = typeHandler.calcArraysToAlloc(bufferId, elementsNeeded, resizing);
- size_t allocElements = allocArrays * typeHandler.getArraySize();
- size_t allocBytes = roundUpToMatchAllocator(allocElements * typeHandler.elementSize());
- size_t maxAllocBytes = typeHandler.getMaxArrays() * typeHandler.getArraySize() * typeHandler.elementSize();
+ size_t alloc_entries = typeHandler.calc_entries_to_alloc(bufferId, free_entries_needed, resizing);
+ size_t entry_size = typeHandler.entry_size();
+ size_t allocBytes = roundUpToMatchAllocator(alloc_entries * entry_size);
+ size_t maxAllocBytes = typeHandler.get_max_entries() * entry_size;
if (allocBytes > maxAllocBytes) {
// Ensure that allocated bytes does not exceed the maximum handled by this type.
allocBytes = maxAllocBytes;
}
- size_t adjustedAllocElements = (allocBytes / typeHandler.elementSize());
- return AllocResult(adjustedAllocElements, allocBytes);
+ size_t adjusted_alloc_entries = allocBytes / entry_size;
+ return AllocResult(adjusted_alloc_entries, allocBytes);
}
}
void
-BufferState::onActive(uint32_t bufferId, uint32_t typeId,
- BufferTypeBase *typeHandler,
- size_t elementsNeeded,
- std::atomic<void*>& buffer)
+BufferState::on_active(uint32_t bufferId, uint32_t typeId,
+ BufferTypeBase *typeHandler,
+ size_t free_entries_needed,
+ std::atomic<void*>& buffer)
{
assert(buffer.load(std::memory_order_relaxed) == nullptr);
assert(_buffer.get() == nullptr);
@@ -88,30 +88,29 @@ BufferState::onActive(uint32_t bufferId, uint32_t typeId,
assert(_typeHandler == nullptr);
assert(capacity() == 0);
assert(size() == 0);
- assert(_stats.dead_elems() == 0u);
- assert(_stats.hold_elems() == 0);
+ assert(_stats.dead_entries() == 0u);
+ assert(_stats.hold_entries() == 0);
assert(_stats.extra_used_bytes() == 0);
assert(_stats.extra_hold_bytes() == 0);
assert(_free_list.empty());
- size_t reservedElements = typeHandler->getReservedElements(bufferId);
- (void) reservedElements;
- AllocResult alloc = calcAllocation(bufferId, *typeHandler, elementsNeeded, false);
- assert(alloc.elements >= reservedElements + elementsNeeded);
+ size_t reserved_entries = typeHandler->get_reserved_entries(bufferId);
+ (void) reserved_entries;
+ AllocResult alloc = calc_allocation(bufferId, *typeHandler, free_entries_needed, false);
+ assert(alloc.entries >= reserved_entries + free_entries_needed);
auto allocator = typeHandler->get_memory_allocator();
_buffer = (allocator != nullptr) ? Alloc::alloc_with_allocator(allocator) : Alloc::alloc(0, MemoryAllocator::HUGEPAGE_SIZE);
_buffer.create(alloc.bytes).swap(_buffer);
- assert(_buffer.get() != nullptr || alloc.elements == 0u);
+ assert(_buffer.get() != nullptr || alloc.entries == 0u);
buffer.store(_buffer.get(), std::memory_order_release);
- _stats.set_alloc_elems(alloc.elements);
+ _stats.set_alloc_entries(alloc.entries);
_typeHandler.store(typeHandler, std::memory_order_release);
assert(typeId <= std::numeric_limits<uint16_t>::max());
_typeId = typeId;
_arraySize = typeHandler->getArraySize();
- _free_list.set_array_size(_arraySize);
_state.store(State::ACTIVE, std::memory_order_release);
- typeHandler->onActive(bufferId, &_stats.used_elems_ref(), &_stats.dead_elems_ref(),
- buffer.load(std::memory_order::relaxed));
+ typeHandler->on_active(bufferId, &_stats.used_entries_ref(), &_stats.dead_entries_ref(),
+ buffer.load(std::memory_order::relaxed));
}
void
@@ -121,11 +120,11 @@ BufferState::onHold(uint32_t buffer_id)
assert(getTypeHandler() != nullptr);
_state.store(State::HOLD, std::memory_order_release);
_compacting = false;
- assert(_stats.dead_elems() <= size());
- assert(_stats.hold_elems() <= (size() - _stats.dead_elems()));
- _stats.set_dead_elems(0);
- _stats.set_hold_elems(size());
- getTypeHandler()->onHold(buffer_id, &_stats.used_elems_ref(), &_stats.dead_elems_ref());
+ assert(_stats.dead_entries() <= size());
+ assert(_stats.hold_entries() <= (size() - _stats.dead_entries()));
+ _stats.set_dead_entries(0);
+ _stats.set_hold_entries(size());
+ getTypeHandler()->on_hold(buffer_id, &_stats.used_entries_ref(), &_stats.dead_entries_ref());
_free_list.disable();
}
@@ -135,20 +134,19 @@ BufferState::onFree(std::atomic<void*>& buffer)
assert(buffer.load(std::memory_order_relaxed) == _buffer.get());
assert(getState() == State::HOLD);
assert(_typeHandler != nullptr);
- assert(_stats.dead_elems() <= size());
- assert(_stats.hold_elems() == (size() - _stats.dead_elems()));
- getTypeHandler()->destroyElements(buffer, size());
+ assert(_stats.dead_entries() <= size());
+ assert(_stats.hold_entries() == (size() - _stats.dead_entries()));
+ getTypeHandler()->destroy_entries(buffer, size());
Alloc::alloc().swap(_buffer);
- getTypeHandler()->onFree(size());
+ getTypeHandler()->on_free(size());
buffer.store(nullptr, std::memory_order_release);
_stats.clear();
_state.store(State::FREE, std::memory_order_release);
_typeHandler = nullptr;
_arraySize = 0;
- _free_list.set_array_size(_arraySize);
assert(!_free_list.enabled());
assert(_free_list.empty());
- _disableElemHoldList = false;
+ _disable_entry_hold_list = false;
}
@@ -171,67 +169,67 @@ BufferState::dropBuffer(uint32_t buffer_id, std::atomic<void*>& buffer)
}
void
-BufferState::disable_elem_hold_list()
+BufferState::disable_entry_hold_list()
{
- _disableElemHoldList = true;
+ _disable_entry_hold_list = true;
}
bool
-BufferState::hold_elems(size_t num_elems, size_t extra_bytes)
+BufferState::hold_entries(size_t num_entries, size_t extra_bytes)
{
assert(isActive());
- if (_disableElemHoldList) {
+ if (_disable_entry_hold_list) {
// The elements are directly marked as dead as they are not put on hold.
- _stats.inc_dead_elems(num_elems);
+ _stats.inc_dead_entries(num_entries);
return true;
}
- _stats.inc_hold_elems(num_elems);
+ _stats.inc_hold_entries(num_entries);
_stats.inc_extra_hold_bytes(extra_bytes);
return false;
}
void
-BufferState::free_elems(EntryRef ref, size_t num_elems, size_t ref_offset)
+BufferState::free_entries(EntryRef ref, size_t num_entries, size_t ref_offset)
{
if (isActive()) {
- if (_free_list.enabled() && (num_elems == getArraySize())) {
+ if (_free_list.enabled() && (num_entries == 1)) {
_free_list.push_entry(ref);
}
} else {
assert(isOnHold());
}
- _stats.inc_dead_elems(num_elems);
- _stats.dec_hold_elems(num_elems);
- getTypeHandler()->cleanHold(_buffer.get(), (ref_offset * _arraySize), num_elems,
- BufferTypeBase::CleanContext(_stats.extra_used_bytes_ref(),
- _stats.extra_hold_bytes_ref()));
+ _stats.inc_dead_entries(num_entries);
+ _stats.dec_hold_entries(num_entries);
+ getTypeHandler()->clean_hold(_buffer.get(), ref_offset, num_entries,
+ BufferTypeBase::CleanContext(_stats.extra_used_bytes_ref(),
+ _stats.extra_hold_bytes_ref()));
}
void
-BufferState::fallbackResize(uint32_t bufferId,
- size_t elementsNeeded,
+BufferState::fallback_resize(uint32_t bufferId,
+ size_t free_entries_needed,
std::atomic<void*>& buffer,
Alloc &holdBuffer)
{
assert(getState() == State::ACTIVE);
assert(_typeHandler != nullptr);
assert(holdBuffer.get() == nullptr);
- AllocResult alloc = calcAllocation(bufferId, *_typeHandler, elementsNeeded, true);
- assert(alloc.elements >= size() + elementsNeeded);
- assert(alloc.elements > capacity());
+ AllocResult alloc = calc_allocation(bufferId, *_typeHandler, free_entries_needed, true);
+ assert(alloc.entries >= size() + free_entries_needed);
+ assert(alloc.entries > capacity());
Alloc newBuffer = _buffer.create(alloc.bytes);
- getTypeHandler()->fallbackCopy(newBuffer.get(), buffer.load(std::memory_order_relaxed), size());
+ getTypeHandler()->fallback_copy(newBuffer.get(), buffer.load(std::memory_order_relaxed), size());
holdBuffer.swap(_buffer);
std::atomic_thread_fence(std::memory_order_release);
_buffer = std::move(newBuffer);
buffer.store(_buffer.get(), std::memory_order_release);
- _stats.set_alloc_elems(alloc.elements);
+ _stats.set_alloc_entries(alloc.entries);
}
void
BufferState::resume_primary_buffer(uint32_t buffer_id)
{
- getTypeHandler()->resume_primary_buffer(buffer_id, &_stats.used_elems_ref(), &_stats.dead_elems_ref());
+ getTypeHandler()->resume_primary_buffer(buffer_id, &_stats.used_entries_ref(), &_stats.dead_entries_ref());
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/bufferstate.h b/vespalib/src/vespa/vespalib/datastore/bufferstate.h
index 5b98099ed69..f714f8e24d5 100644
--- a/vespalib/src/vespa/vespalib/datastore/bufferstate.h
+++ b/vespalib/src/vespa/vespalib/datastore/bufferstate.h
@@ -23,8 +23,8 @@ namespace vespalib::datastore {
* It is kept in this state until all reader threads are no longer accessing the buffer.
* Finally, it transitions back to FREE via onFree() and memory is de-allocated.
*
- * This class also supports use of free lists, where previously allocated elements in the buffer can be re-used.
- * First the element is put on hold, then on the free list (counted as dead) to be re-used.
+ * This class also supports use of free lists, where previously allocated entries in the buffer can be re-used.
+ * First the entry is put on hold, then on the free list (counted as dead) to be re-used.
*/
class BufferState
{
@@ -45,7 +45,7 @@ private:
uint32_t _arraySize;
uint16_t _typeId;
std::atomic<State> _state;
- bool _disableElemHoldList : 1;
+ bool _disable_entry_hold_list : 1;
bool _compacting : 1;
public:
@@ -62,14 +62,14 @@ public:
/**
* Transition from FREE to ACTIVE state.
*
- * @param bufferId Id of buffer to be active.
- * @param typeId Registered data type id for buffer.
- * @param typeHandler Type handler for registered data type.
- * @param elementsNeeded Number of elements needed to be free in the memory allocated.
- * @param buffer Start of allocated buffer return value.
+ * @param bufferId Id of buffer to be active.
+ * @param typeId Registered data type id for buffer.
+ * @param typeHandler Type handler for registered data type.
+ * @param free_entries_needed Number of entries needed to be free in the memory allocated.
+ * @param buffer Start of allocated buffer return value.
*/
- void onActive(uint32_t bufferId, uint32_t typeId, BufferTypeBase *typeHandler,
- size_t elementsNeeded, std::atomic<void*>& buffer);
+ void on_active(uint32_t bufferId, uint32_t typeId, BufferTypeBase *typeHandler,
+ size_t free_entries_needed, std::atomic<void*>& buffer);
/**
* Transition from ACTIVE to HOLD state.
@@ -82,24 +82,24 @@ public:
void onFree(std::atomic<void*>& buffer);
/**
- * Disable hold of elements, just mark elements as dead without cleanup.
+ * Disable hold of entries, just mark entries as dead without cleanup.
* Typically used when tearing down data structure in a controlled manner.
*/
- void disable_elem_hold_list();
+ void disable_entry_hold_list();
/**
- * Update stats to reflect that the given elements are put on hold.
- * Returns true if element hold list is disabled for this buffer.
+ * Update stats to reflect that the given entries are put on hold.
+ * Returns true if entry hold list is disabled for this buffer.
*/
- bool hold_elems(size_t num_elems, size_t extra_bytes);
+ bool hold_entries(size_t num_entries, size_t extra_bytes);
/**
- * Free the given elements and update stats accordingly.
+ * Free the given entries and update stats accordingly.
*
* The given entry ref is put on the free list (if enabled).
- * Hold cleaning of elements is executed on the buffer type.
+ * Hold cleaning of entries is executed on the buffer type.
*/
- void free_elems(EntryRef ref, size_t num_elems, size_t ref_offset);
+ void free_entries(EntryRef ref, size_t num_entries, size_t ref_offset);
BufferStats& stats() { return _stats; }
const BufferStats& stats() const { return _stats; }
@@ -115,8 +115,7 @@ public:
uint32_t getArraySize() const { return _arraySize; }
bool getCompacting() const { return _compacting; }
void setCompacting() { _compacting = true; }
- uint32_t get_used_arrays() const noexcept { return size() / _arraySize; }
- void fallbackResize(uint32_t bufferId, size_t elementsNeeded, std::atomic<void*>& buffer, Alloc &holdBuffer);
+ void fallback_resize(uint32_t bufferId, size_t free_entries_needed, std::atomic<void*>& buffer, Alloc &holdBuffer);
bool isActive(uint32_t typeId) const {
return (isActive() && (_typeId == typeId));
diff --git a/vespalib/src/vespa/vespalib/datastore/datastore.h b/vespalib/src/vespa/vespalib/datastore/datastore.h
index 01b81d0fa58..f81348ce287 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastore.h
+++ b/vespalib/src/vespa/vespalib/datastore/datastore.h
@@ -27,7 +27,7 @@ template <typename RefT = EntryRefT<22> >
class DataStoreT : public DataStoreBase
{
private:
- void free_elem_internal(EntryRef ref, size_t numElems);
+ void free_entry_internal(EntryRef ref, size_t num_entries);
public:
using RefType = RefT;
@@ -38,12 +38,12 @@ public:
~DataStoreT() override;
/**
- * Hold element(s).
+ * Hold entries.
*/
- void holdElem(EntryRef ref, size_t numElems) {
- holdElem(ref, numElems, 0);
- }
- void holdElem(EntryRef ref, size_t numElems, size_t extraBytes);
+ void hold_entry(EntryRef ref) { hold_entries(ref, 1, 0); }
+ void hold_entry(EntryRef ref, size_t extra_bytes) { hold_entries(ref, 1, extra_bytes); }
+ void hold_entries(EntryRef ref, size_t num_entries) { hold_entries(ref, num_entries, 0); }
+ void hold_entries(EntryRef ref, size_t num_entries, size_t extraBytes);
void reclaim_entry_refs(generation_t oldest_used_gen) override;
@@ -75,7 +75,7 @@ class DataStore : public DataStoreT<RefT>
{
protected:
using ParentType = DataStoreT<RefT>;
- using ParentType::ensureBufferCapacity;
+ using ParentType::ensure_buffer_capacity;
using ParentType::getEntry;
using ParentType::dropBuffers;
using ParentType::init_primary_buffers;
diff --git a/vespalib/src/vespa/vespalib/datastore/datastore.hpp b/vespalib/src/vespa/vespalib/datastore/datastore.hpp
index bfb63954875..b21a5954eee 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastore.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/datastore.hpp
@@ -22,21 +22,21 @@ DataStoreT<RefT>::~DataStoreT() = default;
template <typename RefT>
void
-DataStoreT<RefT>::free_elem_internal(EntryRef ref, size_t numElems)
+DataStoreT<RefT>::free_entry_internal(EntryRef ref, size_t num_entries)
{
RefType intRef(ref);
BufferState &state = getBufferState(intRef.bufferId());
- state.free_elems(ref, numElems, intRef.offset());
+ state.free_entries(ref, num_entries, intRef.offset());
}
template <typename RefT>
void
-DataStoreT<RefT>::holdElem(EntryRef ref, size_t numElems, size_t extraBytes)
+DataStoreT<RefT>::hold_entries(EntryRef ref, size_t num_entries, size_t extraBytes)
{
RefType intRef(ref);
BufferState &state = getBufferState(intRef.bufferId());
- if (!state.hold_elems(numElems, extraBytes)) {
- _entry_ref_hold_list.insert({ref, numElems});
+ if (!state.hold_entries(num_entries, extraBytes)) {
+ _entry_ref_hold_list.insert({ref, num_entries});
}
}
@@ -45,7 +45,7 @@ void
DataStoreT<RefT>::reclaim_entry_refs(generation_t oldest_used_gen)
{
_entry_ref_hold_list.reclaim(oldest_used_gen, [this](const auto& elem) {
- free_elem_internal(elem.ref, elem.num_elems);
+ free_entry_internal(elem.ref, elem.num_entries);
});
}
@@ -54,7 +54,7 @@ void
DataStoreT<RefT>::reclaim_all_entry_refs()
{
_entry_ref_hold_list.reclaim_all([this](const auto& elem) {
- free_elem_internal(elem.ref, elem.num_elems);
+ free_entry_internal(elem.ref, elem.num_entries);
});
}
diff --git a/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp b/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
index a40aa713bca..75ffe855a32 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/datastorebase.cpp
@@ -40,18 +40,18 @@ constexpr size_t TOO_DEAD_SLACK = 0x4000u;
bool
primary_buffer_too_dead(const BufferState &state)
{
- size_t deadElems = state.stats().dead_elems();
- size_t deadBytes = deadElems * state.getArraySize();
- return ((deadBytes >= TOO_DEAD_SLACK) && (deadElems * 2 >= state.size()));
+ size_t dead_entries = state.stats().dead_entries();
+ size_t deadBytes = dead_entries * state.getTypeHandler()->entry_size();
+ return ((deadBytes >= TOO_DEAD_SLACK) && (dead_entries * 2 >= state.size()));
}
}
-DataStoreBase::FallbackHold::FallbackHold(size_t bytesSize, BufferState::Alloc &&buffer, size_t usedElems,
+DataStoreBase::FallbackHold::FallbackHold(size_t bytesSize, BufferState::Alloc &&buffer, size_t used_entries,
BufferTypeBase *typeHandler, uint32_t typeId)
: GenerationHeldBase(bytesSize),
_buffer(std::move(buffer)),
- _usedElems(usedElems),
+ _used_entries(used_entries),
_typeHandler(typeHandler),
_typeId(typeId)
{
@@ -59,7 +59,7 @@ DataStoreBase::FallbackHold::FallbackHold(size_t bytesSize, BufferState::Alloc &
DataStoreBase::FallbackHold::~FallbackHold()
{
- _typeHandler->destroyElements(_buffer.get(), _usedElems);
+ _typeHandler->destroy_entries(_buffer.get(), _used_entries);
}
class DataStoreBase::BufferHold : public GenerationHeldBase {
@@ -80,7 +80,7 @@ public:
}
};
-DataStoreBase::DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t maxArrays)
+DataStoreBase::DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t max_entries)
: _entry_ref_hold_list(),
_buffers(numBuffers),
_primary_buffer_ids(),
@@ -89,12 +89,12 @@ DataStoreBase::DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t m
_free_lists(),
_compaction_count(0u),
_genHolder(),
- _maxArrays(maxArrays),
+ _max_entries(max_entries),
_bufferIdLimit(0u),
_hold_buffer_count(0u),
_offset_bits(offset_bits),
_freeListsEnabled(false),
- _disableElemHoldList(false),
+ _disable_entry_hold_list(false),
_initializing(false)
{
}
@@ -105,19 +105,19 @@ DataStoreBase::~DataStoreBase()
}
void
-DataStoreBase::switch_primary_buffer(uint32_t typeId, size_t elemsNeeded)
+DataStoreBase::switch_primary_buffer(uint32_t typeId, size_t entries_needed)
{
size_t buffer_id = getFirstFreeBufferId();
if (buffer_id >= getMaxNumBuffers()) {
LOG_ABORT(vespalib::make_string("switch_primary_buffer(%u, %zu): did not find a free buffer",
- typeId, elemsNeeded).c_str());
+ typeId, entries_needed).c_str());
}
- onActive(buffer_id, typeId, elemsNeeded);
+ on_active(buffer_id, typeId, entries_needed);
_primary_buffer_ids[typeId] = buffer_id;
}
bool
-DataStoreBase::consider_grow_active_buffer(uint32_t type_id, size_t elems_needed)
+DataStoreBase::consider_grow_active_buffer(uint32_t type_id, size_t entries_needed)
{
auto type_handler = _typeHandlers[type_id];
uint32_t buffer_id = primary_buffer_id(type_id);
@@ -126,7 +126,7 @@ DataStoreBase::consider_grow_active_buffer(uint32_t type_id, size_t elems_needed
if (active_buffers_count < min_active_buffers) {
return false;
}
- if (type_handler->get_num_arrays_for_new_buffer() == 0u) {
+ if (type_handler->get_num_entries_for_new_buffer() == 0u) {
return false;
}
assert(!getBufferState(buffer_id).getCompacting());
@@ -146,8 +146,7 @@ DataStoreBase::consider_grow_active_buffer(uint32_t type_id, size_t elems_needed
if (checked_active_buffers < min_active_buffers) {
return false;
}
- auto array_size = type_handler->getArraySize();
- if (elems_needed + min_used > type_handler->getMaxArrays() * array_size) {
+ if (entries_needed + min_used > type_handler->get_max_entries()) {
return false;
}
if (min_buffer_id != buffer_id) {
@@ -181,24 +180,22 @@ DataStoreBase::getBufferState(uint32_t buffer_id) noexcept {
}
void
-DataStoreBase::switch_or_grow_primary_buffer(uint32_t typeId, size_t elemsNeeded)
+DataStoreBase::switch_or_grow_primary_buffer(uint32_t typeId, size_t entries_needed)
{
auto typeHandler = _typeHandlers[typeId];
- uint32_t arraySize = typeHandler->getArraySize();
- size_t numArraysForNewBuffer = typeHandler->get_scaled_num_arrays_for_new_buffer();
- size_t numEntriesForNewBuffer = numArraysForNewBuffer * arraySize;
+ size_t num_entries_for_new_buffer = typeHandler->get_scaled_num_entries_for_new_buffer();
uint32_t bufferId = primary_buffer_id(typeId);
- if (elemsNeeded + getBufferState(bufferId).size() >= numEntriesForNewBuffer) {
- if (consider_grow_active_buffer(typeId, elemsNeeded)) {
+ if (entries_needed + getBufferState(bufferId).size() >= num_entries_for_new_buffer) {
+ if (consider_grow_active_buffer(typeId, entries_needed)) {
bufferId = primary_buffer_id(typeId);
- if (elemsNeeded > getBufferState(bufferId).remaining()) {
- fallbackResize(bufferId, elemsNeeded);
+ if (entries_needed > getBufferState(bufferId).remaining()) {
+ fallback_resize(bufferId, entries_needed);
}
} else {
- switch_primary_buffer(typeId, elemsNeeded);
+ switch_primary_buffer(typeId, entries_needed);
}
} else {
- fallbackResize(bufferId, elemsNeeded);
+ fallback_resize(bufferId, entries_needed);
}
}
@@ -209,7 +206,7 @@ DataStoreBase::init_primary_buffers()
for (uint32_t typeId = 0; typeId < numTypes; ++typeId) {
size_t buffer_id = getFirstFreeBufferId();
assert(buffer_id <= get_bufferid_limit_relaxed());
- onActive(buffer_id, typeId, 0u);
+ on_active(buffer_id, typeId, 0u);
_primary_buffer_ids[typeId] = buffer_id;
}
}
@@ -219,7 +216,7 @@ DataStoreBase::addType(BufferTypeBase *typeHandler)
{
uint32_t typeId = _primary_buffer_ids.size();
assert(typeId == _typeHandlers.size());
- typeHandler->clampMaxArrays(_maxArrays);
+ typeHandler->clamp_max_entries(_max_entries);
_primary_buffer_ids.push_back(0);
_typeHandlers.push_back(typeHandler);
_free_lists.emplace_back();
@@ -328,12 +325,12 @@ DataStoreBase::disableFreeLists()
}
void
-DataStoreBase::disableElemHoldList()
+DataStoreBase::disable_entry_hold_list()
{
for_each_buffer([](BufferState & state) {
- if (!state.isFree()) state.disable_elem_hold_list();
+ if (!state.isFree()) state.disable_entry_hold_list();
});
- _disableElemHoldList = true;
+ _disable_entry_hold_list = true;
}
MemoryStats
@@ -351,13 +348,13 @@ DataStoreBase::getMemStats() const
if ((state == BufferState::State::FREE) || (typeHandler == nullptr)) {
++stats._freeBuffers;
} else if (state == BufferState::State::ACTIVE) {
- size_t elementSize = typeHandler->elementSize();
+ size_t entry_size = typeHandler->entry_size();
++stats._activeBuffers;
- bState->stats().add_to_mem_stats(elementSize, stats);
+ bState->stats().add_to_mem_stats(entry_size, stats);
} else if (state == BufferState::State::HOLD) {
- size_t elementSize = typeHandler->elementSize();
+ size_t entry_size = typeHandler->entry_size();
++stats._holdBuffers;
- bState->stats().add_to_mem_stats(elementSize, stats);
+ bState->stats().add_to_mem_stats(entry_size, stats);
} else {
LOG_ABORT("should not be reached");
}
@@ -373,32 +370,30 @@ vespalib::AddressSpace
DataStoreBase::getAddressSpaceUsage() const
{
uint32_t buffer_id_limit = get_bufferid_limit_acquire();
- size_t usedArrays = 0;
- size_t deadArrays = 0;
- size_t limitArrays = size_t(_maxArrays) * (getMaxNumBuffers() - buffer_id_limit);
+ size_t used_entries = 0;
+ size_t dead_entries = 0;
+ size_t limit_entries = size_t(_max_entries) * (getMaxNumBuffers() - buffer_id_limit);
for (uint32_t bufferId = 0; bufferId < buffer_id_limit; ++bufferId) {
const BufferState * bState = _buffers[bufferId].get_state_acquire();
assert(bState != nullptr);
if (bState->isFree()) {
- limitArrays += _maxArrays;
+ limit_entries += _max_entries;
} else if (bState->isActive()) {
- uint32_t arraySize = bState->getArraySize();
- usedArrays += bState->size() / arraySize;
- deadArrays += bState->stats().dead_elems() / arraySize;
- limitArrays += bState->capacity() / arraySize;
+ used_entries += bState->size();
+ dead_entries += bState->stats().dead_entries();
+ limit_entries += bState->capacity();
} else if (bState->isOnHold()) {
- uint32_t arraySize = bState->getArraySize();
- usedArrays += bState->size() / arraySize;
- limitArrays += bState->capacity() / arraySize;
+ used_entries += bState->size();
+ limit_entries += bState->capacity();
} else {
LOG_ABORT("should not be reached");
}
}
- return {usedArrays, deadArrays, limitArrays};
+ return {used_entries, dead_entries, limit_entries};
}
void
-DataStoreBase::onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded)
+DataStoreBase::on_active(uint32_t bufferId, uint32_t typeId, size_t entries_needed)
{
assert(typeId < _typeHandlers.size());
assert(bufferId <= _bufferIdLimit);
@@ -407,8 +402,8 @@ DataStoreBase::onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded)
BufferState *state = bufferMeta.get_state_relaxed();
if (state == nullptr) {
BufferState & newState = _stash.create<BufferState>();
- if (_disableElemHoldList) {
- newState.disable_elem_hold_list();
+ if (_disable_entry_hold_list) {
+ newState.disable_entry_hold_list();
}
if ( ! _freeListsEnabled) {
newState.disable_free_list();
@@ -418,7 +413,7 @@ DataStoreBase::onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded)
_bufferIdLimit.store(bufferId + 1, std::memory_order_release);
}
assert(state->isFree());
- state->onActive(bufferId, typeId, _typeHandlers[typeId], elemsNeeded, bufferMeta.get_atomic_buffer());
+ state->on_active(bufferId, typeId, _typeHandlers[typeId], entries_needed, bufferMeta.get_atomic_buffer());
bufferMeta.setTypeId(typeId);
bufferMeta.setArraySize(state->getArraySize());
if (_freeListsEnabled && state->isActive() && !state->getCompacting()) {
@@ -436,17 +431,17 @@ DataStoreBase::finishCompact(const std::vector<uint32_t> &toHold)
}
void
-DataStoreBase::fallbackResize(uint32_t bufferId, size_t elemsNeeded)
+DataStoreBase::fallback_resize(uint32_t bufferId, size_t entries_needed)
{
BufferState &state = getBufferState(bufferId);
BufferState::Alloc toHoldBuffer;
- size_t oldUsedElems = state.size();
- size_t oldAllocElems = state.capacity();
- size_t elementSize = state.getTypeHandler()->elementSize();
- state.fallbackResize(bufferId, elemsNeeded, _buffers[bufferId].get_atomic_buffer(), toHoldBuffer);
- auto hold = std::make_unique<FallbackHold>(oldAllocElems * elementSize,
+ size_t old_used_entries = state.size();
+ size_t old_alloc_entries = state.capacity();
+ size_t entry_size = state.getTypeHandler()->entry_size();
+ state.fallback_resize(bufferId, entries_needed, _buffers[bufferId].get_atomic_buffer(), toHoldBuffer);
+ auto hold = std::make_unique<FallbackHold>(old_alloc_entries * entry_size,
std::move(toHoldBuffer),
- oldUsedElems,
+ old_used_entries,
state.getTypeHandler(),
state.getTypeId());
if (!_initializing) {
@@ -465,7 +460,7 @@ DataStoreBase::markCompacting(uint32_t bufferId)
}
assert(!state.getCompacting());
state.setCompacting();
- state.disable_elem_hold_list();
+ state.disable_entry_hold_list();
state.disable_free_list();
inc_compaction_count();
}
@@ -492,15 +487,15 @@ DataStoreBase::start_compact_worst_buffers(CompactionSpec compaction_spec, const
free_buffers++;
} else if (state->isActive()) {
auto typeHandler = state->getTypeHandler();
- uint32_t arraySize = typeHandler->getArraySize();
- uint32_t reservedElements = typeHandler->getReservedElements(bufferId);
- size_t used_elems = state->size();
- size_t deadElems = state->stats().dead_elems() - reservedElements;
+ uint32_t reserved_entries = typeHandler->get_reserved_entries(bufferId);
+ size_t used_entries = state->size();
+ size_t dead_entries = state->stats().dead_entries() - reserved_entries;
+ size_t entry_size = typeHandler->entry_size();
if (compaction_spec.compact_memory()) {
- elem_buffers.add(bufferId, used_elems, deadElems);
+ elem_buffers.add(bufferId, used_entries * entry_size, dead_entries * entry_size);
}
if (compaction_spec.compact_address_space()) {
- array_buffers.add(bufferId, used_elems / arraySize, deadElems / arraySize);
+ array_buffers.add(bufferId, used_entries, dead_entries);
}
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/datastorebase.h b/vespalib/src/vespa/vespalib/datastore/datastorebase.h
index 9cab7a2e375..e5a38e3fd41 100644
--- a/vespalib/src/vespa/vespalib/datastore/datastorebase.h
+++ b/vespalib/src/vespa/vespalib/datastore/datastorebase.h
@@ -35,15 +35,16 @@ public:
void init_primary_buffers();
/**
- * Ensure that the primary buffer for the given type has a given number of elements free at end.
+ * Ensure that the primary buffer for the given type has a given number of entries free at end.
* Switch to new buffer if current buffer is too full.
*
- * @param typeId Registered data type for buffer.
- * @param elemsNeeded Number of elements needed to be free.
+ * @param typeId Registered data type for buffer.
+ * @param entries_needed Number of entries needed to be free.
*/
- void ensureBufferCapacity(uint32_t typeId, size_t elemsNeeded) {
- if (elemsNeeded > getBufferState(primary_buffer_id(typeId)).remaining()) [[unlikely]] {
- switch_or_grow_primary_buffer(typeId, elemsNeeded);
+ void ensure_buffer_capacity(uint32_t typeId, size_t entries_needed) {
+ auto &state = getBufferState(primary_buffer_id(typeId));
+ if (entries_needed > state.remaining()) [[unlikely]] {
+ switch_or_grow_primary_buffer(typeId, entries_needed);
}
}
@@ -58,10 +59,10 @@ public:
* Switch to a new primary buffer, typically in preparation for compaction
* or when the current primary buffer no longer has free space.
*
- * @param typeId Registered data type for buffer.
- * @param elemsNeeded Number of elements needed to be free.
+ * @param typeId Registered data type for buffer.
+ * @param entries_needed Number of entries needed to be free.
*/
- void switch_primary_buffer(uint32_t typeId, size_t elemsNeeded);
+ void switch_primary_buffer(uint32_t typeId, size_t entries_needed);
vespalib::MemoryUsage getMemoryUsage() const;
vespalib::MemoryUsage getDynamicMemoryUsage() const;
@@ -127,7 +128,7 @@ public:
/**
* Enable free list management.
- * This only works for fixed size elements.
+ * This only works for fixed size entries.
*/
void enableFreeLists();
@@ -135,7 +136,7 @@ public:
* Disable free list management.
*/
void disableFreeLists();
- void disableElemHoldList();
+ void disable_entry_hold_list();
bool has_free_lists_enabled() const { return _freeListsEnabled; }
@@ -177,25 +178,25 @@ public:
bool has_held_buffers() const noexcept { return _hold_buffer_count != 0u; }
/**
- * Trim elem hold list, freeing elements that no longer needs to be held.
+ * Trim entry hold list, freeing entries that no longer needs to be held.
*
* @param oldest_used_gen the oldest generation that is still used.
*/
virtual void reclaim_entry_refs(generation_t oldest_used_gen) = 0;
protected:
- DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t maxArrays);
+ DataStoreBase(uint32_t numBuffers, uint32_t offset_bits, size_t max_entries);
virtual ~DataStoreBase();
void* getBuffer(uint32_t bufferId) { return _buffers[bufferId].get_buffer_relaxed(); }
struct EntryRefHoldElem {
EntryRef ref;
- size_t num_elems;
+ size_t num_entries;
- EntryRefHoldElem(EntryRef ref_in, size_t num_elems_in)
+ EntryRefHoldElem(EntryRef ref_in, size_t num_entries_in)
: ref(ref_in),
- num_elems(num_elems_in)
+ num_entries(num_entries_in)
{}
};
@@ -215,11 +216,11 @@ private:
{
public:
BufferState::Alloc _buffer;
- size_t _usedElems;
+ size_t _used_entries;
BufferTypeBase *_typeHandler;
uint32_t _typeId;
- FallbackHold(size_t bytesSize, BufferState::Alloc &&buffer, size_t usedElems,
+ FallbackHold(size_t bytesSize, BufferState::Alloc &&buffer, size_t used_entries,
BufferTypeBase *typeHandler, uint32_t typeId);
~FallbackHold() override;
@@ -227,8 +228,8 @@ private:
class BufferHold;
- bool consider_grow_active_buffer(uint32_t type_id, size_t elems_needed);
- void switch_or_grow_primary_buffer(uint32_t typeId, size_t elemsNeeded);
+ bool consider_grow_active_buffer(uint32_t type_id, size_t entries_needed);
+ void switch_or_grow_primary_buffer(uint32_t typeId, size_t entries_needed);
void markCompacting(uint32_t bufferId);
/**
* Hold of buffer has ended.
@@ -238,14 +239,14 @@ private:
/**
* Switch buffer state to active for the given buffer.
*
- * @param bufferId Id of buffer to be active.
- * @param typeId Registered data type for buffer.
- * @param elemsNeeded Number of elements needed to be free.
+ * @param bufferId Id of buffer to be active.
+ * @param typeId Registered data type for buffer.
+ * @param entries_needed Number of entries needed to be free.
*/
- void onActive(uint32_t bufferId, uint32_t typeId, size_t elemsNeeded);
+ void on_active(uint32_t bufferId, uint32_t typeId, size_t entries_needed);
void inc_hold_buffer_count();
- void fallbackResize(uint32_t bufferId, size_t elementsNeeded);
+ void fallback_resize(uint32_t bufferId, size_t entries_needed);
uint32_t getFirstFreeBufferId();
template<typename FuncType>
@@ -261,7 +262,7 @@ private:
std::vector<BufferAndMeta> _buffers; // For fast mapping with known types
// Provides a mapping from typeId -> primary buffer for that type.
- // The primary buffer is used for allocations of new element(s) if no available slots are found in free lists.
+ // The primary buffer is used for allocations of new entries if no available slots are found in free lists.
std::vector<uint32_t> _primary_buffer_ids;
Stash _stash;
@@ -269,12 +270,12 @@ private:
std::vector<FreeList> _free_lists;
mutable std::atomic<uint64_t> _compaction_count;
vespalib::GenerationHolder _genHolder;
- const uint32_t _maxArrays;
+ const uint32_t _max_entries;
std::atomic<uint32_t> _bufferIdLimit;
uint32_t _hold_buffer_count;
const uint8_t _offset_bits;
bool _freeListsEnabled;
- bool _disableElemHoldList;
+ bool _disable_entry_hold_list;
bool _initializing;
};
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list.h b/vespalib/src/vespa/vespalib/datastore/free_list.h
index 20d4a6b96df..cd475af3104 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list.h
+++ b/vespalib/src/vespa/vespalib/datastore/free_list.h
@@ -30,7 +30,6 @@ public:
bool empty() const { return _free_lists.empty(); }
size_t size() const { return _free_lists.size(); }
- uint32_t array_size() const { return _free_lists.back()->array_size(); }
EntryRef pop_entry() {
return _free_lists.back()->pop_entry();
}
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list_allocator.h b/vespalib/src/vespa/vespalib/datastore/free_list_allocator.h
index cf899a76712..dc2d1ea3c34 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/free_list_allocator.h
@@ -29,7 +29,7 @@ public:
HandleType alloc(Args && ... args);
HandleType allocArray(ConstArrayRef array);
- HandleType allocArray(size_t size);
+ HandleType allocArray();
};
}
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/free_list_allocator.hpp
index b793e4f77a2..4e69db08a3c 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/free_list_allocator.hpp
@@ -71,8 +71,9 @@ FreeListAllocator<EntryT, RefT, ReclaimerT>::allocArray(ConstArrayRef array)
if (free_list.empty()) {
return ParentType::allocArray(array);
}
- assert(free_list.array_size() == array.size());
RefT ref = free_list.pop_entry();
+ auto& state = _store.getBufferState(ref.bufferId());
+ assert(state.getArraySize() == array.size());
EntryT *buf = _store.template getEntryArray<EntryT>(ref, array.size());
for (size_t i = 0; i < array.size(); ++i) {
*(buf + i) = array[i];
@@ -82,14 +83,15 @@ FreeListAllocator<EntryT, RefT, ReclaimerT>::allocArray(ConstArrayRef array)
template <typename EntryT, typename RefT, typename ReclaimerT>
typename Allocator<EntryT, RefT>::HandleType
-FreeListAllocator<EntryT, RefT, ReclaimerT>::allocArray(size_t size)
+FreeListAllocator<EntryT, RefT, ReclaimerT>::allocArray()
{
auto& free_list = _store.getFreeList(_typeId);
if (free_list.empty()) {
- return ParentType::allocArray(size);
+ return ParentType::allocArray();
}
- assert(free_list.array_size() == size);
RefT ref = free_list.pop_entry();
+ auto& state = _store.getBufferState(ref.bufferId());
+ auto size = state.getArraySize();
EntryT *buf = _store.template getEntryArray<EntryT>(ref, size);
return HandleType(ref, buf);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.h b/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.h
index 1b71c22f0ce..29684267546 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.h
@@ -27,7 +27,7 @@ private:
public:
FreeListRawAllocator(DataStoreBase &store, uint32_t typeId);
- HandleType alloc(size_t numElems);
+ HandleType alloc(size_t num_entries);
};
}
diff --git a/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.hpp
index af832955cb7..7680cd8a9a5 100644
--- a/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/free_list_raw_allocator.hpp
@@ -14,16 +14,18 @@ FreeListRawAllocator<EntryT, RefT>::FreeListRawAllocator(DataStoreBase &store, u
template <typename EntryT, typename RefT>
typename FreeListRawAllocator<EntryT, RefT>::HandleType
-FreeListRawAllocator<EntryT, RefT>::alloc(size_t numElems)
+FreeListRawAllocator<EntryT, RefT>::alloc(size_t num_entries)
{
auto& free_list = _store.getFreeList(_typeId);
if (free_list.empty()) {
- return ParentType::alloc(numElems);
+ return ParentType::alloc(num_entries);
}
- assert(free_list.array_size() == numElems);
+ assert(num_entries == 1);
RefT ref = free_list.pop_entry();
+ auto& state = _store.getBufferState(ref.bufferId());
+ auto array_size = state.getArraySize();
// We must scale the offset according to array size as it was divided when the entry ref was created.
- EntryT *entry = _store.template getEntryArray<EntryT>(ref, numElems);
+ EntryT *entry = _store.template getEntryArray<EntryT>(ref, array_size);
return HandleType(ref, entry);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
index 600925969a3..e2718b94cd2 100644
--- a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
+++ b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.h
@@ -14,11 +14,11 @@ namespace vespalib::datastore {
/*
* Class representing buffer type for large arrays in ArrayStore
*/
-template <typename EntryT>
-class LargeArrayBufferType : public BufferType<Array<EntryT>>
+template <typename ElemT>
+class LargeArrayBufferType : public BufferType<Array<ElemT>>
{
using AllocSpec = ArrayStoreConfig::AllocSpec;
- using ArrayType = Array<EntryT>;
+ using ArrayType = Array<ElemT>;
using ParentType = BufferType<ArrayType>;
using ParentType::empty_entry;
using CleanContext = typename ParentType::CleanContext;
@@ -31,7 +31,7 @@ public:
{
}
~LargeArrayBufferType() override;
- void cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
+ void clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
diff --git a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
index 3042bbff73f..72a2662991b 100644
--- a/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/large_array_buffer_type.hpp
@@ -7,32 +7,32 @@
namespace vespalib::datastore {
-template <typename EntryT>
-LargeArrayBufferType<EntryT>::LargeArrayBufferType(const AllocSpec& spec, std::shared_ptr<alloc::MemoryAllocator> memory_allocator) noexcept
- : BufferType<Array<EntryT>>(1u, spec.minArraysInBuffer, spec.maxArraysInBuffer, spec.numArraysForNewBuffer, spec.allocGrowFactor),
+template <typename ElemT>
+LargeArrayBufferType<ElemT>::LargeArrayBufferType(const AllocSpec& spec, std::shared_ptr<alloc::MemoryAllocator> memory_allocator) noexcept
+ : BufferType<Array<ElemT>>(1u, spec.min_entries_in_buffer, spec.max_entries_in_buffer, spec.num_entries_for_new_buffer, spec.allocGrowFactor),
_memory_allocator(std::move(memory_allocator))
{
}
-template <typename EntryT>
-LargeArrayBufferType<EntryT>::~LargeArrayBufferType() = default;
+template <typename ElemT>
+LargeArrayBufferType<ElemT>::~LargeArrayBufferType() = default;
-template <typename EntryT>
+template <typename ElemT>
void
-LargeArrayBufferType<EntryT>::cleanHold(void* buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx)
+LargeArrayBufferType<ElemT>::clean_hold(void* buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx)
{
ArrayType* elem = static_cast<ArrayType*>(buffer) + offset;
const auto& empty = empty_entry();
- for (size_t i = 0; i < numElems; ++i) {
- cleanCtx.extraBytesCleaned(sizeof(EntryT) * elem->size());
+ for (size_t i = 0; i < num_entries; ++i) {
+ cleanCtx.extraBytesCleaned(sizeof(ElemT) * elem->size());
*elem = empty;
++elem;
}
}
-template <typename EntryT>
+template <typename ElemT>
const vespalib::alloc::MemoryAllocator*
-LargeArrayBufferType<EntryT>::get_memory_allocator() const
+LargeArrayBufferType<ElemT>::get_memory_allocator() const
{
return _memory_allocator.get();
}
diff --git a/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp b/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp
index 8e060b4cfb4..5cb04796c5b 100644
--- a/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/memory_stats.cpp
@@ -5,10 +5,10 @@
namespace vespalib::datastore {
MemoryStats::MemoryStats()
- : _allocElems(0),
- _usedElems(0),
- _deadElems(0),
- _holdElems(0),
+ : _alloc_entries(0),
+ _used_entries(0),
+ _dead_entries(0),
+ _hold_entries(0),
_allocBytes(0),
_usedBytes(0),
_deadBytes(0),
@@ -22,10 +22,10 @@ MemoryStats::MemoryStats()
MemoryStats&
MemoryStats::operator+=(const MemoryStats& rhs)
{
- _allocElems += rhs._allocElems;
- _usedElems += rhs._usedElems;
- _deadElems += rhs._deadElems;
- _holdElems += rhs._holdElems;
+ _alloc_entries += rhs._alloc_entries;
+ _used_entries += rhs._used_entries;
+ _dead_entries += rhs._dead_entries;
+ _hold_entries += rhs._hold_entries;
_allocBytes += rhs._allocBytes;
_usedBytes += rhs._usedBytes;
_deadBytes += rhs._deadBytes;
diff --git a/vespalib/src/vespa/vespalib/datastore/memory_stats.h b/vespalib/src/vespa/vespalib/datastore/memory_stats.h
index 18d7dd77559..72a570dd625 100644
--- a/vespalib/src/vespa/vespalib/datastore/memory_stats.h
+++ b/vespalib/src/vespa/vespalib/datastore/memory_stats.h
@@ -13,10 +13,10 @@ namespace vespalib::datastore {
class MemoryStats
{
public:
- size_t _allocElems;
- size_t _usedElems;
- size_t _deadElems;
- size_t _holdElems;
+ size_t _alloc_entries;
+ size_t _used_entries;
+ size_t _dead_entries;
+ size_t _hold_entries;
size_t _allocBytes;
size_t _usedBytes;
size_t _deadBytes;
diff --git a/vespalib/src/vespa/vespalib/datastore/raw_allocator.h b/vespalib/src/vespa/vespalib/datastore/raw_allocator.h
index c10c8152e72..e7a59fadcf8 100644
--- a/vespalib/src/vespa/vespalib/datastore/raw_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/raw_allocator.h
@@ -25,10 +25,10 @@ protected:
public:
RawAllocator(DataStoreBase &store, uint32_t typeId);
- HandleType alloc(size_t numElems) {
- return alloc(numElems, 0);
+ HandleType alloc(size_t num_entries) {
+ return alloc(num_entries, 0);
}
- HandleType alloc(size_t numElems, size_t extraElems);
+ HandleType alloc(size_t num_entries, size_t extra_entries);
};
}
diff --git a/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
index 04d99588218..9de361a8b19 100644
--- a/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/raw_allocator.hpp
@@ -16,19 +16,15 @@ RawAllocator<EntryT, RefT>::RawAllocator(DataStoreBase &store, uint32_t typeId)
template <typename EntryT, typename RefT>
typename RawAllocator<EntryT, RefT>::HandleType
-RawAllocator<EntryT, RefT>::alloc(size_t numElems, size_t extraElems)
+RawAllocator<EntryT, RefT>::alloc(size_t num_entries, size_t extra_entries)
{
- _store.ensureBufferCapacity(_typeId, numElems + extraElems);
+ _store.ensure_buffer_capacity(_typeId, num_entries + extra_entries);
uint32_t buffer_id = _store.primary_buffer_id(_typeId);
BufferState &state = _store.getBufferState(buffer_id);
assert(state.isActive());
- size_t oldBufferSize = state.size();
- // Must perform scaling ourselves, according to array size
- size_t arraySize = state.getArraySize();
- assert((numElems % arraySize) == 0u);
- RefT ref((oldBufferSize / arraySize), buffer_id);
- EntryT *buffer = _store.getEntryArray<EntryT>(ref, arraySize);
- state.stats().pushed_back(numElems);
+ RefT ref(state.size(), buffer_id);
+ EntryT *buffer = _store.getEntryArray<EntryT>(ref, state.getArraySize());
+ state.stats().pushed_back(num_entries);
return HandleType(ref, buffer);
}
diff --git a/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.h b/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.h
index 676a9d3790f..6bb6601839d 100644
--- a/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.h
+++ b/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.h
@@ -13,8 +13,8 @@ namespace vespalib::datastore {
/*
* Class representing buffer type for small arrays in ArrayStore
*/
-template <typename EntryT>
-class SmallArrayBufferType : public BufferType<EntryT>
+template <typename ElemT>
+class SmallArrayBufferType : public BufferType<ElemT>
{
using AllocSpec = ArrayStoreConfig::AllocSpec;
std::shared_ptr<alloc::MemoryAllocator> _memory_allocator;
diff --git a/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.hpp b/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.hpp
index 414804417eb..c9033936bd6 100644
--- a/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/small_array_buffer_type.hpp
@@ -6,19 +6,19 @@
namespace vespalib::datastore {
-template <typename EntryT>
-SmallArrayBufferType<EntryT>::SmallArrayBufferType(uint32_t array_size, const AllocSpec& spec, std::shared_ptr<alloc::MemoryAllocator> memory_allocator) noexcept
- : BufferType<EntryT>(array_size, spec.minArraysInBuffer, spec.maxArraysInBuffer, spec.numArraysForNewBuffer, spec.allocGrowFactor),
+template <typename ElemT>
+SmallArrayBufferType<ElemT>::SmallArrayBufferType(uint32_t array_size, const AllocSpec& spec, std::shared_ptr<alloc::MemoryAllocator> memory_allocator) noexcept
+ : BufferType<ElemT>(array_size, spec.min_entries_in_buffer, spec.max_entries_in_buffer, spec.num_entries_for_new_buffer, spec.allocGrowFactor),
_memory_allocator(std::move(memory_allocator))
{
}
-template <typename EntryT>
-SmallArrayBufferType<EntryT>::~SmallArrayBufferType() = default;
+template <typename ElemT>
+SmallArrayBufferType<ElemT>::~SmallArrayBufferType() = default;
-template <typename EntryT>
+template <typename ElemT>
const vespalib::alloc::MemoryAllocator*
-SmallArrayBufferType<EntryT>::get_memory_allocator() const
+SmallArrayBufferType<ElemT>::get_memory_allocator() const
{
return _memory_allocator.get();
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
index 52b0798543f..0efaf04b26e 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store.hpp
@@ -106,7 +106,7 @@ private:
_mapping.resize(data_store.get_bufferid_limit_relaxed());
for (const auto bufferId : _compacting_buffers->get_buffer_ids()) {
BufferState &state = data_store.getBufferState(bufferId);
- _mapping[bufferId].resize(state.get_used_arrays());
+ _mapping[bufferId].resize(state.size());
}
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
index 8ad11b18218..49d2631e73f 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_allocator.hpp
@@ -43,7 +43,7 @@ template <typename EntryT, typename RefT>
void
UniqueStoreAllocator<EntryT, RefT>::hold(EntryRef ref)
{
- _store.holdElem(ref, 1);
+ _store.hold_entry(ref);
}
template <typename EntryT, typename RefT>
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_enumerator.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_enumerator.hpp
index 32513d09c72..4a517521d77 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_enumerator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_enumerator.hpp
@@ -44,7 +44,7 @@ UniqueStoreEnumerator<RefT>::allocate_enum_values(DataStoreBase & store)
{
_enumValues.resize(store.get_bufferid_limit_relaxed());
store.for_each_active_buffer([this](uint32_t buffer_id, const BufferState & state) {
- _enumValues[buffer_id].resize(state.get_used_arrays());
+ _enumValues[buffer_id].resize(state.size());
});
}
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.cpp b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.cpp
index 1d3ba27d6bf..9f2105b7f08 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.cpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.cpp
@@ -42,28 +42,28 @@ UniqueStoreSmallStringBufferType::UniqueStoreSmallStringBufferType(uint32_t arra
UniqueStoreSmallStringBufferType::~UniqueStoreSmallStringBufferType() = default;
void
-UniqueStoreSmallStringBufferType::destroyElements(void *, ElemCount)
+UniqueStoreSmallStringBufferType::destroy_entries(void *, EntryCount)
{
static_assert(std::is_trivially_destructible<UniqueStoreSmallStringEntry>::value,
"UniqueStoreSmallStringEntry must be trivially destructable");
}
void
-UniqueStoreSmallStringBufferType::fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems)
+UniqueStoreSmallStringBufferType::fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount num_entries)
{
static_assert(std::is_trivially_copyable<UniqueStoreSmallStringEntry>::value,
"UniqueStoreSmallStringEntry must be trivially copyable");
- if (numElems > 0) {
- memcpy(newBuffer, oldBuffer, numElems);
+ if (num_entries > 0) {
+ memcpy(newBuffer, oldBuffer, num_entries * getArraySize());
}
}
void
-UniqueStoreSmallStringBufferType::cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext)
+UniqueStoreSmallStringBufferType::clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext)
{
- void *e = static_cast<char *>(buffer) + offset;
- void *e_end = static_cast<char *>(e) + numElems;
size_t array_size = getArraySize();
+ void *e = static_cast<char *>(buffer) + offset * array_size;
+ void *e_end = static_cast<char *>(e) + num_entries * array_size;
while (e < e_end) {
static_cast<UniqueStoreSmallStringEntry *>(e)->clean_hold(array_size);
e = static_cast<char *>(e) + array_size;
@@ -86,10 +86,10 @@ UniqueStoreExternalStringBufferType::UniqueStoreExternalStringBufferType(uint32_
UniqueStoreExternalStringBufferType::~UniqueStoreExternalStringBufferType() = default;
void
-UniqueStoreExternalStringBufferType::cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx)
+UniqueStoreExternalStringBufferType::clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx)
{
UniqueStoreEntry<std::string> *elem = static_cast<UniqueStoreEntry<std::string> *>(buffer) + offset;
- for (size_t i = 0; i < numElems; ++i) {
+ for (size_t i = 0; i < num_entries; ++i) {
cleanCtx.extraBytesCleaned(elem->value().size() + 1);
std::string().swap(elem->value());
++elem;
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
index a85b73f423d..d3348950891 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.h
@@ -62,9 +62,9 @@ class UniqueStoreSmallStringBufferType : public BufferType<char> {
public:
UniqueStoreSmallStringBufferType(uint32_t array_size, uint32_t max_arrays, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator);
~UniqueStoreSmallStringBufferType() override;
- void destroyElements(void *, ElemCount) override;
- void fallbackCopy(void *newBuffer, const void *oldBuffer, ElemCount numElems) override;
- void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext) override;
+ void destroy_entries(void *, EntryCount) override;
+ void fallback_copy(void *newBuffer, const void *oldBuffer, EntryCount numElems) override;
+ void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
@@ -76,7 +76,7 @@ class UniqueStoreExternalStringBufferType : public BufferType<UniqueStoreEntry<s
public:
UniqueStoreExternalStringBufferType(uint32_t array_size, uint32_t max_arrays, std::shared_ptr<vespalib::alloc::MemoryAllocator> memory_allocator);
~UniqueStoreExternalStringBufferType() override;
- void cleanHold(void *buffer, size_t offset, ElemCount numElems, CleanContext cleanCtx) override;
+ void clean_hold(void *buffer, size_t offset, EntryCount num_entries, CleanContext cleanCtx) override;
const vespalib::alloc::MemoryAllocator* get_memory_allocator() const override;
};
diff --git a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
index 65cab4850ba..4ff8e1e1ab4 100644
--- a/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
+++ b/vespalib/src/vespa/vespalib/datastore/unique_store_string_allocator.hpp
@@ -42,7 +42,7 @@ UniqueStoreStringAllocator<RefT>::allocate(const char *value)
uint32_t type_id = string_allocator::get_type_id(value_len);
if (type_id != 0) {
size_t array_size = string_allocator::array_sizes[type_id - 1];
- auto handle = _store.template freeListRawAllocator<char>(type_id).alloc(array_size);
+ auto handle = _store.template freeListRawAllocator<char>(type_id).alloc(1);
new (static_cast<void *>(handle.data)) UniqueStoreSmallStringEntry(value, value_len, array_size);
return handle.ref;
} else {
@@ -61,11 +61,10 @@ UniqueStoreStringAllocator<RefT>::hold(EntryRef ref)
RefT iRef(ref);
uint32_t type_id = _store.getTypeId(iRef.bufferId());
if (type_id != 0) {
- size_t array_size = string_allocator::array_sizes[type_id - 1];
- _store.holdElem(ref, array_size);
+ _store.hold_entry(ref);
} else {
auto &value = _store.template getEntry<WrappedExternalEntryType>(iRef)->value();
- _store.holdElem(ref, 1, value.size() + 1);
+ _store.hold_entry(ref, value.size() + 1);
}
}
@@ -79,7 +78,7 @@ UniqueStoreStringAllocator<RefT>::move_on_compact(EntryRef ref)
static_assert(std::is_trivially_copyable<UniqueStoreSmallStringEntry>::value,
"UniqueStoreSmallStringEntry must be trivially copyable");
size_t array_size = string_allocator::array_sizes[type_id - 1];
- auto handle = _store.template rawAllocator<char>(type_id).alloc(array_size);
+ auto handle = _store.template rawAllocator<char>(type_id).alloc(1);
auto orig = _store.template getEntryArray<char>(iRef, array_size);
memcpy(handle.data, orig, array_size);
return handle.ref;
diff --git a/vespalib/src/vespa/vespalib/gtest/matchers/elements_are_distinct.h b/vespalib/src/vespa/vespalib/gtest/matchers/elements_are_distinct.h
new file mode 100644
index 00000000000..374dafbf893
--- /dev/null
+++ b/vespalib/src/vespa/vespalib/gtest/matchers/elements_are_distinct.h
@@ -0,0 +1,35 @@
+// Copyright Yahoo. Licensed under the terms of the Apache 2.0 license. See LICENSE in the project root.
+#include <gtest/gtest.h>
+#include <gmock/gmock.h>
+#include <ranges>
+
+/**
+ * Checks that all elements of a forward iterable range are distinct, i.e. the following must hold:
+ * - for any single element `foo`, foo == foo is true
+ * - for any two separate elements `foo` and `bar`, foo == bar is false
+ */
+MATCHER(ElementsAreDistinct, "") {
+ const auto& range = arg;
+ static_assert(std::ranges::forward_range<decltype(range)>);
+ const auto end = std::ranges::cend(range);
+ // Explicitly count element positions instead of comparing iterators to avoid depending
+ // on iterators being comparable with each other.
+ size_t i = 0;
+ for (auto lhs = std::ranges::cbegin(range); lhs != end; ++lhs, ++i) {
+ size_t j = 0;
+ for (auto rhs = std::ranges::cbegin(range); rhs != end; ++rhs, ++j) {
+ if (i != j) {
+ if (*lhs == *rhs) {
+ *result_listener << "Expected elements to be distinct, but element at position "
+ << i << " (" << *lhs << ") is equal to element at position "
+ << j << " (" << *rhs << ")";
+ return false;
+ }
+ } else if (!(*lhs == *rhs)) {
+ *result_listener << "Element at position " << i << " (" << *lhs << ") does not equal itself";
+ return false;
+ }
+ }
+ }
+ return true;
+}
diff --git a/vespalib/src/vespa/vespalib/util/CMakeLists.txt b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
index 663b3b65638..91365d446c1 100644
--- a/vespalib/src/vespa/vespalib/util/CMakeLists.txt
+++ b/vespalib/src/vespa/vespalib/util/CMakeLists.txt
@@ -31,6 +31,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT
exceptions.cpp
execution_profiler.cpp
executor_idle_tracking.cpp
+ featureset.cpp
file_area_freelist.cpp
foregroundtaskexecutor.cpp
gate.cpp
@@ -100,3 +101,7 @@ vespa_add_library(vespalib_vespalib_util OBJECT
zstdcompressor.cpp
DEPENDS
)
+
+# Don't convert call to jump when returning a value from a function with
+# a compatible stack.
+set_source_files_properties(signalhandler.cpp PROPERTIES COMPILE_OPTIONS "-fno-optimize-sibling-calls")
diff --git a/searchlib/src/vespa/searchlib/common/featureset.cpp b/vespalib/src/vespa/vespalib/util/featureset.cpp
index 5c8d4c6d9c4..6ac90461cfb 100644
--- a/searchlib/src/vespa/searchlib/common/featureset.cpp
+++ b/vespalib/src/vespa/vespalib/util/featureset.cpp
@@ -2,7 +2,7 @@
#include "featureset.h"
-namespace search {
+namespace vespalib {
FeatureSet::FeatureSet()
: _names(),
@@ -87,4 +87,4 @@ FeatureSet::getFeaturesByDocId(uint32_t docId) const
return 0;
}
-} // namespace search
+}
diff --git a/searchlib/src/vespa/searchlib/common/featureset.h b/vespalib/src/vespa/vespalib/util/featureset.h
index adda8a2728b..ae7a0c6932f 100644
--- a/searchlib/src/vespa/searchlib/common/featureset.h
+++ b/vespalib/src/vespa/vespalib/util/featureset.h
@@ -2,14 +2,13 @@
#pragma once
-#include "feature.h"
#include <vespa/vespalib/stllike/string.h>
#include <vespa/vespalib/data/memory.h>
#include <map>
#include <vector>
#include <memory>
-namespace search {
+namespace vespalib {
/**
* This class holds information about a set of features for a set of
@@ -153,4 +152,4 @@ struct FeatureValues {
std::vector<Value> values; // values.size() == names.size() * N
};
-} // namespace search
+}
diff --git a/vespalib/src/vespa/vespalib/util/time.cpp b/vespalib/src/vespa/vespalib/util/time.cpp
index cba26f24059..dd4972b1c21 100644
--- a/vespalib/src/vespa/vespalib/util/time.cpp
+++ b/vespalib/src/vespa/vespalib/util/time.cpp
@@ -93,7 +93,7 @@ Timer::waitAtLeast(duration dur, bool busyWait) {
}
-#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000) || (!defined(_LIBCPP_VERSION) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12)
+#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000) || (!defined(_LIBCPP_VERSION) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12)
// Temporary workaround until libc++ supports stream operators for duration
// Temporary workaround while using libstdc++ 11
diff --git a/vespalib/src/vespa/vespalib/util/time.h b/vespalib/src/vespa/vespalib/util/time.h
index b893661832f..10077a0b49a 100644
--- a/vespalib/src/vespa/vespalib/util/time.h
+++ b/vespalib/src/vespa/vespalib/util/time.h
@@ -101,7 +101,7 @@ duration adjustTimeoutByHz(duration timeout, long hz);
}
-#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000) || (!defined(_LIBCPP_VERSION) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12)
+#if (defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 170000) || (!defined(_LIBCPP_VERSION) && defined(_GLIBCXX_RELEASE) && _GLIBCXX_RELEASE < 12)
// Temporary workaround until libc++ supports stream operators for duration
// Temporary workaround while using libstdc++ 11