aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorOla Aunronning <olaa@yahooinc.com>2023-04-26 09:36:43 +0200
committerOla Aunronning <olaa@yahooinc.com>2023-04-26 09:36:43 +0200
commit48b7ffe757ffa25bc3ca1eeaab8153db30623fa3 (patch)
treeb41007ada3c803cc3282cc0556acbb13f3642e6a
parentd46b56f3f8577d8c4cc4b08f5f13b3b983ef7d2a (diff)
parent9a4376dae10e986c7061633e5a02f18c24a951da (diff)
Merge branch 'master' into olaa/versioned-feature-flag
-rw-r--r--client/go/go.mod6
-rw-r--r--client/go/go.sum12
-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.go27
-rw-r--r--client/go/internal/admin/prog/vespamalloc.go10
-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.go83
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go97
-rw-r--r--client/go/internal/admin/vespa-wrapper/startcbinary/vespamalloc.go57
-rw-r--r--client/go/internal/cli/cmd/feed.go77
-rw-r--r--client/go/internal/cli/cmd/feed_test.go45
-rw-r--r--client/go/internal/cli/cmd/visit.go31
-rw-r--r--client/go/internal/cli/cmd/visit_test.go3
-rw-r--r--client/go/internal/util/http.go5
-rw-r--r--client/go/internal/vespa/document/dispatcher.go34
-rw-r--r--client/go/internal/vespa/document/dispatcher_test.go35
-rw-r--r--client/go/internal/vespa/document/http.go22
-rw-r--r--client/go/internal/vespa/document/http_test.go23
-rw-r--r--cloud-tenant-base-dependencies-enforcer/pom.xml2
-rw-r--r--config-model/src/main/java/com/yahoo/config/model/ConfigModelRepo.java2
-rw-r--r--config-model/src/main/java/com/yahoo/schema/Schema.java11
-rw-r--r--config-model/src/main/java/com/yahoo/schema/parser/ConvertParsedSchemas.java25
-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/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/content/ClusterControllerConfig.java133
-rw-r--r--config-model/src/main/java/com/yahoo/vespa/model/content/cluster/ContentCluster.java16
-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/test/derived/multiplesummaries/index-info.cfg2
-rw-r--r--config-model/src/test/derived/multiplesummaries/multiplesummaries.sd5
-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/vespa/model/admin/DedicatedAdminV4Test.java4
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/application/validation/ContainerInCloudValidatorTest.java4
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/ContentClusterTest.java8
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/content/FleetControllerClusterTest.java5
-rw-r--r--config-model/src/test/java/com/yahoo/vespa/model/test/VespaModelTester.java7
-rw-r--r--configdefinitions/src/vespa/logforwarder.def1
-rw-r--r--configserver/src/main/java/com/yahoo/vespa/config/server/filedistribution/FileDirectory.java17
-rw-r--r--container-search/src/main/java/com/yahoo/vespa/streamingvisitors/VdsVisitor.java22
-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/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/role/PathGroup.java4
-rw-r--r--controller-api/src/main/java/com/yahoo/vespa/hosted/controller/api/role/Policy.java4
-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/restapi/application/ApplicationApiHandler.java73
-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/integration/NodeRepositoryMock.java6
-rw-r--r--controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java40
-rw-r--r--controller-server/src/test/resources/application-packages/with-certificate.zipbin0 -> 1640 bytes
-rw-r--r--documentapi/abi-spec.json90
-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
-rw-r--r--documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java56
-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.java153
-rw-r--r--documentapi/src/tests/messages/messages60test.cpp202
-rw-r--r--documentapi/src/tests/messages/messages60test.h2
-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/searchresultmessage.cpp42
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/messages/searchresultmessage.h26
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp60
-rw-r--r--documentapi/src/vespa/documentapi/messagebus/routablefactories60.h20
-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-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-SearchResultReply.datbin5 -> 0 bytes
-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.java14
-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/identity/AthenzCredentialsMaintainer.java42
-rw-r--r--node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/nodeagent/NodeAgentImpl.java8
-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-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/restapi/LoadBalancersResponse.java3
-rw-r--r--node-repository/src/main/java/com/yahoo/vespa/hosted/provision/restapi/NodePatcher.java2
-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/restapi/NodesV2ApiTest.java35
-rw-r--r--parent/pom.xml3
-rwxr-xr-xscrewdriver/update-vespa-version-in-sample-apps.sh6
-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/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/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/stringattribute/stringattribute_test.cpp2
-rw-r--r--searchlib/src/tests/common/summaryfeatures/summaryfeatures.cpp4
-rw-r--r--searchlib/src/tests/query/streaming_query_test.cpp12
-rw-r--r--searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp181
-rw-r--r--searchlib/src/vespa/searchcommon/attribute/i_search_context.h5
-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/enumhintsearchcontext.h1
-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/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_read_view.h1
-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/singlenumericattribute.hpp2
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlesmallnumericattribute.cpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp3
-rw-r--r--searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp3
-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/parsequery/stackdumpiterator.h2
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp28
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h34
-rw-r--r--searchlib/src/vespa/searchlib/query/streaming/querynode.cpp12
-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/tensor/CMakeLists.txt2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/angular_distance.cpp7
-rw-r--r--searchlib/src/vespa/searchlib/tensor/angular_distance.h4
-rw-r--r--searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp55
-rw-r--r--searchlib/src/vespa/searchlib/tensor/bound_distance_function.h48
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp21
-rw-r--r--searchlib/src/vespa/searchlib/tensor/distance_functions.h1
-rw-r--r--searchlib/src/vespa/searchlib/tensor/euclidean_distance.cpp70
-rw-r--r--searchlib/src/vespa/searchlib/tensor/euclidean_distance.h12
-rw-r--r--searchlib/src/vespa/searchlib/tensor/inner_product_distance.h2
-rw-r--r--searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp82
-rw-r--r--searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.h27
-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--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--storage/src/tests/distributor/CMakeLists.txt4
-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/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/storageserver/documentapiconverter.cpp14
-rw-r--r--storage/src/vespa/storageapi/message/CMakeLists.txt2
-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/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/src/tests/hitcollector/hitcollector_test.cpp2
-rw-r--r--streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp61
-rw-r--r--streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp12
-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.cpp40
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/rankprocessor.h2
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp4
-rw-r--r--streamingvisitors/src/vespa/searchvisitor/searchvisitor.h2
-rw-r--r--streamingvisitors/src/vespa/vsm/common/document.h2
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp48
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h6
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp3
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp2
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp3
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp23
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h15
-rw-r--r--streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp9
-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/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/vsm-adapter.h10
-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.java6
-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/DefaultIdentityDocumentClient.java11
-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.java2
-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/IdentityDocumentSignerTest.java68
-rw-r--r--vespa-dependencies-enforcer/allowed-maven-dependencies.txt28
-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--vespaclient-container-plugin/src/main/java/com/yahoo/document/restapi/resource/DocumentV1ApiHandler.java39
-rw-r--r--vespaclient-container-plugin/src/test/java/com/yahoo/document/restapi/resource/DocumentV1ApiTest.java7
-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/test/java/com/yahoo/slime/BinaryViewTest.java64
-rw-r--r--vespajlib/src/test/java/com/yahoo/slime/DecodeIndexTest.java217
-rw-r--r--vespalib/src/vespa/vespalib/gtest/matchers/elements_are_distinct.h35
-rw-r--r--vespalib/src/vespa/vespalib/util/CMakeLists.txt1
-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
264 files changed, 2923 insertions, 3099 deletions
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/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 2d7f0a597d9..b949102d6bd 100644
--- a/client/go/internal/admin/prog/valgrind.go
+++ b/client/go/internal/admin/prog/valgrind.go
@@ -21,25 +21,18 @@ const (
func (p *Spec) ConfigureValgrind() {
p.shouldUseValgrind = false
p.shouldUseCallgrind = false
- env := p.Getenv(envvars.VESPA_USE_VALGRIND)
- allValgrind := env == "all"
- parts := strings.Split(env, " ")
- for _, part := range parts {
- if p.BaseName == part || allValgrind {
- 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/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/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 cccb37df8e5..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind.go
+++ /dev/null
@@ -1,83 +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)
- allValgrind := env == "all"
- parts := strings.Split(env, " ")
- for _, part := range parts {
- if p.BaseName == part || allValgrind {
- 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 1a105d66c4a..00000000000
--- a/client/go/internal/admin/vespa-wrapper/startcbinary/valgrind_test.go
+++ /dev/null
@@ -1,97 +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", "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)
- 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/cmd/feed.go b/client/go/internal/cli/cmd/feed.go
index 06568dd35c3..a6447ef8d2e 100644
--- a/client/go/internal/cli/cmd/feed.go
+++ b/client/go/internal/cli/cmd/feed.go
@@ -18,10 +18,11 @@ import (
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")
- cmd.PersistentFlags().IntVar(&options.timeoutSecs, "timeout", 0, "Feed operation timeout in seconds. 0 to disable")
- cmd.PersistentFlags().BoolVar(&options.verbose, "verbose", false, "Verbose mode. Print successful operations in addition to errors")
memprofile := "memprofile"
cpuprofile := "cpuprofile"
cmd.PersistentFlags().StringVar(&options.memprofile, memprofile, "", "Write a heap profile to given file")
@@ -38,44 +39,34 @@ type feedOptions struct {
verbose bool
traceLevel int
timeoutSecs int
- memprofile string
- cpuprofile string
+ doomSecs int
+
+ memprofile string
+ cpuprofile string
}
func newFeedCmd(cli *CLI) *cobra.Command {
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 a 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
-$ cat documents.jsonl | vespa feed -
-`,
- 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 {
- var r io.Reader
- if args[0] == "-" {
- r = cli.Stdin
- } else {
- f, err := os.Open(args[0])
- if err != nil {
- return err
- }
- defer f.Close()
- r = f
- }
if options.cpuprofile != "" {
f, err := os.Create(options.cpuprofile)
if err != nil {
@@ -84,7 +75,7 @@ $ cat documents.jsonl | vespa feed -
pprof.StartCPUProfile(f)
defer pprof.StopCPUProfile()
}
- err := feed(r, cli, options)
+ err := feed(args, options, cli)
if options.memprofile != "" {
f, err := os.Create(options.memprofile)
if err != nil {
@@ -123,7 +114,7 @@ func (opts feedOptions) compressionMode() (document.Compression, error) {
return 0, errHint(fmt.Errorf("invalid compression mode: %s", opts.compression), `Must be "auto", "gzip" or "none"`)
}
-func feed(r io.Reader, cli *CLI, options feedOptions) error {
+func feed(files []string, options feedOptions, cli *CLI) error {
service, err := documentService(cli)
if err != nil {
return err
@@ -139,25 +130,37 @@ func feed(r io.Reader, cli *CLI, options feedOptions) error {
Route: options.route,
TraceLevel: options.traceLevel,
BaseURL: service.BaseURL,
+ NowFunc: cli.now,
}, clients)
throttler := document.NewThrottler(options.connections)
- // TODO(mpolden): Make doom duration configurable
- circuitBreaker := document.NewCircuitBreaker(10*time.Second, 0)
+ circuitBreaker := document.NewCircuitBreaker(10*time.Second, time.Duration(options.doomSecs)*time.Second)
dispatcher := document.NewDispatcher(client, throttler, circuitBreaker, cli.Stderr, options.verbose)
- dec := document.NewDecoder(r)
-
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 521d2b2abd0..467d55a0a6e 100644
--- a/client/go/internal/cli/cmd/feed_test.go
+++ b/client/go/internal/cli/cmd/feed_test.go
@@ -31,47 +31,50 @@ 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())
want := `{
- "feeder.seconds": 1.000,
- "feeder.ok.count": 1,
- "feeder.ok.rate": 1.000,
+ "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
}
}
`
assert.Equal(t, want, stdout.String())
stdout.Reset()
- cli.Stdin = bytes.NewBuffer([]byte(`{
- "put": "id:ns:type::doc1",
- "fields": {"foo": "123"}
-}`))
+ 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/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 b6e5b893e0b..9bb8f61554a 100644
--- a/client/go/internal/cli/cmd/visit_test.go
+++ b/client/go/internal/cli/cmd/visit_test.go
@@ -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/util/http.go b/client/go/internal/util/http.go
index 8a67b24dffb..30874153510 100644
--- a/client/go/internal/util/http.go
+++ b/client/go/internal/util/http.go
@@ -81,8 +81,9 @@ func ForceHTTP2(client HTTPClient, certificates []tls.Certificate, caCertificate
// https://github.com/golang/go/issues/16582
// https://github.com/golang/go/issues/22091
c.client.Transport = &http2.Transport{
- AllowHTTP: true,
- DialTLSContext: dialFunc,
+ AllowHTTP: true,
+ DialTLSContext: dialFunc,
+ StrictMaxConcurrentStreams: true,
}
ConfigureTLS(client, certificates, caCertificate, trustAll)
}
diff --git a/client/go/internal/vespa/document/dispatcher.go b/client/go/internal/vespa/document/dispatcher.go
index 5c99f3bf056..b87dfaf55eb 100644
--- a/client/go/internal/vespa/document/dispatcher.go
+++ b/client/go/internal/vespa/document/dispatcher.go
@@ -20,7 +20,6 @@ type Dispatcher struct {
stats Stats
started bool
- ready chan Id
results chan Result
msgs chan string
@@ -128,7 +127,6 @@ func (d *Dispatcher) start() {
return
}
d.listPool.New = func() any { return list.New() }
- d.ready = make(chan Id, 4096)
d.results = make(chan Result, 4096)
d.msgs = make(chan string, 4096)
d.started = true
@@ -164,27 +162,35 @@ func (d *Dispatcher) enqueue(op documentOp) error {
}
d.mu.Unlock()
group.add(op, op.attempts > 0)
- d.enqueueWithSlot(op.document.Id)
+ d.dispatch(op.document.Id, group)
return nil
}
-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.throttler.Sent()
- d.dispatch()
-}
-
-func (d *Dispatcher) dispatch() {
d.workerWg.Add(1)
go func() {
defer d.workerWg.Done()
- id := <-d.ready
- d.mu.RLock()
- group := d.inflight[id.String()]
- d.mu.RUnlock()
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() {
diff --git a/client/go/internal/vespa/document/dispatcher_test.go b/client/go/internal/vespa/document/dispatcher_test.go
index d066f5bc9ae..2e2e9a5abbd 100644
--- a/client/go/internal/vespa/document/dispatcher_test.go
+++ b/client/go/internal/vespa/document/dispatcher_test.go
@@ -36,6 +36,12 @@ func (f *mockFeeder) Send(doc Document) Result {
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}
@@ -130,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/http.go b/client/go/internal/vespa/document/http.go
index 51b6fa4de39..877bcc5edce 100644
--- a/client/go/internal/vespa/document/http.go
+++ b/client/go/internal/vespa/document/http.go
@@ -11,6 +11,7 @@ import (
"net/url"
"strconv"
"strings"
+ "sync"
"sync/atomic"
"time"
@@ -31,6 +32,7 @@ type Client struct {
httpClients []countingHTTPClient
now func() time.Time
sendCount int32
+ gzippers sync.Pool
}
// ClientOptions specifices the configuration options of a feed client.
@@ -40,6 +42,7 @@ type ClientOptions struct {
Route string
TraceLevel int
Compression Compression
+ NowFunc func() time.Time
}
type countingHTTPClient struct {
@@ -73,11 +76,17 @@ func NewClient(options ClientOptions, httpClients []util.HTTPClient) *Client {
for _, client := range httpClients {
countingClients = append(countingClients, countingHTTPClient{client: client})
}
- return &Client{
+ nowFunc := options.NowFunc
+ if nowFunc == nil {
+ nowFunc = time.Now
+ }
+ c := &Client{
options: options,
httpClients: countingClients,
- now: time.Now,
+ now: nowFunc,
}
+ c.gzippers.New = func() any { return gzip.NewWriter(io.Discard) }
+ return c
}
func (c *Client) queryParams() url.Values {
@@ -162,18 +171,25 @@ func (c *Client) leastBusyClient() *countingHTTPClient {
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 := gzip.NewWriter(&buf)
+ 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)
diff --git a/client/go/internal/vespa/document/http_test.go b/client/go/internal/vespa/document/http_test.go
index 314113c53be..f67368b5128 100644
--- a/client/go/internal/vespa/document/http_test.go
+++ b/client/go/internal/vespa/document/http_test.go
@@ -293,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/cloud-tenant-base-dependencies-enforcer/pom.xml b/cloud-tenant-base-dependencies-enforcer/pom.xml
index 69e1a94a813..7971f4eebcf 100644
--- a/cloud-tenant-base-dependencies-enforcer/pom.xml
+++ b/cloud-tenant-base-dependencies-enforcer/pom.xml
@@ -45,7 +45,7 @@
<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>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 -->
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/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/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/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/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/content/ClusterControllerConfig.java b/config-model/src/main/java/com/yahoo/vespa/model/content/ClusterControllerConfig.java
index 5a96e33c522..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
@@ -9,6 +9,7 @@ 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;
@@ -47,9 +48,29 @@ public class ClusterControllerConfig extends AnyConfigProducer implements Fleetc
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 {
+ // Reduce to 1 for tests (ancestor is a mock class)
+ tuningConfig = tuningConfig.withMaxGroupsAllowedDown(1);
+ }
+
return new ClusterControllerConfig(ancestor,
clusterName,
- new ClusterControllerTuning(clusterControllerTuning, minNodeRatioPerGroup, bucketSplittingMinimumBits),
+ tuningConfig,
resourceLimits,
allowMoreThanOneContentGroupDown);
}
@@ -99,45 +120,81 @@ public class ClusterControllerConfig extends AnyConfigProducer implements Fleetc
resourceLimits.getConfig(builder);
}
- public ClusterControllerTuning tuning() { return tuning; }
-
- public static class ClusterControllerTuning {
-
- 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;
-
- ClusterControllerTuning(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"));
- }
+ 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"));
}
+ }
- public Optional<Integer> maxGroupsAllowedDown() { return maxGroupsAllowedDown; }
+ 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 f1f210b013c..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
@@ -164,18 +164,12 @@ public class ContentCluster extends TreeConfigProducer<AnyConfigProducer> implem
DeployState deployState,
ContentCluster c,
ClusterResourceLimits resourceLimits) {
- var config = new ClusterControllerConfig.Builder(c.clusterId,
- contentElement,
- resourceLimits.getClusterControllerLimits(),
- deployState.featureFlags()
- .allowMoreThanOneContentGroupDown(new ClusterSpec.Id(c.clusterId)))
+ return new ClusterControllerConfig.Builder(c.clusterId,
+ contentElement,
+ resourceLimits.getClusterControllerLimits(),
+ deployState.featureFlags()
+ .allowMoreThanOneContentGroupDown(new ClusterSpec.Id(c.clusterId)))
.build(deployState, c, contentElement.getXml());
- config.tuning().maxGroupsAllowedDown().ifPresent(m -> {
- int numberOfLeafGroups = c.getRootGroup().getNumberOfLeafGroups();
- if (m > numberOfLeafGroups)
- throw new IllegalArgumentException("Cannot set max-groups-allowed-down (" + m + ") larger than number of groups (" + numberOfLeafGroups + ")");
- });
- return config;
}
private void setupSearchCluster(ContentSearchCluster csc, ModelElement element, DeployLogger logger) {
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/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/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/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
index 3feb8888821..1c22423147c 100644
--- 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
@@ -21,7 +21,7 @@ public class ContainerInCloudValidatorTest {
void failsWhenNoContainerInCloud() throws IOException, SAXException {
String noContainer = "";
String container = """
- <container id='default' version='1.0'>
+ <container id='routing' version='1.0'>
<nodes count='2' />
</container>
""";
@@ -38,7 +38,7 @@ public class ContainerInCloudValidatorTest {
String servicesXml = """
<services version='1.0'>
%s
- <content version='1.0'>
+ <content id='foo' version='1.0'>
<redundancy>2</redundancy>
<documents>
</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 a33b30f7d93..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
@@ -1331,14 +1331,10 @@ public class ContentClusterTest extends ContentBaseTest {
" </engine>" +
" </content>" +
" </services>";
- VespaModel model = createEnd2EndOneNode(new TestProperties()
- .setHostedVespa(false)
- .setMultitenant(true)
- .setAllowMoreThanOneContentGroupDown(true),
- services);
+ VespaModel model = createEnd2EndOneNode(new TestProperties().setAllowMoreThanOneContentGroupDown(true), services);
var fleetControllerConfigBuilder = new FleetcontrollerConfig.Builder();
- model.getConfig(fleetControllerConfigBuilder, "admin/standalone/cluster-controllers/0/components/clustercontroller-storage-configurer");
+ 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 ae22542de6c..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
@@ -41,14 +41,14 @@ public class FleetControllerClusterTest {
parse("""
<cluster id="storage">
<documents/> <tuning>
- <bucket-splitting minimum-bits="7" /> <cluster-controller>
+ <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>
- <max-groups-allowed-down>2</max-groups-allowed-down>
</cluster-controller>
</tuning>
</cluster>""",
@@ -63,7 +63,6 @@ public class FleetControllerClusterTest {
assertEquals(0.7, config.min_distributor_up_ratio(), 0.01);
assertEquals(0.3, config.min_storage_up_ratio(), 0.01);
assertEquals(7, config.ideal_distribution_bits());
- assertEquals(2, config.max_number_of_groups_allowed_to_be_down());
}
@Test
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/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/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/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/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/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/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 bb53ae61525..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
@@ -87,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. */
@@ -102,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/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/restapi/application/ApplicationApiHandler.java b/controller-server/src/main/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiHandler.java
index ded27ee1060..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;
@@ -165,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;
@@ -186,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,
@@ -195,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
@@ -281,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);
@@ -346,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);
@@ -2017,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));
@@ -3004,7 +3070,12 @@ 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);
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/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/restapi/application/ApplicationApiTest.java b/controller-server/src/test/java/com/yahoo/vespa/hosted/controller/restapi/application/ApplicationApiTest.java
index 76bcbe078ff..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();
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/documentapi/abi-spec.json b/documentapi/abi-spec.json
index dbea284e39e..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" : [
@@ -2459,8 +2440,8 @@
"public int getType()",
"public com.yahoo.document.TestAndSetCondition getCondition()",
"public void setCondition(com.yahoo.document.TestAndSetCondition)",
- "public boolean getCreateIfNonExistent()",
- "public void setCreateIfNonExistent(boolean)"
+ "public void setCreateIfNonExistent(boolean)",
+ "public boolean getCreateIfNonExistent()"
],
"fields" : [ ]
},
@@ -2672,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" : [ ],
@@ -2936,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" : [ ],
@@ -3095,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" : [
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/RoutableFactories60.java b/documentapi/src/main/java/com/yahoo/documentapi/messagebus/protocol/RoutableFactories60.java
index 9812f214066..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
@@ -728,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
@@ -759,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 97cef53695a..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,78 +448,6 @@ 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());
-
- routable = deserialize("SearchResultMessage-4", DocumentProtocol.MESSAGE_SEARCHRESULT, Language.CPP);
- assertTrue(routable instanceof SearchResultMessage);
-
- 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]);
- }
- }
- }
-
private static String CONDITION_STRING = "There's just one condition";
public class testPutDocumentMessage implements RunnableTest {
@@ -750,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/tests/messages/messages60test.cpp b/documentapi/src/tests/messages/messages60test.cpp
index 93c7b5ef7cb..12cecefb072 100644
--- a/documentapi/src/tests/messages/messages60test.cpp
+++ b/documentapi/src/tests/messages/messages60test.cpp
@@ -39,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));
@@ -49,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));
@@ -58,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));
@@ -68,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));
@@ -269,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");
@@ -561,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();
@@ -913,12 +723,6 @@ Messages60Test::testDocumentListReply()
}
bool
-Messages60Test::testDocumentSummaryReply()
-{
- return tryVisitorReply("DocumentSummaryReply", DocumentProtocol::REPLY_DOCUMENTSUMMARY);
-}
-
-bool
Messages60Test::testGetDocumentReply()
{
document::Document::SP doc =
@@ -947,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/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/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/routablefactories60.cpp b/documentapi/src/vespa/documentapi/messagebus/routablefactories60.cpp
index 1dce7ff281f..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>();
@@ -642,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>();
@@ -680,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/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-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-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/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 345a6f700fd..500adab72cf 100644
--- a/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
+++ b/flags/src/main/java/com/yahoo/vespa/flags/Flags.java
@@ -380,7 +380,7 @@ public class Flags {
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",
+ 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);
@@ -402,6 +402,18 @@ public class Flags {
"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}. */
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/identity/AthenzCredentialsMaintainer.java b/node-admin/src/main/java/com/yahoo/vespa/hosted/node/admin/maintenance/identity/AthenzCredentialsMaintainer.java
index 45973ee6784..9e295b6a8e6 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
@@ -15,6 +15,7 @@ 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;
@@ -78,6 +79,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private final ServiceIdentityProvider hostIdentityProvider;
private final IdentityDocumentClient identityDocumentClient;
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<>();
@@ -99,6 +101,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
new AthenzIdentityVerifier(Set.of(configServerInfo.getConfigServerIdentity())));
this.clock = clock;
this.tenantServiceIdentityFlag = Flags.NODE_ADMIN_TENANT_SERVICE_REGISTRY.bindTo(flagSource);
+ this.useNewIdentityDocumentLayout = Flags.NEW_IDDOC_LAYOUT.bindTo(flagSource);
}
public boolean converge(NodeAgentContext context) {
@@ -134,7 +137,7 @@ 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, identityType, athenzIdentity);
return true;
@@ -154,7 +157,7 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
return false;
} else {
lastRefreshAttempt.put(context.containerName(), now);
- refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc, identityType, athenzIdentity);
+ refreshIdentity(context, privateKeyFile, certificateFile, identityDocumentFile, doc.identityDocument(), identityType, athenzIdentity);
return true;
}
}
@@ -165,6 +168,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);
@@ -219,7 +227,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private void registerIdentity(NodeAgentContext context, ContainerPath privateKeyFile, ContainerPath certificateFile, ContainerPath identityDocumentFile, IdentityType identityType, AthenzIdentity identity) {
KeyPair keyPair = KeyUtils.generateKeypair(KeyAlgorithm.RSA);
- SignedIdentityDocument doc = signedIdentityDocument(context, identityType);
+ SignedIdentityDocument signedDoc = signedIdentityDocument(context, identityType);
+ IdentityDocument doc = signedDoc.identityDocument();
CsrGenerator csrGenerator = new CsrGenerator(certificateDnsSuffix, doc.providerService().getFullName());
Pkcs10Csr csr = csrGenerator.generateInstanceCsr(
identity, doc.providerUniqueId(), doc.ipAddresses(), doc.clusterType(), keyPair);
@@ -231,9 +240,9 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
ztsClient.registerInstance(
doc.providerService(),
identity,
- EntityBindingsMapper.toAttestationData(doc),
+ 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");
}
@@ -242,14 +251,14 @@ 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, IdentityType identityType, AthenzIdentity identity) {
+ 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(
@@ -310,8 +319,8 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private SignedIdentityDocument signedIdentityDocument(NodeAgentContext context, IdentityType identityType) {
return switch (identityType) {
- case NODE -> identityDocumentClient.getNodeIdentityDocument(context.hostname().value());
- case TENANT -> identityDocumentClient.getTenantIdentityDocument(context.hostname().value());
+ case NODE -> identityDocumentClient.getNodeIdentityDocument(context.hostname().value(), documentVersion(context));
+ case TENANT -> identityDocumentClient.getTenantIdentityDocument(context.hostname().value(), documentVersion(context));
};
}
@@ -324,9 +333,9 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
private AthenzIdentity getTenantIdentity(NodeAgentContext context, ContainerPath identityDocumentFile) {
if (Files.exists(identityDocumentFile)) {
- return EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile).serviceIdentity();
+ return EntityBindingsMapper.readSignedIdentityDocumentFromFile(identityDocumentFile).identityDocument().serviceIdentity();
} else {
- return identityDocumentClient.getTenantIdentityDocument(context.hostname().value()).serviceIdentity();
+ return identityDocumentClient.getTenantIdentityDocument(context.hostname().value(), documentVersion(context)).identityDocument().serviceIdentity();
}
}
@@ -340,6 +349,17 @@ public class AthenzCredentialsMaintainer implements CredentialsMaintainer {
.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");
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 f2f690106fa..7c84afc8397 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
@@ -509,7 +509,8 @@ public class NodeAgentImpl implements NodeAgent {
// 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));
+ Optional<Container> finalContainer = container;
+ wireguardTasks.forEach(task -> task.converge(context, finalContainer.get().id()));
runOrdinaryWireguardTasks = false;
}
@@ -530,7 +531,10 @@ public class NodeAgentImpl implements NodeAgent {
}
aclMaintainer.ifPresent(maintainer -> maintainer.converge(context));
- if (runOrdinaryWireguardTasks) wireguardTasks.forEach(task -> task.converge(context));
+ if (runOrdinaryWireguardTasks) {
+ 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/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-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/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 bbe287fc034..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
@@ -58,7 +58,7 @@ import static com.yahoo.config.provision.NodeResources.StorageType.remote;
public class NodePatcher {
// Same as in DropDocumentsReport.java
- private static final String DROP_DOCUMENTS_REPORT = "dropDocuments";
+ public static final String DROP_DOCUMENTS_REPORT = "dropDocuments";
private static final String WANT_TO_RETIRE = "wantToRetire";
private static final String WANT_TO_DEPROVISION = "wantToDeprovision";
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/restapi/NodesV2ApiTest.java b/node-repository/src/test/java/com/yahoo/vespa/hosted/provision/restapi/NodesV2ApiTest.java
index 7affcfebdb3..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
@@ -647,22 +647,43 @@ public class NodesV2ApiTest {
Request.Method.PATCH),
"{\"message\":\"Updated dockerhost1.yahoo.com\"}");
assertFile(new Request("http://localhost:8080/nodes/v2/node/dockerhost1.yahoo.com"), "docker-node1-reports-4.json");
+ }
+
+ @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}}");
- assertResponse(new Request("http://localhost:8080/nodes/v2/node/host1.yahoo.com",
+ // 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 host1.yahoo.com\"}");
- tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host1.yahoo.com"),
+ "{\"message\":\"Updated host4.yahoo.com\"}");
+ tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host4.yahoo.com"),
"{\"dropDocuments\":{\"createdMillis\":25,\"droppedAt\":36}}");
- assertResponse(new Request("http://localhost:8080/nodes/v2/node/host10.yahoo.com",
+ // 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 host10.yahoo.com\"}");
- tester.assertResponseContains(new Request("http://localhost:8080/nodes/v2/node/host10.yahoo.com"),
+ "{\"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/host1.yahoo.com"),
+ 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
diff --git a/parent/pom.xml b/parent/pom.xml
index 76f4ef30dda..f3b9fc9baeb 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>
@@ -1154,7 +1155,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>
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/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/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/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/stringattribute/stringattribute_test.cpp b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
index 2804e3f74e4..5ba90d2b077 100644
--- a/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
+++ b/searchlib/src/tests/attribute/stringattribute/stringattribute_test.cpp
@@ -390,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/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/query/streaming_query_test.cpp b/searchlib/src/tests/query/streaming_query_test.cpp
index 2c202d9131b..210f32af15e 100644
--- a/searchlib/src/tests/query/streaming_query_test.cpp
+++ b/searchlib/src/tests/query/streaming_query_test.cpp
@@ -814,7 +814,7 @@ TEST("test_nearest_neighbor_query_node")
constexpr uint32_t target_num_hits = 100;
constexpr bool allow_approximate = false;
constexpr uint32_t explore_additional_hits = 800;
- constexpr double raw_score = 0.5;
+ 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);
@@ -830,14 +830,14 @@ TEST("test_nearest_neighbor_query_node")
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_raw_score().has_value());
+ EXPECT_FALSE(node->get_distance().has_value());
EXPECT_FALSE(node->evaluate());
- node->set_raw_score(raw_score);
- EXPECT_TRUE(node->get_raw_score().has_value());
- EXPECT_EQUAL(raw_score, node->get_raw_score().value());
+ 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_raw_score().has_value());
+ EXPECT_FALSE(node->get_distance().has_value());
EXPECT_FALSE(node->evaluate());
}
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 86b83b2c651..e5bed3ebae5 100644
--- a/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp
+++ b/searchlib/src/tests/tensor/distance_functions/distance_functions_test.cpp
@@ -44,6 +44,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 { constexpr double sq_root_half = std::sqrt(0.5); }
TEST(DistanceFunctionsTest, euclidean_gives_expected_score)
{
@@ -55,19 +81,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,14 +148,13 @@ 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(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(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, 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) {
@@ -115,7 +181,7 @@ TEST(DistanceFunctionsTest, angular_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};
@@ -143,7 +209,7 @@ TEST(DistanceFunctionsTest, angular_gives_expected_score)
EXPECT_DOUBLE_EQ(threshold, 0.5);
double a34 = computeAngularChecked(t(p3), t(p4));
- EXPECT_FLOAT_EQ(a34, (1.0 - 0.707107));
+ 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);
@@ -193,6 +259,89 @@ TEST(DistanceFunctionsTest, angular_gives_expected_score)
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)
{
auto ct = vespalib::eval::CellType::DOUBLE;
@@ -203,7 +352,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};
@@ -219,7 +368,7 @@ TEST(DistanceFunctionsTest, innerproduct_gives_expected_score)
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);
@@ -228,6 +377,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);
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/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/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/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/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_read_view.h b/searchlib/src/vespa/searchlib/attribute/multi_value_mapping_read_view.h
index 41138ff0890..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
@@ -33,6 +33,7 @@ public:
}
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/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/singlenumericattribute.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
index c75ee0aacb5..606c7a92ef5 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericattribute.hpp
@@ -164,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.hpp b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
index b840a0516b2..e459d3d9c9c 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericenumattribute.hpp
@@ -160,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 e353d03a9e8..a4b9abb084a 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlenumericpostattribute.hpp
@@ -143,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 69fe6435a03..c3f5c295260 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringattribute.hpp
@@ -46,7 +46,8 @@ 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 5b5214f6d3e..60847636baa 100644
--- a/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
+++ b/searchlib/src/vespa/searchlib/attribute/singlestringpostattribute.hpp
@@ -145,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/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/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/query/streaming/nearest_neighbor_query_node.cpp b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp
index d1c37cd6dcd..b2d8a0ee4be 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp
+++ b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.cpp
@@ -1,15 +1,21 @@
// 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& term, const string& index, int32_t id, search::query::Weight weight, double distance_threshold)
- : QueryTerm(std::move(resultBase), term, index, Type::NEAREST_NEIGHBOR),
+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),
- _raw_score()
+ _distance(),
+ _calc()
{
- setUniqueId(id);
+ setUniqueId(unique_id);
setWeight(weight);
}
@@ -18,13 +24,13 @@ NearestNeighborQueryNode::~NearestNeighborQueryNode() = default;
bool
NearestNeighborQueryNode::evaluate() const
{
- return _raw_score.has_value();
+ return _distance.has_value();
}
void
NearestNeighborQueryNode::reset()
{
- _raw_score.reset();
+ _distance.reset();
}
NearestNeighborQueryNode*
@@ -33,4 +39,14 @@ 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
index 0beb130c53d..c66364b0c52 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h
+++ b/searchlib/src/vespa/searchlib/query/streaming/nearest_neighbor_query_node.h
@@ -8,16 +8,34 @@
namespace search::streaming {
/*
- * Nearest neighbor query node.
+ * 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
- std::optional<double> _raw_score;
+ // 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& term, const string& index, int32_t id, search::query::Weight weight, double distance_threshold);
+ 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;
@@ -27,9 +45,13 @@ public:
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(double value) { _raw_score = value; }
- const std::optional<double>& get_raw_score() const noexcept { return _raw_score; }
+ 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 226cb92c894..84344831cbc 100644
--- a/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
+++ b/searchlib/src/vespa/searchlib/query/streaming/querynode.cpp
@@ -200,15 +200,17 @@ QueryNode::build_nearest_neighbor_query_node(const QueryNodeResultFactory& facto
{
vespalib::stringref query_tensor_name = query_rep.getTerm();
vespalib::stringref field_name = query_rep.getIndexName();
- int32_t id = query_rep.getUniqueId();
- search::query::Weight weight = query_rep.GetWeight();
+ 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,
- id,
- weight,
- distance_threshold);
+ target_hits,
+ distance_threshold,
+ unique_id,
+ weight);
}
}
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/tensor/CMakeLists.txt b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
index 090042e5b83..2e874ffa4ae 100644
--- a/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
+++ b/searchlib/src/vespa/searchlib/tensor/CMakeLists.txt
@@ -30,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 5101373c047..efc1170baf5 100644
--- a/searchlib/src/vespa/searchlib/tensor/angular_distance.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/angular_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 "angular_distance.h"
+#include "temporary_vector_store.h"
using vespalib::typify_invoke;
using vespalib::eval::TypifyCellType;
@@ -65,15 +66,15 @@ public:
_tmpSpace(lhs.size),
_lhs(_tmpSpace.storeLhs(lhs))
{
- auto a = &_lhs[0];
+ 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[0];
- auto b = &rhs_vector[0];
+ 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);
diff --git a/searchlib/src/vespa/searchlib/tensor/angular_distance.h b/searchlib/src/vespa/searchlib/tensor/angular_distance.h
index 97f692d05a2..bba83576153 100644
--- a/searchlib/src/vespa/searchlib/tensor/angular_distance.h
+++ b/searchlib/src/vespa/searchlib/tensor/angular_distance.h
@@ -60,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;
diff --git a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp
index 56edbf9fede..33b94e5218c 100644
--- a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.cpp
@@ -1,58 +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"
-#include <vespa/log/log.h>
-
-LOG_SETUP(".searchlib.tensor.bound_distance_function");
-
-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<float>;
-template class TemporaryVectorStore<double>;
-
-template class ConvertingBoundDistance<float>;
-template class ConvertingBoundDistance<double>;
-
-}
diff --git a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h
index 0f51e8a33ef..5d602a52227 100644
--- a/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h
+++ b/searchlib/src/vespa/searchlib/tensor/bound_distance_function.h
@@ -5,7 +5,6 @@
#include <memory>
#include <vespa/eval/eval/cell_type.h>
#include <vespa/eval/eval/typed_cells.h>
-#include <vespa/vespalib/util/array.h>
#include <vespa/vespalib/util/arrayref.h>
#include "distance_function.h"
@@ -43,51 +42,4 @@ public:
double limit) const = 0;
};
-
-/** helper class - temporary storage of possibly-converted vector cells */
-template <typename FloatType>
-class TemporaryVectorStore {
-private:
- vespalib::Array<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);
- }
- }
-};
-
-template<typename FloatType>
-class ConvertingBoundDistance : public BoundDistanceFunction {
- mutable TemporaryVectorStore<FloatType> _tmpSpace;
- const vespalib::eval::TypedCells _lhs;
- const DistanceFunction &_df;
-public:
- ConvertingBoundDistance(const vespalib::eval::TypedCells& lhs, const DistanceFunction &df)
- : BoundDistanceFunction(vespalib::eval::get_cell_type<FloatType>()),
- _tmpSpace(lhs.size),
- _lhs(_tmpSpace.storeLhs(lhs)),
- _df(df)
- {}
- double calc(const vespalib::eval::TypedCells& rhs) const override {
- return _df.calc(_lhs, vespalib::eval::TypedCells(_tmpSpace.convertRhs(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, vespalib::eval::TypedCells(_tmpSpace.convertRhs(rhs)), limit);
- }
-};
-
}
diff --git a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
index 7ccca655943..4553f39a525 100644
--- a/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
+++ b/searchlib/src/vespa/searchlib/tensor/distance_function_factory.cpp
@@ -100,6 +100,27 @@ make_distance_function_factory(search::attribute::DistanceMetric variant,
}
return std::make_unique<AngularDistanceFunctionFactory<float>>();
}
+ if (variant == 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>>();
+ }
+ }
+ if (variant == DistanceMetric::PrenormalizedAngular) {
+ if (cell_type == CellType::DOUBLE) {
+ return std::make_unique<PrenormalizedAngularDistanceFunctionFactory<double>>();
+ }
+ return std::make_unique<PrenormalizedAngularDistanceFunctionFactory<float>>();
+ }
+ /*
+ if (variant == DistanceMetric::GeoDegrees) {
+ return std::make_unique<GeoDistanceFunctionFactory>();
+ }
+ if (variant == DistanceMetric::Hamming) {
+ return std::make_unique<HammingDistanceFunctionFactory>();
+ }
+ */
auto df = make_distance_function(variant, cell_type);
return std::make_unique<SimpleDistanceFunctionFactory>(std::move(df));
}
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..9c37b191637 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,73 @@ 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)
+ : BoundDistanceFunction(vespalib::eval::get_cell_type<FloatType>()),
+ _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/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/prenormalized_angular_distance.cpp b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp
new file mode 100644
index 00000000000..d2693f9f443
--- /dev/null
+++ b/searchlib/src/vespa/searchlib/tensor/prenormalized_angular_distance.cpp
@@ -0,0 +1,82 @@
+// 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)
+ : BoundDistanceFunction(vespalib::eval::get_cell_type<FloatType>()),
+ _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/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/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/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/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/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/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/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/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/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/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/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp b/streamingvisitors/src/tests/nearest_neighbor_field_searcher/nearest_neighbor_field_searcher_test.cpp
index 43c77398be8..b64d477fd4c 100644
--- 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
@@ -31,9 +31,11 @@ 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", 7, search::query::Weight(11), distance_threshold);
+ 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;
@@ -90,34 +92,71 @@ public:
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, raw_score_calculated_with_distance_threshold)
+TEST_F(NearestNeighborSearcherTest, distance_heap_keeps_the_best_target_hits)
{
- query.add("qt1", 3);
+ query.add("qt1", 2, 100.0);
+ const auto& node = query.get(0);
set_query_tensor("qt1", "tensor(x[2]):[1,3]");
prepare();
- match("tensor(x[2]):[1,5]");
- expect_match((5-3)*(5-3), query.get(0));
+ expect_match("tensor(x[2]):[1,7]", (7-3)*(7-3), node);
+ expect_match("tensor(x[2]):[1,9]", (9-3)*(9-3), node);
- match("tensor(x[2]):[1,6]");
- expect_match((6-3)*(6-3), query.get(0));
+ // 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);
- 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_not_match("tensor(x[2]):[1,7]", node);
}
TEST_F(NearestNeighborSearcherTest, raw_score_calculated_for_two_query_operators)
{
- query.add("qt1", 3);
- query.add("qt2", 4);
+ 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();
diff --git a/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp b/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp
index 9f3f3d770e4..4d425d9dedd 100644
--- a/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp
+++ b/streamingvisitors/src/tests/rank_processor/rank_processor_test.cpp
@@ -55,6 +55,10 @@ RankProcessorTest::build_query(QueryBuilder<SimpleQueryNodeTypes> &builder)
_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)
{
@@ -71,6 +75,8 @@ TEST_F(RankProcessorTest, unpack_match_data_for_nearest_neighbor_query_node)
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;
@@ -82,11 +88,11 @@ TEST_F(RankProcessorTest, unpack_match_data_for_nearest_neighbor_query_node)
EXPECT_EQ(invalid_id, tfmd->getDocId());
RankProcessor::unpack_match_data(1, *md, *_query_wrapper);
EXPECT_EQ(invalid_id, tfmd->getDocId());
- constexpr double raw_score = 1.5;
- node->set_raw_score(raw_score);
+ constexpr double distance = 1.5;
+ node->set_distance(distance);
RankProcessor::unpack_match_data(2, *md, *_query_wrapper);
EXPECT_EQ(2, tfmd->getDocId());
- EXPECT_EQ(raw_score, tfmd->getRawScore());
+ EXPECT_EQ(distance * 2, tfmd->getRawScore());
node->reset();
RankProcessor::unpack_match_data(3, *md, *_query_wrapper);
EXPECT_EQ(2, tfmd->getDocId());
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 b41eb041c57..3ce137bffe5 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.cpp
@@ -10,7 +10,7 @@
#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;
@@ -56,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();
@@ -83,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());
@@ -237,7 +241,7 @@ RankProcessor::unpack_match_data(uint32_t docid, MatchData &matchData, QueryWrap
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();
+ 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();
diff --git a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
index c541f62646e..c74a2d1e3ee 100644
--- a/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
+++ b/streamingvisitors/src/vespa/searchvisitor/rankprocessor.h
@@ -65,7 +65,7 @@ public:
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; }
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
index 7dc0c05cfaa..8980bc1f54d 100644
--- a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
+++ b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.cpp
@@ -664,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);
}
}
diff --git a/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h b/streamingvisitors/src/vespa/searchvisitor/searchvisitor.h
index 162c031ac9e..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:
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/searcher/fieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.cpp
index cbf8903caab..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,16 +88,14 @@ 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;
@@ -132,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());
}
}
@@ -232,26 +228,26 @@ void FieldIdTSearcherMap::prepare(const DocumentTypeIndexFieldMapT& difm,
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, field_paths, query_env);
+ 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 879902ca514..abc2bc9d870 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/fieldsearcher.h
@@ -52,7 +52,7 @@ 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);
@@ -61,8 +61,8 @@ public:
const vsm::FieldPathMapT& field_paths,
search::fef::IQueryEnvironment& query_env);
- const FieldIdT & field() const { return _field; }
- void field(const FieldIdT & v) { _field = v; prepareFieldId(); }
+ 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; }
diff --git a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
index 8585975ca3c..578fc9fe0e5 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/floatfieldsearcher.cpp
@@ -36,8 +36,7 @@ void FloatFieldSearcherT<T>::prepare(search::streaming::QueryTermList& qtl,
{
_floatTerm.clear();
FieldSearcher::prepare(qtl, buf, field_paths, query_env);
- for (QueryTermList::const_iterator it=qtl.begin(); it < qtl.end(); it++) {
- const QueryTerm * qt = *it;
+ for (auto qt : qtl) {
size_t sz(qt->termLen());
if (sz) {
double low;
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 6b0bbbb368d..43ecba29b33 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/geo_pos_field_searcher.cpp
@@ -35,7 +35,7 @@ void GeoPosFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
{
_geoPosTerm.clear();
FieldSearcher::prepare(qtl, buf, field_paths, query_env);
- for (const QueryTerm * qt : qtl) {
+ 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/intfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
index 18b286946f7..0fb71a3c3c6 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/intfieldsearcher.cpp
@@ -26,8 +26,7 @@ void IntFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
{
_intTerm.clear();
FieldSearcher::prepare(qtl, buf, field_paths, query_env);
- for (QueryTermList::const_iterator it=qtl.begin(); it < qtl.end(); it++) {
- const QueryTerm * qt = *it;
+ for (auto qt : qtl) {
size_t sz(qt->termLen());
if (sz) {
int64_t low;
diff --git a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp
index 045ec9b04a3..db4ee12438e 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.cpp
@@ -48,11 +48,20 @@ NearestNeighborFieldSearcher::NodeAndCalc::NodeAndCalc(search::streaming::Neares
std::unique_ptr<search::tensor::DistanceCalculator> calc_in)
: node(node_in),
calc(std::move(calc_in)),
- distance_threshold(calc->function().convert_threshold(node->get_distance_threshold()))
+ heap(node->get_target_hits())
{
+ node->set_raw_score_calc(this);
+ heap.set_distance_threshold(calc->function().convert_threshold(node->get_distance_threshold()));
}
-NearestNeighborFieldSearcher::NearestNeighborFieldSearcher(const FieldIdT& fid,
+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),
@@ -100,7 +109,7 @@ NearestNeighborFieldSearcher::prepare(search::streaming::QueryTermList& qtl,
}
try {
auto calc = DistanceCalculator::make_with_validation(*_attr, *tensor_value);
- _calcs.emplace_back(nn_term, std::move(calc));
+ _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());
@@ -116,10 +125,10 @@ NearestNeighborFieldSearcher::onValue(const document::FieldValue& fv)
if (tfv && tfv->getAsTensorPtr()) {
_attr->add(*tfv->getAsTensorPtr(), 1);
for (auto& elem : _calcs) {
- double distance = elem.calc->calc_with_limit(scratch_docid, elem.distance_threshold);
- if (distance <= elem.distance_threshold) {
- double score = elem.calc->function().to_rawscore(distance);
- elem.node->set_raw_score(score);
+ 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);
}
}
}
diff --git a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h
index 83f2c444e5a..d5d751cd637 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h
+++ b/streamingvisitors/src/vespa/vsm/searcher/nearest_neighbor_field_searcher.h
@@ -5,6 +5,8 @@
#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>
@@ -14,8 +16,6 @@ namespace search::tensor {
class TensorExtAttribute;
}
-namespace search::streaming { class NearestNeighborQueryNode; }
-
namespace vsm {
/**
@@ -26,19 +26,22 @@ namespace vsm {
*/
class NearestNeighborFieldSearcher : public FieldSearcher {
private:
- struct NodeAndCalc {
+ class NodeAndCalc : search::streaming::NearestNeighborQueryNode::RawScoreCalculator {
+ public:
search::streaming::NearestNeighborQueryNode* node;
std::unique_ptr<search::tensor::DistanceCalculator> calc;
- double distance_threshold;
+ 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<NodeAndCalc> _calcs;
+ std::vector<std::unique_ptr<NodeAndCalc>> _calcs;
public:
- NearestNeighborFieldSearcher(const FieldIdT& fid,
+ NearestNeighborFieldSearcher(FieldIdT fid,
search::attribute::DistanceMetric metric);
~NearestNeighborFieldSearcher();
diff --git a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
index f15290526d9..6a46e4604be 100644
--- a/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
+++ b/streamingvisitors/src/vespa/vsm/searcher/strchrfieldsearcher.cpp
@@ -34,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);
}
@@ -49,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/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/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/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/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..0e13cba8de9 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,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;
+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);
+ 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..1858653c9b4 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);
+ 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/DefaultIdentityDocumentClient.java b/vespa-athenz/src/main/java/com/yahoo/vespa/athenz/identityprovider/client/DefaultIdentityDocumentClient.java
index 5b884e3dfb3..48fc021dced 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
@@ -56,16 +56,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);
}
@Override
- public SignedIdentityDocument getTenantIdentityDocument(String host) {
- return getIdentityDocument(host, "tenant");
+ public SignedIdentityDocument getTenantIdentityDocument(String host, int documentVersion) {
+ return getIdentityDocument(host, "tenant", documentVersion);
}
- private SignedIdentityDocument getIdentityDocument(String host, String type) {
+ private SignedIdentityDocument getIdentityDocument(String host, String type, int documentVersion) {
try (CloseableHttpClient client = createHttpClient(sslContextSupplier.get(), hostnameVerifier)) {
URI uri = configserverUri
@@ -76,6 +76,7 @@ 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());
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
index e67af028402..d699564a4ee 100644
--- 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
@@ -298,7 +298,7 @@ public final class LegacyAthenzIdentityProviderImpl extends AbstractComponent im
}
private X509Certificate requestRoleCertificate(AthenzRole role) {
- var doc = credentials.getIdentityDocument();
+ var doc = credentials.getIdentityDocument().identityDocument();
Pkcs10Csr csr = csrGenerator.generateRoleCsr(
identity, role, doc.providerUniqueId(), doc.clusterType(), credentials.getKeyPair());
try (ZtsClient client = createZtsClient()) {
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/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-dependencies-enforcer/allowed-maven-dependencies.txt b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
index 0d007097fa2..1c2db273653 100644
--- a/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
+++ b/vespa-dependencies-enforcer/allowed-maven-dependencies.txt
@@ -154,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
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/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 5126f7e0f43..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,9 +523,7 @@ 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);
+ parameters = parameters.withDeadline(Instant.ofEpochMilli(doomMillis(request)).minus(handlerTimeout));
for (String name : names)
parameters = switch (name) {
case CLUSTER ->
@@ -1168,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;
}
@@ -1188,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(),
@@ -1345,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);
@@ -1426,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() + "'"));
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 7696fd2196c..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;
@@ -221,7 +222,7 @@ public class DocumentV1ApiTest {
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/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/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/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 8ee3957af32..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
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
+}